From 686e0ce8d3e210a027bfd74d0abdf1a6ad6237a0 Mon Sep 17 00:00:00 2001 From: jiisanda Date: Sun, 15 Dec 2024 01:43:14 +0530 Subject: [PATCH 01/10] Create pylint.yml --- .github/workflows/pylint.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/pylint.yml diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml new file mode 100644 index 0000000..c73e032 --- /dev/null +++ b/.github/workflows/pylint.yml @@ -0,0 +1,23 @@ +name: Pylint + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.8", "3.9", "3.10"] + steps: + - uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pylint + - name: Analysing the code with pylint + run: | + pylint $(git ls-files '*.py') From 84ddbbaad71596106eb3f5b9716f3aedd566ff0e Mon Sep 17 00:00:00 2001 From: jiisanda Date: Sun, 15 Dec 2024 01:51:39 +0530 Subject: [PATCH 02/10] Update pylint.yml --- .github/workflows/pylint.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index c73e032..d091dc4 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.12"] steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} @@ -18,6 +18,7 @@ jobs: run: | python -m pip install --upgrade pip pip install pylint + pip install -r requirements.txt - name: Analysing the code with pylint run: | - pylint $(git ls-files '*.py') + pylint $(git ls-files '*.py') --disable=E0401 # Temporarily disable import errors From 7349a3e1efe27dce5032321b56fe18bcfab92b42 Mon Sep 17 00:00:00 2001 From: jiisanda Date: Sun, 15 Dec 2024 01:52:56 +0530 Subject: [PATCH 03/10] Update requirements.txt --- requirements.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index 82fcbbe..05cea07 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,19 @@ -alembic~=1.12.1 +alembic>=1.12.1 annotated-types==0.6.0 anyio==4.2.0 asyncpg==0.29.0 -boto3~=1.34.34 -botocore~=1.34.34 +boto3>=1.34.34 +botocore>=1.34.34 certifi==2024.7.4 click==8.1.7 colorama==0.4.6 dnspython>=2.6.1 email-validator==2.1.0.post1 -fastapi~=0.109.2 +fastapi>=0.109.2 greenlet==3.0.3 h11==0.14.0 httpcore==0.17.3 -httpx~=0.24.1 +httpx>=0.24.1 idna>=3.7 iniconfig==2.0.0 jmespath==1.0.1 @@ -27,9 +27,9 @@ pyasn1==0.5.1 pydantic~=2.5.3 pydantic-settings==2.1.0 pydantic_core==2.14.6 -pytest~=7.4.4 +pytest>=7.4.4 python-dateutil==2.8.2 -python-dotenv~=1.0.0 +python-dotenv>=1.0.0 python-jose==3.3.0 python-multipart>=0.0.18 python-ulid==2.2.0 @@ -37,7 +37,7 @@ rsa==4.9 s3transfer==0.10.0 six==1.16.0 sniffio==1.3.0 -SQLAlchemy~=1.4.51 +SQLAlchemy>=1.4.51 starlette>=0.40.0 typing_extensions==4.9.0 urllib3>=2.2.2 From 469525b992155291c8a1a24a04ce317f5f23ae43 Mon Sep 17 00:00:00 2001 From: jiisanda Date: Sun, 15 Dec 2024 02:00:26 +0530 Subject: [PATCH 04/10] Update pylint.yml --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index d091dc4..7875037 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,4 +21,4 @@ jobs: pip install -r requirements.txt - name: Analysing the code with pylint run: | - pylint $(git ls-files '*.py') --disable=E0401 # Temporarily disable import errors + pylint $(git ls-files '*.py') --disable=E0401,C0116 From 0453c4416314e7330e6d7852dbbf53a0ef295d09 Mon Sep 17 00:00:00 2001 From: jiisanda Date: Sun, 15 Dec 2024 02:25:20 +0530 Subject: [PATCH 05/10] update pylint --- .github/workflows/pylint.yml | 2 +- Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 7875037..aaea09d 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,4 +21,4 @@ jobs: pip install -r requirements.txt - name: Analysing the code with pylint run: | - pylint $(git ls-files '*.py') --disable=E0401,C0116 + pylint $(git ls-files '*.py') --disable=E0401,C0116,C0114,C0115 diff --git a/Dockerfile b/Dockerfile index 624c2bd..b65d0e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11.6 +FROM python:3.12 LABEL authors="jiisanda" WORKDIR /usr/src/app From 35bedb6ec13996d67acc7fecfc965535e1d9010a Mon Sep 17 00:00:00 2001 From: jiisanda Date: Sun, 15 Dec 2024 02:29:11 +0530 Subject: [PATCH 06/10] update pylint --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index aaea09d..52556f3 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,4 +21,4 @@ jobs: pip install -r requirements.txt - name: Analysing the code with pylint run: | - pylint $(git ls-files '*.py') --disable=E0401,C0116,C0114,C0115 + pylint $(git ls-files '*.py') --disable=E0401,C0116,C0114,C0115,C0301 From 7bf19cc7809526b09cda0151e39d3f159d02b25d Mon Sep 17 00:00:00 2001 From: jiisanda Date: Sun, 15 Dec 2024 03:35:14 +0530 Subject: [PATCH 07/10] =?UTF-8?q?=F0=9F=94=A5=20pylint=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/dependencies/auth_utils.py | 4 +- app/api/dependencies/mail_service.py | 4 +- app/api/routes/documents/document.py | 26 +++-- .../routes/documents/document_organization.py | 18 ++-- app/api/routes/documents/document_sharing.py | 60 +++++++---- .../routes/documents/documents_metadata.py | 6 +- app/api/routes/documents/notify.py | 4 +- app/core/config.py | 7 +- app/core/exceptions.py | 12 +-- app/db/models.py | 6 +- app/db/repositories/auth/auth.py | 8 +- .../documents/document_organization.py | 26 +++-- .../documents/document_sharing.py | 60 +++++++---- app/db/repositories/documents/documents.py | 51 ++++++--- .../documents/documents_metadata.py | 43 ++++---- app/db/repositories/documents/notify.py | 12 +-- app/db/tables/base_class.py | 6 ++ app/logs/logger.py | 1 - app/main.py | 6 +- app/schemas/documents/bands.py | 5 +- app/tests/__init__.py | 0 app/tests/config_tests.py | 97 ----------------- app/tests/repositories_tests/__init__.py | 0 .../test_document_metadata.py | 70 ------------ app/tests/routes_tests/__init__.py | 0 .../routes_tests/test_document_metadata.py | 100 ------------------ app/tests/schemas_tests/__init__.py | 0 .../schemas_tests/test_documents_metadat.py | 24 ----- migrations/env.py | 5 +- 29 files changed, 224 insertions(+), 437 deletions(-) delete mode 100644 app/tests/__init__.py delete mode 100644 app/tests/config_tests.py delete mode 100644 app/tests/repositories_tests/__init__.py delete mode 100644 app/tests/repositories_tests/test_document_metadata.py delete mode 100644 app/tests/routes_tests/__init__.py delete mode 100644 app/tests/routes_tests/test_document_metadata.py delete mode 100644 app/tests/schemas_tests/__init__.py delete mode 100644 app/tests/schemas_tests/test_documents_metadat.py diff --git a/app/api/dependencies/auth_utils.py b/app/api/dependencies/auth_utils.py index 8fb6199..dbd4720 100644 --- a/app/api/dependencies/auth_utils.py +++ b/app/api/dependencies/auth_utils.py @@ -7,7 +7,7 @@ from passlib.context import CryptContext from app.core.config import settings -from app.core.exceptions import HTTP_401 +from app.core.exceptions import http_401 from app.schemas.auth.bands import TokenData @@ -62,7 +62,7 @@ def verify_access_token(token: str, credentials_exception): def get_current_user(token: str = Depends(oauth2_scheme)): - credentials_exception = HTTP_401( + credentials_exception = http_401( msg="Could not validate credentials", headers={ "WWW-Authenticate": "Bearer" diff --git a/app/api/dependencies/mail_service.py b/app/api/dependencies/mail_service.py index c52cd14..450389e 100644 --- a/app/api/dependencies/mail_service.py +++ b/app/api/dependencies/mail_service.py @@ -8,7 +8,7 @@ from email.mime.text import MIMEText from app.core.config import settings -from app.core.exceptions import HTTP_500 +from app.core.exceptions import http_500 def mail_service(mail_to: str, subject: str, content: str, file_path: str = None) -> None: @@ -50,6 +50,6 @@ def mail_service(mail_to: str, subject: str, content: str, file_path: str = None server.login(sender_email, password) server.sendmail(sender_email, receiver_email, message.as_string()) except Exception as e: - raise HTTP_500( + raise http_500( msg="There was some error sending email..." ) from e diff --git a/app/api/routes/documents/document.py b/app/api/routes/documents/document.py index b0fe98c..52f36a8 100644 --- a/app/api/routes/documents/document.py +++ b/app/api/routes/documents/document.py @@ -7,7 +7,7 @@ from app.api.dependencies.auth_utils import get_current_user from app.api.dependencies.repositories import get_repository -from app.core.exceptions import HTTP_400, HTTP_404 +from app.core.exceptions import http_400, http_404 from app.db.repositories.auth.auth import AuthRepository from app.db.repositories.documents.documents import DocumentRepository, perm_delete as perm_delete_file from app.db.repositories.documents.documents_metadata import DocumentMetadataRepository @@ -28,7 +28,9 @@ async def upload( files: List[UploadFile] = File(...), folder: Optional[str] = None, repository: DocumentRepository = Depends(DocumentRepository), - metadata_repository: DocumentMetadataRepository = Depends(get_repository(DocumentMetadataRepository)), + metadata_repository: DocumentMetadataRepository = Depends( + get_repository(DocumentMetadataRepository) + ), user_repository: AuthRepository = Depends(get_repository(AuthRepository)), user: TokenData = Depends(get_current_user) ) -> Union[List[DocumentMetadataRead], List[Dict[str, str]]]: @@ -40,12 +42,14 @@ async def upload( files (List[UploadFile]): The files to be uploaded. folder (Optional[str]): The folder where the document will be stored. Defaults to None. repository (DocumentRepository): The repository for managing documents. - metadata_repository (DocumentMetadataRepository): The repository for managing document metadata. + metadata_repository (DocumentMetadataRepository): The repository for managing document + metadata. user_repository (AuthRepository): The repository for managing user authentication. user (TokenData): The token data of the authenticated user. Returns: - Union[DocumentMetadataRead, Dict[str, str]]: If the file is added, returns the uploaded document metadata. + Union[DocumentMetadataRead, Dict[str, str]]: If the file is added, returns the + uploaded document metadata. If the file is updated, returns the patched document metadata. Otherwise, returns a response dictionary. @@ -54,7 +58,7 @@ async def upload( """ if not files: - raise HTTP_400( + raise http_400( msg="No input files provided..." ) @@ -110,7 +114,7 @@ async def download( """ if not file_name: - raise HTTP_400( + raise http_400( msg="No file name..." ) try: @@ -118,7 +122,7 @@ async def download( return await repository.download(s3_url=get_document_metadata["s3_url"], name=get_document_metadata["name"]) except Exception as e: - raise HTTP_404( + raise http_404( msg=f"No file with {file_name}" ) from e @@ -214,7 +218,7 @@ async def perm_delete( ) except Exception as e: - raise HTTP_404( + raise http_404( msg=f"No file with {file_name}" ) from e @@ -301,17 +305,17 @@ async def get_document_preview( """ if not document: - raise HTTP_404( + raise http_404( msg="Enter document id or name." ) try: get_document_metadata = dict(await metadata_repository.get(document=document, owner=user)) return await repository.preview(document=get_document_metadata) except TypeError as e: - raise HTTP_404( + raise http_404( msg="Document does not exists." ) from e except ValueError as e: - raise HTTP_400( + raise http_400( msg="File type is not supported for preview" ) from e diff --git a/app/api/routes/documents/document_organization.py b/app/api/routes/documents/document_organization.py index 268d9f8..c75932c 100644 --- a/app/api/routes/documents/document_organization.py +++ b/app/api/routes/documents/document_organization.py @@ -38,7 +38,8 @@ async def search_document( file_types (str, optional): The file types to filter documents by. Defaults to None. doc_status (str, optional): The status of documents to filter by. Defaults to None. repository (DocumentOrgRepository): The repository for managing document organization. - repository_metadata (DocumentMetadataRepository): The repository for managing document metadata. + repository_metadata (DocumentMetadataRepository): The repository for managing + document metadata. user (TokenData): The token data of the authenticated user. Returns: @@ -50,11 +51,10 @@ async def search_document( if tag is None and category is None and file_types is None and doc_status is None: return doc_list - else: - return await repository.search_doc( - docs=doc_list, - tags=tag, - categories=category, - file_types=file_types, - status=doc_status - ) + return await repository.search_doc( + docs=doc_list, + tags=tag, + categories=category, + file_types=file_types, + status=doc_status + ) diff --git a/app/api/routes/documents/document_sharing.py b/app/api/routes/documents/document_sharing.py index 6e18d76..065387b 100644 --- a/app/api/routes/documents/document_sharing.py +++ b/app/api/routes/documents/document_sharing.py @@ -1,4 +1,4 @@ -from typing import Dict, Union +from typing import Union from uuid import UUID from fastapi import APIRouter, Depends, status @@ -6,7 +6,7 @@ from app.api.dependencies.auth_utils import get_current_user from app.api.dependencies.repositories import get_repository, get_key -from app.core.exceptions import HTTP_404 +from app.core.exceptions import http_404 from app.db.repositories.auth.auth import AuthRepository from app.db.repositories.documents.documents import DocumentRepository from app.db.repositories.documents.documents_metadata import DocumentMetadataRepository @@ -28,8 +28,12 @@ async def share_link_document( document: Union[str, UUID], share_request: SharingRequest, repository: DocumentSharingRepository = Depends(get_repository(DocumentSharingRepository)), - auth_repository: AuthRepository = Depends(get_repository(AuthRepository)), - metadata_repository: DocumentMetadataRepository = Depends(get_repository(DocumentMetadataRepository)), + auth_repository: AuthRepository = Depends( + get_repository(AuthRepository) + ), + metadata_repository: DocumentMetadataRepository = Depends( + get_repository(DocumentMetadataRepository) + ), notify_repository: NotifyRepo = Depends(get_repository(NotifyRepo)), user: TokenData = Depends(get_current_user) ): @@ -39,10 +43,12 @@ async def share_link_document( Args: document (Union[str, UUID]): The ID or name of the document to be shared. - share_request (SharingRequest): The sharing request containing the details of the sharing operation. + share_request (SharingRequest): The sharing request containing the + details of the sharing operation. repository (DocumentSharingRepository): The repository for managing document sharing. auth_repository (AuthRepository): The repository for managing User-related queries. - metadata_repository (DocumentMetadataRepository): The repository for managing document metadata. + metadata_repository (DocumentMetadataRepository): The repository for managing + document metadata. notify_repository (NotifyRepo): The repository for managing notification user (TokenData): The token data of the authenticated user. @@ -73,7 +79,9 @@ async def share_link_document( # send a notification to the receiver await notify_repository.notify( - user=user, receivers=share_to, filename=doc.__dict__["name"], auth_repo=auth_repository + user=user, receivers=share_to, + filename=doc.__dict__["name"], + auth_repo=auth_repository, ) return { @@ -82,7 +90,7 @@ async def share_link_document( } except KeyError as e: - raise HTTP_404( + raise http_404( msg=f"No doc: {document}" ) from e @@ -90,7 +98,9 @@ async def share_link_document( @router.get("/doc/{url_id}", tags=["Document Sharing"]) async def redirect_to_share( url_id: str, - repository: DocumentSharingRepository = Depends(get_repository(DocumentSharingRepository)), + repository: DocumentSharingRepository = Depends(get_repository( + DocumentSharingRepository) + ), user: TokenData = Depends(get_current_user) ): @@ -121,9 +131,13 @@ async def share_document( document: Union[str, UUID], share_request: SharingRequest, notify: bool = True, - repository: DocumentSharingRepository = Depends(get_repository(DocumentSharingRepository)), + repository: DocumentSharingRepository = Depends( + get_repository(DocumentSharingRepository) + ), document_repo: DocumentRepository = Depends(DocumentRepository), - metadata_repo: DocumentMetadataRepository = Depends(get_repository(DocumentMetadataRepository)), + metadata_repo: DocumentMetadataRepository = Depends( + get_repository(DocumentMetadataRepository) + ), notify_repo: NotifyRepo = Depends(get_repository(NotifyRepo)), auth_repo: AuthRepository = Depends(get_repository(AuthRepository)), user: TokenData = Depends(get_current_user), @@ -133,15 +147,17 @@ async def share_document( Share a document with other users, and notifies if notify is set to True (default). Args: - document (Union[str, UUID]): The ID or UUID of the document to be shared. - share_request (SharingRequest): The sharing request containing the recipients and permissions. - notify (bool, optional): Whether to send notifications to the recipients. Defaults to True. - repository (DocumentSharingRepository, optional): The repository for document sharing operations. - document_repo (DocumentRepository, optional): The repository for document operations. - metadata_repo (DocumentMetadataRepository, optional): The repository for document metadata operations. - notify_repo (NotifyRepo, optional): The repository for notification operations. - auth_repo (AuthRepository, optional): The repository for authentication operations. - user (TokenData, optional): The authenticated user. + document (Union[str, UUID]): The ID or UUID of the document to be shared. + share_request (SharingRequest): The sharing request containing the recipients and permissions. + notify (bool, optional): Whether to send notifications to the recipients. Defaults to True. + repository (DocumentSharingRepository, optional): The repository for document sharing + operations. + document_repo (DocumentRepository, optional): The repository for document operations. + metadata_repo (DocumentMetadataRepository, optional): The repository for document metadata + operations. + notify_repo (NotifyRepo, optional): The repository for notification operations. + auth_repo (AuthRepository, optional): The repository for authentication operations. + user (TokenData, optional): The authenticated user. Raises: HTTP_404: If the document is not found. @@ -151,7 +167,7 @@ async def share_document( """ if not document: - raise HTTP_404( + raise http_404( msg="Enter document id or UUID." ) try: @@ -171,4 +187,4 @@ async def share_document( auth_repo=auth_repo ) except Exception as e: - raise HTTP_404() from e + raise http_404() from e diff --git a/app/api/routes/documents/documents_metadata.py b/app/api/routes/documents/documents_metadata.py index 9d5395e..b9f509d 100644 --- a/app/api/routes/documents/documents_metadata.py +++ b/app/api/routes/documents/documents_metadata.py @@ -5,7 +5,7 @@ from app.api.dependencies.repositories import get_repository from app.api.dependencies.auth_utils import get_current_user -from app.core.exceptions import HTTP_404 +from app.core.exceptions import http_404 from app.db.repositories.auth.auth import AuthRepository from app.db.repositories.documents.documents_metadata import DocumentMetadataRepository from app.schemas.auth.bands import TokenData @@ -135,7 +135,7 @@ async def update_doc_metadata_details( try: await repository.get(document=document, owner=user) except Exception as e: - raise HTTP_404( + raise http_404( msg=f"No Document with: {document}" ) from e @@ -177,7 +177,7 @@ async def delete_document_metadata( try: await repository.get(document=document, owner=user) except Exception as e: - raise HTTP_404( + raise http_404( msg=f"No document with the detail: {document}." ) from e diff --git a/app/api/routes/documents/notify.py b/app/api/routes/documents/notify.py index 19431de..a4d9b75 100644 --- a/app/api/routes/documents/notify.py +++ b/app/api/routes/documents/notify.py @@ -5,7 +5,7 @@ from app.api.dependencies.auth_utils import get_current_user from app.api.dependencies.repositories import get_repository -from app.core.exceptions import HTTP_404 +from app.core.exceptions import http_404 from app.db.repositories.documents.notify import NotifyRepo from app.schemas.auth.bands import TokenData from app.schemas.documents.bands import Notification, NotifyPatchStatus @@ -71,7 +71,7 @@ async def patch_status( elif notification_id: return await repository.update_status(n_id=notification_id, updated_status=updated_status, user=user) else: - raise HTTP_404( + raise http_404( msg="Bad Request: Make sure to either flag mark_all " "or enter notification_id along with correct status as payload." ) diff --git a/app/core/config.py b/app/core/config.py index 9912c47..c7b8def 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -7,6 +7,9 @@ class GlobalConfig(BaseSettings): + """ + Global Configuration for the FastAPI application. + """ title: str = os.environ.get("TITLE") version: str = "1.0.0" description: str = os.environ.get("DESCRIPTION") @@ -15,13 +18,13 @@ class GlobalConfig(BaseSettings): redoc_url: str = "/redoc" openapi_url: str = "/openapi.json" api_prefix: str = "/v2" - debug: bool = os.environ.get("DEBUG") + debug: bool = str(os.environ.get("DEBUG", "False")).lower() == "true" postgres_user: str = os.environ.get("POSTGRES_USER") postgres_password: str = os.environ.get("POSTGRES_PASSWORD") postgres_hostname: str = os.environ.get("DATABASE_HOSTNAME") postgres_port: int = int(os.environ.get("POSTGRES_PORT")) postgres_db: str = os.environ.get("POSTGRES_DB") - db_echo_log: bool = True if os.environ.get("DEBUG") is True else False + db_echo_log: bool = str(os.environ.get("DEBUG", "False")).lower() == "true" aws_access_key_id: str = os.environ.get("AWS_ACCESS_KEY_ID") aws_secret_key: str = os.environ.get("AWS_SECRET_ACCESS_KEY") aws_region: str = os.environ.get("AWS_REGION") diff --git a/app/core/exceptions.py b/app/core/exceptions.py index 80c2bfc..601f49e 100644 --- a/app/core/exceptions.py +++ b/app/core/exceptions.py @@ -4,31 +4,31 @@ from starlette import status -def HTTP_400(msg: str = "Bad Request...") -> HTTPException: +def http_400(msg: str = "Bad Request...") -> HTTPException: """Invalid Input""" return HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=msg) -def HTTP_401(msg: str = "Unauthorized", headers: Dict[str, str] = None) -> HTTPException: +def http_401(msg: str = "Unauthorized", headers: Dict[str, str] = None) -> HTTPException: """Unauthorized Access""" return HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=msg, headers=headers) -def HTTP_403(msg: str = "Forbidden") -> HTTPException: +def http_403(msg: str = "Forbidden") -> HTTPException: """Forbidden access""" return HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=msg) -def HTTP_404(msg: str = "Entity does not exists...") -> HTTPException: +def http_404(msg: str = "Entity does not exists...") -> HTTPException: """Raised when entity was not found on database.""" return HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=msg) -def HTTP_409(msg: str = "Entity already exists...") -> HTTPException: +def http_409(msg: str = "Entity already exists...") -> HTTPException: """Raised when entity already exists on database.""" return HTTPException(status_code=status.HTTP_409_CONFLICT, detail=msg) -def HTTP_500(msg: str = "Internal Server Error") -> HTTPException: +def http_500(msg: str = "Internal Server Error") -> HTTPException: """Raised when error caused due to internal server""" return HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=msg) diff --git a/app/db/models.py b/app/db/models.py index 810001d..982e45e 100644 --- a/app/db/models.py +++ b/app/db/models.py @@ -7,7 +7,7 @@ from sqlalchemy.orm import sessionmaker, Session from app.core.config import settings -from app.core.exceptions import HTTP_500 +from app.core.exceptions import http_500 logger = logging.getLogger("sqlalchemy") @@ -48,5 +48,5 @@ async def check_tables(): _session.commit() logger.info("Tables created if they didn't already exist.") except OperationalError as e: - logger.error(f"Error Creating table: {e}") - raise HTTP_500(msg="An error occurred while creating tables.") + logger.error("Error Creating table: %s", e) + raise http_500(msg="An error occurred while creating tables.") from e diff --git a/app/db/repositories/auth/auth.py b/app/db/repositories/auth/auth.py index 8d47a2a..16b7bed 100644 --- a/app/db/repositories/auth/auth.py +++ b/app/db/repositories/auth/auth.py @@ -4,7 +4,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.api.dependencies.auth_utils import get_hashed_password, verify_password, create_access_token, create_refresh_token -from app.core.exceptions import HTTP_400, HTTP_403 +from app.core.exceptions import http_400, http_403 from app.db.tables.auth.auth import User from app.schemas.auth.bands import UserOut, UserAuth @@ -39,7 +39,7 @@ async def get_user(self, field: str, detail: str): async def signup(self, userdata: UserAuth) -> UserOut: # Checking if the user already exists if await self._check_user_or_none(userdata) is not None: - raise HTTP_400(msg="User with details already exists") + raise http_400(msg="User with details already exists") # hashing the password hashed_password = get_hashed_password(password=userdata.password) @@ -56,11 +56,11 @@ async def signup(self, userdata: UserAuth) -> UserOut: async def login(self, ipdata): user = await self.get_user(field="username", detail=ipdata.username) if user is None: - raise HTTP_403(msg="Recheck the credentials") + raise http_403(msg="Recheck the credentials") user = user.__dict__ hashed_password = user.get("password") if not verify_password(password=ipdata.password, hashed_password=hashed_password): - raise HTTP_403("Incorrect Password") + raise http_403("Incorrect Password") return { "token_type": "bearer", diff --git a/app/db/repositories/documents/document_organization.py b/app/db/repositories/documents/document_organization.py index 45c16c0..c0b0f32 100644 --- a/app/db/repositories/documents/document_organization.py +++ b/app/db/repositories/documents/document_organization.py @@ -5,12 +5,18 @@ class DocumentOrgRepository: + """ + Repository for managing document organization. + """ def __init__(self): ... @staticmethod - async def _search_tags(docs: List[DocumentMetadataRead], tags: List[str]) -> List[Dict[str, str]]: + async def _search_tags( + docs: List[DocumentMetadataRead], + tags: List[str] + ) -> List[Dict[str, str]]: result = [] for doc in docs: @@ -24,7 +30,9 @@ async def _search_tags(docs: List[DocumentMetadataRead], tags: List[str]) -> Lis return result or None @staticmethod - async def _search_category(docs: List[DocumentMetadataRead], categories: List[str]) -> List[Dict[str, str]]: + async def _search_category( + docs: List[DocumentMetadataRead], categories: List[str] + ) -> List[Dict[str, str]]: result = [] for doc in docs: @@ -38,7 +46,9 @@ async def _search_category(docs: List[DocumentMetadataRead], categories: List[st return result or None @staticmethod - async def _search_file_type(docs: List[DocumentMetadataRead], file_types: List[str]) -> List[Dict[str, str]]: + async def _search_file_type( + docs: List[DocumentMetadataRead], file_types: List[str] + ) -> List[Dict[str, str]]: result = [] for doc in docs: @@ -54,7 +64,9 @@ async def _search_file_type(docs: List[DocumentMetadataRead], file_types: List[s return result or None @staticmethod - async def _search_by_status(docs: List[DocumentMetadataRead], status: List[str]) -> List[Dict[str, str]]: + async def _search_by_status( + docs: List[DocumentMetadataRead], status: List[str] + ) -> List[Dict[str, str]]: result = [] for doc in docs: @@ -68,11 +80,11 @@ async def _search_by_status(docs: List[DocumentMetadataRead], status: List[str]) return result or None async def search_doc( - self, + self, docs: List[DocumentMetadataRead], - tags: str, + tags: str, categories: str, - file_types: str, + file_types: str, status: str ) -> Union[List[List[Dict[str, Any]]], None]: diff --git a/app/db/repositories/documents/document_sharing.py b/app/db/repositories/documents/document_sharing.py index fad374c..5bc3169 100644 --- a/app/db/repositories/documents/document_sharing.py +++ b/app/db/repositories/documents/document_sharing.py @@ -1,12 +1,11 @@ -import os.path - -import boto3 import hashlib +import os import tempfile from datetime import datetime, timedelta, timezone from random import randint -from typing import Any, Dict, List, Union +from typing import Dict, Any, Union, List +import boto3 from botocore.exceptions import NoCredentialsError from sqlalchemy import select, update, delete from sqlalchemy.ext.asyncio import AsyncSession @@ -14,7 +13,7 @@ from app.api.dependencies.mail_service import mail_service from app.api.dependencies.repositories import get_key from app.core.config import settings -from app.core.exceptions import HTTP_404, HTTP_500 +from app.core.exceptions import http_404, http_500 from app.db.tables.auth.auth import User from app.db.tables.documents.document_sharing import DocumentSharing from app.db.repositories.auth.auth import AuthRepository @@ -24,6 +23,9 @@ class DocumentSharingRepository: + """ + Repository for managing document sharing. + """ def __init__(self, session: AsyncSession) -> None: self.client = boto3.client('s3') @@ -84,9 +86,11 @@ async def cleanup_expired_links(self): try: await self.session.execute(stmt) except Exception as e: - raise HTTP_500() from e + raise http_500() from e - async def get_presigned_url(self, doc: Dict[str, Any]) -> Union[str, Dict[str, str]]: + async def get_presigned_url( + self, doc: Dict[str, Any] + ) -> Union[str, Dict[str, str]]: try: params = { 'Bucket': settings.s3_bucket, @@ -104,7 +108,9 @@ async def get_presigned_url(self, doc: Dict[str, Any]) -> Union[str, Dict[str, s return response - async def get_shareable_link(self, owner_id: str, url: str, visits: int, filename: str, share_to: List[str]): + async def get_shareable_link( + self, owner_id: str, url: str, visits: int, filename: str, share_to: List[str] + ): # task to clean uo the database for expired links await self.cleanup_expired_links() @@ -140,7 +146,7 @@ async def get_shareable_link(self, owner_id: str, url: str, visits: int, filenam "visits": response["visits"] } except Exception as e: - raise HTTP_500() from e + raise http_500() from e async def get_redirect_url(self, url_id: str): @@ -157,24 +163,29 @@ async def get_redirect_url(self, url_id: str): return result["url"] except AttributeError as e: - raise HTTP_404( + raise http_404( msg="Shared URL link either expired or reached the limit of visits..." ) from e - async def send_mail(self, user: TokenData, mail_to: Union[List[str], None], link: str) -> None: + async def send_mail( + self, user: TokenData, mail_to: Union[List[str], None], link: str + ) -> None: if mail_to: user_mail = await self.get_user_mail(user) subj = f"DocFlow: {user.username} share a document" content = f""" - Visit the link: {link}, to access the document shared by {user.username} | {user_mail}. + Visit the link: {link}, to access the document + shared by {user.username} | {user_mail}. """ for mails in mail_to: mail_service(mail_to=mails, subject=subj, content=content, file_path=None) - async def confirm_access(self, user: TokenData, url_id: str | None) -> bool: + async def confirm_access( + self, user: TokenData, url_id: str | None + ) -> bool: # check if login user is owner or to whom it is shared stmt = ( select(DocumentSharing) @@ -192,7 +203,7 @@ async def confirm_access(self, user: TokenData, url_id: str | None) -> bool: or user.username in result.get("share_to") ) except Exception as e: - raise HTTP_404( + raise http_404( msg="The link has expired..." ) from e @@ -221,14 +232,25 @@ async def share_document( subject = f"{owner.username} shared a file with you using DocFlow" for mails in share_to: content = f""" - Hello {mails}! - - Hope you are well? {owner.username} | {user_mail} shared a file with you as an attachment. + Hello {mails}! + Hope you are well? {owner.username} | {user_mail} shared a file + with you as an attachment. + Regards, DocFlow """ - mail_service(mail_to=mails, subject=subject, content=content, file_path=temp_path) + mail_service( + mail_to=mails, + subject=subject, + content=content, + file_path=temp_path + ) if notify: - return await notify_repo.notify(user=owner, receivers=share_to, filename=filename, auth_repo=auth_repo) + return await notify_repo.notify( + user=owner, + receivers=share_to, + filename=filename, + auth_repo=auth_repo + ) diff --git a/app/db/repositories/documents/documents.py b/app/db/repositories/documents/documents.py index e566dd9..88bd838 100644 --- a/app/db/repositories/documents/documents.py +++ b/app/db/repositories/documents/documents.py @@ -1,18 +1,18 @@ -import os.path -import tempfile -from typing import Any, Dict -import boto3 import hashlib +import os +import tempfile +from typing import Dict, Any +import boto3 from botocore.exceptions import ClientError from fastapi import File -from fastapi.responses import FileResponse +from starlette.responses import FileResponse from ulid import ULID from app.api.dependencies.constants import SUPPORTED_FILE_TYPES from app.api.dependencies.repositories import TempFileResponse, get_key, get_s3_url from app.core.config import settings -from app.core.exceptions import HTTP_400, HTTP_404 +from app.core.exceptions import http_400, http_404 from app.db.repositories.documents.documents_metadata import DocumentMetadataRepository from app.schemas.auth.bands import TokenData @@ -91,7 +91,9 @@ async def _upload_new_file( } async def _upload_new_version( - self, doc: dict, file: File, contents, file_type: str, new_file_hash: str, is_owner: bool + self, doc: dict, file: File, + contents, file_type: str, new_file_hash: str, + is_owner: bool ) -> Dict[str, Any]: key = await get_key(s3_url=doc["s3_url"]) @@ -110,7 +112,9 @@ async def _upload_new_version( } } - async def upload(self, metadata_repo, user_repo, file: File, folder: str, user: TokenData) -> Dict[str, Any]: + async def upload( + self, metadata_repo, user_repo, file: File, folder: str, user: TokenData + ) -> Dict[str, Any]: """ Uploads a file to the specified folder in the document repository. @@ -130,7 +134,7 @@ async def upload(self, metadata_repo, user_repo, file: File, folder: str, user: file_type = file.content_type if file_type not in SUPPORTED_FILE_TYPES: - raise HTTP_400( + raise http_400( msg=f"File type {file_type} not supported." ) @@ -142,11 +146,14 @@ async def upload(self, metadata_repo, user_repo, file: File, folder: str, user: try: if "status_code" in doc.keys(): # getting document irrespective of user - if get_doc := (await metadata_repo.get_doc(filename=file.filename)): + if get_doc := await metadata_repo.get_doc(filename=file.filename): get_doc = get_doc.__dict__ # Check if logged-in user has update access - logged_in_user = (await user_repo.get_user(field="username", detail=user.username)).__dict__ - if (get_doc["access_to"] is not None) and logged_in_user["email"] in get_doc["access_to"]: + logged_in_user = (await user_repo.get_user( + field="username", detail=user.username + )).__dict__ + if ((get_doc["access_to"] is not None) and + logged_in_user["email"] in get_doc["access_to"]): if get_doc['file_hash'] != new_file_hash: # can upload a version to a file... print(f"Have update access, to a file... owner: {get_doc['owner_id']}") @@ -157,7 +164,11 @@ async def upload(self, metadata_repo, user_repo, file: File, folder: str, user: ) else: return await self._upload_new_file( - file=file, folder=folder, contents=contents, file_type=file_type, user=user + file=file, + folder=folder, + contents=contents, + file_type=file_type, + user=user ) return await self._upload_new_file( file=file, folder=folder, contents=contents, file_type=file_type, user=user @@ -167,15 +178,21 @@ async def upload(self, metadata_repo, user_repo, file: File, folder: str, user: if doc["file_hash"] != new_file_hash: print("File has been updated, uploading new version...") - return await self._upload_new_version(doc=doc, file=file, contents=contents, file_type=file_type, - new_file_hash=new_file_hash, is_owner=True) + return await self._upload_new_version( + doc=doc, + file=file, + contents=contents, + file_type=file_type, + new_file_hash=new_file_hash, + is_owner=True + ) return { "response": "File already present and no changes detected.", "upload": "Noting to update..." } except Exception as e: - raise HTTP_404(msg="Error uploading the file...") from e + raise http_404(msg="Error uploading the file...") from e async def download(self, s3_url: str, name: str) -> Dict[str, str]: @@ -188,7 +205,7 @@ async def download(self, s3_url: str, name: str) -> Dict[str, str]: Filename=r"/app/downloads/docflow_" + f"{name}" ) except ClientError as e: - raise HTTP_404( + raise http_404( msg=f"File not found: {e}" ) from e diff --git a/app/db/repositories/documents/documents_metadata.py b/app/db/repositories/documents/documents_metadata.py index 097931e..e9770a6 100644 --- a/app/db/repositories/documents/documents_metadata.py +++ b/app/db/repositories/documents/documents_metadata.py @@ -9,7 +9,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import aliased -from app.core.exceptions import HTTP_409, HTTP_404 +from app.core.exceptions import http_409, http_404 from app.db.repositories.auth.auth import AuthRepository from app.db.tables.documents.documents_metadata import DocumentMetadata, doc_user_access from app.db.tables.base_class import StatusEnum @@ -73,7 +73,7 @@ async def _execute_update(self, db_document: DocumentMetadata | Dict[str, Any], try: await self.session.execute(stmt) except Exception as e: - raise HTTP_409( + raise http_409( msg=f"Error while updating document: {doc_name}" ) from e @@ -88,11 +88,11 @@ async def _update_access_and_permission(self, db_document, changes, user_repo): await self._update_doc_user_access(db_document, user_id) except IntegrityError as e: - raise HTTP_409( + raise http_409( msg=f"User '{user_email}' already has access..." ) from e except AttributeError as e: - raise HTTP_404( + raise http_404( msg=f"The user with '{user_email}' does not exists, make sure user has account in DocFlow." ) from e @@ -150,7 +150,7 @@ async def upload(self, document_upload: DocumentMetadataCreate) -> DocumentMetad await self.session.commit() await self.session.refresh(db_document) except IntegrityError as e: - raise HTTP_404( + raise http_404( msg=f"Document with name: {document_upload.name} already exists.", ) from e @@ -182,13 +182,13 @@ async def doc_list( "no_of_docs": len(result) } except Exception as e: - raise HTTP_404(msg="No Documents found") from e + raise http_404(msg="No Documents found") from e async def get(self, document: Union[str, UUID], owner: TokenData) -> Union[DocumentMetadataRead, HTTPException]: db_document = await self._get_instance(document=document, owner=owner) if db_document is None: - return HTTP_409( + return http_409( msg=f"No Document with {document}" ) @@ -239,7 +239,7 @@ async def delete(self, document: Union[str, UUID], owner: TokenData) -> None: await self.session.commit() except Exception as e: - raise HTTP_404( + raise http_404( msg=f"No file with {document}" ) from e @@ -271,12 +271,11 @@ async def restore(self, file: str, owner: TokenData) -> DocumentMetadataRead: change = {'status': StatusEnum.private} await self._execute_update(db_document=doc.DocumentMetadata, changes=change) return DocumentMetadataRead(**doc.DocumentMetadata.__dict__) - else: - raise HTTP_409( - msg="Doc is not deleted" - ) + raise http_409( + msg="Doc is not deleted" + ) else: - raise HTTP_404( + raise http_404( msg="Doc does not exists" ) @@ -310,11 +309,10 @@ async def archive(self, file: str, user: TokenData): await self._execute_update(db_document=doc, changes=change) return DocumentMetadataRead(**doc.__dict__) - elif doc and doc.status == StatusEnum.archived: - raise HTTP_409(msg="Doc is already archived") + if doc and doc.status == StatusEnum.archived: + raise http_409(msg="Doc is already archived") - else: - raise HTTP_404(msg="Doc does not exist") + raise http_404(msg="Doc does not exist") async def archive_list(self, user: TokenData) -> Dict[str, List[str] | int]: @@ -338,11 +336,10 @@ async def un_archive(self, file: str, user: TokenData) -> DocumentMetadataRead: change = {'status': 'private'} await self._execute_update(db_document=doc, changes=change) return DocumentMetadataRead(**doc.__dict__) - elif doc and doc.status != StatusEnum.archived: - raise HTTP_409( + if doc and doc.status != StatusEnum.archived: + raise http_409( msg="Doc is not archived" ) - else: - raise HTTP_404( - msg="Doc does not exits" - ) + raise http_404( + msg="Doc does not exits" + ) diff --git a/app/db/repositories/documents/notify.py b/app/db/repositories/documents/notify.py index 6cb9f27..c1698ac 100644 --- a/app/db/repositories/documents/notify.py +++ b/app/db/repositories/documents/notify.py @@ -4,7 +4,7 @@ from sqlalchemy import select, update, delete from sqlalchemy.ext.asyncio import AsyncSession -from app.core.exceptions import HTTP_500, HTTP_409, HTTP_404 +from app.core.exceptions import http_500, http_409, http_404 from app.db.repositories.auth.auth import AuthRepository from app.db.tables.base_class import NotifyEnum from app.db.tables.documents.notify import Notify @@ -49,11 +49,11 @@ async def notify(self, user: TokenData, receivers: List[str], filename: str, aut await self.session.commit() await self.session.refresh(notify_entry) except Exception as e: - raise HTTP_500( + raise http_500( msg="Error notifying the user, but the mail has been sent successfully." ) from e except Exception as e: - raise HTTP_404( + raise http_404( msg="The user does not exists, make sure the user has an account on docflow..." ) from e @@ -81,7 +81,7 @@ async def get_notification_by_id(self, n_id: UUID, user: TokenData) -> Notificat result = (await self.session.execute(stmt)).scalar_one_or_none() return Notification(**result.__dict__) except Exception as e: - raise HTTP_404( + raise http_404( msg=f"No notification with id: {n_id}" ) from e @@ -132,7 +132,7 @@ async def mark_all_read(self, user: TokenData) -> List[Notification]: await self.session.execute(stmt) return await self.get_notifications(user=user) except Exception as e: - raise HTTP_409( + raise http_409( msg="Error updating marking notification read..." ) from e @@ -162,7 +162,7 @@ async def update_status(self, n_id: UUID, updated_status: NotifyPatchStatus, use await self.session.execute(stmt) return await self.get_notification_by_id(n_id=n_id, user=user) except Exception as e: - raise HTTP_409( + raise http_409( msg="Error updating notification status..." ) from e diff --git a/app/db/tables/base_class.py b/app/db/tables/base_class.py index 473fadd..bdda169 100644 --- a/app/db/tables/base_class.py +++ b/app/db/tables/base_class.py @@ -2,6 +2,9 @@ class StatusEnum(enum.Enum): + """ + Enum for status of document + """ public = "public" private = "private" shared = "shared" @@ -10,6 +13,9 @@ class StatusEnum(enum.Enum): class NotifyEnum(enum.Enum): + """ + Enum for status of notification + """ read = "read" unread = "unread" diff --git a/app/logs/logger.py b/app/logs/logger.py index 51d9920..8fe2bec 100644 --- a/app/logs/logger.py +++ b/app/logs/logger.py @@ -81,4 +81,3 @@ docflow_logger = logging.getLogger("docflow") s3_logger = logging.getLogger("s3") sqlalchemy_logger = logging.getLogger("sqlalchemy") - diff --git a/app/main.py b/app/main.py index 13916b4..01b1df4 100644 --- a/app/main.py +++ b/app/main.py @@ -17,12 +17,12 @@ app.include_router(router=router, prefix=settings.api_prefix) -favicon_path = 'favicon.ico' +FAVICON_PATH = 'favicon.ico' -@app.get(favicon_path, include_in_schema=False, tags=["Default"]) +@app.get(FAVICON_PATH, include_in_schema=False, tags=["Default"]) async def favicon(): - return FileResponse(favicon_path) + return FileResponse(FAVICON_PATH) @app.get("/", tags=["Default"]) diff --git a/app/schemas/documents/bands.py b/app/schemas/documents/bands.py index ef45e4a..2ff7623 100644 --- a/app/schemas/documents/bands.py +++ b/app/schemas/documents/bands.py @@ -1,11 +1,10 @@ from datetime import datetime +from typing import Optional, List from uuid import UUID from pydantic import BaseModel -from typing import Optional, List -from app.db.tables.base_class import StatusEnum -from app.db.tables.base_class import NotifyEnum +from app.db.tables.base_class import StatusEnum, NotifyEnum # Document Metadata diff --git a/app/tests/__init__.py b/app/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/tests/config_tests.py b/app/tests/config_tests.py deleted file mode 100644 index 64ae905..0000000 --- a/app/tests/config_tests.py +++ /dev/null @@ -1,97 +0,0 @@ -import asyncio -import pytest -import pytest_asyncio - -from fastapi import FastAPI -from httpx import AsyncClient -from typing import Callable, Generator - -from sqlalchemy.ext.asyncio import create_async_engine -from sqlalchemy.ext.asyncio.session import AsyncSession -from sqlalchemy.orm import sessionmaker - -from app.core.config import settings -from app.schemas.documents.documents_metadata import DocumentMetadataCreate -from app.db.models import metadata - -test_db = ( - f"postgresql+asyncpg://{settings.postgres_user}:{settings.postgres_password}" - f"@{settings.postgres_server}:{settings.postgres_port}/{settings.postgres_db}" -) - -engine = create_async_engine( - test_db, - echo=settings.db_echo_log, - future=True, -) - -async_session = sessionmaker( - engine, - class_=AsyncSession, - expire_on_commit=False -) - - -@pytest_asyncio.fixture(scope="session") -def event_loop(request) -> Generator: - loop = asyncio.get_event_loop_policy().new_event_loop() - yield loop - loop.close() - - -@pytest_asyncio.fixture() -async def db_session() -> AsyncSession: - async with engine.begin() as connection: - await connection.run_sync(metadata.create_all(engine)) - await connection.run_sync(metadata.drop_all(engine)) - async with async_session(bind=connection) as session: - yield session - await session.flush() - await session.rollback() - - -@pytest.fixture() -def override_get_db(db_session: AsyncSession) -> Callable: - async def _override_get_db(): - yield db_session - - return _override_get_db - - -@pytest.fixture() -def app(override_get_db: Callable) -> FastAPI: - from app.api.dependencies.repositories import get_db - from main import app - - app.dependency_overrides[get_db] = override_get_db - - return app - - -@pytest_asyncio.fixture() -async def async_client(app: FastAPI) -> AsyncSession: - async with AsyncClient(app=app, base_url="http://test") as ac: - yield ac - - -@pytest.fixture() -def upload_document_metadata(): - def _upload_document_metadata( - name: str = "hello.pdf", - s3_url: str = f"s3://{settings.s3_test_bucket}/codeakey_logo.png", - status = "private", - ): - return DocumentMetadataCreate(name=name, s3_url=s3_url, status=status) - - return _upload_document_metadata - - -@pytest.fixture() -def upload_documents_metadata(upload_document_metadata): - def _upload_documents_metadata(_qty: int = 1): - return [ - upload_document_metadata(name=f"{i}.test", s3_url=f"s3://{settings.s3_test_bucket}/{i}.test", status="private") - for i in range(_qty) - ] - - return _upload_documents_metadata diff --git a/app/tests/repositories_tests/__init__.py b/app/tests/repositories_tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/tests/repositories_tests/test_document_metadata.py b/app/tests/repositories_tests/test_document_metadata.py deleted file mode 100644 index 328756b..0000000 --- a/app/tests/repositories_tests/test_document_metadata.py +++ /dev/null @@ -1,70 +0,0 @@ -import pytest -from uuid import UUID, uuid4 - -from sqlalchemy.ext.asyncio import AsyncSession -from starlette import status - -from app.core.exceptions import HTTP_404 -from app.db.repositories.documents.documents_metadata import DocumentMetadataRepository - - -@pytest.mark.asyncio -async def test_upload_document_metadata(async_client: AsyncSession, upload_document_metadata): - document = upload_document_metadata() - repository = DocumentMetadataRepository(async_client) - - response = await async_client.post( - "/api/document-metadata/documents", - json=document.dict() - ) - - assert response.status_code == status.HTTP_201_CREATED - assert response.json()["name"] == document.name - assert response.json()["s3_url"] == document.s3_url - assert UUID(response.json()["_id"]) - - -@pytest.mark.asyncio -async def test_get_documents_metadata(async_client: AsyncSession, upload_document_metadata): - document = upload_document_metadata() - repository = DocumentMetadataRepository(async_client) - await repository.upload(document) - - response = await repository.doc_list() - - assert isinstance(response, list) - assert response[0].name == document.name - assert response[0].s3_url == document.s3_url - - -@pytest.mark.asyncio -async def test_get_document_metadata(async_client: AsyncSession, upload_document_metadata): - document = upload_document_metadata() - repository = DocumentMetadataRepository(async_client) - - document_uploaded = await repository.upload(document) - response = await repository.get(document=document.id) - - assert document_uploaded == response - - -@pytest.mark.asyncio -async def test_get_document_metadata_not_found(async_client: AsyncSession): - repository = DocumentMetadataRepository(async_client) - - with pytest.raises(expected_exception=HTTP_404()): - await repository.get(document=uuid4()) - - -@pytest.mark.asyncio -async def test_soft_delete_transaction(async_client: AsyncSession, upload_document_metadata): - document = upload_document_metadata() - repository = DocumentMetadataRepository(async_client) - - response = await repository.upload(document) - - delete_response = await repository.delete(document=response.id) - - assert delete_response is None - with pytest.raises(expected_exception=HTTP_404()): - await repository.get(document=response.id) diff --git a/app/tests/routes_tests/__init__.py b/app/tests/routes_tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/tests/routes_tests/test_document_metadata.py b/app/tests/routes_tests/test_document_metadata.py deleted file mode 100644 index 571708b..0000000 --- a/app/tests/routes_tests/test_document_metadata.py +++ /dev/null @@ -1,100 +0,0 @@ -from uuid import UUID - -import pytest - -from fastapi import status - -from app.db.repositories.documents.documents_metadata import DocumentMetadataRepository - - -@pytest.mark.asyncio -async def test_get_documents_metadata(async_client): - response = await async_client.get("/api/document_metadata/documents-metadata") - - assert response.status_code == status.HTTP_200_OK - assert response.json() == 0 - - -@pytest.mark.asyncio -async def test_upload_document_metadata(async_client, upload_document_metadata): - document = upload_document_metadata() - response = await async_client.post( - "/api/document_metadata/upload-document-metadata", - json=document.dict() - ) - - assert response.status_code == status.HTTP_201_CREATED - assert response.json()["name"] == document.name - assert response.json()["s3_url"] == document.s3_url - assert UUID(response.json()["_id"]) - - -@pytest.mark.asyncio -async def test_get_document_metadata(async_client, upload_document_metadata): - document = upload_document_metadata() - response_create = await async_client.post( - "/api/document-metadata/documents", - json=document.dict() - ) - response = await async_client.get( - f"/api/document-metadata/documents/{response_create.json()['_id']}" - ) - - assert response.status_code == status.HTTP_200_OK - assert response.json()["name"] == document.name - assert response.json()["s3_url"] == document.s3_url - assert response.json()["_id"] == response_create.json()["_id"] - - -@pytest.mark.asyncio -async def test_delete_document_metadata(async_client, upload_document_metadata): - document = upload_document_metadata() - response_create = await async_client.post( - "/api/document-metadata/documents", - json=document.dict() - ) - response = await async_client.delete( - f"/api/document-metadata/documents/{response_create.json()['_id']}" - ) - - assert response.status_code == status.HTTP_204_NO_CONTENT - - -@pytest.mark.asyncio -async def test_update_document_metadata(async_client, upload_document_metadata): - document = upload_document_metadata(name="test_update.pdf", s3_url="s3://docflow-test/test_update.pdf") - response_create = await async_client.post( - "/api/document-metadata/documents", - json=document.dict() - ) - - new_name = "new_test_update.pdf" - new_s3_url = "s3://docflow-test/new_test_update.pdf" - response = await async_client.post( - "/api/document-metadata/documents", - json={ - "name": new_name, - "s3_url": new_s3_url, - } - ) - - assert response.status_code == status.HTTP_200_OK - assert response.json()["name"] == new_name - assert response.json()["s3_url"] == new_s3_url - assert response.json()["_id"] == response_create.json()["_id"] - - -@pytest.mark.asyncio -async def test_get_document_paginated(db_session, async_client, upload_document_client): - repository = DocumentMetadataRepository(db_session) - for document in upload_document_client(_qty=4): - await (repository.upload(document)) - - response_page_1 = await async_client.get("/api/document-metadata/documents?limit=2&offset=2") - assert len(response_page_1.json()) == 2 - - response_page_2 = await async_client.get("/api/document-metadata/documents?limit=2&offset=2") - assert len(response_page_2.json()) == 2 - - response = await async_client.get("/api/document_metadata/documents") - assert len(response.json()) == 4 diff --git a/app/tests/schemas_tests/__init__.py b/app/tests/schemas_tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/tests/schemas_tests/test_documents_metadat.py b/app/tests/schemas_tests/test_documents_metadat.py deleted file mode 100644 index e3a1503..0000000 --- a/app/tests/schemas_tests/test_documents_metadat.py +++ /dev/null @@ -1,24 +0,0 @@ -import pytest -from pydantic import ValidationError - -from app.schemas.documents.documents_metadata import DocumentMetadataCreate - - -def test_document_metadata_instance_empty(): - with pytest.raises(expected_exception=ValidationError): - DocumentMetadataCreate() - - -def test_document_metadata_instance_name_empty(): - with pytest.raises(expected_exception=ValidationError): - DocumentMetadataCreate(s3_url="s3://docflow-test/name.test") - - -def test_document_metadata_instance_s3_url_empty(): - with pytest.raises(expected_exception=ValidationError): - DocumentMetadataCreate(name="name.test") - - -def test_document_metadata_instance_s3_url_wrong(): - with pytest.raises(expected_exception=ValidationError): - DocumentMetadataCreate(name="name.test", s3_url="s3://docflow-test") diff --git a/migrations/env.py b/migrations/env.py index 21f3484..a5b9bbe 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -8,6 +8,10 @@ from app.db.models import Base from app.core.config import settings +from app.db.tables.documents.documents_metadata import DocumentMetadata +from app.db.tables.auth.auth import User +from app.db.tables.documents.document_sharing import DocumentSharing +from app.db.tables.documents.notify import Notify # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -21,7 +25,6 @@ # add your model's MetaData object here # for 'autogenerate' support -from app.db.tables.documents.document_sharing import DocumentSharing target_metadata = Base.metadata From cfa22b915f0a71adbfff294d111333d9296468cd Mon Sep 17 00:00:00 2001 From: jiisanda Date: Sun, 15 Dec 2024 03:38:15 +0530 Subject: [PATCH 08/10] disable not-important --- .github/workflows/pylint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 52556f3..977aaa7 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,4 +21,4 @@ jobs: pip install -r requirements.txt - name: Analysing the code with pylint run: | - pylint $(git ls-files '*.py') --disable=E0401,C0116,C0114,C0115,C0301 + pylint $(git ls-files '*.py') --disable=E0401,C0116,C0114,C0115,C0301,R0913,R0917,R0903,C0103,E1101 From 21b98b11f0d7482b214afa7ff1c0f47522131cb1 Mon Sep 17 00:00:00 2001 From: jiisanda Date: Sun, 15 Dec 2024 03:43:55 +0530 Subject: [PATCH 09/10] =?UTF-8?q?=F0=9F=94=A5=20pylint=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pylint.yml | 2 +- app/api/routes/documents/notify.py | 11 +++++------ app/db/repositories/documents/documents_metadata.py | 7 +++---- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 977aaa7..e6bc066 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -21,4 +21,4 @@ jobs: pip install -r requirements.txt - name: Analysing the code with pylint run: | - pylint $(git ls-files '*.py') --disable=E0401,C0116,C0114,C0115,C0301,R0913,R0917,R0903,C0103,E1101 + pylint $(git ls-files '*.py') --disable=E0401,C0116,C0114,C0115,C0301,R0913,R0914,R0917,R0903,C0103,E1101,W0611 diff --git a/app/api/routes/documents/notify.py b/app/api/routes/documents/notify.py index a4d9b75..03a1ac9 100644 --- a/app/api/routes/documents/notify.py +++ b/app/api/routes/documents/notify.py @@ -68,13 +68,12 @@ async def patch_status( if updated_status.mark_all: return await repository.mark_all_read(user=user) - elif notification_id: + if notification_id: return await repository.update_status(n_id=notification_id, updated_status=updated_status, user=user) - else: - raise http_404( - msg="Bad Request: Make sure to either flag mark_all " - "or enter notification_id along with correct status as payload." - ) + raise http_404( + msg="Bad Request: Make sure to either flag mark_all " + "or enter notification_id along with correct status as payload." + ) @router.delete( diff --git a/app/db/repositories/documents/documents_metadata.py b/app/db/repositories/documents/documents_metadata.py index e9770a6..c660218 100644 --- a/app/db/repositories/documents/documents_metadata.py +++ b/app/db/repositories/documents/documents_metadata.py @@ -274,10 +274,9 @@ async def restore(self, file: str, owner: TokenData) -> DocumentMetadataRead: raise http_409( msg="Doc is not deleted" ) - else: - raise http_404( - msg="Doc does not exists" - ) + raise http_404( + msg="Doc does not exists" + ) async def perm_delete_a_doc(self, document: UUID | None, owner: TokenData) -> None: From 97b910f72e49dce6449d2b064baa6b9d4cb30862 Mon Sep 17 00:00:00 2001 From: jiisanda Date: Sun, 15 Dec 2024 03:44:24 +0530 Subject: [PATCH 10/10] =?UTF-8?q?=F0=9F=94=A5=20pylint=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/db/repositories/documents/document_sharing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/db/repositories/documents/document_sharing.py b/app/db/repositories/documents/document_sharing.py index 5bc3169..8edd9e9 100644 --- a/app/db/repositories/documents/document_sharing.py +++ b/app/db/repositories/documents/document_sharing.py @@ -176,7 +176,7 @@ async def send_mail( user_mail = await self.get_user_mail(user) subj = f"DocFlow: {user.username} share a document" content = f""" - Visit the link: {link}, to access the document + Visit the link: {link}, to access the document shared by {user.username} | {user_mail}. """