diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 8eaf35d..0000000 --- a/.dockerignore +++ /dev/null @@ -1,7 +0,0 @@ -.venv/ -.vscode/ -.ops/.helm/ -.git/ -Dockerfile -*.Dockerfile -README.md diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 8506cb5..0000000 --- a/.editorconfig +++ /dev/null @@ -1,17 +0,0 @@ -root = true - -[*] -end_of_line = lf -insert_final_newline = true -indent_style = space -indent_size = 2 - -[*.py] -indent_size = 4 -line_length = 88 -known_first_party = app -multi_line_output = 3 -recursive = true -include_trailing_comma = true -force_grid_wrap = 0 -use_parentheses = true diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..85f2ca6 --- /dev/null +++ b/.env.template @@ -0,0 +1,23 @@ +# Base +SECRET_KEY=some-secret-key +PROJECT_NAME='Some Project Name' +SERVER_HOST=http://0.0.0.0:8881 +DEBUG=false + +# Databases +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_HOST=postgres +POSTGRES_DB=postgres +POSTGRES_TEST_DB=test +PGDATA=/var/lib/postgresql/data/pgdata + +# SMTP +SMTP_USER=some-email +SMTP_PASSWORD=some-password + +# Auth +FIRST_SUPERUSER_USERNAME=user@user.com +FIRST_SUPERUSER_PASSWORD=password +FIRST_SUPERUSER_FIRST_NAME='first name' +FIRST_SUPERUSER_LAST_NAME='last name' diff --git a/.flake8 b/.flake8 index 1db6598..0eadb76 100644 --- a/.flake8 +++ b/.flake8 @@ -1,5 +1,5 @@ [flake8] -max-line-length = 120 -exclude = .git,__pycache__,__init__.py,.mypy_cache,.pytest_cache,app/db/base.py -select = C,E,F,W,B,B950 -extend-ignore = E203, E501 +max-line-length = 88 +select = C,E,F,W,B,B9 +ignore = E203, E501, W503 +exclude = __init__.py,app/db/base.py diff --git a/.mypy.ini b/.mypy.ini deleted file mode 100644 index 47db4e7..0000000 --- a/.mypy.ini +++ /dev/null @@ -1,36 +0,0 @@ -[mypy] -plugins = pydantic.mypy, sqlmypy - - -; Output format options -error_summary = True -show_error_context = True -show_error_codes = True -color_output = True - -; Usual checks -warn_return_any = True -warn_unused_configs = True -warn_unused_ignores = True - -; Important checks -strict_equality = True -warn_unreachable = True - -; Additional moderate checks -warn_no_return = True - -; Additional strict checks -disallow_untyped_defs = True -no_implicit_optional = True - - -; Module-level whitelist -[mypy-alembic.*] -ignore_missing_imports = True -[mypy-asyncpg.exceptions.*] -ignore_missing_imports = True -[mypy-pytest.*] -ignore_missing_imports = True -[mypy-sqlalchemy_utils.*] -ignore_missing_imports = True diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 763c5b3..9d96ad7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,27 +1,45 @@ -exclude: 'node_modules|alembic|migrations|.git|.tox' -default_stages: [commit] - repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.0.1 + rev: v4.3.0 hooks: - - id: trailing-whitespace - - id: end-of-file-fixer - - id: check-yaml + - id: check-added-large-files - id: check-toml - - - repo: https://github.com/psf/black - rev: 22.3.0 + - id: check-yaml + args: + - --unsafe + - id: end-of-file-fixer + - id: trailing-whitespace + - repo: https://github.com/asottile/pyupgrade + rev: v2.37.1 hooks: - - id: black - - - repo: https://github.com/timothycrosley/isort - rev: 5.9.3 + - id: pyupgrade + args: + - --py39-plus + - --keep-runtime-typing + - repo: https://github.com/myint/autoflake + rev: v1.4 + hooks: + - id: autoflake + args: + - --recursive + - --in-place + - --remove-all-unused-imports + - --remove-unused-variables + - --exclude + - __init__.py,base.py,app/db/base.py + - --remove-duplicate-keys + - repo: https://github.com/pycqa/isort + rev: 5.10.1 hooks: - id: isort - - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 + args: + - --line-length=88 + - --profile + - black + - repo: https://github.com/psf/black + rev: 22.6.0 hooks: - - id: flake8 - additional_dependencies: [flake8-isort] + - id: black + args: + - --preview + - --line-length=88 diff --git a/Dockerfile b/Dockerfile index d1c9591..a2b676f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,11 @@ -FROM python:3.10 +# Dockerfile -ENV PATH="${PATH}:/root/.poetry/bin" - -RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python - && \ - poetry config virtualenvs.create false -COPY ./pyproject.toml ./poetry.lock ./ -RUN poetry install --no-interaction --no-dev --no-root --no-ansi -vvv +FROM python:3.10-bullseye +# copy source and install dependencies +ENV VIRTUAL_ENV=/venv +RUN python3.10 -m venv $VIRTUAL_ENV +ENV PATH="$VIRTUAL_ENV/bin:$PATH" ENV PYTHONPATH=/app @@ -14,9 +13,14 @@ WORKDIR /app COPY . . COPY docker-entrypoint.sh ./docker-entrypoint.sh -RUN chmod +x ./docker-entrypoint.sh && \ +COPY requirements.txt ./requirements.txt +RUN apt-get update && \ + apt-get install -y libpq-dev python3-dev && \ + chmod +x ./docker-entrypoint.sh && \ ln -s ./docker-entrypoint.sh / -EXPOSE 8080 +RUN pip install -r ./requirements.txt -ENTRYPOINT ["sh", "./docker-entrypoint.sh" ] +# start service +STOPSIGNAL SIGTERM +CMD ["sh", "docker-entrypoint.sh"] diff --git a/alembic.ini b/alembic.ini index 859dba2..86a4e7f 100644 --- a/alembic.ini +++ b/alembic.ini @@ -1,18 +1,73 @@ -[alembic] -databases = postgres +# A generic, single database configuration. -[DEFAULT] -prepend_sys_path = . +[alembic] +# path to migration scripts script_location = alembic + +# template used to generate migration files file_template = %%(year)d-%%(month).2d-%%(day).2d_%%(hour).2d-%%(minute).2d_%%(slug)s -truncate_slug_length = 60 -version_path_separator = space +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. +prepend_sys_path = . + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the python-dateutil library that can be +# installed by adding `alembic[tz]` to the pip requirements +# string value is passed to dateutil.tz.gettz() +# leave blank for localtime +timezone = UTC + +# max length of characters to apply to the +# "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to migrations/versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "version_path_separator" below. +# version_locations = %(here)s/bar:%(here)s/bat:migrations/versions + +# version path separator; As mentioned above, this is the character used to split +# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. +# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. +# Valid values for version_path_separator are: +# +# version_path_separator = : +# version_path_separator = ; +# version_path_separator = space +version_path_separator = os +# Use os.pathsep. Default configuration used for new projects. + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +sqlalchemy.url = postgresql+asyncpg://postgres:postgres@localhost:5432/postgres + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples -[postgres] -version_locations = alembic/pg_versions -sqlalchemy.engine = app.db.session.postgres_engine +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME +# Logging configuration [loggers] keys = root,sqlalchemy,alembic diff --git a/alembic/env.py b/alembic/env.py index 737009c..09be5c6 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -1,68 +1,90 @@ -from __future__ import with_statement - -import os -import sys +import asyncio from logging.config import fileConfig -from sqlalchemy.engine import Engine -from sqlalchemy.schema import MetaData +from sqlalchemy import engine_from_config, pool +from sqlalchemy.ext.asyncio import AsyncEngine from alembic import context -from app.db.base import postgres_metadata -from app.db.session import postgres_engine - -parent_dir = os.path.abspath(os.getcwd()) -sys.path.append(parent_dir) +from app.core.config import settings +from app.db.base import Base +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. fileConfig(config.config_file_name) +# add your model"s MetaData object here +# for "autogenerate" support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = Base.metadata -def render_item(obj_type, obj, autogen_context): - """Apply custom rendering for selected items.""" - if obj_type == "type" and obj.__class__.__module__.startswith("sqlalchemy_utils."): - autogen_context.imports.add(f"import {obj.__class__.__module__}") - if hasattr(obj, "choices"): - return f"{obj.__class__.__module__}.{obj.__class__.__name__}(choices={obj.choices})" - else: - return f"{obj.__class__.__module__}.{obj.__class__.__name__}()" - - # default rendering for other objects - return False - - -class Migrator: - def __init__(self, engine: Engine, target_metadata: MetaData) -> None: - self.engine = engine - self.target_metadata = target_metadata - - def migrate(self) -> None: - connectable = config.attributes.get("connection", None) - if connectable is None: - connectable = self.engine.connect() - context.configure( - connection=connectable, - target_metadata=self.target_metadata, - compare_type=True, - render_item=render_item, - ) - with context.begin_transaction(): - self.prepare_migration_context() - context.run_migrations() - def prepare_migration_context(self) -> None: - pass +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in "offline" mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don"t even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + """ + url = settings.POSTGRES_URL + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) -class PostgresMigrator(Migrator): - pass + with context.begin_transaction(): + context.run_migrations() -def run_migrations_online() -> None: - if config.config_ini_section == "postgres": - migrator = PostgresMigrator(postgres_engine, postgres_metadata) +def do_run_migrations(connection): + context.configure(connection=connection, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + + +async def run_migrations_online(): + """Run migrations in "online" mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + configuration = config.get_section(config.config_ini_section) + configuration["sqlalchemy.url"] = settings.POSTGRES_URL + connectable = AsyncEngine( + engine_from_config( + configuration, + prefix="sqlalchemy.", + poolclass=pool.NullPool, + future=True, + ) + ) + + async with connectable.connect() as connection: + await connection.run_sync(do_run_migrations) - migrator.migrate() + await connectable.dispose() -run_migrations_online() +if context.is_offline_mode(): + run_migrations_offline() +else: + asyncio.run(run_migrations_online()) diff --git a/alembic/pg_versions/2022-04-29_12-12_init_db.py b/alembic/pg_versions/2022-04-29_12-12_init_db.py index 423ee4b..8acd0fa 100644 --- a/alembic/pg_versions/2022-04-29_12-12_init_db.py +++ b/alembic/pg_versions/2022-04-29_12-12_init_db.py @@ -1,16 +1,14 @@ """init db Revision ID: 2a124563c49c -Revises: +Revises: Create Date: 2022-04-29 12:12:18.292607 """ -from alembic import op -import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = '2a124563c49c' +revision = "2a124563c49c" down_revision = None branch_labels = None depends_on = None diff --git a/alembic/pg_versions/2022-06-01_12-35_add_user_table.py b/alembic/pg_versions/2022-06-01_12-35_add_user_table.py index 393a796..bb48573 100644 --- a/alembic/pg_versions/2022-06-01_12-35_add_user_table.py +++ b/alembic/pg_versions/2022-06-01_12-35_add_user_table.py @@ -5,38 +5,40 @@ Create Date: 2022-06-01 12:35:15.854573 """ -from alembic import op import sqlalchemy as sa import sqlalchemy_utils.types.email +from alembic import op + # revision identifiers, used by Alembic. -revision = 'e0538c856957' -down_revision = '2a124563c49c' +revision = "e0538c856957" +down_revision = "2a124563c49c" branch_labels = None depends_on = None def upgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.create_table('user', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('email', sqlalchemy_utils.types.email.EmailType(), nullable=False), - sa.Column('hashed_password', sa.String(), nullable=False), - sa.Column('phone_number', sa.String(), nullable=True), - sa.Column('first_name', sa.String(), nullable=True), - sa.Column('last_name', sa.String(), nullable=True), - sa.Column('is_active', sa.Boolean(), nullable=True), - sa.Column('is_superuser', sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint('id') + op.create_table( + "user", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("email", sqlalchemy_utils.types.email.EmailType(), nullable=False), + sa.Column("hashed_password", sa.String(), nullable=False), + sa.Column("phone_number", sa.String(), nullable=True), + sa.Column("first_name", sa.String(), nullable=True), + sa.Column("last_name", sa.String(), nullable=True), + sa.Column("is_active", sa.Boolean(), nullable=True), + sa.Column("is_superuser", sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint("id"), ) - op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True) - op.create_index(op.f('ix_user_id'), 'user', ['id'], unique=False) + op.create_index(op.f("ix_user_email"), "user", ["email"], unique=True) + op.create_index(op.f("ix_user_id"), "user", ["id"], unique=False) # ### end Alembic commands ### def downgrade(): # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_user_id'), table_name='user') - op.drop_index(op.f('ix_user_email'), table_name='user') - op.drop_table('user') + op.drop_index(op.f("ix_user_id"), table_name="user") + op.drop_index(op.f("ix_user_email"), table_name="user") + op.drop_table("user") # ### end Alembic commands ### diff --git a/app/api/deps.py b/app/api/deps.py index a7a6911..eea9c97 100644 --- a/app/api/deps.py +++ b/app/api/deps.py @@ -1,4 +1,5 @@ -from typing import Any, Generator, Optional +from collections.abc import Generator +from typing import Any, Optional from databases import Database from fastapi import Depends, HTTPException, status diff --git a/app/core/config.py b/app/core/config.py index f715df2..0ca74b1 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -1,6 +1,7 @@ import logging +from collections.abc import Mapping from pathlib import Path -from typing import Any, Mapping +from typing import Any from pydantic import AnyHttpUrl, BaseSettings, EmailStr, PostgresDsn, validator @@ -17,6 +18,7 @@ class Settings(BaseSettings): # e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \ # "http://localhost:8080", "http://0.0.0.0:8001"]' BACKEND_CORS_ORIGINS: list[AnyHttpUrl] = [] + DEBUG: bool = False @validator("BACKEND_CORS_ORIGINS", pre=True) def assemble_cors_origins(cls, v: str | list[str]) -> list[str] | str: diff --git a/app/crud/base.py b/app/crud/base.py index 8d5a114..37ae0e5 100644 --- a/app/crud/base.py +++ b/app/crud/base.py @@ -10,7 +10,7 @@ class CRUDBase(Generic[ModelTable, CreateSchemaType, UpdateSchemaType]): - def __init__(self, model: Type[ModelTable]): + def __init__(self, model: type[ModelTable]): """ CRUD object with async default methods to Create, Read, Update, Delete (CRUD). diff --git a/app/db/base.py b/app/db/base.py index ef89b00..0bb56b3 100644 --- a/app/db/base.py +++ b/app/db/base.py @@ -1,2 +1,2 @@ -from app.db.metadata import postgres_metadata +from app.db.base_class import Base from app.models import * diff --git a/app/db/session.py b/app/db/session.py index 4779c95..abf6a3a 100644 --- a/app/db/session.py +++ b/app/db/session.py @@ -1,30 +1,15 @@ -from contextlib import contextmanager -from typing import Generator - -from sqlalchemy import create_engine -from sqlalchemy.engine import Engine +from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine from sqlalchemy.orm import sessionmaker from app.core.config import settings - -def create_postgres_engine() -> Engine: - return create_engine(settings.POSTGRES_URL, pool_pre_ping=True) - - -postgres_engine = create_postgres_engine() - -engines = {"postgres": postgres_engine} - -SessionLocalPG = sessionmaker( - autocommit=False, autoflush=False, bind=engines["postgres"] +engine = create_async_engine( + settings.POSTGRES_URL, echo=settings.DEBUG, pool_pre_ping=True +) +Session = sessionmaker( + engine, + class_=AsyncSession, + autoflush=False, + autocommit=False, + expire_on_commit=False, ) - - -@contextmanager -def postgres_session() -> Generator: - session = SessionLocalPG() - try: - yield session - finally: - session.close() diff --git a/app/models/user.py b/app/models/user.py index b900bd2..28601e0 100644 --- a/app/models/user.py +++ b/app/models/user.py @@ -1,19 +1,18 @@ -import sqlalchemy -import sqlalchemy_utils +from sqlalchemy import Boolean, Column, Integer, String -from app.db.metadata import postgres_metadata +from app.db.base_class import Base -user = sqlalchemy.Table( - "user", - postgres_metadata, - sqlalchemy.Column("id", sqlalchemy.Integer, primary_key=True, index=True), - sqlalchemy.Column( - "email", sqlalchemy_utils.EmailType, unique=True, index=True, nullable=False - ), - sqlalchemy.Column("hashed_password", sqlalchemy.String, nullable=False), - sqlalchemy.Column("phone_number", sqlalchemy.String), - sqlalchemy.Column("first_name", sqlalchemy.String), - sqlalchemy.Column("last_name", sqlalchemy.String), - sqlalchemy.Column("is_active", sqlalchemy.Boolean, default=True), - sqlalchemy.Column("is_superuser", sqlalchemy.Boolean, default=False), -) + +class User(Base): + """ + Table that contains each user of system. + """ + + id: int = Column(Integer, primary_key=True, auto_created=True) + email: str = Column(String(255), unique=True, index=True, nullable=False) + hashed_password: str = Column(String(127), nullable=False) + phone_number: str = Column(String(63)) + first_name: str = Column(String(63)) + last_name: str = Column(String(63)) + is_active: bool = Column(Boolean, server_default=True) + is_superuser: bool = Column(Boolean, server_default=False) diff --git a/app/utils/email.py b/app/utils/email.py index c0a2d6e..e060700 100644 --- a/app/utils/email.py +++ b/app/utils/email.py @@ -12,9 +12,7 @@ def send_email(email_to: str, email_subject: str, email_message: str) -> None: return message = ( - f"From: {settings.EMAILS_FROM_NAME}\n" - f"Subject: {email_subject}\n" - f"{email_message}" + f"From: {settings.EMAILS_FROM_NAME}\nSubject: {email_subject}\n{email_message}" ) context = ssl.create_default_context() with smtplib.SMTP(settings.SMTP_HOST, settings.SMTP_PORT) as server: @@ -41,10 +39,11 @@ def send_reset_password_email(email_to: str, fullname: str, token: str) -> None: """ subject = f"{settings.PROJECT_NAME} - Reset Password" message = ( - f"We received a request to recover the password for {fullname} with email {email_to}\n" - f"Reset your password by clicking the link below:\n" - f"{settings.SERVER_HOST}/reset-password?token={token}\n" - f"The reset password link will expire in {settings.EMAIL_RESET_TOKEN_EXPIRE_MINUTES} minutes.\n" - f"If you didn't request a password recovery you can disregard this email." + f"We received a request to recover the password for {fullname} with email" + f" {email_to}\nReset your password by clicking the link" + f" below:\n{settings.SERVER_HOST}/reset-password?token={token}\nThe reset" + " password link will expire in" + f" {settings.EMAIL_RESET_TOKEN_EXPIRE_MINUTES} minutes.\nIf you didn't request" + " a password recovery you can disregard this email." ) send_email(email_to=email_to, email_subject=subject, email_message=message) diff --git a/docker-compose.yml b/docker-compose.dev.yml similarity index 93% rename from docker-compose.yml rename to docker-compose.dev.yml index a0c5658..163a4c8 100644 --- a/docker-compose.yml +++ b/docker-compose.dev.yml @@ -3,8 +3,6 @@ version: "3.10" services: web: build: . - volumes: - - './:/app' depends_on: - postgres ports: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index f48a345..fa61091 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,13 +1,10 @@ #!/bin/bash -# Set local server config -APP_UVICORN_OPTIONS="${APP_UVICORN_OPTIONS:---host 0.0.0.0 --port=8080}" - # Run migrations -alembic -n postgres upgrade head +alembic upgrade head # Create initial data in DB python app/initial_data.py # Run local server -uvicorn ${APP_UVICORN_OPTIONS} app.fastapi_app:app +uvicorn app.fastapi_app:app "${APP_UVICORN_OPTIONS}" diff --git a/init.sql b/init.sql deleted file mode 100644 index 14379bd..0000000 --- a/init.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE DATABASE test; diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..44b5238 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,44 @@ +alembic==1.8.1 +anyio==3.6.2 +asyncpg==0.27.0 +attrs==22.1.0 +certifi==2022.9.24 +click==8.1.3 +databases==0.6.2 +dnspython==2.2.1 +ecdsa==0.18.0 +email-validator==1.3.0 +exceptiongroup==1.0.4 +factory-boy==3.2.1 +Faker==15.3.4 +fastapi==0.88.0 +greenlet==2.0.1 +h11==0.14.0 +httpcore==0.16.2 +httpx==0.23.1 +idna==3.4 +iniconfig==1.1.1 +Mako==1.2.4 +MarkupSafe==2.1.1 +orjson==3.8.3 +packaging==21.3 +passlib==1.7.4 +pluggy==1.0.0 +psycopg2==2.9.5 +pyasn1==0.4.8 +pydantic==1.10.2 +pyparsing==3.0.9 +pytest==7.2.0 +pytest-asyncio==0.20.2 +python-dateutil==2.8.2 +python-jose==3.3.0 +python-multipart==0.0.5 +rfc3986==1.5.0 +rsa==4.9 +six==1.16.0 +sniffio==1.3.0 +SQLAlchemy==1.4.41 +starlette==0.22.0 +tomli==2.0.1 +typing_extensions==4.4.0 +uvicorn==0.20.0 diff --git a/tests/api/v1/test_user.py b/tests/api/v1/test_user.py index c7b146e..d31313c 100644 --- a/tests/api/v1/test_user.py +++ b/tests/api/v1/test_user.py @@ -1,5 +1,3 @@ -from typing import Dict - import pytest from databases import Database from httpx import AsyncClient @@ -13,7 +11,7 @@ async def test_get_users_superuser_me( - api_client: AsyncClient, superuser_token_headers: Dict[str, str] + api_client: AsyncClient, superuser_token_headers: dict[str, str] ) -> None: response = await api_client.get( f"{settings.API_V1_STR}/user/me", headers=superuser_token_headers @@ -29,7 +27,7 @@ async def test_get_users_superuser_me( async def test_get_users_normal_user_me( - api_client: AsyncClient, normal_user_token_headers: Dict[str, str] + api_client: AsyncClient, normal_user_token_headers: dict[str, str] ) -> None: response = await api_client.get( f"{settings.API_V1_STR}/user/me", headers=normal_user_token_headers @@ -120,7 +118,7 @@ async def test_create_user_existing_username( async def test_create_user_by_normal_user( - api_client: AsyncClient, normal_user_token_headers: Dict[str, str] + api_client: AsyncClient, normal_user_token_headers: dict[str, str] ) -> None: username = random_email() password = random_lower_string() diff --git a/tests/conftest.py b/tests/conftest.py index 6358d6d..9c0fa90 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1 @@ # flake8: noqa -from .fixtures.common import * -from .fixtures.db import * -from .fixtures.fastapi import * -from .fixtures.postgres import * diff --git a/tests/fixtures/fastapi.py b/tests/fixtures/fastapi.py index ba620bc..c76a304 100644 --- a/tests/fixtures/fastapi.py +++ b/tests/fixtures/fastapi.py @@ -1,4 +1,4 @@ -from typing import Generator +from collections.abc import Generator import pytest import pytest_asyncio diff --git a/tests/fixtures/postgres.py b/tests/fixtures/postgres.py index b05767d..5181060 100644 --- a/tests/fixtures/postgres.py +++ b/tests/fixtures/postgres.py @@ -1,4 +1,4 @@ -from typing import Generator +from collections.abc import Generator import pytest import pytest_asyncio