Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #233 from Ivareh/212-privatize-the-api-in-deployed…
Browse files Browse the repository at this point in the history
…-remote-version

212 privatize the api in deployed remote version
bogadisa authored May 14, 2024
2 parents 8cc58d8 + 20b93f4 commit 2497905
Showing 22 changed files with 553 additions and 127 deletions.
1 change: 1 addition & 0 deletions src/.env
Original file line number Diff line number Diff line change
@@ -16,6 +16,7 @@ DOCKER_IMAGE_BACKEND=pom_backend
DOCKER_IMAGE_FRONTEND=pom_frontend

# Backend
PRIVATIZE_API=False
TESTING=False
BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,https://localhost,https://localhost:5173"
PROJECT_NAME="pathofmodifiers"
65 changes: 55 additions & 10 deletions src/backend_api/app/app/api/api_v1/endpoints/account.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, HTTPException
from typing import List, Union

from app.api.deps import get_db
@@ -10,6 +10,8 @@

from sqlalchemy.orm import Session

from app.core.security import verification


router = APIRouter()

@@ -18,25 +20,44 @@
"/{accountName}",
response_model=Union[schemas.Account, List[schemas.Account]],
)
async def get_account(accountName: str, db: Session = Depends(get_db)):
async def get_account(
accountName: str,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Get the account by mapping with key and value for "accountName" .
Get the account by mapping with key and value for "accountName" .
Always returns one account.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorize API access for {get_account.__name__}",
)

account_map = {"accountName": accountName}
account = await CRUD_account.get(db=db, filter=account_map)

return account


@router.get("/", response_model=Union[schemas.Account, List[schemas.Account]])
async def get_all_accounts(db: Session = Depends(get_db)):
async def get_all_accounts(
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Get all accounts.
Returns a list of all accounts.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {get_all_accounts.__name__}",
)

all_accounts = await CRUD_account.get(db=db)

return all_accounts
@@ -49,12 +70,19 @@ async def get_all_accounts(db: Session = Depends(get_db)):
async def create_account(
account: Union[schemas.AccountCreate, List[schemas.AccountCreate]],
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Create one or a list of accounts.
Returns the created account or list of accounts.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {create_account.__name__}",
)

return await CRUD_account.create(db=db, obj_in=account)


@@ -63,12 +91,19 @@ async def update_account(
accountName: str,
account_update: schemas.AccountUpdate,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Update an account by key and value for "accountName".
Returns the updated account.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {update_account.__name__}",
)

