Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve tests #76

Merged
merged 157 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
157 commits
Select commit Hold shift + click to select a range
4de880a
remove pymark
EduardSchwarzkopf Feb 25, 2024
0947924
no dev profile
EduardSchwarzkopf Feb 25, 2024
d02a419
set db session scop to session
EduardSchwarzkopf Feb 25, 2024
4506964
add helpers function for http requests
EduardSchwarzkopf Feb 25, 2024
dc7328e
use helpers function for http requests
EduardSchwarzkopf Feb 25, 2024
845e1b2
update arguments for session and client
EduardSchwarzkopf Feb 25, 2024
46d03cc
update helper function params
EduardSchwarzkopf Feb 25, 2024
6eaf853
add RequestMethod enum
EduardSchwarzkopf Feb 25, 2024
68c6fcf
update client scope to class
EduardSchwarzkopf Feb 25, 2024
cf6c7f4
update helper function
EduardSchwarzkopf Feb 25, 2024
0347ce5
update tests for helper function
EduardSchwarzkopf Feb 25, 2024
850559d
update scoping
EduardSchwarzkopf Feb 25, 2024
40e02fa
add helper function
EduardSchwarzkopf Feb 25, 2024
78c7e5b
imports
EduardSchwarzkopf Feb 25, 2024
a76fad4
use cleanup helper function
EduardSchwarzkopf Feb 25, 2024
e3ff384
fix-me added
EduardSchwarzkopf Feb 25, 2024
687c406
Merge branch 'main' into improve-tests
EduardSchwarzkopf Feb 25, 2024
58e4ab7
fix route
EduardSchwarzkopf Feb 25, 2024
25daeba
autoformatting
EduardSchwarzkopf Feb 25, 2024
b18c9b8
close session when you're done
EduardSchwarzkopf Feb 25, 2024
080a035
remove helper function
EduardSchwarzkopf Feb 25, 2024
8364633
update create transaction test
EduardSchwarzkopf Feb 25, 2024
e5e7c85
add TODO
EduardSchwarzkopf Feb 25, 2024
dac816c
transform into class
EduardSchwarzkopf Feb 25, 2024
2183422
update service class
EduardSchwarzkopf Feb 25, 2024
33dd0e2
use service class
EduardSchwarzkopf Feb 25, 2024
7edf788
fix update_transaction test
EduardSchwarzkopf Feb 25, 2024
ab24bcd
use TransactionService class
EduardSchwarzkopf Feb 25, 2024
e86e6ec
jesus christ, i really need to cleanup this mess someday
EduardSchwarzkopf Feb 25, 2024
1dc0e48
add asyncio
EduardSchwarzkopf Feb 25, 2024
6751b9f
Merge branch 'main' into improve-tests
EduardSchwarzkopf Feb 25, 2024
83977a3
add fixture for account transactions
EduardSchwarzkopf Feb 26, 2024
b8151b4
update delete transaction test
EduardSchwarzkopf Feb 26, 2024
41e1223
update docstring
EduardSchwarzkopf Feb 26, 2024
8c7d37f
update name of delete transaction
EduardSchwarzkopf Feb 26, 2024
1a21926
update test logic
EduardSchwarzkopf Feb 26, 2024
0c78246
update docstring
EduardSchwarzkopf Feb 26, 2024
0d3e871
enhance filter_by function with dynamic operator
EduardSchwarzkopf Feb 26, 2024
fcd25d7
add database operator enum
EduardSchwarzkopf Feb 26, 2024
e36f872
remove await on cleanup
EduardSchwarzkopf Feb 26, 2024
7e9ef2d
add todo
EduardSchwarzkopf Feb 26, 2024
f102c4a
update test create offset_transaction
EduardSchwarzkopf Feb 26, 2024
b451567
make use of service classes
EduardSchwarzkopf Feb 29, 2024
d16af8f
refactor db init
EduardSchwarzkopf Feb 29, 2024
fa09681
update to lifespans
EduardSchwarzkopf Feb 29, 2024
1d05b56
use service class
EduardSchwarzkopf Feb 29, 2024
e6561e1
use service class
EduardSchwarzkopf Feb 29, 2024
5532609
update to service class
EduardSchwarzkopf Feb 29, 2024
60470eb
only update when all previous check are ok
EduardSchwarzkopf Feb 29, 2024
079524e
remove helper function
EduardSchwarzkopf Feb 29, 2024
d0493cb
use db session
EduardSchwarzkopf Feb 29, 2024
8a97bda
update test
EduardSchwarzkopf Feb 29, 2024
01b0e73
use generator
EduardSchwarzkopf Mar 3, 2024
b9bec3c
session factory
EduardSchwarzkopf Mar 3, 2024
525f600
fix lifespan
EduardSchwarzkopf Mar 3, 2024
e72c473
use session from db
EduardSchwarzkopf Mar 3, 2024
77dcc56
fix naming
EduardSchwarzkopf Mar 3, 2024
94ac266
remove None variable from log
EduardSchwarzkopf Mar 3, 2024
63416b1
variable init needs to be done first
EduardSchwarzkopf Mar 3, 2024
e634470
use account service
EduardSchwarzkopf Mar 3, 2024
9c020ca
use transaction manager
EduardSchwarzkopf Mar 3, 2024
f8b0346
add delete back in
EduardSchwarzkopf Mar 3, 2024
f260623
update test cleanup
EduardSchwarzkopf Mar 3, 2024
2486fff
update delete offset_transaction test
EduardSchwarzkopf Mar 3, 2024
a4f3a10
use model_dump
EduardSchwarzkopf Mar 3, 2024
2961e2e
use correct transaction_information
EduardSchwarzkopf Mar 3, 2024
f26382b
add offset_accoun helper function
EduardSchwarzkopf Mar 3, 2024
31dab9b
move date ranger function to helpers.py
EduardSchwarzkopf Mar 3, 2024
9075e7a
use date ranger helper function
EduardSchwarzkopf Mar 3, 2024
0a0bfba
update transaction fixture
EduardSchwarzkopf Mar 3, 2024
24bb8de
update offset transaction test failed
EduardSchwarzkopf Mar 3, 2024
b0d292a
fix: wrong variable returned
EduardSchwarzkopf Mar 3, 2024
05cb5fe
remove unused class
EduardSchwarzkopf Mar 3, 2024
54ee850
update save order
EduardSchwarzkopf Mar 3, 2024
5e2a8cc
wip
EduardSchwarzkopf Mar 3, 2024
9c6466f
split fixture into getter sand setters
EduardSchwarzkopf Mar 3, 2024
398ccd5
create dedicated test session for fixtures
EduardSchwarzkopf Mar 3, 2024
555ac1c
update route
EduardSchwarzkopf Mar 4, 2024
ea9ce24
fix user id
EduardSchwarzkopf Mar 4, 2024
fcd21ec
add get test account transactions fixture
EduardSchwarzkopf Mar 4, 2024
02a8fc6
update test
EduardSchwarzkopf Mar 4, 2024
9ba3d02
assert first before hitting db
EduardSchwarzkopf Mar 4, 2024
3494ab8
update transaction schema for 2 decimals
EduardSchwarzkopf Mar 4, 2024
63a547c
simplify populate_db fnc
EduardSchwarzkopf Mar 4, 2024
916966a
use same session
EduardSchwarzkopf Mar 4, 2024
1cb395c
revert changes to db.session
EduardSchwarzkopf Mar 4, 2024
c24a8d7
fix: message should be User not Transaction
EduardSchwarzkopf Mar 4, 2024
f5b5c44
rename
EduardSchwarzkopf Mar 4, 2024
58d40f8
add unique to filter_by
EduardSchwarzkopf Mar 4, 2024
e9b8ede
await engine.dispose
EduardSchwarzkopf Mar 4, 2024
3323374
yield current session
EduardSchwarzkopf Mar 4, 2024
f92fa1f
make db.session fixture scope=session
EduardSchwarzkopf Mar 4, 2024
e32b9b1
update fixture
EduardSchwarzkopf Mar 4, 2024
b218490
rename helpers to utils
EduardSchwarzkopf Mar 4, 2024
ac0e449
refactor for new session logic
EduardSchwarzkopf Mar 4, 2024
8a0c2e6
remove ClientSessionWrapper
EduardSchwarzkopf Mar 4, 2024
d7874a0
only posts please
EduardSchwarzkopf Mar 4, 2024
f47c71a
refactor for single session
EduardSchwarzkopf Mar 4, 2024
013261d
remove ClientSessionWrapper import
EduardSchwarzkopf Mar 4, 2024
ff50925
update amount and balance to Decimal type
EduardSchwarzkopf Mar 4, 2024
15ed1a3
update amount and balance to decimal type
EduardSchwarzkopf Mar 4, 2024
4999b21
use service for transaction creation
EduardSchwarzkopf Mar 4, 2024
d85b044
use test_user in update_account
EduardSchwarzkopf Mar 4, 2024
481a8d7
use new make_http_request method
EduardSchwarzkopf Mar 4, 2024
0396e3e
update from float to Decimal type
EduardSchwarzkopf Mar 4, 2024
2c5849c
update to Decimal
EduardSchwarzkopf Mar 5, 2024
9520b36
remove Decimal type
EduardSchwarzkopf Mar 5, 2024
7c602f6
test_invalid_title_create_account added
EduardSchwarzkopf Mar 5, 2024
c21ef8f
RoundField added
EduardSchwarzkopf Mar 5, 2024
6cadffc
session.commit added
EduardSchwarzkopf Mar 5, 2024
072740a
remove obselete fixture
EduardSchwarzkopf Mar 5, 2024
e3f40f0
add dynamic relationship loading
EduardSchwarzkopf Mar 5, 2024
42475f0
update relationship loading
EduardSchwarzkopf Mar 5, 2024
5bb6206
update relationship loading
EduardSchwarzkopf Mar 5, 2024
b8c6316
make create transactions async
EduardSchwarzkopf Mar 5, 2024
f3845f1
use relationship
EduardSchwarzkopf Mar 5, 2024
8bf1b8c
load relationship for user in accounts
EduardSchwarzkopf Mar 5, 2024
83b5d6f
update to InstrumentedAttribute type in load_relationship_list
EduardSchwarzkopf Mar 5, 2024
411d785
remove transaction_manager import
EduardSchwarzkopf Mar 5, 2024
ffcc9d0
test for single account
EduardSchwarzkopf Mar 5, 2024
1672cbd
also check if transaction is removed
EduardSchwarzkopf Mar 5, 2024
55465db
fix typo
EduardSchwarzkopf Mar 5, 2024
4016d53
update test_update_account: check for actual db values as well
EduardSchwarzkopf Mar 6, 2024
4f0b2f4
remove session close
EduardSchwarzkopf Mar 6, 2024
3833482
remove comment
EduardSchwarzkopf Mar 6, 2024
2f47ddc
remove minimal docstring
EduardSchwarzkopf Mar 6, 2024
8f9e6b3
add check if removed from database
EduardSchwarzkopf Mar 6, 2024
5316db1
add docstring
EduardSchwarzkopf Mar 9, 2024
8af2fd2
fix unused argument
EduardSchwarzkopf Mar 9, 2024
474a010
add docstring
EduardSchwarzkopf Mar 9, 2024
eabad76
fix remove unused imports
EduardSchwarzkopf Mar 9, 2024
0d3f742
add docstring
EduardSchwarzkopf Mar 9, 2024
863cd71
add docstring
EduardSchwarzkopf Mar 9, 2024
ce4dd6d
fix docstrings
EduardSchwarzkopf Mar 9, 2024
bc56d4d
ignore unused-argument with reference why
EduardSchwarzkopf Mar 9, 2024
2f74403
add types to arguments
EduardSchwarzkopf Mar 9, 2024
ddcad92
add docstrings
EduardSchwarzkopf Mar 9, 2024
217b389
add docstring
EduardSchwarzkopf Mar 9, 2024
91e6a27
fix imports
EduardSchwarzkopf Mar 9, 2024
77f7bf5
update fixture name
EduardSchwarzkopf Mar 9, 2024
b32eb84
remove comments
EduardSchwarzkopf Mar 9, 2024
19b0a2d
use correct fixture
EduardSchwarzkopf Mar 9, 2024
fc69ab0
remove comment
EduardSchwarzkopf Mar 9, 2024
2f79ad0
update user handling
EduardSchwarzkopf Mar 9, 2024
9bf937a
add filter_by_multiple attributes
EduardSchwarzkopf Mar 9, 2024
d726e0b
assert first for status code before continue
EduardSchwarzkopf Mar 9, 2024
6ca12a3
use new filter_by_multiple function
EduardSchwarzkopf Mar 9, 2024
8663b60
fix line-too-long
EduardSchwarzkopf Mar 9, 2024
6c60c29
linting: update function name
EduardSchwarzkopf Mar 9, 2024
9076a39
linting
EduardSchwarzkopf Mar 9, 2024
14b4154
update docstring
EduardSchwarzkopf Mar 9, 2024
f07e059
add types
EduardSchwarzkopf Mar 9, 2024
147d29d
update docstring
EduardSchwarzkopf Mar 9, 2024
90a80b9
remove old docstrings
EduardSchwarzkopf Mar 9, 2024
d0bdcdb
add types
EduardSchwarzkopf Mar 9, 2024
8c72c36
update docstrings
EduardSchwarzkopf Mar 9, 2024
6f620fb
add docstring
EduardSchwarzkopf Mar 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Set the correct env in VS Code with:
### Starting backend

