From 0cc802eec8a5e61b52210f38d9e53a95d627b293 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 25 Feb 2024 19:39:33 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Format=20files=20with=20pre-comm?= =?UTF-8?q?it=20and=20Ruff=20(#611)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 2 +- src/backend/app/alembic/README | 2 +- src/backend/app/alembic/env.py | 8 +-- .../e2412789c190_initialize_models.py | 52 +++++++++++-------- src/backend/app/api/api_v1/endpoints/items.py | 6 +-- src/backend/app/api/api_v1/endpoints/users.py | 8 ++- src/backend/app/api/deps.py | 3 +- src/backend/app/core/config.py | 32 ++++++------ src/backend/app/core/security.py | 6 +-- src/backend/app/crud/__init__.py | 19 ++++--- src/backend/app/crud/base.py | 8 +-- src/backend/app/crud/crud_item.py | 4 +- src/backend/app/crud/crud_user.py | 8 +-- .../email-templates/build/new_account.html | 2 +- .../email-templates/build/reset_password.html | 2 +- .../app/email-templates/build/test_email.html | 2 +- src/backend/app/models.py | 30 +++++------ src/backend/app/schemas/item.py | 6 +-- src/backend/app/schemas/token.py | 4 +- src/backend/app/schemas/user.py | 12 ++--- .../app/tests/api/api_v1/test_celery.py | 4 +- .../app/tests/api/api_v1/test_items.py | 7 ++- .../app/tests/api/api_v1/test_login.py | 7 ++- .../app/tests/api/api_v1/test_users.py | 23 ++++---- src/backend/app/tests/conftest.py | 6 +-- src/backend/app/tests/utils/item.py | 4 +- src/backend/app/tests/utils/user.py | 6 +-- src/backend/app/tests/utils/utils.py | 3 +- src/backend/app/utils.py | 19 ++++--- src/docker-compose.override.yml | 2 +- src/docker-compose.yml | 12 ++--- src/frontend/.nvmrc | 2 +- src/frontend/babel.config.js | 2 +- src/frontend/nginx.conf | 4 +- .../main/profile/UserProfileEditPassword.vue | 2 +- 35 files changed, 156 insertions(+), 163 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index aabe188..491acb1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,7 +27,7 @@ jobs: - name: Run tests run: docker compose build - - name: Docker Compose remove old containers and volumes + - name: Docker Compose remove old containers and volumes run: docker compose down -v --remove-orphans - name: Docker Compose up run: docker compose up -d diff --git a/src/backend/app/alembic/README b/src/backend/app/alembic/README index 98e4f9c..2500aa1 100755 --- a/src/backend/app/alembic/README +++ b/src/backend/app/alembic/README @@ -1 +1 @@ -Generic single-database configuration. \ No newline at end of file +Generic single-database configuration. diff --git a/src/backend/app/alembic/env.py b/src/backend/app/alembic/env.py index 3e002a2..c146cf6 100755 --- a/src/backend/app/alembic/env.py +++ b/src/backend/app/alembic/env.py @@ -1,10 +1,8 @@ -from __future__ import with_statement - import os +from logging.config import fileConfig from alembic import context from sqlalchemy import engine_from_config, pool -from logging.config import fileConfig # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -69,7 +67,9 @@ def run_migrations_online(): configuration = config.get_section(config.config_ini_section) configuration["sqlalchemy.url"] = get_url() connectable = engine_from_config( - configuration, prefix="sqlalchemy.", poolclass=pool.NullPool, + configuration, + prefix="sqlalchemy.", + poolclass=pool.NullPool, ) with connectable.connect() as connection: diff --git a/src/backend/app/alembic/versions/e2412789c190_initialize_models.py b/src/backend/app/alembic/versions/e2412789c190_initialize_models.py index 7232ed4..7529ea9 100644 --- a/src/backend/app/alembic/versions/e2412789c190_initialize_models.py +++ b/src/backend/app/alembic/versions/e2412789c190_initialize_models.py @@ -1,17 +1,16 @@ """Initialize models Revision ID: e2412789c190 -Revises: +Revises: Create Date: 2023-11-24 22:55:43.195942 """ -from alembic import op import sqlalchemy as sa import sqlmodel.sql.sqltypes - +from alembic import op # revision identifiers, used by Alembic. -revision = 'e2412789c190' +revision = "e2412789c190" down_revision = None branch_labels = None depends_on = None @@ -19,30 +18,37 @@ depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('user', - sa.Column('email', sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), - sa.Column('is_superuser', sa.Boolean(), nullable=False), - sa.Column('full_name', sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('hashed_password', sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.PrimaryKeyConstraint('id') + op.create_table( + "user", + sa.Column("email", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("is_active", sa.Boolean(), nullable=False), + sa.Column("is_superuser", sa.Boolean(), nullable=False), + sa.Column("full_name", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column( + "hashed_password", sqlmodel.sql.sqltypes.AutoString(), nullable=False + ), + sa.PrimaryKeyConstraint("id"), ) - op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True) - op.create_table('item', - sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('title', sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column('owner_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['owner_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') + op.create_index(op.f("ix_user_email"), "user", ["email"], unique=True) + op.create_table( + "item", + sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True), + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("title", sqlmodel.sql.sqltypes.AutoString(), nullable=False), + sa.Column("owner_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["owner_id"], + ["user.id"], + ), + sa.PrimaryKeyConstraint("id"), ) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('item') - op.drop_index(op.f('ix_user_email'), table_name='user') - op.drop_table('user') + op.drop_table("item") + op.drop_index(op.f("ix_user_email"), table_name="user") + op.drop_table("user") # ### end Alembic commands ### diff --git a/src/backend/app/api/api_v1/endpoints/items.py b/src/backend/app/api/api_v1/endpoints/items.py index 9878c09..ddbe93e 100644 --- a/src/backend/app/api/api_v1/endpoints/items.py +++ b/src/backend/app/api/api_v1/endpoints/items.py @@ -1,10 +1,10 @@ from typing import Any from fastapi import APIRouter, HTTPException -from sqlmodel import select, func +from sqlmodel import func, select from app.api.deps import CurrentUser, SessionDep -from app.models import Item, ItemCreate, ItemOut, ItemUpdate, Message, ItemsOut +from app.models import Item, ItemCreate, ItemOut, ItemsOut, ItemUpdate, Message router = APIRouter() @@ -22,7 +22,7 @@ def read_items( if current_user.is_superuser: statement = select(Item).offset(skip).limit(limit) - items = session.exec(statement).all() + items = session.exec(statement).all() else: statement = ( select(Item) diff --git a/src/backend/app/api/api_v1/endpoints/users.py b/src/backend/app/api/api_v1/endpoints/users.py index 0e71f1f..0b9a511 100644 --- a/src/backend/app/api/api_v1/endpoints/users.py +++ b/src/backend/app/api/api_v1/endpoints/users.py @@ -1,7 +1,7 @@ -from typing import Any, List +from typing import Any from fastapi import APIRouter, Depends, HTTPException -from sqlmodel import select, func +from sqlmodel import func, select from app import crud from app.api.deps import ( @@ -28,9 +28,7 @@ router = APIRouter() @router.get( - "/", - dependencies=[Depends(get_current_active_superuser)], - response_model=UsersOut + "/", dependencies=[Depends(get_current_active_superuser)], response_model=UsersOut ) def read_users(session: SessionDep, skip: int = 0, limit: int = 100) -> Any: """ diff --git a/src/backend/app/api/deps.py b/src/backend/app/api/deps.py index abe067b..1810ce0 100644 --- a/src/backend/app/api/deps.py +++ b/src/backend/app/api/deps.py @@ -1,4 +1,5 @@ -from typing import Annotated, Generator +from collections.abc import Generator +from typing import Annotated from fastapi import Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer diff --git a/src/backend/app/core/config.py b/src/backend/app/core/config.py index 5aa0f83..abc96e5 100644 --- a/src/backend/app/core/config.py +++ b/src/backend/app/core/config.py @@ -1,5 +1,5 @@ import secrets -from typing import Any, Dict, List, Optional, Union +from typing import Any from pydantic import AnyHttpUrl, BaseSettings, EmailStr, HttpUrl, PostgresDsn, validator @@ -14,21 +14,21 @@ class Settings(BaseSettings): # BACKEND_CORS_ORIGINS is a JSON-formatted list of origins # e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \ # "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]' - BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = [] + BACKEND_CORS_ORIGINS: list[AnyHttpUrl] = [] @validator("BACKEND_CORS_ORIGINS", pre=True) - def assemble_cors_origins(cls, v: Union[str, List[str]]) -> Union[List[str], str]: + def assemble_cors_origins(cls, v: str | list[str]) -> list[str] | str: if isinstance(v, str) and not v.startswith("["): return [i.strip() for i in v.split(",")] - elif isinstance(v, (list, str)): + elif isinstance(v, list | str): return v raise ValueError(v) PROJECT_NAME: str - SENTRY_DSN: Optional[HttpUrl] = None + SENTRY_DSN: HttpUrl | None = None @validator("SENTRY_DSN", pre=True) - def sentry_dsn_can_be_blank(cls, v: str) -> Optional[str]: + def sentry_dsn_can_be_blank(cls, v: str) -> str | None: if len(v) == 0: return None return v @@ -37,10 +37,10 @@ class Settings(BaseSettings): POSTGRES_USER: str POSTGRES_PASSWORD: str POSTGRES_DB: str - SQLALCHEMY_DATABASE_URI: Optional[PostgresDsn] = None + SQLALCHEMY_DATABASE_URI: PostgresDsn | None = None @validator("SQLALCHEMY_DATABASE_URI", pre=True) - def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any: + def assemble_db_connection(cls, v: str | None, values: dict[str, Any]) -> Any: if isinstance(v, str): return v return PostgresDsn.build( @@ -52,15 +52,15 @@ class Settings(BaseSettings): ) SMTP_TLS: bool = True - SMTP_PORT: Optional[int] = None - SMTP_HOST: Optional[str] = None - SMTP_USER: Optional[str] = None - SMTP_PASSWORD: Optional[str] = None - EMAILS_FROM_EMAIL: Optional[EmailStr] = None - EMAILS_FROM_NAME: Optional[str] = None + SMTP_PORT: int | None = None + SMTP_HOST: str | None = None + SMTP_USER: str | None = None + SMTP_PASSWORD: str | None = None + EMAILS_FROM_EMAIL: EmailStr | None = None + EMAILS_FROM_NAME: str | None = None @validator("EMAILS_FROM_NAME") - def get_project_name(cls, v: Optional[str], values: Dict[str, Any]) -> str: + def get_project_name(cls, v: str | None, values: dict[str, Any]) -> str: if not v: return values["PROJECT_NAME"] return v @@ -70,7 +70,7 @@ class Settings(BaseSettings): EMAILS_ENABLED: bool = False @validator("EMAILS_ENABLED", pre=True) - def get_emails_enabled(cls, v: bool, values: Dict[str, Any]) -> bool: + def get_emails_enabled(cls, v: bool, values: dict[str, Any]) -> bool: return bool( values.get("SMTP_HOST") and values.get("SMTP_PORT") diff --git a/src/backend/app/core/security.py b/src/backend/app/core/security.py index 6c6ee8b..c9e5085 100644 --- a/src/backend/app/core/security.py +++ b/src/backend/app/core/security.py @@ -1,5 +1,5 @@ from datetime import datetime, timedelta -from typing import Any, Union +from typing import Any from jose import jwt from passlib.context import CryptContext @@ -12,9 +12,7 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") ALGORITHM = "HS256" -def create_access_token( - subject: Union[str, Any], expires_delta: timedelta = None -) -> str: +def create_access_token(subject: str | Any, expires_delta: timedelta = None) -> str: if expires_delta: expire = datetime.utcnow() + expires_delta else: diff --git a/src/backend/app/crud/__init__.py b/src/backend/app/crud/__init__.py index d452138..ee949e5 100644 --- a/src/backend/app/crud/__init__.py +++ b/src/backend/app/crud/__init__.py @@ -1,16 +1,15 @@ -from .crud_item import item -from .crud_user import user - # For a new basic set of CRUD operations you could just do - # from .base import CRUDBase # from app.models.item import Item # from app.schemas.item import ItemCreate, ItemUpdate - # item = CRUDBase[Item, ItemCreate, ItemUpdate](Item) from sqlmodel import Session, select + from app.core.security import get_password_hash, verify_password -from app.models import UserCreate, User +from app.models import User, UserCreate + +from .crud_item import item as item +from .crud_user import user as user def create_user(*, session: Session, user_create: UserCreate) -> User: @@ -30,9 +29,9 @@ def get_user_by_email(*, session: Session, email: str) -> User | None: def authenticate(*, session: Session, email: str, password: str) -> User | None: - user = get_user_by_email(session=session, email=email) - if not user: + db_user = get_user_by_email(session=session, email=email) + if not db_user: return None - if not verify_password(password, user.hashed_password): + if not verify_password(password, db_user.hashed_password): return None - return user + return db_user diff --git a/src/backend/app/crud/base.py b/src/backend/app/crud/base.py index da93c49..6556e3c 100644 --- a/src/backend/app/crud/base.py +++ b/src/backend/app/crud/base.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Generic, List, Optional, Type, TypeVar, Union +from typing import Any, Generic, TypeVar from fastapi.encoders import jsonable_encoder from pydantic import BaseModel @@ -10,7 +10,7 @@ UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel) class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): - def __init__(self, model: Type[ModelType]): + def __init__(self, model: type[ModelType]): """ CRUD object with default methods to Create, Read, Update, Delete (CRUD). @@ -21,7 +21,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): """ self.model = model - def get(self, db: Session, id: Any) -> Optional[ModelType]: + def get(self, db: Session, id: Any) -> ModelType | None: return db.query(self.model).filter(self.model.id == id).first() def create(self, db: Session, *, obj_in: CreateSchemaType) -> ModelType: @@ -37,7 +37,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]): db: Session, *, db_obj: ModelType, - obj_in: Union[UpdateSchemaType, Dict[str, Any]] + obj_in: UpdateSchemaType | dict[str, Any], ) -> ModelType: obj_data = jsonable_encoder(db_obj) if isinstance(obj_in, dict): diff --git a/src/backend/app/crud/crud_item.py b/src/backend/app/crud/crud_item.py index c62d80e..02c5060 100644 --- a/src/backend/app/crud/crud_item.py +++ b/src/backend/app/crud/crud_item.py @@ -1,5 +1,3 @@ -from typing import List - from fastapi.encoders import jsonable_encoder from sqlalchemy.orm import Session @@ -21,7 +19,7 @@ class CRUDItem(CRUDBase[Item, ItemCreate, ItemUpdate]): def get_multi_by_owner( self, db: Session, *, owner_id: int, skip: int = 0, limit: int = 100 - ) -> List[Item]: + ) -> list[Item]: return ( db.query(self.model) .filter(Item.owner_id == owner_id) diff --git a/src/backend/app/crud/crud_user.py b/src/backend/app/crud/crud_user.py index 1f2509b..b444502 100644 --- a/src/backend/app/crud/crud_user.py +++ b/src/backend/app/crud/crud_user.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Union +from typing import Any from sqlalchemy.orm import Session @@ -9,7 +9,7 @@ from app.schemas.user import UserCreate, UserUpdate class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): - def get_by_email(self, db: Session, *, email: str) -> Optional[User]: + def get_by_email(self, db: Session, *, email: str) -> User | None: return db.query(User).filter(User.email == email).first() def create(self, db: Session, *, obj_in: UserCreate) -> User: @@ -25,7 +25,7 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): return db_obj def update( - self, db: Session, *, db_obj: User, obj_in: Union[UserUpdate, Dict[str, Any]] + self, db: Session, *, db_obj: User, obj_in: UserUpdate | dict[str, Any] ) -> User: if isinstance(obj_in, dict): update_data = obj_in @@ -37,7 +37,7 @@ class CRUDUser(CRUDBase[User, UserCreate, UserUpdate]): update_data["hashed_password"] = hashed_password return super().update(db, db_obj=db_obj, obj_in=update_data) - def authenticate(self, db: Session, *, email: str, password: str) -> Optional[User]: + def authenticate(self, db: Session, *, email: str, password: str) -> User | None: user = self.get_by_email(db, email=email) if not user: return None diff --git a/src/backend/app/email-templates/build/new_account.html b/src/backend/app/email-templates/build/new_account.html index 395c7bd..9d82b56 100644 --- a/src/backend/app/email-templates/build/new_account.html +++ b/src/backend/app/email-templates/build/new_account.html @@ -23,4 +23,4 @@ .mj-column-per-100 { width:100% !important; max-width: 100%; } }

{{ project_name }} - New Account
You have a new account:
Username: {{ username }}
Password: {{ password }}
Go to Dashboard

\ No newline at end of file + diff --git a/src/backend/app/email-templates/build/reset_password.html b/src/backend/app/email-templates/build/reset_password.html index 7fbf368..888ce3b 100644 --- a/src/backend/app/email-templates/build/reset_password.html +++ b/src/backend/app/email-templates/build/reset_password.html @@ -23,4 +23,4 @@ .mj-column-per-100 { width:100% !important; max-width: 100%; } }