account_map = {"accountName": accountName}
account = await CRUD_account.get(
db=db,
@@ -79,13 +114,23 @@ async def update_account(


@router.delete("/{accountName}", response_model=str)
async def delete_account(accountName: str, db: Session = Depends(get_db)):
async def delete_account(
accountName: str,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Delete an account by key and value "accountName".
Returns a message indicating the account was deleted.
Always deletes one account.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {delete_account.__name__}",
)

account_map = {"accountName": accountName}
await CRUD_account.remove(db=db, filter=account_map)

30 changes: 28 additions & 2 deletions src/backend_api/app/app/api/api_v1/endpoints/currency.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, HTTPException
from typing import List, Union

from app.api.deps import get_db
@@ -11,6 +11,8 @@
from sqlalchemy.orm import Session
from sqlalchemy import text

from app.core.security import verification


router = APIRouter()

@@ -64,12 +66,19 @@ async def get_latest_currency_id(db: Session = Depends(get_db)):
async def create_currency(
currency: Union[schemas.CurrencyCreate, List[schemas.CurrencyCreate]],
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Create one or a list of currencies.
Returns the created currency or list of currencies.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {create_currency.__name__}",
)

return await CRUD_currency.create(db=db, obj_in=currency)


@@ -78,12 +87,19 @@ async def update_currency(
currencyId: str,
currency_update: schemas.CurrencyUpdate,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Update a currency by key and value for "currencyId".
Returns the updated currency.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {update_currency.__name__}",
)

currency_map = {"currencyId": currencyId}
currency = await CRUD_currency.get(
db=db,
@@ -94,13 +110,23 @@ async def update_currency(


@router.delete("/{currencyId}", response_model=str)
async def delete_currency(currencyId: str, db: Session = Depends(get_db)):
async def delete_currency(
currencyId: str,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Delete a currency by key and value for "currencyId".
Returns a message indicating the currency was deleted.
Always deletes one currency.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {delete_currency.__name__}",
)

currency_map = {"currencyId": currencyId}
await CRUD_currency.remove(db=db, filter=currency_map)

30 changes: 28 additions & 2 deletions src/backend_api/app/app/api/api_v1/endpoints/item.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, HTTPException
from typing import List, Union

from app.api.deps import get_db
@@ -11,6 +11,8 @@
from sqlalchemy.orm import Session
from sqlalchemy import text

from app.core.security import verification


router = APIRouter()

@@ -87,12 +89,19 @@ async def get_all_items(db: Session = Depends(get_db)):
async def create_item(
item: Union[schemas.ItemCreate, List[schemas.ItemCreate]],
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Create one or a list of new items.
Returns the created item or list of items.
"""
if not verification:
return HTTPException(
status_code=401,
detail=f"Unauthorized API access for {create_item.__name__}",
)

return await CRUD_item.create(db=db, obj_in=item)


@@ -101,12 +110,19 @@ async def update_item(
itemId: str,
item_update: schemas.ItemUpdate,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Update an item by key and value for "itemId".
Returns the updated item.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {update_item.__name__}",
)

item_map = {"itemId": itemId}
item = await CRUD_item.get(
db=db,
@@ -117,13 +133,23 @@ async def update_item(


@router.delete("/{itemId}", response_model=str)
async def delete_item(itemId: str, db: Session = Depends(get_db)):
async def delete_item(
itemId: str,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Delete an item by key and value for "itemId".
Returns a message indicating the item was deleted.
Always deletes one item.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {delete_item.__name__}",
)

item_map = {"itemId": itemId}
await CRUD_item.remove(db=db, filter=item_map)

38 changes: 34 additions & 4 deletions src/backend_api/app/app/api/api_v1/endpoints/item_base_type.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, HTTPException
from typing import List, Union

from app.api.deps import get_db
@@ -10,6 +10,8 @@

from sqlalchemy.orm import Session

from app.core.security import verification


router = APIRouter()

@@ -58,7 +60,9 @@ async def get_base_types(db: Session = Depends(get_db)):

@router.get(
"/uniqueCategories/",
response_model=Union[schemas.ItemBaseTypeCategory, List[schemas.ItemBaseTypeCategory]],
response_model=Union[
schemas.ItemBaseTypeCategory, List[schemas.ItemBaseTypeCategory]
],
)
async def get_unique_categories(db: Session = Depends(get_db)):
"""
@@ -73,7 +77,9 @@ async def get_unique_categories(db: Session = Depends(get_db)):

@router.get(
"/uniqueSubCategories/",
response_model=Union[schemas.ItemBaseTypeSubCategory, List[schemas.ItemBaseTypeSubCategory]],
response_model=Union[
schemas.ItemBaseTypeSubCategory, List[schemas.ItemBaseTypeSubCategory]
],
)
async def get_unique_sub_categories(db: Session = Depends(get_db)):
"""
@@ -93,12 +99,19 @@ async def get_unique_sub_categories(db: Session = Depends(get_db)):
async def create_item_base_type(
itemBaseType: Union[schemas.ItemBaseTypeCreate, List[schemas.ItemBaseTypeCreate]],
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Create one or a list of new item base types.
Returns the created item base type or list of item base types.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {create_item_base_type.__name__}",
)

return await CRUD_itemBaseType.create(db=db, obj_in=itemBaseType)


@@ -107,12 +120,19 @@ async def update_item_base_type(
baseType: str,
item_base_type_update: schemas.ItemBaseTypeUpdate,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Update an item base type by key and value for "baseType".
Returns the updated item base type.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {update_item_base_type.__name__}",
)

item_base_type_map = {"baseType": baseType}
itemBaseType = await CRUD_itemBaseType.get(
db=db,
@@ -125,13 +145,23 @@ async def update_item_base_type(


@router.delete("/{baseType}", response_model=str)
async def delete_item_base_type(baseType: str, db: Session = Depends(get_db)):
async def delete_item_base_type(
baseType: str,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Delete an item base type by key and value for "baseType".
Returns a message that the item base type was deleted successfully.
Always deletes one item base type.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {delete_item_base_type.__name__}",
)

item_base_type_map = {"baseType": baseType}
await CRUD_itemBaseType.remove(db=db, filter=item_base_type_map)

45 changes: 34 additions & 11 deletions src/backend_api/app/app/api/api_v1/endpoints/item_modifier.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, HTTPException
from typing import List, Optional, Union

from app.api.deps import get_db
@@ -10,6 +10,8 @@

from sqlalchemy.orm import Session

from app.core.security import verification


router = APIRouter()

@@ -25,9 +27,9 @@ async def get_item_modifier(
db: Session = Depends(get_db),
):
"""
Get item modifier or list of item modifiers by key and
value for "itemId", optional "modifierId" and optional "position".
Get item modifier or list of item modifiers by key and
value for "itemId", optional "modifierId" and optional "position".
Dominant key is "itemId".
Returns one or a list of item modifiers.
@@ -46,7 +48,7 @@ async def get_item_modifier(
async def get_all_item_modifiers(db: Session = Depends(get_db)):
"""
Get all item modifiers.
Returns a list of all item modifiers.
"""
all_itemModifiers = await CRUD_itemModifier.get(db=db)
@@ -61,12 +63,19 @@ async def get_all_item_modifiers(db: Session = Depends(get_db)):
async def create_item_modifier(
itemModifier: Union[schemas.ItemModifierCreate, List[schemas.ItemModifierCreate]],
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Create one or a list item modifiers.
Returns the created item modifier or list of item modifiers.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {create_item_modifier.__name__}",
)

return await CRUD_itemModifier.create(db=db, obj_in=itemModifier)


@@ -77,15 +86,22 @@ async def update_item_modifier(
position: int,
itemModifier_update: schemas.ItemModifierUpdate,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Update an item modifier by key and value for
Update an item modifier by key and value for
"itemId", optional "modifierId" and optional "position".
Dominant key is "itemId".
Returns the updated item modifier.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {update_item_modifier.__name__}",
)

itemModifier_map = {
"itemId": itemId,
"modifierId": modifierId,
@@ -107,16 +123,23 @@ async def delete_item_modifier(
modifierId: Optional[int] = None,
position: Optional[int] = None,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Delete an item modifier by key and value for
Delete an item modifier by key and value for
"itemId", optional "modifierId" and optional "position".
Dominant key is "itemId".
Returns a message that the item modifier was deleted successfully.
Always deletes one item modifier.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {delete_item_modifier.__name__}",
)

itemModifier_map = {"itemId": itemId}
if modifierId is not None:
itemModifier_map["modifierId"] = modifierId
29 changes: 27 additions & 2 deletions src/backend_api/app/app/api/api_v1/endpoints/modifier.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, HTTPException
from typing import List, Optional, Union

from app.api.deps import get_db
@@ -10,6 +10,8 @@

from sqlalchemy.orm import Session

from app.core.security import verification


router = APIRouter()

@@ -75,12 +77,19 @@ async def get_grouped_modifier_by_effect(db: Session = Depends(get_db)):
async def create_modifier(
modifier: Union[schemas.ModifierCreate, List[schemas.ModifierCreate]],
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Create one or a list of new modifiers.
Returns the created modifier or list of modifiers.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {create_modifier.__name__}",
)

return await CRUD_modifier.create(db=db, obj_in=modifier)


@@ -90,6 +99,7 @@ async def update_modifier(
position: int,
modifier_update: schemas.ModifierUpdate,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Update a modifier by key and value for "modifierId" and "position".
@@ -98,6 +108,12 @@ async def update_modifier(
Returns the updated modifier.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {update_modifier.__name__}",
)

modifier_map = {"modifierId": modifierId, "position": position}
modifier = await CRUD_modifier.get(
db=db,
@@ -109,7 +125,10 @@ async def update_modifier(

@router.delete("/{modifierId}", response_model=str)
async def delete_modifier(
modifierId: int, position: Optional[int] = None, db: Session = Depends(get_db)
modifierId: int,
position: Optional[int] = None,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Delete a modifier by key and value for "modifierId"
@@ -120,6 +139,12 @@ async def delete_modifier(
Returns a message that the modifier was deleted.
Always deletes one modifier.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {delete_modifier.__name__}",
)

modifier_map = {"modifierId": modifierId}
if position is not None:
modifier_map["position"] = position
8 changes: 7 additions & 1 deletion src/backend_api/app/app/api/api_v1/endpoints/plot.py
Original file line number Diff line number Diff line change
@@ -10,16 +10,22 @@

from sqlalchemy.orm import Session

from app.core.security import verification


router = APIRouter()


@router.post("/", response_model=schemas.PlotData)
async def get_plot_data(query: schemas.PlotQuery, db: Session = Depends(get_db)):
async def get_plot_data(
query: schemas.PlotQuery,
db: Session = Depends(get_db),
):
"""
Takes a query based on the 'PlotQuery' schema and retrieves data
to be used for plotting in the format of the 'PlotData' schema.
The 'PlotQuery' schema allows for modifier restriction and item specifications.
"""

return await plotter_tool.plot(db, query=query)
40 changes: 33 additions & 7 deletions src/backend_api/app/app/api/api_v1/endpoints/stash.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from __future__ import annotations
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, HTTPException
from typing import List, Union

from app.api.deps import get_db
@@ -10,6 +10,8 @@

from sqlalchemy.orm import Session

from app.core.security import verification


router = APIRouter()

@@ -21,7 +23,7 @@
async def get_stash(stashId: str, db: Session = Depends(get_db)):
"""
Get stash by key and value for "stashId".
Always returns one stash.
"""
stash_map = {"stashId": stashId}
@@ -34,7 +36,7 @@ async def get_stash(stashId: str, db: Session = Depends(get_db)):
async def get_all_stashes(db: Session = Depends(get_db)):
"""
Get all stashes.
Returns a list of all stashes.
"""
all_stashes = await CRUD_stash.get(db=db)
@@ -49,12 +51,19 @@ async def get_all_stashes(db: Session = Depends(get_db)):
async def create_stash(
stash: Union[schemas.StashCreate, List[schemas.StashCreate]],
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Create one or a list of new stashes.
Returns the created stash or list of stashes.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {create_stash.__name__}",
)

return await CRUD_stash.create(db=db, obj_in=stash)


@@ -63,12 +72,19 @@ async def update_stash(
stashId: str,
stash_update: schemas.StashUpdate,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Update a stash by key and value for "stashId".
Returns the updated stash.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {update_stash.__name__}",
)

stash_map = {"stashId": stashId}
stash = await CRUD_stash.get(
db=db,
@@ -79,13 +95,23 @@ async def update_stash(


@router.delete("/{stashId}", response_model=str)
async def delete_stash(stashId: str, db: Session = Depends(get_db)):
async def delete_stash(
stashId: str,
db: Session = Depends(get_db),
verification: bool = Depends(verification),
):
"""
Delete a stash by key and value for "stashId".
Returns a message that the stash was deleted successfully.
Always deletes one stash.
"""
if not verification:
raise HTTPException(
status_code=401,
detail=f"Unauthorized API access for {delete_stash.__name__}",
)

stash_map = {"stashId": stashId}
await CRUD_stash.remove(db=db, filter=stash_map)

50 changes: 50 additions & 0 deletions src/backend_api/app/app/core/security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import secrets
from typing import Annotated
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials

import os


PRIVATIZE_API = os.getenv("PRIVATIZE_API")
FIRST_SUPERUSER = os.getenv("FIRST_SUPERUSER")
FIRST_SUPERUSER_PASSWORD = os.getenv("FIRST_SUPERUSER_PASSWORD")

security = HTTPBasic()


def verification(
credentials: HTTPBasicCredentials = Depends(security),
) -> None:
"""Verify the username and password for the API.
Args:
credentials (HTTPBasicCredentials, optional): HTTP Credentials. Defaults to Depends(security).
Raises:
HTTPException: If the username or password is incorrect.
Returns:
None
"""
if PRIVATIZE_API:
current_username_bytes = credentials.username.encode("utf8")
correct_username_bytes = FIRST_SUPERUSER.encode("utf8")
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8")
correct_password_bytes = FIRST_SUPERUSER_PASSWORD.encode("utf8")
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)
if is_correct_password and is_correct_username:
return True
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Basic"},
)
else:
return True
26 changes: 18 additions & 8 deletions src/backend_api/app/app/tests/crud/crud_models/test_item.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import asyncio
from sqlalchemy.orm import Session
from typing import Callable, Dict, Tuple, List, Union
import pytest

@@ -8,9 +6,9 @@
CRUD_account,
CRUD_itemBaseType,
CRUD_currency,
CRUD_stash,
)
from app.core.models.database import engine
from app.core.models.models import Item, Account, ItemBaseType, Currency
from app.core.models.models import Account, Item, ItemBaseType, Currency, Stash
from app.crud.base import CRUDBase
import app.tests.crud.cascade_tests as cascade_test
from app.tests.utils.model_utils.item import generate_random_item
@@ -23,14 +21,25 @@ def object_generator_func() -> Callable[[], Dict]:

@pytest.fixture(scope="module")
def object_generator_func_w_deps() -> (
Callable[
[], Tuple[Dict, Item, List[Union[Dict, Account, ItemBaseType, Currency]]]
]
Callable[[], Tuple[Dict, Item, List[Union[Dict, Stash, ItemBaseType, Currency, Account]]]]
):
def generate_random_item_w_deps(
db,
) -> Callable[
[], Tuple[Dict, Item, List[Union[Dict, Account, ItemBaseType, Currency]]]
[],
Tuple[
Dict,
Item,
List[
Union[
Dict,
Stash,
ItemBaseType,
Currency,
Account
]
],
],
]:
return generate_random_item(db, retrieve_dependencies=True)

@@ -46,6 +55,7 @@ def crud_instance() -> CRUDBase:
def crud_deps_instances() -> CRUDBase:
return [
CRUD_account,
CRUD_stash,
CRUD_itemBaseType,
CRUD_currency,
]
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
CRUD_currency,
CRUD_item,
CRUD_modifier,
CRUD_stash,
)
from app.core.models.models import (
ItemModifier,
@@ -17,6 +18,7 @@
Currency,
Item,
Modifier,
Stash,
)
from app.crud.base import CRUDBase
import app.tests.crud.cascade_tests as cascade_test
@@ -34,7 +36,7 @@ def object_generator_func_w_deps() -> Callable[
Tuple[
Dict,
ItemModifier,
List[Union[Dict, Account, ItemBaseType, Currency, Item, Modifier]],
List[Union[Dict, Item, Stash, Account, ItemBaseType, Currency, Modifier]],
],
]:
def generate_random_item_modifier_w_deps(
@@ -44,7 +46,7 @@ def generate_random_item_modifier_w_deps(
Tuple[
Dict,
ItemModifier,
List[Union[Dict, Item, Account, ItemBaseType, Currency, Modifier]],
List[Union[Dict, Item, Stash, Account, ItemBaseType, Currency, Modifier]],
],
]:
return generate_random_item_modifier(db, retrieve_dependencies=True)
@@ -61,6 +63,7 @@ def crud_instance() -> CRUDBase:
def crud_deps_instances() -> List[CRUDBase]:
return [
CRUD_account,
CRUD_stash,
CRUD_itemBaseType,
CRUD_currency,
CRUD_item,
134 changes: 68 additions & 66 deletions src/backend_api/app/app/tests/crud/crud_models/test_modifier.py
Original file line number Diff line number Diff line change
@@ -34,69 +34,71 @@ def crud_instance() -> CRUDBase:
return CRUD_modifier


class TestModifierCRUD(test_crud.TestCRUD):
@pytest.mark.asyncio
async def test_main_key_get(
self,
db: Session,
crud_instance: CRUDBase,
object_generator_func_w_main_key: Callable[[], Tuple[Dict, ModelType]],
*,
count: int = 5,
) -> None:
"""
A test function.
1. Creates multiple objects, which all have the same key
2. Creates a map, only containing the 'modifierId' key
3. Retrieves all objects with matching 'modifierId' key
4. Sorts the objects so that they line up
5. Tests if the retrieved objects are the same as the initial
"""
multiple_object_dict, multiple_object_out = await self._create_multiple_objects(
db, object_generator_func_w_main_key, count=count
)

self._test_object(multiple_object_out, multiple_object_dict)

modifier_map = {"modifierId": multiple_object_out[0].modifierId}
multiple_object_db = await crud_instance.get(
db, filter=modifier_map, sort_key="position", sort="asc"
)
dict_refrence = [item["position"] for item in multiple_object_dict]
multiple_object_dict = sort_with_refrence(multiple_object_dict, dict_refrence)

assert len(multiple_object_db) == count
self._test_object(multiple_object_db, multiple_object_dict)

@pytest.mark.asyncio
async def test_main_key_delete(
self,
db: Session,
crud_instance: CRUDBase,
object_generator_func_w_main_key: Callable[[], Tuple[Dict, ModelType]],
count: Optional[int] = 5,
) -> None:
"""
A test function.
1. Creates multiple objects, which all have the same key
2. Creates a map, only containing the 'modifierId' key
3. Deletes all objects with matching 'modifierId' key
4. Sorts the objects so that they line up
5. Tests if the deleted objects are the same as the initial
"""
multiple_object_dict, multiple_object_out = await self._create_multiple_objects(
db, object_generator_func_w_main_key, count=count
)
self._test_object(multiple_object_out, multiple_object_dict)

modifier_map = {"modifierId": multiple_object_out[0].modifierId}
deleted_objects = await crud_instance.remove(
db=db, filter=modifier_map, sort_key="position", sort="asc"
)

dict_refrence = [item["position"] for item in multiple_object_dict]
multiple_object_out = sort_with_refrence(multiple_object_out, dict_refrence)
assert deleted_objects
self._test_object(deleted_objects, multiple_object_out)
# Modifier has only one key, which is 'modifierId'. Therefore, the following tests are not needed.

# class TestModifierCRUD(test_crud.TestCRUD):
# @pytest.mark.asyncio
# async def test_main_key_get(
# self,
# db: Session,
# crud_instance: CRUDBase,
# object_generator_func_w_main_key: Callable[[], Tuple[Dict, ModelType]],
# *,
# count: int = 5,
# ) -> None:
# """
# A test function.

# 1. Creates multiple objects, which all have the same key
# 2. Creates a map, only containing the 'modifierId' key
# 3. Retrieves all objects with matching 'modifierId' key
# 4. Sorts the objects so that they line up
# 5. Tests if the retrieved objects are the same as the initial
# """
# multiple_object_dict, multiple_object_out = await self._create_multiple_objects(
# db, object_generator_func_w_main_key, count=count
# )

# self._test_object(multiple_object_out, multiple_object_dict)

# modifier_map = {"modifierId": multiple_object_out[0].modifierId}
# multiple_object_db = await crud_instance.get(
# db, filter=modifier_map, sort_key="position", sort="asc"
# )
# dict_refrence = [item["position"] for item in multiple_object_dict]
# multiple_object_dict = sort_with_refrence(multiple_object_dict, dict_refrence)

# assert len(multiple_object_db) == count
# self._test_object(multiple_object_db, multiple_object_dict)

# @pytest.mark.asyncio
# async def test_main_key_delete(
# self,
# db: Session,
# crud_instance: CRUDBase,
# object_generator_func_w_main_key: Callable[[], Tuple[Dict, ModelType]],
# count: Optional[int] = 5,
# ) -> None:
# """
# A test function.

# 1. Creates multiple objects, which all have the same key
# 2. Creates a map, only containing the 'modifierId' key
# 3. Deletes all objects with matching 'modifierId' key
# 4. Sorts the objects so that they line up
# 5. Tests if the deleted objects are the same as the initial
# """
# multiple_object_dict, multiple_object_out = await self._create_multiple_objects(
# db, object_generator_func_w_main_key, count=count
# )
# self._test_object(multiple_object_out, multiple_object_dict)

# modifier_map = {"modifierId": multiple_object_out[0].modifierId}
# deleted_objects = await crud_instance.remove(
# db=db, filter=modifier_map, sort_key="position", sort="asc"
# )

# dict_refrence = [item["position"] for item in multiple_object_dict]
# multiple_object_out = sort_with_refrence(multiple_object_out, dict_refrence)
# assert deleted_objects
# self._test_object(deleted_objects, multiple_object_out)
44 changes: 44 additions & 0 deletions src/backend_api/app/app/tests/crud/crud_models/test_stash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from typing import Callable, Dict, Tuple, List, Union
import pytest

from app.crud import (
CRUD_stash,
CRUD_account,
)
from app.core.models.models import Stash, Account
from app.crud.base import CRUDBase
import app.tests.crud.cascade_tests as cascade_test
from app.tests.utils.model_utils.stash import generate_random_stash


@pytest.fixture(scope="module")
def object_generator_func() -> Callable[[], Dict]:
return generate_random_stash


@pytest.fixture(scope="module")
def object_generator_func_w_deps() -> (
Callable[[], Tuple[Dict, Stash, List[Union[Dict, Account]]]]
):
def generate_random_stash_w_deps(
db,
) -> Callable[[], Tuple[Dict, Stash, List[Union[Dict, Account]]]]:
return generate_random_stash(db, retrieve_dependencies=True)

return generate_random_stash_w_deps


@pytest.fixture(scope="module")
def crud_instance() -> CRUDBase:
return CRUD_stash


@pytest.fixture(scope="module")
def crud_deps_instances() -> CRUDBase:
return [
CRUD_account,
]


class TestStashCRUD(cascade_test.TestCascade):
pass
26 changes: 20 additions & 6 deletions src/backend_api/app/app/tests/utils/model_utils/item.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
from sqlalchemy.orm import Session

from app import crud
from app.core.models.models import Item, Account, Currency, ItemBaseType
from app.core.models.models import Item, Currency, ItemBaseType, Stash, Account
from app.core.schemas.item import ItemCreate
from app.tests.utils.utils import (
random_lower_string,
@@ -16,12 +16,13 @@

from app.tests.utils.model_utils.item_base_type import generate_random_item_base_type
from app.tests.utils.model_utils.currency import generate_random_currency
from app.tests.utils.model_utils.stash import generate_random_stash


async def create_random_item_dict(
db: Session, retrieve_dependencies: Optional[bool] = False
) -> Union[
Dict, Tuple[Dict, List[Union[Dict, Account, ItemBaseType, Currency]]]
Dict, Tuple[Dict, List[Union[Dict, Stash, ItemBaseType, Currency, Account]]]
]:
gameItemId = random_lower_string()
changeId = random_lower_string()
@@ -42,10 +43,12 @@ async def create_random_item_dict(
elder = random_bool()
shaper = random_bool()
influences_type_dict = {
"shaper": "bool",
"elder": "bool",
"shaper": "bool",
"crusader": "bool",
"redeemer": "bool",
"hunter": "bool",
"warlord": "bool",
}
influences = random_json(influences_type_dict)
searing = random_bool()
@@ -54,15 +57,25 @@ async def create_random_item_dict(
prefixes = random_int(small_int=True)
suffixes = random_int(small_int=True)
foilVariation = random_int(small_int=True)


if not retrieve_dependencies:
stash_dict, stash = await generate_random_stash(db)
else:
stash_dict, stash, deps = await generate_random_stash(
db, retrieve_dependencies=retrieve_dependencies
)


stashId = stash.stashId
item_base_type_dict, item_base_type = await generate_random_item_base_type(db)
baseType = item_base_type.baseType
currency_dict, currency = await generate_random_currency(db)
currencyId = currency.currencyId

item = {
"stashId": stashId,
"gameItemId": gameItemId,
"changeId": changeId,
"changeId": changeId,
"name": name,
"iconUrl": iconUrl,
"league": league,
@@ -93,6 +106,7 @@ async def create_random_item_dict(
if not retrieve_dependencies:
return item
else:
deps += [stash_dict, stash]
deps += [item_base_type_dict, item_base_type]
deps += [currency_dict, currency]
return item, deps
@@ -101,7 +115,7 @@ async def create_random_item_dict(
async def generate_random_item(
db: Session, retrieve_dependencies: Optional[bool] = False
) -> Tuple[
Dict, Item, Optional[List[Union[Dict, Account, ItemBaseType, Currency]]]
Dict, Item, Optional[List[Union[Dict, Stash, ItemBaseType, Currency, Account]]]
]:
output = await create_random_item_dict(db, retrieve_dependencies)
if not retrieve_dependencies:
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
Currency,
Item,
Modifier,
Stash,
)
from app.core.schemas.item_modifier import ItemModifierCreate
from app.tests.utils.utils import random_float
@@ -25,7 +26,7 @@ async def create_random_item_modifier_dict(
Tuple[
Dict,
Optional[
List[Union[Dict, Account, ItemBaseType, Currency, Item, Modifier]]
List[Union[Dict, Item, Stash, Account, ItemBaseType, Currency, Modifier]]
],
],
]:
@@ -41,6 +42,7 @@ async def create_random_item_modifier_dict(
modifier_dict, modifier = await generate_random_modifier(db)
modifierId = modifier.modifierId
position = modifier.position


item_modifier_dict = {
"itemId": itemId,
@@ -60,7 +62,7 @@ async def generate_random_item_modifier(
) -> Tuple[
Dict,
ItemModifier,
Optional[List[Union[Dict, Account, ItemBaseType, Currency, Item, Modifier]]],
Optional[List[Union[Dict, Item, Stash, Account, ItemBaseType, Currency, Modifier]]],
]:
output = await create_random_item_modifier_dict(db, retrieve_dependencies)
if not retrieve_dependencies:
2 changes: 2 additions & 0 deletions src/backend_api/app/app/tests/utils/model_utils/modifier.py
Original file line number Diff line number Diff line change
@@ -43,6 +43,7 @@ def create_random_modifier_dict() -> Dict:
delve = random_bool()
fractured = random_bool()
synthesized = random_bool()
unique = random_bool()
corrupted = random_bool()
enchanted = random_bool()
veiled = random_bool()
@@ -60,6 +61,7 @@ def create_random_modifier_dict() -> Dict:
"delve": delve,
"fractured": fractured,
"synthesized": synthesized,
"unique": unique,
"corrupted": corrupted,
"enchanted": enchanted,
"veiled": veiled,
52 changes: 52 additions & 0 deletions src/backend_api/app/app/tests/utils/model_utils/stash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import asyncio
from typing import Dict, List, Optional, Tuple, Union
from sqlalchemy.orm import Session

from app import crud
from app.tests.conftest import db
from app.core.models.models import Account, Stash
from app.core.schemas.stash import StashCreate
from app.tests.utils.utils import random_lower_string, random_bool
from app.tests.utils.model_utils.account import generate_random_account


async def create_random_stash_dict(
db: Session, retrieve_dependencies: Optional[bool] = False
) -> Union[Dict, Tuple[Dict, List[Union[Dict, Account]]]]:
stashId = random_lower_string()
public: bool = random_bool()
league = random_lower_string()

account_dict, account = await generate_random_account(db)
accountName = account.accountName

stash = {
"stashId": stashId,
"accountName": accountName,
"public": public,
"league": league,
}

if not retrieve_dependencies:
return stash
else:
deps = []
deps += [account_dict, account]
return stash, deps


async def generate_random_stash(
db: Session, retrieve_dependencies: Optional[bool] = False
) -> Tuple[Dict, Stash, Optional[List[Union[Dict, Account]]]]:
output = await create_random_stash_dict(db, retrieve_dependencies)
if not retrieve_dependencies:
stash_dict = output
else:
stash_dict, deps = output
stash_create = StashCreate(**stash_dict)
stash = await crud.CRUD_stash.create(db, obj_in=stash_create)

if not retrieve_dependencies:
return stash_dict, stash
else:
return stash_dict, stash, deps
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
import pandas as pd
from typing import List

from pom_api_authentication import get_super_authentication
from modifier_data_deposit.utils import insert_data
from external_data_retrieval.transforming_data.utils import (
get_rolls,
@@ -24,6 +25,7 @@ def __init__(self, main_logger: logging.Logger):
self.url += "/api/api_v1"

self.logger = main_logger.getChild("transform_poe")
self.pom_api_authentication = get_super_authentication()

def _create_account_table(self, df: pd.DataFrame) -> pd.DataFrame:
"""
@@ -44,7 +46,12 @@ def _transform_account_table(self, account_df: pd.DataFrame) -> pd.DataFrame:
account_df.drop_duplicates("accountName", inplace=True)

account_df["isBanned"] = None
db_account_df = pd.read_json(self.url + "/account/", dtype=str)
account_response = requests.get(
self.url + "/account/", auth=self.pom_api_authentication
)
account_json = account_response.json()
db_account_df = pd.json_normalize(account_json)

if db_account_df.empty:
return account_df

Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@
check_for_additional_modifier_types,
)
from modifier_data_deposit.utils import df_to_JSON
from pom_api_authentication import get_super_authentication

logging.basicConfig(
filename="modifier_data_deposit.log",
@@ -32,6 +33,7 @@ def __init__(self) -> None:
self.url = "http://src-backend-1"
self.url += "/api/api_v1/modifier/"
self.update_disabled = not CASCADING_UPDATE
self.pom_api_authentication = get_super_authentication()

self.modifier_types = [
"implicit",
@@ -125,6 +127,8 @@ def _update_duplicates(
"accept": "application/json",
"Content-Type": "application/json",
},
# add HTTP Basic Auth
auth=self.pom_api_authentication,
)
response.raise_for_status()

@@ -160,6 +164,7 @@ def _insert_data(self, df: pd.DataFrame) -> None:
self.url,
json=df_json,
headers={"accept": "application/json", "Content-Type": "application/json"},
auth=self.pom_api_authentication,
)
response.raise_for_status()

Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import requests
from requests.auth import HTTPBasicAuth
import logging
import pandas as pd
from typing import List, Dict, Any, Union, Optional

from pom_api_authentication import get_super_authentication

logger = logging.getLogger(__name__)
logging.basicConfig(
filename="modifier_data_deposit.log",
@@ -56,25 +59,32 @@ def insert_data(
*,
url: str,
table_name: str,
authentication: Optional[HTTPBasicAuth] = get_super_authentication(),
logger: Optional[logging.Logger] = None,
) -> None:
if df.empty:
return None
data = df_to_JSON(df, request_method="post")
response = requests.post(url + f"/{table_name}/", json=data)
response = requests.post(url + f"/{table_name}/", json=data, auth=authentication)
if response.status_code == 422:
logger.warning(
f"Recieved a 422 response, indicating an unprocessable entity was submitted, while posting a {table_name} table.\nSending smaller batches, trying to locate specific error."
)
for data_chunk in _chunks(data, n=15):
response = requests.post(url + f"/{table_name}/", json=data_chunk)
response = requests.post(
url + f"/{table_name}/",
json=data_chunk,
auth=authentication,
)
if response.status_code == 422:
logger.warning(
"Located chunk of data that contains the unprocessable entity."
)
for individual_data in data_chunk:
response = requests.post(
url + f"/{table_name}/", json=individual_data
url + f"/{table_name}/",
json=individual_data,
auth=authentication,
)
if response.status_code == 422:
logger.warning(
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from requests.auth import HTTPBasicAuth
import os


FIRST_SUPERUSER = os.getenv("FIRST_SUPERUSER")
FIRST_SUPERUSER_PASSWORD = os.getenv("FIRST_SUPERUSER_PASSWORD")


def get_super_authentication() -> HTTPBasicAuth:
"""
Get the super authentication for the Path of Modifiers API.
Returns:
HTTPBasicAuth: POM user and password authentication.
"""
authentication = HTTPBasicAuth(FIRST_SUPERUSER, FIRST_SUPERUSER_PASSWORD)
return authentication

0 comments on commit 2497905

Please sign in to comment.