Database
`docker compose up --profile dev -d `
`docker compose up -d `

FastAPI in docker
`docker run --name=pecuny --rm -dp 8000:8000 --env-file .env pecuny`
Expand Down
48 changes: 27 additions & 21 deletions app/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,50 @@


class Database:
def __init__(self, url):
self.session = None
self.engine = None
def __init__(self, url: str) -> None:
"""
Initializer/Constructor for Database class.

Parameters:
url: str - The URL of the database.
"""
self.url = url
self.engine = None
self.session = None

async def init(self):
"""Initialize the database connection.
"""
Initializes the database connection.

Args:
self
self: The instance of the class.

Returns:
None

Raises:
None
"""
# closes connections if a session is created,
# so as not to create repeated connections

if self.session:
await self.session.close()

self.engine = create_async_engine(self.url, future=True)
self.session = self.get_session()

def get_session(self) -> AsyncSession:
"""Get the database session.
self.session = await self.get_session()

Args:
self
async def get_session(self) -> AsyncSession:
"""
Creates and provides a new database session.

Returns:
AsyncSession: The database session.

Raises:
None
AsyncSession: A database session
"""
return sessionmaker(self.engine, expire_on_commit=False, class_=AsyncSession)()
session_factory = sessionmaker(
self.engine, expire_on_commit=False, class_=AsyncSession
)
async with session_factory() as session:
try:
return session
except Exception as e:
await session.rollback()
raise e