{{ project_name }} - Password Recovery
We received a request to recover the password for user {{ username }} with email {{ email }}
Reset your password by clicking the button below:
Reset Password
Or open the following link:

The reset password link / button will expire in {{ valid_hours }} hours.
If you didn't request a password recovery you can disregard this email.
\ No newline at end of file +
The reset password link / button will expire in {{ valid_hours }} hours.
If you didn't request a password recovery you can disregard this email.
diff --git a/src/backend/app/email-templates/build/test_email.html b/src/backend/app/email-templates/build/test_email.html index 294d576..da78c57 100644 --- a/src/backend/app/email-templates/build/test_email.html +++ b/src/backend/app/email-templates/build/test_email.html @@ -22,4 +22,4 @@

{{ project_name }}
Test email for: {{ email }}
\ No newline at end of file +
{{ project_name }}
Test email for: {{ email }}
diff --git a/src/backend/app/models.py b/src/backend/app/models.py index 5331a96..1b837ef 100644 --- a/src/backend/app/models.py +++ b/src/backend/app/models.py @@ -1,5 +1,3 @@ -from typing import Union - from pydantic import EmailStr from sqlmodel import Field, Relationship, SQLModel @@ -9,7 +7,7 @@ class UserBase(SQLModel): email: EmailStr = Field(unique=True, index=True) is_active: bool = True is_superuser: bool = False - full_name: Union[str, None] = None + full_name: str | None = None # Properties to receive via API on creation @@ -20,18 +18,18 @@ class UserCreate(UserBase): class UserCreateOpen(SQLModel): email: EmailStr password: str - full_name: Union[str, None] = None + full_name: str | None = None # Properties to receive via API on update, all are optional class UserUpdate(UserBase): - email: Union[EmailStr, None] = None - password: Union[str, None] = None + email: EmailStr | None = None + password: str | None = None class UserUpdateMe(SQLModel): - full_name: Union[str, None] = None - email: Union[EmailStr, None] = None + full_name: str | None = None + email: EmailStr | None = None class UpdatePassword(SQLModel): @@ -41,7 +39,7 @@ class UpdatePassword(SQLModel): # Database model, database table inferred from class name class User(UserBase, table=True): - id: Union[int, None] = Field(default=None, primary_key=True) + id: int | None = Field(default=None, primary_key=True) hashed_password: str items: list["Item"] = Relationship(back_populates="owner") @@ -59,7 +57,7 @@ class UsersOut(SQLModel): # Shared properties class ItemBase(SQLModel): title: str - description: Union[str, None] = None + description: str | None = None # Properties to receive on item creation @@ -69,17 +67,15 @@ class ItemCreate(ItemBase): # Properties to receive on item update class ItemUpdate(ItemBase): - title: Union[str, None] = None + title: str | None = None # Database model, database table inferred from class name class Item(ItemBase, table=True): - id: Union[int, None] = Field(default=None, primary_key=True) + id: int | None = Field(default=None, primary_key=True) title: str - owner_id: Union[int, None] = Field( - default=None, foreign_key="user.id", nullable=False - ) - owner: Union[User, None] = Relationship(back_populates="items") + owner_id: int | None = Field(default=None, foreign_key="user.id", nullable=False) + owner: User | None = Relationship(back_populates="items") # Properties to return via API, id is always required @@ -106,7 +102,7 @@ class Token(SQLModel): # Contents of JWT token class TokenPayload(SQLModel): - sub: Union[int, None] = None + sub: int | None = None class NewPassword(SQLModel): diff --git a/src/backend/app/schemas/item.py b/src/backend/app/schemas/item.py index ac992cf..5d3e691 100644 --- a/src/backend/app/schemas/item.py +++ b/src/backend/app/schemas/item.py @@ -1,12 +1,10 @@ -from typing import Optional - from pydantic import BaseModel # Shared properties class ItemBase(BaseModel): - title: Optional[str] = None - description: Optional[str] = None + title: str | None = None + description: str | None = None # Properties to receive on item creation diff --git a/src/backend/app/schemas/token.py b/src/backend/app/schemas/token.py index ea85b46..17c7402 100644 --- a/src/backend/app/schemas/token.py +++ b/src/backend/app/schemas/token.py @@ -1,5 +1,3 @@ -from typing import Optional - from pydantic import BaseModel @@ -9,4 +7,4 @@ class Token(BaseModel): class TokenPayload(BaseModel): - sub: Optional[int] = None + sub: int | None = None diff --git a/src/backend/app/schemas/user.py b/src/backend/app/schemas/user.py index 7f5c85a..0601e8d 100644 --- a/src/backend/app/schemas/user.py +++ b/src/backend/app/schemas/user.py @@ -1,14 +1,12 @@ -from typing import Optional - from pydantic import BaseModel, EmailStr # Shared properties class UserBase(BaseModel): - email: Optional[EmailStr] = None - is_active: Optional[bool] = True + email: EmailStr | None = None + is_active: bool | None = True is_superuser: bool = False - full_name: Optional[str] = None + full_name: str | None = None # Properties to receive via API on creation @@ -19,11 +17,11 @@ class UserCreate(UserBase): # Properties to receive via API on update class UserUpdate(UserBase): - password: Optional[str] = None + password: str | None = None class UserInDBBase(UserBase): - id: Optional[int] = None + id: int | None = None class Config: orm_mode = True diff --git a/src/backend/app/tests/api/api_v1/test_celery.py b/src/backend/app/tests/api/api_v1/test_celery.py index 8097306..bb1becd 100644 --- a/src/backend/app/tests/api/api_v1/test_celery.py +++ b/src/backend/app/tests/api/api_v1/test_celery.py @@ -1,12 +1,10 @@ -from typing import Dict - from fastapi.testclient import TestClient from app.core.config import settings def test_celery_worker_test( - client: TestClient, superuser_token_headers: Dict[str, str] + client: TestClient, superuser_token_headers: dict[str, str] ) -> None: data = {"message": "test"} r = client.post( diff --git a/src/backend/app/tests/api/api_v1/test_items.py b/src/backend/app/tests/api/api_v1/test_items.py index 6d799b6..f9abdcb 100644 --- a/src/backend/app/tests/api/api_v1/test_items.py +++ b/src/backend/app/tests/api/api_v1/test_items.py @@ -10,7 +10,9 @@ def test_create_item( ) -> None: data = {"title": "Foo", "description": "Fighters"} response = client.post( - f"{settings.API_V1_STR}/items/", headers=superuser_token_headers, json=data, + f"{settings.API_V1_STR}/items/", + headers=superuser_token_headers, + json=data, ) assert response.status_code == 200 content = response.json() @@ -25,7 +27,8 @@ def test_read_item( ) -> None: item = create_random_item(db) response = client.get( - f"{settings.API_V1_STR}/items/{item.id}", headers=superuser_token_headers, + f"{settings.API_V1_STR}/items/{item.id}", + headers=superuser_token_headers, ) assert response.status_code == 200 content = response.json() diff --git a/src/backend/app/tests/api/api_v1/test_login.py b/src/backend/app/tests/api/api_v1/test_login.py index fd2c65a..a8b3322 100644 --- a/src/backend/app/tests/api/api_v1/test_login.py +++ b/src/backend/app/tests/api/api_v1/test_login.py @@ -1,5 +1,3 @@ -from typing import Dict - from fastapi.testclient import TestClient from app.core.config import settings @@ -18,10 +16,11 @@ def test_get_access_token(client: TestClient) -> None: def test_use_access_token( - client: TestClient, superuser_token_headers: Dict[str, str] + client: TestClient, superuser_token_headers: dict[str, str] ) -> None: r = client.post( - f"{settings.API_V1_STR}/login/test-token", headers=superuser_token_headers, + f"{settings.API_V1_STR}/login/test-token", + headers=superuser_token_headers, ) result = r.json() assert r.status_code == 200 diff --git a/src/backend/app/tests/api/api_v1/test_users.py b/src/backend/app/tests/api/api_v1/test_users.py index 44d97f6..adc97e0 100644 --- a/src/backend/app/tests/api/api_v1/test_users.py +++ b/src/backend/app/tests/api/api_v1/test_users.py @@ -1,5 +1,3 @@ -from typing import Dict - from fastapi.testclient import TestClient from sqlalchemy.orm import Session @@ -10,7 +8,7 @@ from app.tests.utils.utils import random_email, random_lower_string def test_get_users_superuser_me( - client: TestClient, superuser_token_headers: Dict[str, str] + client: TestClient, superuser_token_headers: dict[str, str] ) -> None: r = client.get(f"{settings.API_V1_STR}/users/me", headers=superuser_token_headers) current_user = r.json() @@ -21,7 +19,7 @@ def test_get_users_superuser_me( def test_get_users_normal_user_me( - client: TestClient, normal_user_token_headers: Dict[str, str] + client: TestClient, normal_user_token_headers: dict[str, str] ) -> None: r = client.get(f"{settings.API_V1_STR}/users/me", headers=normal_user_token_headers) current_user = r.json() @@ -38,7 +36,9 @@ def test_create_user_new_email( password = random_lower_string() data = {"email": username, "password": password} r = client.post( - f"{settings.API_V1_STR}/users/", headers=superuser_token_headers, json=data, + f"{settings.API_V1_STR}/users/", + headers=superuser_token_headers, + json=data, ) assert 200 <= r.status_code < 300 created_user = r.json() @@ -56,7 +56,8 @@ def test_get_existing_user( user = crud.user.create(db, obj_in=user_in) user_id = user.id r = client.get( - f"{settings.API_V1_STR}/users/{user_id}", headers=superuser_token_headers, + f"{settings.API_V1_STR}/users/{user_id}", + headers=superuser_token_headers, ) assert 200 <= r.status_code < 300 api_user = r.json() @@ -75,7 +76,9 @@ def test_create_user_existing_username( crud.user.create(db, obj_in=user_in) data = {"email": username, "password": password} r = client.post( - f"{settings.API_V1_STR}/users/", headers=superuser_token_headers, json=data, + f"{settings.API_V1_STR}/users/", + headers=superuser_token_headers, + json=data, ) created_user = r.json() assert r.status_code == 400 @@ -83,13 +86,15 @@ def test_create_user_existing_username( def test_create_user_by_normal_user( - client: TestClient, normal_user_token_headers: Dict[str, str] + client: TestClient, normal_user_token_headers: dict[str, str] ) -> None: username = random_email() password = random_lower_string() data = {"email": username, "password": password} r = client.post( - f"{settings.API_V1_STR}/users/", headers=normal_user_token_headers, json=data, + f"{settings.API_V1_STR}/users/", + headers=normal_user_token_headers, + json=data, ) assert r.status_code == 400 diff --git a/src/backend/app/tests/conftest.py b/src/backend/app/tests/conftest.py index 722b968..0c76fa9 100644 --- a/src/backend/app/tests/conftest.py +++ b/src/backend/app/tests/conftest.py @@ -1,4 +1,4 @@ -from typing import Dict, Generator +from collections.abc import Generator import pytest from fastapi.testclient import TestClient @@ -24,12 +24,12 @@ def client() -> Generator: @pytest.fixture(scope="module") -def superuser_token_headers(client: TestClient) -> Dict[str, str]: +def superuser_token_headers(client: TestClient) -> dict[str, str]: return get_superuser_token_headers(client) @pytest.fixture(scope="module") -def normal_user_token_headers(client: TestClient, db: Session) -> Dict[str, str]: +def normal_user_token_headers(client: TestClient, db: Session) -> dict[str, str]: return authentication_token_from_email( client=client, email=settings.EMAIL_TEST_USER, db=db ) diff --git a/src/backend/app/tests/utils/item.py b/src/backend/app/tests/utils/item.py index e28f967..1ba088a 100644 --- a/src/backend/app/tests/utils/item.py +++ b/src/backend/app/tests/utils/item.py @@ -1,5 +1,3 @@ -from typing import Optional - from sqlalchemy.orm import Session from app import crud, models @@ -8,7 +6,7 @@ from app.tests.utils.user import create_random_user from app.tests.utils.utils import random_lower_string -def create_random_item(db: Session, *, owner_id: Optional[int] = None) -> models.Item: +def create_random_item(db: Session, *, owner_id: int | None = None) -> models.Item: if owner_id is None: user = create_random_user(db) owner_id = user.id diff --git a/src/backend/app/tests/utils/user.py b/src/backend/app/tests/utils/user.py index 2d239fb..e08c0b2 100644 --- a/src/backend/app/tests/utils/user.py +++ b/src/backend/app/tests/utils/user.py @@ -1,5 +1,3 @@ -from typing import Dict - from fastapi.testclient import TestClient from sqlalchemy.orm import Session @@ -12,7 +10,7 @@ from app.tests.utils.utils import random_email, random_lower_string def user_authentication_headers( *, client: TestClient, email: str, password: str -) -> Dict[str, str]: +) -> dict[str, str]: data = {"username": email, "password": password} r = client.post(f"{settings.API_V1_STR}/login/access-token", data=data) @@ -32,7 +30,7 @@ def create_random_user(db: Session) -> User: def authentication_token_from_email( *, client: TestClient, email: str, db: Session -) -> Dict[str, str]: +) -> dict[str, str]: """ Return a valid token for the user with given email. diff --git a/src/backend/app/tests/utils/utils.py b/src/backend/app/tests/utils/utils.py index 021fc22..184bac4 100644 --- a/src/backend/app/tests/utils/utils.py +++ b/src/backend/app/tests/utils/utils.py @@ -1,6 +1,5 @@ import random import string -from typing import Dict from fastapi.testclient import TestClient @@ -15,7 +14,7 @@ def random_email() -> str: return f"{random_lower_string()}@{random_lower_string()}.com" -def get_superuser_token_headers(client: TestClient) -> Dict[str, str]: +def get_superuser_token_headers(client: TestClient) -> dict[str, str]: login_data = { "username": settings.FIRST_SUPERUSER, "password": settings.FIRST_SUPERUSER_PASSWORD, diff --git a/src/backend/app/utils.py b/src/backend/app/utils.py index b1aba6b..ec788ef 100644 --- a/src/backend/app/utils.py +++ b/src/backend/app/utils.py @@ -1,7 +1,7 @@ import logging from datetime import datetime, timedelta from pathlib import Path -from typing import Any, Dict, Optional +from typing import Any import emails from emails.template import JinjaTemplate @@ -14,8 +14,9 @@ def send_email( email_to: str, subject_template: str = "", html_template: str = "", - environment: Dict[str, Any] = {}, + environment: dict[str, Any] | None = None, ) -> None: + current_environment = environment or {} assert settings.EMAILS_ENABLED, "no provided configuration for email variables" message = emails.Message( subject=JinjaTemplate(subject_template), @@ -29,7 +30,7 @@ def send_email( smtp_options["user"] = settings.SMTP_USER if settings.SMTP_PASSWORD: smtp_options["password"] = settings.SMTP_PASSWORD - response = message.send(to=email_to, render=environment, smtp=smtp_options) + response = message.send(to=email_to, render=current_environment, smtp=smtp_options) logging.info(f"send email result: {response}") @@ -42,7 +43,7 @@ def send_test_email(email_to: str) -> None: email_to=email_to, subject_template=subject, html_template=template_str, - environment={"project_name": settings.PROJECT_NAME, "email": email_to}, + current_environment={"project_name": settings.PROJECT_NAME, "email": email_to}, ) @@ -57,7 +58,7 @@ def send_reset_password_email(email_to: str, email: str, token: str) -> None: email_to=email_to, subject_template=subject, html_template=template_str, - environment={ + current_environment={ "project_name": settings.PROJECT_NAME, "username": email, "email": email_to, @@ -77,7 +78,7 @@ def send_new_account_email(email_to: str, username: str, password: str) -> None: email_to=email_to, subject_template=subject, html_template=template_str, - environment={ + current_environment={ "project_name": settings.PROJECT_NAME, "username": username, "password": password, @@ -93,12 +94,14 @@ def generate_password_reset_token(email: str) -> str: expires = now + delta exp = expires.timestamp() encoded_jwt = jwt.encode( - {"exp": exp, "nbf": now, "sub": email}, settings.SECRET_KEY, algorithm="HS256", + {"exp": exp, "nbf": now, "sub": email}, + settings.SECRET_KEY, + algorithm="HS256", ) return encoded_jwt -def verify_password_reset_token(token: str) -> Optional[str]: +def verify_password_reset_token(token: str) -> str | None: try: decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) return decoded_token["email"] diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index 4dde2a4..91abca8 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -40,7 +40,7 @@ services: - "5672:5672" - "15672:15672" - "15671:15671" - + flower: ports: - "5555:5555" diff --git a/src/docker-compose.yml b/src/docker-compose.yml index dca0638..271dbf8 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -56,7 +56,7 @@ services: # Redirect a domain without www to www # To enable it remove the previous line and uncomment the next # - traefik.http.middlewares.${STACK_NAME}-www-redirect.redirectregex.replacement=https://www.${DOMAIN}/$${3} - # Middleware to redirect www, to disable it remove the next line + # Middleware to redirect www, to disable it remove the next line - traefik.http.routers.${STACK_NAME?Variable not set}-proxy-https.middlewares=${STACK_NAME?Variable not set}-www-redirect # Middleware to redirect www, and redirect HTTP to HTTPS # to disable www redirection remove the section: ${STACK_NAME?Variable not set}-www-redirect, @@ -99,7 +99,7 @@ services: # image: rabbitmq:3-management # # You also have to change the flower command - + flower: image: mher/flower:0.9.7 networks: @@ -124,7 +124,7 @@ services: - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.tls=true - traefik.http.routers.${STACK_NAME?Variable not set}-flower-https.tls.certresolver=le - traefik.http.services.${STACK_NAME?Variable not set}-flower.loadbalancer.server.port=5555 - + backend: image: '${DOCKER_IMAGE_BACKEND?Variable not set}:${TAG-latest}' depends_on: @@ -146,7 +146,7 @@ services: - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - traefik.http.routers.${STACK_NAME?Variable not set}-backend-http.rule=PathPrefix(`/api`) || PathPrefix(`/docs`) || PathPrefix(`/redoc`) - traefik.http.services.${STACK_NAME?Variable not set}-backend.loadbalancer.server.port=80 - + celeryworker: image: '${DOCKER_IMAGE_CELERYWORKER?Variable not set}:${TAG-latest}' depends_on: @@ -164,7 +164,7 @@ services: dockerfile: celeryworker.dockerfile args: INSTALL_DEV: ${INSTALL_DEV-false} - + frontend: image: '${DOCKER_IMAGE_FRONTEND?Variable not set}:${TAG-latest}' build: @@ -176,7 +176,7 @@ services: - traefik.constraint-label-stack=${TRAEFIK_TAG?Variable not set} - traefik.http.routers.${STACK_NAME?Variable not set}-frontend-http.rule=PathPrefix(`/`) - traefik.http.services.${STACK_NAME?Variable not set}-frontend.loadbalancer.server.port=80 - + # new-frontend: # image: '${DOCKER_IMAGE_NEW_FRONTEND?Variable not set}:${TAG-latest}' # build: diff --git a/src/frontend/.nvmrc b/src/frontend/.nvmrc index 72c7744..b460d6f 100644 --- a/src/frontend/.nvmrc +++ b/src/frontend/.nvmrc @@ -1 +1 @@ -18.12.1 \ No newline at end of file +18.12.1 diff --git a/src/frontend/babel.config.js b/src/frontend/babel.config.js index 5902d7d..07b43d2 100644 --- a/src/frontend/babel.config.js +++ b/src/frontend/babel.config.js @@ -7,4 +7,4 @@ module.exports = { } ] ] -} \ No newline at end of file +} diff --git a/src/frontend/nginx.conf b/src/frontend/nginx.conf index ed11d3a..fd4472f 100644 --- a/src/frontend/nginx.conf +++ b/src/frontend/nginx.conf @@ -1,11 +1,11 @@ server { listen 80; - + location / { root /usr/share/nginx/html; index index.html index.htm; try_files $uri $uri/ /index.html =404; } - + include /etc/nginx/extra-conf.d/*.conf; } diff --git a/src/frontend/src/views/main/profile/UserProfileEditPassword.vue b/src/frontend/src/views/main/profile/UserProfileEditPassword.vue index 80e2cc5..be69392 100644 --- a/src/frontend/src/views/main/profile/UserProfileEditPassword.vue +++ b/src/frontend/src/views/main/profile/UserProfileEditPassword.vue @@ -12,7 +12,7 @@
{{userProfile.email}}
-