mirror of
https://github.com/grillazz/fastapi-sqlalchemy-asyncpg.git
synced 2025-08-26 16:40:40 +03:00
code format
This commit is contained in:
parent
43fe665608
commit
c2975fd260
4
Makefile
4
Makefile
@ -29,6 +29,10 @@ docker-create-db-migration: ## Create new alembic database migration aka databa
|
|||||||
docker-test: ## Run project tests
|
docker-test: ## Run project tests
|
||||||
docker-compose -f docker-compose.yml -f docker-compose.test.yml run --rm app pytest
|
docker-compose -f docker-compose.yml -f docker-compose.test.yml run --rm app pytest
|
||||||
|
|
||||||
|
.PHONY: docker-test-snapshot
|
||||||
|
docker-test-snapshot: ## Run project tests with inline snapshot
|
||||||
|
docker-compose -f docker-compose.yml -f docker-compose.test.yml run --rm app pytest --inline-snapshot=create
|
||||||
|
|
||||||
.PHONY: safety
|
.PHONY: safety
|
||||||
safety: ## Check project and dependencies with safety https://github.com/pyupio/safety
|
safety: ## Check project and dependencies with safety https://github.com/pyupio/safety
|
||||||
docker-compose run --rm app safety check
|
docker-compose run --rm app safety check
|
||||||
|
@ -31,7 +31,9 @@ async def run_migrations_online():
|
|||||||
and associate a connection with the context.
|
and associate a connection with the context.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
connectable = create_async_engine(settings.asyncpg_url.unicode_string(), future=True)
|
connectable = create_async_engine(
|
||||||
|
settings.asyncpg_url.unicode_string(), future=True
|
||||||
|
)
|
||||||
|
|
||||||
async with connectable.connect() as connection:
|
async with connectable.connect() as connection:
|
||||||
await connection.run_sync(do_run_migrations)
|
await connection.run_sync(do_run_migrations)
|
||||||
|
@ -5,12 +5,13 @@ Revises:
|
|||||||
Create Date: 2024-01-02 20:12:46.214651
|
Create Date: 2024-01-02 20:12:46.214651
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from alembic import op
|
from alembic import op
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '68cd4c3a0af0'
|
revision = "68cd4c3a0af0"
|
||||||
down_revision = None
|
down_revision = None
|
||||||
branch_labels = None
|
branch_labels = None
|
||||||
depends_on = None
|
depends_on = None
|
||||||
@ -18,125 +19,168 @@ depends_on = None
|
|||||||
|
|
||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table('nonsense',
|
op.create_table(
|
||||||
sa.Column('id', sa.UUID(), autoincrement=True, nullable=False),
|
"nonsense",
|
||||||
sa.Column('name', sa.String(), nullable=False),
|
sa.Column("id", sa.UUID(), autoincrement=True, nullable=False),
|
||||||
sa.Column('description', sa.String(), nullable=True),
|
sa.Column("name", sa.String(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('name'),
|
sa.Column("description", sa.String(), nullable=True),
|
||||||
sa.UniqueConstraint('id'),
|
sa.PrimaryKeyConstraint("name"),
|
||||||
sa.UniqueConstraint('name'),
|
sa.UniqueConstraint("id"),
|
||||||
schema='happy_hog'
|
sa.UniqueConstraint("name"),
|
||||||
|
schema="happy_hog",
|
||||||
)
|
)
|
||||||
op.create_table('stuff',
|
op.create_table(
|
||||||
sa.Column('id', sa.UUID(), autoincrement=True, nullable=False),
|
"stuff",
|
||||||
sa.Column('name', sa.String(), nullable=False),
|
sa.Column("id", sa.UUID(), autoincrement=True, nullable=False),
|
||||||
sa.Column('description', sa.String(), nullable=True),
|
sa.Column("name", sa.String(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('name'),
|
sa.Column("description", sa.String(), nullable=True),
|
||||||
sa.UniqueConstraint('id'),
|
sa.PrimaryKeyConstraint("name"),
|
||||||
sa.UniqueConstraint('name'),
|
sa.UniqueConstraint("id"),
|
||||||
schema='happy_hog'
|
sa.UniqueConstraint("name"),
|
||||||
|
schema="happy_hog",
|
||||||
)
|
)
|
||||||
op.create_table('character',
|
op.create_table(
|
||||||
sa.Column('id', sa.String(length=32), nullable=False),
|
"character",
|
||||||
sa.Column('name', sa.String(length=64), nullable=False),
|
sa.Column("id", sa.String(length=32), nullable=False),
|
||||||
sa.Column('speech_count', sa.Integer(), nullable=False),
|
sa.Column("name", sa.String(length=64), nullable=False),
|
||||||
sa.Column('abbrev', sa.String(length=32), nullable=True),
|
sa.Column("speech_count", sa.Integer(), nullable=False),
|
||||||
sa.Column('description', sa.String(length=2056), nullable=True),
|
sa.Column("abbrev", sa.String(length=32), nullable=True),
|
||||||
sa.PrimaryKeyConstraint('id', name='character_pkey'),
|
sa.Column("description", sa.String(length=2056), nullable=True),
|
||||||
schema='shakespeare'
|
sa.PrimaryKeyConstraint("id", name="character_pkey"),
|
||||||
|
schema="shakespeare",
|
||||||
)
|
)
|
||||||
op.create_table('wordform',
|
op.create_table(
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
"wordform",
|
||||||
sa.Column('plain_text', sa.String(length=64), nullable=False),
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
sa.Column('phonetic_text', sa.String(length=64), nullable=False),
|
sa.Column("plain_text", sa.String(length=64), nullable=False),
|
||||||
sa.Column('stem_text', sa.String(length=64), nullable=False),
|
sa.Column("phonetic_text", sa.String(length=64), nullable=False),
|
||||||
sa.Column('occurences', sa.Integer(), nullable=False),
|
sa.Column("stem_text", sa.String(length=64), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id', name='wordform_pkey'),
|
sa.Column("occurences", sa.Integer(), nullable=False),
|
||||||
schema='shakespeare'
|
sa.PrimaryKeyConstraint("id", name="wordform_pkey"),
|
||||||
|
schema="shakespeare",
|
||||||
)
|
)
|
||||||
op.create_table('work',
|
op.create_table(
|
||||||
sa.Column('id', sa.String(length=32), nullable=False),
|
"work",
|
||||||
sa.Column('title', sa.String(length=32), nullable=False),
|
sa.Column("id", sa.String(length=32), nullable=False),
|
||||||
sa.Column('long_title', sa.String(length=64), nullable=False),
|
sa.Column("title", sa.String(length=32), nullable=False),
|
||||||
sa.Column('year', sa.Integer(), nullable=False),
|
sa.Column("long_title", sa.String(length=64), nullable=False),
|
||||||
sa.Column('genre_type', sa.String(length=1), nullable=False),
|
sa.Column("year", sa.Integer(), nullable=False),
|
||||||
sa.Column('source', sa.String(length=16), nullable=False),
|
sa.Column("genre_type", sa.String(length=1), nullable=False),
|
||||||
sa.Column('total_words', sa.Integer(), nullable=False),
|
sa.Column("source", sa.String(length=16), nullable=False),
|
||||||
sa.Column('total_paragraphs', sa.Integer(), nullable=False),
|
sa.Column("total_words", sa.Integer(), nullable=False),
|
||||||
sa.Column('notes', sa.Text(), nullable=True),
|
sa.Column("total_paragraphs", sa.Integer(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id', name='work_pkey'),
|
sa.Column("notes", sa.Text(), nullable=True),
|
||||||
schema='shakespeare'
|
sa.PrimaryKeyConstraint("id", name="work_pkey"),
|
||||||
|
schema="shakespeare",
|
||||||
)
|
)
|
||||||
op.create_table('user',
|
op.create_table(
|
||||||
sa.Column('id', sa.UUID(), nullable=False),
|
"user",
|
||||||
sa.Column('email', sa.String(), nullable=False),
|
sa.Column("id", sa.UUID(), nullable=False),
|
||||||
sa.Column('first_name', sa.String(), nullable=False),
|
sa.Column("email", sa.String(), nullable=False),
|
||||||
sa.Column('last_name', sa.String(), nullable=False),
|
sa.Column("first_name", sa.String(), nullable=False),
|
||||||
sa.Column('_password', sa.LargeBinary(), nullable=False),
|
sa.Column("last_name", sa.String(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id'),
|
sa.Column("_password", sa.LargeBinary(), nullable=False),
|
||||||
sa.UniqueConstraint('email')
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
sa.UniqueConstraint("email"),
|
||||||
)
|
)
|
||||||
op.create_table('stuff_full_of_nonsense',
|
op.create_table(
|
||||||
sa.Column('id', sa.UUID(), nullable=False),
|
"stuff_full_of_nonsense",
|
||||||
sa.Column('stuff_id', sa.UUID(), nullable=False),
|
sa.Column("id", sa.UUID(), nullable=False),
|
||||||
sa.Column('nonsense_id', sa.UUID(), nullable=False),
|
sa.Column("stuff_id", sa.UUID(), nullable=False),
|
||||||
sa.Column('but_why', sa.String(), nullable=True),
|
sa.Column("nonsense_id", sa.UUID(), nullable=False),
|
||||||
sa.ForeignKeyConstraint(['nonsense_id'], ['happy_hog.nonsense.id'], ),
|
sa.Column("but_why", sa.String(), nullable=True),
|
||||||
sa.ForeignKeyConstraint(['stuff_id'], ['happy_hog.stuff.id'], ),
|
sa.ForeignKeyConstraint(
|
||||||
sa.PrimaryKeyConstraint('id'),
|
["nonsense_id"],
|
||||||
schema='happy_hog'
|
["happy_hog.nonsense.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["stuff_id"],
|
||||||
|
["happy_hog.stuff.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
schema="happy_hog",
|
||||||
)
|
)
|
||||||
op.create_table('chapter',
|
op.create_table(
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
"chapter",
|
||||||
sa.Column('work_id', sa.String(length=32), nullable=False),
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
sa.Column('section_number', sa.Integer(), nullable=False),
|
sa.Column("work_id", sa.String(length=32), nullable=False),
|
||||||
sa.Column('chapter_number', sa.Integer(), nullable=False),
|
sa.Column("section_number", sa.Integer(), nullable=False),
|
||||||
sa.Column('description', sa.String(length=256), nullable=False),
|
sa.Column("chapter_number", sa.Integer(), nullable=False),
|
||||||
sa.ForeignKeyConstraint(['work_id'], ['shakespeare.work.id'], name='chapter_work_id_fkey'),
|
sa.Column("description", sa.String(length=256), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id', name='chapter_pkey'),
|
sa.ForeignKeyConstraint(
|
||||||
sa.UniqueConstraint('work_id', 'section_number', 'chapter_number', name='chapter_work_id_section_number_chapter_number_key'),
|
["work_id"], ["shakespeare.work.id"], name="chapter_work_id_fkey"
|
||||||
schema='shakespeare'
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id", name="chapter_pkey"),
|
||||||
|
sa.UniqueConstraint(
|
||||||
|
"work_id",
|
||||||
|
"section_number",
|
||||||
|
"chapter_number",
|
||||||
|
name="chapter_work_id_section_number_chapter_number_key",
|
||||||
|
),
|
||||||
|
schema="shakespeare",
|
||||||
)
|
)
|
||||||
op.create_table('character_work',
|
op.create_table(
|
||||||
sa.Column('character_id', sa.String(length=32), nullable=False),
|
"character_work",
|
||||||
sa.Column('work_id', sa.String(length=32), nullable=False),
|
sa.Column("character_id", sa.String(length=32), nullable=False),
|
||||||
sa.ForeignKeyConstraint(['character_id'], ['shakespeare.character.id'], name='character_work_character_id_fkey'),
|
sa.Column("work_id", sa.String(length=32), nullable=False),
|
||||||
sa.ForeignKeyConstraint(['work_id'], ['shakespeare.work.id'], name='character_work_work_id_fkey'),
|
sa.ForeignKeyConstraint(
|
||||||
sa.PrimaryKeyConstraint('character_id', 'work_id', name='character_work_pkey'),
|
["character_id"],
|
||||||
schema='shakespeare'
|
["shakespeare.character.id"],
|
||||||
|
name="character_work_character_id_fkey",
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["work_id"], ["shakespeare.work.id"], name="character_work_work_id_fkey"
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("character_id", "work_id", name="character_work_pkey"),
|
||||||
|
schema="shakespeare",
|
||||||
)
|
)
|
||||||
op.create_table('paragraph',
|
op.create_table(
|
||||||
sa.Column('id', sa.Integer(), nullable=False),
|
"paragraph",
|
||||||
sa.Column('work_id', sa.String(length=32), nullable=False),
|
sa.Column("id", sa.Integer(), nullable=False),
|
||||||
sa.Column('paragraph_num', sa.Integer(), nullable=False),
|
sa.Column("work_id", sa.String(length=32), nullable=False),
|
||||||
sa.Column('character_id', sa.String(length=32), nullable=False),
|
sa.Column("paragraph_num", sa.Integer(), nullable=False),
|
||||||
sa.Column('plain_text', sa.Text(), nullable=False),
|
sa.Column("character_id", sa.String(length=32), nullable=False),
|
||||||
sa.Column('phonetic_text', sa.Text(), nullable=False),
|
sa.Column("plain_text", sa.Text(), nullable=False),
|
||||||
sa.Column('stem_text', sa.Text(), nullable=False),
|
sa.Column("phonetic_text", sa.Text(), nullable=False),
|
||||||
sa.Column('paragraph_type', sa.String(length=1), nullable=False),
|
sa.Column("stem_text", sa.Text(), nullable=False),
|
||||||
sa.Column('section_number', sa.Integer(), nullable=False),
|
sa.Column("paragraph_type", sa.String(length=1), nullable=False),
|
||||||
sa.Column('chapter_number', sa.Integer(), nullable=False),
|
sa.Column("section_number", sa.Integer(), nullable=False),
|
||||||
sa.Column('char_count', sa.Integer(), nullable=False),
|
sa.Column("chapter_number", sa.Integer(), nullable=False),
|
||||||
sa.Column('word_count', sa.Integer(), nullable=False),
|
sa.Column("char_count", sa.Integer(), nullable=False),
|
||||||
sa.ForeignKeyConstraint(['character_id'], ['shakespeare.character.id'], name='paragraph_character_id_fkey'),
|
sa.Column("word_count", sa.Integer(), nullable=False),
|
||||||
sa.ForeignKeyConstraint(['work_id', 'section_number', 'chapter_number'], ['shakespeare.chapter.work_id', 'shakespeare.chapter.section_number', 'shakespeare.chapter.chapter_number'], name='paragraph_chapter_fkey'),
|
sa.ForeignKeyConstraint(
|
||||||
sa.ForeignKeyConstraint(['work_id'], ['shakespeare.work.id'], name='paragraph_work_id_fkey'),
|
["character_id"],
|
||||||
sa.PrimaryKeyConstraint('id', name='paragraph_pkey'),
|
["shakespeare.character.id"],
|
||||||
schema='shakespeare'
|
name="paragraph_character_id_fkey",
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["work_id", "section_number", "chapter_number"],
|
||||||
|
[
|
||||||
|
"shakespeare.chapter.work_id",
|
||||||
|
"shakespeare.chapter.section_number",
|
||||||
|
"shakespeare.chapter.chapter_number",
|
||||||
|
],
|
||||||
|
name="paragraph_chapter_fkey",
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["work_id"], ["shakespeare.work.id"], name="paragraph_work_id_fkey"
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id", name="paragraph_pkey"),
|
||||||
|
schema="shakespeare",
|
||||||
)
|
)
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.drop_table('paragraph', schema='shakespeare')
|
op.drop_table("paragraph", schema="shakespeare")
|
||||||
op.drop_table('character_work', schema='shakespeare')
|
op.drop_table("character_work", schema="shakespeare")
|
||||||
op.drop_table('chapter', schema='shakespeare')
|
op.drop_table("chapter", schema="shakespeare")
|
||||||
op.drop_table('stuff_full_of_nonsense', schema='happy_hog')
|
op.drop_table("stuff_full_of_nonsense", schema="happy_hog")
|
||||||
op.drop_table('user')
|
op.drop_table("user")
|
||||||
op.drop_table('work', schema='shakespeare')
|
op.drop_table("work", schema="shakespeare")
|
||||||
op.drop_table('wordform', schema='shakespeare')
|
op.drop_table("wordform", schema="shakespeare")
|
||||||
op.drop_table('character', schema='shakespeare')
|
op.drop_table("character", schema="shakespeare")
|
||||||
op.drop_table('stuff', schema='happy_hog')
|
op.drop_table("stuff", schema="happy_hog")
|
||||||
op.drop_table('nonsense', schema='happy_hog')
|
op.drop_table("nonsense", schema="happy_hog")
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
@ -12,7 +12,9 @@ router = APIRouter(prefix="/v1/nonsense")
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=NonsenseResponse)
|
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=NonsenseResponse)
|
||||||
async def create_nonsense(payload: NonsenseSchema, db_session: AsyncSession = Depends(get_db)):
|
async def create_nonsense(
|
||||||
|
payload: NonsenseSchema, db_session: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
nonsense = Nonsense(**payload.model_dump())
|
nonsense = Nonsense(**payload.model_dump())
|
||||||
await nonsense.save(db_session)
|
await nonsense.save(db_session)
|
||||||
return nonsense
|
return nonsense
|
||||||
@ -103,7 +105,9 @@ async def import_nonsense(
|
|||||||
# If an error occurs, roll back the session
|
# If an error occurs, roll back the session
|
||||||
await db_session.rollback()
|
await db_session.rollback()
|
||||||
# Raise an HTTP exception with a 422 status code
|
# Raise an HTTP exception with a 422 status code
|
||||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)) from ex
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)
|
||||||
|
) from ex
|
||||||
finally:
|
finally:
|
||||||
# Ensure that the database session is closed, regardless of whether an error occurred or not
|
# Ensure that the database session is closed, regardless of whether an error occurred or not
|
||||||
await db_session.close()
|
await db_session.close()
|
||||||
@ -147,4 +151,4 @@ async def import_nonsense(
|
|||||||
#
|
#
|
||||||
|
|
||||||
# TODO: https://medium.com/@amitosh/full-text-search-fts-with-postgresql-and-sqlalchemy-edc436330a0c
|
# TODO: https://medium.com/@amitosh/full-text-search-fts-with-postgresql-and-sqlalchemy-edc436330a0c
|
||||||
# TODO: https://www.postgresql.org/docs/13/textsearch-intro.html
|
# TODO: https://www.postgresql.org/docs/13/textsearch-intro.html
|
||||||
|
@ -13,21 +13,29 @@ router = APIRouter(prefix="/v1/stuff")
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/add_many", status_code=status.HTTP_201_CREATED)
|
@router.post("/add_many", status_code=status.HTTP_201_CREATED)
|
||||||
async def create_multi_stuff(payload: list[StuffSchema], db_session: AsyncSession = Depends(get_db)):
|
async def create_multi_stuff(
|
||||||
|
payload: list[StuffSchema], db_session: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
try:
|
try:
|
||||||
stuff_instances = [Stuff(**stuff.model_dump()) for stuff in payload]
|
stuff_instances = [Stuff(**stuff.model_dump()) for stuff in payload]
|
||||||
db_session.add_all(stuff_instances)
|
db_session.add_all(stuff_instances)
|
||||||
await db_session.commit()
|
await db_session.commit()
|
||||||
except SQLAlchemyError as ex:
|
except SQLAlchemyError as ex:
|
||||||
# logger.exception(ex)
|
# logger.exception(ex)
|
||||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)) from ex
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)
|
||||||
|
) from ex
|
||||||
else:
|
else:
|
||||||
logger.info(f"{len(stuff_instances)} instances of Stuff inserted into database.")
|
logger.info(
|
||||||
|
f"{len(stuff_instances)} instances of Stuff inserted into database."
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@router.post("", status_code=status.HTTP_201_CREATED, response_model=StuffResponse)
|
@router.post("", status_code=status.HTTP_201_CREATED, response_model=StuffResponse)
|
||||||
async def create_stuff(payload: StuffSchema, db_session: AsyncSession = Depends(get_db)):
|
async def create_stuff(
|
||||||
|
payload: StuffSchema, db_session: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
stuff = Stuff(**payload.model_dump())
|
stuff = Stuff(**payload.model_dump())
|
||||||
await stuff.save(db_session)
|
await stuff.save(db_session)
|
||||||
return stuff
|
return stuff
|
||||||
|
@ -13,7 +13,9 @@ router = APIRouter(prefix="/v1/user")
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=UserResponse)
|
@router.post("/", status_code=status.HTTP_201_CREATED, response_model=UserResponse)
|
||||||
async def create_user(payload: UserSchema, request: Request, db_session: AsyncSession = Depends(get_db)):
|
async def create_user(
|
||||||
|
payload: UserSchema, request: Request, db_session: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
logger.info(f"Creating user: {payload}")
|
logger.info(f"Creating user: {payload}")
|
||||||
_user: User = User(**payload.model_dump())
|
_user: User = User(**payload.model_dump())
|
||||||
await _user.save(db_session)
|
await _user.save(db_session)
|
||||||
@ -23,15 +25,23 @@ async def create_user(payload: UserSchema, request: Request, db_session: AsyncSe
|
|||||||
return _user
|
return _user
|
||||||
|
|
||||||
|
|
||||||
@router.post("/token", status_code=status.HTTP_201_CREATED, response_model=TokenResponse)
|
@router.post(
|
||||||
async def get_token_for_user(user: UserLogin, request: Request, db_session: AsyncSession = Depends(get_db)):
|
"/token", status_code=status.HTTP_201_CREATED, response_model=TokenResponse
|
||||||
|
)
|
||||||
|
async def get_token_for_user(
|
||||||
|
user: UserLogin, request: Request, db_session: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
_user: User = await User.find(db_session, [User.email == user.email])
|
_user: User = await User.find(db_session, [User.email == user.email])
|
||||||
|
|
||||||
# TODO: out exception handling to external module
|
# TODO: out exception handling to external module
|
||||||
if not _user:
|
if not _user:
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||||
|
)
|
||||||
if not _user.check_password(user.password):
|
if not _user.check_password(user.password):
|
||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Password is incorrect")
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED, detail="Password is incorrect"
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: add refresh token
|
# TODO: add refresh token
|
||||||
_token = await create_access_token(_user, request)
|
_token = await create_access_token(_user, request)
|
||||||
|
@ -7,9 +7,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|||||||
|
|
||||||
class Settings(BaseSettings):
|
class Settings(BaseSettings):
|
||||||
model_config = SettingsConfigDict(
|
model_config = SettingsConfigDict(
|
||||||
env_file=".env",
|
env_file=".env", env_ignore_empty=True, extra="ignore"
|
||||||
env_ignore_empty=True,
|
|
||||||
extra="ignore"
|
|
||||||
)
|
)
|
||||||
jwt_algorithm: str = os.getenv("JWT_ALGORITHM")
|
jwt_algorithm: str = os.getenv("JWT_ALGORITHM")
|
||||||
jwt_expire: int = os.getenv("JWT_EXPIRE")
|
jwt_expire: int = os.getenv("JWT_EXPIRE")
|
||||||
@ -46,7 +44,6 @@ class Settings(BaseSettings):
|
|||||||
host=self.REDIS_HOST,
|
host=self.REDIS_HOST,
|
||||||
port=self.REDIS_PORT,
|
port=self.REDIS_PORT,
|
||||||
path=self.REDIS_DB,
|
path=self.REDIS_DB,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@computed_field
|
@computed_field
|
||||||
|
@ -21,8 +21,6 @@ async def lifespan(app: FastAPI):
|
|||||||
# Load the redis connection
|
# Load the redis connection
|
||||||
app.state.redis = await get_redis()
|
app.state.redis = await get_redis()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Initialize the cache with the redis connection
|
# Initialize the cache with the redis connection
|
||||||
redis_cache = await get_cache()
|
redis_cache = await get_cache()
|
||||||
@ -43,4 +41,9 @@ app.include_router(user_router)
|
|||||||
|
|
||||||
|
|
||||||
app.include_router(health_router, prefix="/v1/public/health", tags=["Health, Public"])
|
app.include_router(health_router, prefix="/v1/public/health", tags=["Health, Public"])
|
||||||
app.include_router(health_router, prefix="/v1/health", tags=["Health, Bearer"], dependencies=[Depends(AuthBearer())])
|
app.include_router(
|
||||||
|
health_router,
|
||||||
|
prefix="/v1/health",
|
||||||
|
tags=["Health, Bearer"],
|
||||||
|
dependencies=[Depends(AuthBearer())],
|
||||||
|
)
|
||||||
|
@ -26,7 +26,9 @@ class Base(DeclarativeBase):
|
|||||||
db_session.add(self)
|
db_session.add(self)
|
||||||
return await db_session.commit()
|
return await db_session.commit()
|
||||||
except SQLAlchemyError as ex:
|
except SQLAlchemyError as ex:
|
||||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)) from ex
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)
|
||||||
|
) from ex
|
||||||
|
|
||||||
async def delete(self, db_session: AsyncSession):
|
async def delete(self, db_session: AsyncSession):
|
||||||
"""
|
"""
|
||||||
@ -39,7 +41,9 @@ class Base(DeclarativeBase):
|
|||||||
await db_session.commit()
|
await db_session.commit()
|
||||||
return True
|
return True
|
||||||
except SQLAlchemyError as ex:
|
except SQLAlchemyError as ex:
|
||||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)) from ex
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)
|
||||||
|
) from ex
|
||||||
|
|
||||||
async def update(self, db: AsyncSession, **kwargs):
|
async def update(self, db: AsyncSession, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -53,7 +57,9 @@ class Base(DeclarativeBase):
|
|||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
return await db.commit()
|
return await db.commit()
|
||||||
except SQLAlchemyError as ex:
|
except SQLAlchemyError as ex:
|
||||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)) from ex
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=repr(ex)
|
||||||
|
) from ex
|
||||||
|
|
||||||
async def save_or_update(self, db: AsyncSession):
|
async def save_or_update(self, db: AsyncSession):
|
||||||
try:
|
try:
|
||||||
|
@ -12,7 +12,9 @@ from app.models.base import Base
|
|||||||
class Nonsense(Base):
|
class Nonsense(Base):
|
||||||
__tablename__ = "nonsense"
|
__tablename__ = "nonsense"
|
||||||
__table_args__ = ({"schema": "happy_hog"},)
|
__table_args__ = ({"schema": "happy_hog"},)
|
||||||
id: Mapped[uuid:UUID] = mapped_column(UUID(as_uuid=True), unique=True, default=uuid.uuid4, autoincrement=True)
|
id: Mapped[uuid:UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), unique=True, default=uuid.uuid4, autoincrement=True
|
||||||
|
)
|
||||||
name: Mapped[str] = mapped_column(String, primary_key=True, unique=True)
|
name: Mapped[str] = mapped_column(String, primary_key=True, unique=True)
|
||||||
description: Mapped[str | None]
|
description: Mapped[str | None]
|
||||||
# TODO: apply relation to other tables
|
# TODO: apply relation to other tables
|
||||||
@ -31,7 +33,9 @@ class Nonsense(Base):
|
|||||||
if instance is None:
|
if instance is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_404_NOT_FOUND,
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
detail={"Record not found": f"There is no record for requested name value : {name}"},
|
detail={
|
||||||
|
"Record not found": f"There is no record for requested name value : {name}"
|
||||||
|
},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return instance
|
return instance
|
||||||
|
@ -34,7 +34,10 @@ class Character(Base):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "character"
|
__tablename__ = "character"
|
||||||
__table_args__ = (PrimaryKeyConstraint("id", name="character_pkey"), {"schema": "shakespeare"})
|
__table_args__ = (
|
||||||
|
PrimaryKeyConstraint("id", name="character_pkey"),
|
||||||
|
{"schema": "shakespeare"},
|
||||||
|
)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(32), primary_key=True)
|
id: Mapped[str] = mapped_column(String(32), primary_key=True)
|
||||||
name: Mapped[str] = mapped_column(String(64))
|
name: Mapped[str] = mapped_column(String(64))
|
||||||
@ -45,7 +48,9 @@ class Character(Base):
|
|||||||
work: Mapped[list["Work"]] = relationship(
|
work: Mapped[list["Work"]] = relationship(
|
||||||
"Work", secondary="shakespeare.character_work", back_populates="character"
|
"Work", secondary="shakespeare.character_work", back_populates="character"
|
||||||
)
|
)
|
||||||
paragraph: Mapped[list["Paragraph"]] = relationship("Paragraph", back_populates="character")
|
paragraph: Mapped[list["Paragraph"]] = relationship(
|
||||||
|
"Paragraph", back_populates="character"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Wordform(Base):
|
class Wordform(Base):
|
||||||
@ -65,7 +70,10 @@ class Wordform(Base):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "wordform"
|
__tablename__ = "wordform"
|
||||||
__table_args__ = (PrimaryKeyConstraint("id", name="wordform_pkey"), {"schema": "shakespeare"})
|
__table_args__ = (
|
||||||
|
PrimaryKeyConstraint("id", name="wordform_pkey"),
|
||||||
|
{"schema": "shakespeare"},
|
||||||
|
)
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
||||||
plain_text: Mapped[str] = mapped_column(String(64))
|
plain_text: Mapped[str] = mapped_column(String(64))
|
||||||
@ -98,7 +106,10 @@ class Work(Base):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = "work"
|
__tablename__ = "work"
|
||||||
__table_args__ = (PrimaryKeyConstraint("id", name="work_pkey"), {"schema": "shakespeare"})
|
__table_args__ = (
|
||||||
|
PrimaryKeyConstraint("id", name="work_pkey"),
|
||||||
|
{"schema": "shakespeare"},
|
||||||
|
)
|
||||||
|
|
||||||
id: Mapped[str] = mapped_column(String(32), primary_key=True)
|
id: Mapped[str] = mapped_column(String(32), primary_key=True)
|
||||||
title: Mapped[str] = mapped_column(String(32))
|
title: Mapped[str] = mapped_column(String(32))
|
||||||
@ -114,7 +125,9 @@ class Work(Base):
|
|||||||
"Character", secondary="shakespeare.character_work", back_populates="work"
|
"Character", secondary="shakespeare.character_work", back_populates="work"
|
||||||
)
|
)
|
||||||
chapter: Mapped[list["Chapter"]] = relationship("Chapter", back_populates="work")
|
chapter: Mapped[list["Chapter"]] = relationship("Chapter", back_populates="work")
|
||||||
paragraph: Mapped[list["Paragraph"]] = relationship("Paragraph", back_populates="work")
|
paragraph: Mapped[list["Paragraph"]] = relationship(
|
||||||
|
"Paragraph", back_populates="work"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Chapter(Base):
|
class Chapter(Base):
|
||||||
@ -137,10 +150,15 @@ class Chapter(Base):
|
|||||||
|
|
||||||
__tablename__ = "chapter"
|
__tablename__ = "chapter"
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
ForeignKeyConstraint(["work_id"], ["shakespeare.work.id"], name="chapter_work_id_fkey"),
|
ForeignKeyConstraint(
|
||||||
|
["work_id"], ["shakespeare.work.id"], name="chapter_work_id_fkey"
|
||||||
|
),
|
||||||
PrimaryKeyConstraint("id", name="chapter_pkey"),
|
PrimaryKeyConstraint("id", name="chapter_pkey"),
|
||||||
UniqueConstraint(
|
UniqueConstraint(
|
||||||
"work_id", "section_number", "chapter_number", name="chapter_work_id_section_number_chapter_number_key"
|
"work_id",
|
||||||
|
"section_number",
|
||||||
|
"chapter_number",
|
||||||
|
name="chapter_work_id_section_number_chapter_number_key",
|
||||||
),
|
),
|
||||||
{"schema": "shakespeare"},
|
{"schema": "shakespeare"},
|
||||||
)
|
)
|
||||||
@ -152,7 +170,9 @@ class Chapter(Base):
|
|||||||
description: Mapped[str] = mapped_column(String(256))
|
description: Mapped[str] = mapped_column(String(256))
|
||||||
|
|
||||||
work: Mapped["Work"] = relationship("Work", back_populates="chapter")
|
work: Mapped["Work"] = relationship("Work", back_populates="chapter")
|
||||||
paragraph: Mapped[list["Paragraph"]] = relationship("Paragraph", back_populates="chapter")
|
paragraph: Mapped[list["Paragraph"]] = relationship(
|
||||||
|
"Paragraph", back_populates="chapter"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
t_character_work = Table(
|
t_character_work = Table(
|
||||||
@ -160,8 +180,14 @@ t_character_work = Table(
|
|||||||
Base.metadata,
|
Base.metadata,
|
||||||
Column("character_id", String(32), primary_key=True, nullable=False),
|
Column("character_id", String(32), primary_key=True, nullable=False),
|
||||||
Column("work_id", String(32), primary_key=True, nullable=False),
|
Column("work_id", String(32), primary_key=True, nullable=False),
|
||||||
ForeignKeyConstraint(["character_id"], ["shakespeare.character.id"], name="character_work_character_id_fkey"),
|
ForeignKeyConstraint(
|
||||||
ForeignKeyConstraint(["work_id"], ["shakespeare.work.id"], name="character_work_work_id_fkey"),
|
["character_id"],
|
||||||
|
["shakespeare.character.id"],
|
||||||
|
name="character_work_character_id_fkey",
|
||||||
|
),
|
||||||
|
ForeignKeyConstraint(
|
||||||
|
["work_id"], ["shakespeare.work.id"], name="character_work_work_id_fkey"
|
||||||
|
),
|
||||||
PrimaryKeyConstraint("character_id", "work_id", name="character_work_pkey"),
|
PrimaryKeyConstraint("character_id", "work_id", name="character_work_pkey"),
|
||||||
schema="shakespeare",
|
schema="shakespeare",
|
||||||
)
|
)
|
||||||
@ -198,13 +224,23 @@ class Paragraph(Base):
|
|||||||
|
|
||||||
__tablename__ = "paragraph"
|
__tablename__ = "paragraph"
|
||||||
__table_args__ = (
|
__table_args__ = (
|
||||||
ForeignKeyConstraint(["character_id"], ["shakespeare.character.id"], name="paragraph_character_id_fkey"),
|
ForeignKeyConstraint(
|
||||||
|
["character_id"],
|
||||||
|
["shakespeare.character.id"],
|
||||||
|
name="paragraph_character_id_fkey",
|
||||||
|
),
|
||||||
ForeignKeyConstraint(
|
ForeignKeyConstraint(
|
||||||
["work_id", "section_number", "chapter_number"],
|
["work_id", "section_number", "chapter_number"],
|
||||||
["shakespeare.chapter.work_id", "shakespeare.chapter.section_number", "shakespeare.chapter.chapter_number"],
|
[
|
||||||
|
"shakespeare.chapter.work_id",
|
||||||
|
"shakespeare.chapter.section_number",
|
||||||
|
"shakespeare.chapter.chapter_number",
|
||||||
|
],
|
||||||
name="paragraph_chapter_fkey",
|
name="paragraph_chapter_fkey",
|
||||||
),
|
),
|
||||||
ForeignKeyConstraint(["work_id"], ["shakespeare.work.id"], name="paragraph_work_id_fkey"),
|
ForeignKeyConstraint(
|
||||||
|
["work_id"], ["shakespeare.work.id"], name="paragraph_work_id_fkey"
|
||||||
|
),
|
||||||
PrimaryKeyConstraint("id", name="paragraph_pkey"),
|
PrimaryKeyConstraint("id", name="paragraph_pkey"),
|
||||||
{"schema": "shakespeare"},
|
{"schema": "shakespeare"},
|
||||||
)
|
)
|
||||||
@ -222,7 +258,9 @@ class Paragraph(Base):
|
|||||||
char_count: Mapped[int] = mapped_column(Integer)
|
char_count: Mapped[int] = mapped_column(Integer)
|
||||||
word_count: Mapped[int] = mapped_column(Integer)
|
word_count: Mapped[int] = mapped_column(Integer)
|
||||||
|
|
||||||
character: Mapped["Character"] = relationship("Character", back_populates="paragraph")
|
character: Mapped["Character"] = relationship(
|
||||||
|
"Character", back_populates="paragraph"
|
||||||
|
)
|
||||||
chapter: Mapped["Chapter"] = relationship("Chapter", back_populates="paragraph")
|
chapter: Mapped["Chapter"] = relationship("Chapter", back_populates="paragraph")
|
||||||
work: Mapped["Work"] = relationship("Work", back_populates="paragraph")
|
work: Mapped["Work"] = relationship("Work", back_populates="paragraph")
|
||||||
|
|
||||||
@ -244,6 +282,12 @@ class Paragraph(Base):
|
|||||||
- List[Paragraph]: A list of `Paragraph` objects that are associated with the specified character.
|
- List[Paragraph]: A list of `Paragraph` objects that are associated with the specified character.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
stmt = select(cls).join(Character).join(Chapter).join(Work).where(Character.name == character)
|
stmt = (
|
||||||
|
select(cls)
|
||||||
|
.join(Character)
|
||||||
|
.join(Chapter)
|
||||||
|
.join(Work)
|
||||||
|
.where(Character.name == character)
|
||||||
|
)
|
||||||
result = await db_session.execute(stmt)
|
result = await db_session.execute(stmt)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
@ -13,11 +13,15 @@ from app.models.nonsense import Nonsense
|
|||||||
class Stuff(Base):
|
class Stuff(Base):
|
||||||
__tablename__ = "stuff"
|
__tablename__ = "stuff"
|
||||||
__table_args__ = ({"schema": "happy_hog"},)
|
__table_args__ = ({"schema": "happy_hog"},)
|
||||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), unique=True, default=uuid.uuid4, autoincrement=True)
|
id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), unique=True, default=uuid.uuid4, autoincrement=True
|
||||||
|
)
|
||||||
name: Mapped[str] = mapped_column(String, primary_key=True, unique=True)
|
name: Mapped[str] = mapped_column(String, primary_key=True, unique=True)
|
||||||
description: Mapped[str | None]
|
description: Mapped[str | None]
|
||||||
|
|
||||||
nonsense: Mapped["Nonsense"] = relationship("Nonsense", secondary="happy_hog.stuff_full_of_nonsense")
|
nonsense: Mapped["Nonsense"] = relationship(
|
||||||
|
"Nonsense", secondary="happy_hog.stuff_full_of_nonsense"
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def find(cls, db_session: AsyncSession, name: str):
|
async def find(cls, db_session: AsyncSession, name: str):
|
||||||
@ -42,7 +46,11 @@ class Stuff(Base):
|
|||||||
class StuffFullOfNonsense(Base):
|
class StuffFullOfNonsense(Base):
|
||||||
__tablename__ = "stuff_full_of_nonsense"
|
__tablename__ = "stuff_full_of_nonsense"
|
||||||
__table_args__ = ({"schema": "happy_hog"},)
|
__table_args__ = ({"schema": "happy_hog"},)
|
||||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), default=uuid.uuid4, primary_key=True)
|
id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), default=uuid.uuid4, primary_key=True
|
||||||
|
)
|
||||||
stuff_id: Mapped[Stuff] = mapped_column(UUID, ForeignKey("happy_hog.stuff.id"))
|
stuff_id: Mapped[Stuff] = mapped_column(UUID, ForeignKey("happy_hog.stuff.id"))
|
||||||
nonsense_id: Mapped["Nonsense"] = mapped_column(UUID, ForeignKey("happy_hog.nonsense.id"))
|
nonsense_id: Mapped["Nonsense"] = mapped_column(
|
||||||
|
UUID, ForeignKey("happy_hog.nonsense.id")
|
||||||
|
)
|
||||||
but_why: Mapped[str | None]
|
but_why: Mapped[str | None]
|
||||||
|
@ -15,7 +15,9 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
|||||||
|
|
||||||
|
|
||||||
class User(Base):
|
class User(Base):
|
||||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), default=uuid.uuid4, primary_key=True)
|
id: Mapped[uuid.UUID] = mapped_column(
|
||||||
|
UUID(as_uuid=True), default=uuid.uuid4, primary_key=True
|
||||||
|
)
|
||||||
email: Mapped[str] = mapped_column(String, nullable=False, unique=True)
|
email: Mapped[str] = mapped_column(String, nullable=False, unique=True)
|
||||||
first_name: Mapped[str] = mapped_column(String, nullable=False)
|
first_name: Mapped[str] = mapped_column(String, nullable=False)
|
||||||
last_name: Mapped[str] = mapped_column(String, nullable=False)
|
last_name: Mapped[str] = mapped_column(String, nullable=False)
|
||||||
@ -28,7 +30,9 @@ class User(Base):
|
|||||||
@password.setter
|
@password.setter
|
||||||
def password(self, password: SecretStr):
|
def password(self, password: SecretStr):
|
||||||
_password_string = password.get_secret_value()
|
_password_string = password.get_secret_value()
|
||||||
self._password = bcrypt.hashpw(_password_string.encode("utf-8"), bcrypt.gensalt())
|
self._password = bcrypt.hashpw(
|
||||||
|
_password_string.encode("utf-8"), bcrypt.gensalt()
|
||||||
|
)
|
||||||
|
|
||||||
def check_password(self, password: SecretStr):
|
def check_password(self, password: SecretStr):
|
||||||
return pwd_context.verify(password.get_secret_value(), self.password)
|
return pwd_context.verify(password.get_secret_value(), self.password)
|
||||||
|
@ -8,10 +8,20 @@ config = ConfigDict(from_attributes=True)
|
|||||||
# TODO: add pydantic field validator for strong password
|
# TODO: add pydantic field validator for strong password
|
||||||
class UserSchema(BaseModel):
|
class UserSchema(BaseModel):
|
||||||
model_config = config
|
model_config = config
|
||||||
email: EmailStr = Field(title="User’s email", description="User’s email", examples=["john@domain.com"])
|
email: EmailStr = Field(
|
||||||
first_name: str = Field(title="User’s first name", description="User’s first name", examples=["John"])
|
title="User’s email", description="User’s email", examples=["john@domain.com"]
|
||||||
last_name: str = Field(title="User’s last name", description="User’s last name", examples=["Doe"])
|
)
|
||||||
password: SecretStr = Field(title="User’s password", description="User’s password", examples=["@SuperSecret123"])
|
first_name: str = Field(
|
||||||
|
title="User’s first name", description="User’s first name", examples=["John"]
|
||||||
|
)
|
||||||
|
last_name: str = Field(
|
||||||
|
title="User’s last name", description="User’s last name", examples=["Doe"]
|
||||||
|
)
|
||||||
|
password: SecretStr = Field(
|
||||||
|
title="User’s password",
|
||||||
|
description="User’s password",
|
||||||
|
examples=["@SuperSecret123"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserResponse(BaseModel):
|
class UserResponse(BaseModel):
|
||||||
@ -23,11 +33,19 @@ class UserResponse(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class TokenResponse(BaseModel):
|
class TokenResponse(BaseModel):
|
||||||
access_token: str = Field(title="User’s access token", description="User’s access token")
|
access_token: str = Field(
|
||||||
|
title="User’s access token", description="User’s access token"
|
||||||
|
)
|
||||||
token_type: str = Field(title="User’s token type", description="User’s token type")
|
token_type: str = Field(title="User’s token type", description="User’s token type")
|
||||||
|
|
||||||
|
|
||||||
class UserLogin(BaseModel):
|
class UserLogin(BaseModel):
|
||||||
model_config = config
|
model_config = config
|
||||||
email: EmailStr = Field(title="User’s email", description="User’s email", examples=["john@domain.com"])
|
email: EmailStr = Field(
|
||||||
password: SecretStr = Field(title="User’s password", description="User’s password", examples=["@SuperSecret123"])
|
title="User’s email", description="User’s email", examples=["john@domain.com"]
|
||||||
|
)
|
||||||
|
password: SecretStr = Field(
|
||||||
|
title="User’s password",
|
||||||
|
description="User’s password",
|
||||||
|
examples=["@SuperSecret123"],
|
||||||
|
)
|
||||||
|
@ -30,9 +30,13 @@ class AuthBearer(HTTPBearer):
|
|||||||
if not credentials:
|
if not credentials:
|
||||||
raise HTTPException(status_code=403, detail="Invalid authorization code.")
|
raise HTTPException(status_code=403, detail="Invalid authorization code.")
|
||||||
if credentials.scheme != "Bearer":
|
if credentials.scheme != "Bearer":
|
||||||
raise HTTPException(status_code=403, detail="Invalid authentication scheme.")
|
raise HTTPException(
|
||||||
|
status_code=403, detail="Invalid authentication scheme."
|
||||||
|
)
|
||||||
if not await verify_jwt(request, credentials.credentials):
|
if not await verify_jwt(request, credentials.credentials):
|
||||||
raise HTTPException(status_code=403, detail="Invalid token or expired token.")
|
raise HTTPException(
|
||||||
|
status_code=403, detail="Invalid token or expired token."
|
||||||
|
)
|
||||||
return credentials.credentials
|
return credentials.credentials
|
||||||
|
|
||||||
|
|
||||||
@ -43,8 +47,12 @@ async def create_access_token(user: User, request: Request):
|
|||||||
"expiry": time.time() + global_settings.jwt_expire,
|
"expiry": time.time() + global_settings.jwt_expire,
|
||||||
"platform": request.headers.get("User-Agent"),
|
"platform": request.headers.get("User-Agent"),
|
||||||
}
|
}
|
||||||
token = jwt.encode(payload, str(user.password), algorithm=global_settings.jwt_algorithm)
|
token = jwt.encode(
|
||||||
|
payload, str(user.password), algorithm=global_settings.jwt_algorithm
|
||||||
|
)
|
||||||
|
|
||||||
_bool = await set_to_redis(request, token, str(payload), ex=global_settings.jwt_expire)
|
_bool = await set_to_redis(
|
||||||
|
request, token, str(payload), ex=global_settings.jwt_expire
|
||||||
|
)
|
||||||
if _bool:
|
if _bool:
|
||||||
return token
|
return token
|
||||||
|
@ -19,4 +19,6 @@ class AppLogger(metaclass=SingletonMeta):
|
|||||||
|
|
||||||
class RichConsoleHandler(RichHandler):
|
class RichConsoleHandler(RichHandler):
|
||||||
def __init__(self, width=200, style=None, **kwargs):
|
def __init__(self, width=200, style=None, **kwargs):
|
||||||
super().__init__(console=Console(color_system="256", width=width, style=style), **kwargs)
|
super().__init__(
|
||||||
|
console=Console(color_system="256", width=width, style=style), **kwargs
|
||||||
|
)
|
||||||
|
@ -2,18 +2,36 @@ import pytest
|
|||||||
from httpx import AsyncClient
|
from httpx import AsyncClient
|
||||||
from starlette import status
|
from starlette import status
|
||||||
import jwt
|
import jwt
|
||||||
|
from inline_snapshot import snapshot
|
||||||
|
from dirty_equals import IsStr, IsUUID, IsPositiveFloat
|
||||||
|
|
||||||
pytestmark = pytest.mark.anyio
|
pytestmark = pytest.mark.anyio
|
||||||
|
|
||||||
|
|
||||||
# TODO: parametrize test with diff urls
|
# TODO: parametrize test with diff urls
|
||||||
async def test_add_user(client: AsyncClient):
|
async def test_add_user(client: AsyncClient):
|
||||||
payload = {"email": "joe@grillazz.com", "first_name": "Joe", "last_name": "Garcia", "password": "s1lly"}
|
payload = {
|
||||||
|
"email": "joe@grillazz.com",
|
||||||
|
"first_name": "Joe",
|
||||||
|
"last_name": "Garcia",
|
||||||
|
"password": "s1lly",
|
||||||
|
}
|
||||||
response = await client.post("/user/", json=payload)
|
response = await client.post("/user/", json=payload)
|
||||||
assert response.status_code == status.HTTP_201_CREATED
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
claimset = jwt.decode(response.json()["access_token"], options={"verify_signature": False})
|
assert response.json() == snapshot(
|
||||||
assert claimset["email"] == payload["email"]
|
{
|
||||||
assert claimset["expiry"] > 0
|
"id": IsUUID(4),
|
||||||
|
"email": "joe@grillazz.com",
|
||||||
|
"first_name": "Joe",
|
||||||
|
"last_name": "Garcia",
|
||||||
|
"access_token": IsStr(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
claimset = jwt.decode(
|
||||||
|
response.json()["access_token"], options={"verify_signature": False}
|
||||||
|
)
|
||||||
|
assert claimset["expiry"] == IsPositiveFloat()
|
||||||
assert claimset["platform"] == "python-httpx/0.27.0"
|
assert claimset["platform"] == "python-httpx/0.27.0"
|
||||||
|
|
||||||
|
|
||||||
@ -22,9 +40,11 @@ async def test_get_token(client: AsyncClient):
|
|||||||
payload = {"email": "joe@grillazz.com", "password": "s1lly"}
|
payload = {"email": "joe@grillazz.com", "password": "s1lly"}
|
||||||
response = await client.post("/user/token", json=payload)
|
response = await client.post("/user/token", json=payload)
|
||||||
assert response.status_code == status.HTTP_201_CREATED
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
claimset = jwt.decode(response.json()["access_token"], options={"verify_signature": False})
|
claimset = jwt.decode(
|
||||||
|
response.json()["access_token"], options={"verify_signature": False}
|
||||||
|
)
|
||||||
assert claimset["email"] == payload["email"]
|
assert claimset["email"] == payload["email"]
|
||||||
assert claimset["expiry"] > 0
|
assert claimset["expiry"] == IsPositiveFloat()
|
||||||
assert claimset["platform"] == "python-httpx/0.27.0"
|
assert claimset["platform"] == "python-httpx/0.27.0"
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,4 +24,4 @@ async def test_import_animals(client: AsyncClient):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == expected_status
|
assert response.status_code == expected_status
|
||||||
assert response.json() == {'filename': 'nonsense.xlsx', 'nonsense_records': 10}
|
assert response.json() == {"filename": "nonsense.xlsx", "nonsense_records": 10}
|
||||||
|
@ -32,7 +32,6 @@ async def client(start_db) -> AsyncClient:
|
|||||||
|
|
||||||
transport = ASGITransport(
|
transport = ASGITransport(
|
||||||
app=app,
|
app=app,
|
||||||
|
|
||||||
)
|
)
|
||||||
async with AsyncClient(
|
async with AsyncClient(
|
||||||
# app=app,
|
# app=app,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user