async def get_user_db():
Expand Down
54 changes: 21 additions & 33 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from contextlib import asynccontextmanager

import arel
from fastapi import FastAPI, Request, status
Expand All @@ -20,7 +21,26 @@
from app.utils import BreadcrumbBuilder
from app.utils.exceptions import UnauthorizedPageException

app = FastAPI()

@asynccontextmanager
async def lifespan(_api_app: FastAPI):
"""
Context manager for managing the lifespan of the FastAPI application.

Args:
app (FastAPI): The FastAPI application instance.

Yields:
None

"""

await db.init()
yield
await db.session.close()


app = FastAPI(lifespan=lifespan)
logger = get_logger(__name__)

app.mount("/static", StaticFiles(directory="static"), name="static")
Expand Down Expand Up @@ -161,38 +181,6 @@ async def page_not_found_exception_handler(request: Request, exc: HTTPException)
)


@app.on_event("startup")
async def startup_event():
"""Event handler for the startup event.

Args:
None

Returns:
None

Raises:
None
"""
await db.init()


@app.on_event("shutdown")
async def shutdown_event():
"""Event handler for the shutdown event.

Args:
None

Returns:
None

Raises:
None
"""
await db.session.close()


for route in router_list:
app.include_router(
route["router"], prefix=route.get("prefix", ""), tags=route.get("tags", [])
Expand Down
154 changes: 110 additions & 44 deletions app/repository.py
Original file line number Diff line number Diff line change
@@ -1,76 +1,149 @@
from datetime import datetime
from typing import List, Type, TypeVar
from typing import List, Optional, Tuple, Type, TypeVar

from sqlalchemy import Select, text
from sqlalchemy import update as sql_update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
from sqlalchemy.orm import joinedload, selectinload
from sqlalchemy.orm.attributes import InstrumentedAttribute

from app.database import db
from app.models import BaseModel
from app.utils.enums import DatabaseFilterOperator

from . import models

ModelT = TypeVar("ModelT", bound=BaseModel)


async def get_all(cls: Type[ModelT]) -> List[ModelT]:
def load_relationships(query: Select, relationships: InstrumentedAttribute = None):
"""Apply loading options for specified relationships to a query.

Args:
cls: The model class.
query: The SQLAlchemy query object.
*relationships: Class-bound attributes representing relationships to load.

Returns:
The modified query with loading options applied.
"""
if relationships:
options = [selectinload(rel) for rel in relationships]
query = query.options(*options)
return query


async def get_all(
cls: Type, load_relationships_list: Optional[List[InstrumentedAttribute]] = None
) -> List[ModelT]:
"""Retrieve all instances of the specified model from the database.

Args:
cls: The type of the model.
load_relationships: Optional list of relationships to load.

Returns:
List[ModelT]: A list of instances of the specified model.

Raises:
None
"""
q = select(cls)
q = load_relationships(q, load_relationships_list)
result = await db.session.execute(q)
result.unique()
return result.scalars().all()
return result.unique().scalars().all()


async def filter_by(cls: Type[ModelT], attribute: str, value: str) -> List[ModelT]:
"""Filter instances of the specified model by the given attribute and value.
async def get(
cls: Type,
instance_id: int,
load_relationships_list: Optional[List[InstrumentedAttribute]] = None,
) -> ModelT:
"""Retrieve an instance of the specified model by its ID.

Args:
cls: The type of the model.
instance_id: The ID of the instance to retrieve.
load_relationships: Optional list of relationships to load.

Returns:
ModelT: The instance of the specified model with the given ID.
"""
q = select(cls).where(cls.id == instance_id)
q = load_relationships(q, load_relationships_list)
result = await db.session.execute(q)
return result.scalars().first()


async def filter_by(
cls: Type[ModelT],
attribute: str, # TODO: InstrumentedAttribute,
value: str,
operator: DatabaseFilterOperator = DatabaseFilterOperator.EQUAL,
load_relationships_list: Optional[List[str]] = None,
) -> List[ModelT]:
"""
Filters the records of a given model by a specified attribute and value.

Args:
cls: The model class.
attribute: The attribute to filter by.
value: The value to filter with.
operator: The operator to use for the filter (default: EQUAL).

Returns:
List[ModelT]: A list of instances of the specified model that match the filter criteria.
List[ModelT]: The filtered records.

Raises:
None
"""
query = select(cls).where(getattr(cls, attribute) == value)
result = await db.session.execute(query)
return result.scalars().all()
# TODO: Update for InstrumentedAttribute
# condition = text(f"{attribute.key} {operator.value} :val")
condition = text(f"{attribute} {operator.value} :val")
EduardSchwarzkopf marked this conversation as resolved.
Show resolved Hide resolved

q = select(cls).where(condition).params(val=value)
q = load_relationships(q, load_relationships_list)

async def get(cls: Type[ModelT], instance_id: int, load_relationships=None) -> ModelT:
"""Retrieve an instance of the specified model by its ID.
result = await db.session.execute(q)

return result.unique().scalars().all()


async def filter_by_multiple(
cls: Type[ModelT],
conditions: List[Tuple[str, str, DatabaseFilterOperator]],
load_relationships_list: Optional[List[str]] = None,
) -> List[ModelT]:
"""
Filters the records of a given model by multiple attributes and values.

Args:
cls: The type of the model.
instance_id: The ID of the instance to retrieve.
load_relationships: Optional list of relationships to load.
cls: The model class.
conditions: A list of tuples where each tuple contains an attribute to filter by,
a value to filter with, and an optional operator
(if not provided, EQUAL is used).
load_relationships_list: Optional list of relationships to load.

Returns:
ModelT: The instance of the specified model with the given ID.

Raises:
None
List[Model]: The filtered records.
"""
stmt = select(cls).where(cls.id == instance_id)
if load_relationships:
options = [selectinload(rel) for rel in load_relationships]
stmt = stmt.options(*options)
result = await db.session.execute(stmt)
return result.scalars().first()

# Construct the WHERE clause
where_conditions = []
params = {}
for i, (attribute, value, operator) in enumerate(conditions):
param_name = f"val{i}"
where_conditions.append(text(f"{attribute} {operator.value} :{param_name}"))
params[param_name] = value

q = select(cls)
if where_conditions:
for condition in where_conditions:
q = q.where(condition)
q = q.params(**params)

q = load_relationships(q, load_relationships_list)

result = await db.session.execute(q)

return result.scalars().unique().all()


async def get_scheduled_transactions_from_period(
Expand Down Expand Up @@ -157,21 +230,6 @@ async def save(obj: Type[ModelT]) -> None:
db.session.add(obj)


async def get_session() -> AsyncSession:
"""Get the database session.

Args:
None

Returns:
AsyncSession: The database session.

Raises:
None
"""
return db.session


async def commit(session) -> None:
"""Commit the changes made in the session to the database.

Expand Down Expand Up @@ -222,6 +280,14 @@ async def delete(obj: Type[ModelT]) -> None:
Raises:
None
"""

# TODO: Test this
# if isinstance(obj, list):
# for object in obj:
# db.session.delete(object)
# return

# TODO: Await needed?
await db.session.delete(obj)


Expand Down
6 changes: 4 additions & 2 deletions app/routers/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@
from app import transaction_manager as tm
from app.auth_manager import current_active_user
from app.routers.dashboard import router as dashboard_router
from app.services import accounts as service
from app.services import transactions as transaction_service
from app.services.accounts import AccountService
from app.services.transactions import TransactionService
from app.utils import PageRouter
from app.utils.account_utils import get_account_list_template
from app.utils.enums import FeedbackType
from app.utils.template_utils import add_breadcrumb, render_template, set_feedback

PREFIX = f"{dashboard_router.prefix}/accounts"
router = PageRouter(prefix=PREFIX, tags=["Accounts"])
service = AccountService()
EduardSchwarzkopf marked this conversation as resolved.
Show resolved Hide resolved
transaction_service = TransactionService()


async def handle_account_route(
Expand Down
Loading