From 376378a15c0e61dee05809f49eef8a0ec15ecdf2 Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Tue, 16 Jul 2024 14:14:47 +0300 Subject: [PATCH 01/24] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=80=D1=83=D1=87=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BE=20=D0=B2=D1=81=D0=B5=D1=85=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- userdata_api/routes/user.py | 38 +++++++++++++++++++++++++++++++++--- userdata_api/schemas/user.py | 3 +++ userdata_api/utils/user.py | 23 ++++++++++++++++++++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index 491f368..27f024c 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -1,11 +1,13 @@ from typing import Any from auth_lib.fastapi import UnionAuth -from fastapi import APIRouter, Depends - +from fastapi import APIRouter, Depends, Query +from fastapi_sqlalchemy import db +from userdata_api.models.db import Info from userdata_api.schemas.response_model import StatusResponseModel -from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate +from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate, UsersInfoGet from userdata_api.utils.user import get_user_info as get +from userdata_api.utils.user import get_users_ids_by_category from userdata_api.utils.user import patch_user_info as patch @@ -64,3 +66,33 @@ async def update_user( """ await patch(new_info, id, user) return StatusResponseModel(status='Success', message='User patch succeeded', ru="Изменение успешно") + + +@user.get("", response_model=UsersInfoGet, response_model_exclude_unset=True) +async def get_users_info( + users: list[int] = Query(None), + categories: list[str] = Query(None), + user: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.info.admin"], allow_none=False, auto_error=True)), +) -> UsersInfoGet: + """ + Получить информацию о пользователях. + Если не указать users и categories, то вернется информация обо всех пользователях + :param users: список id юзеров, про которых нужно вернуть информацию + :param categories: список категорий, про юзеров принажделащих которым нужно вернуть информацию + :return: словарь, где ключь - id пользователя, значение - информация. + Например: {users: {1: {}, 2: {}}} + """ + result = {} + required_users = [] + if users is None and categories is None: + required_users = db.session.query(Info.owner_id).distinct().all() + else: + for user_id in users: + required_users_users.append(user_id) + for category in categories: + category_users_ids = get_users_ids_by_category(category, user) + required_users_users.extend(category_users_ids) + required_users_users = list(set(required_users_users)) + for user_id in required_users: + result[user_id] = await get_user_info(user_id, user) + return UsersInfoGet(users=result) diff --git a/userdata_api/schemas/user.py b/userdata_api/schemas/user.py index 0b16cc3..9c37631 100644 --- a/userdata_api/schemas/user.py +++ b/userdata_api/schemas/user.py @@ -23,6 +23,9 @@ def unique_validator(cls, v): raise ValueError("Category list is not unique") return v +class UsersInfoGet(Base): + users: dict[int, list[int]] | None = None + class UserInfoUpdate(UserInfoGet): source: constr(min_length=1) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 490eaf5..37d3273 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -166,3 +166,26 @@ async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | else: result.append({"category": item.category.name, "param": item.param.name, "value": item.value}) return UserInfoGet(items=result) + + +async def get_users_ids_by_category(category_name: str, user: dict[str, int | list[dict[str, str | int]]]) -> list[int]: + """ + Возврщает список id всех юзеров в данной категории + :param category_name: Название категории + :param user: Сессия выполняющего запрос данных + :return: Лист айди юзеров в этой категории + """ + scope_names = [scope["name"] for scope in user["session_scopes"]] + category = Category.query(session=db.session).filter(Category.name == category_name).one_or_none() + if not category: + raise ObjectNotFound(Category, category_name) + if category.read_scope and category.read_scope not in scope_names: + raise Forbidden(f"Reading category {category_name} requires {category.read_scope} scope") + user_ids = ( + db.session.query(Info.owner_id) + .join(Param) + .filter(Param.category_id == category.id, not_(Param.is_deleted), not_(Info.is_deleted)) + .distinct() + .all() + ) + return [user_id for user_id in user_ids] \ No newline at end of file From 8d900e926659ef6501c5c9587d67b2a379562f71 Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Tue, 16 Jul 2024 14:16:36 +0300 Subject: [PATCH 02/24] lint --- userdata_api/schemas/user.py | 3 ++- userdata_api/utils/user.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/userdata_api/schemas/user.py b/userdata_api/schemas/user.py index 9c37631..6cb0aa2 100644 --- a/userdata_api/schemas/user.py +++ b/userdata_api/schemas/user.py @@ -23,9 +23,10 @@ def unique_validator(cls, v): raise ValueError("Category list is not unique") return v + class UsersInfoGet(Base): users: dict[int, list[int]] | None = None - + class UserInfoUpdate(UserInfoGet): source: constr(min_length=1) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 37d3273..929adb1 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -188,4 +188,4 @@ async def get_users_ids_by_category(category_name: str, user: dict[str, int | li .distinct() .all() ) - return [user_id for user_id in user_ids] \ No newline at end of file + return [user_id for user_id in user_ids] From 837a3931415fbf333dda5283e017ae318947a481 Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Tue, 16 Jul 2024 14:19:15 +0300 Subject: [PATCH 03/24] lint --- userdata_api/routes/user.py | 3 +-- userdata_api/utils/user.py | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index 27f024c..2f6af4d 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -10,7 +10,6 @@ from userdata_api.utils.user import get_users_ids_by_category from userdata_api.utils.user import patch_user_info as patch - user = APIRouter(prefix="/user", tags=["User"]) @@ -95,4 +94,4 @@ async def get_users_info( required_users_users = list(set(required_users_users)) for user_id in required_users: result[user_id] = await get_user_info(user_id, user) - return UsersInfoGet(users=result) + return UsersInfoGet(users=result) \ No newline at end of file diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 929adb1..cf38dd9 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -2,7 +2,6 @@ from fastapi_sqlalchemy import db from sqlalchemy import not_ - from userdata_api.exceptions import Forbidden, ObjectNotFound from userdata_api.models.db import Category, Info, Param, Source, ViewType from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate From c843f196a2a37ee251da8d657a1e6462026482b6 Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Tue, 16 Jul 2024 14:20:51 +0300 Subject: [PATCH 04/24] Update user.py --- userdata_api/routes/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index 2f6af4d..6a261d7 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -94,4 +94,4 @@ async def get_users_info( required_users_users = list(set(required_users_users)) for user_id in required_users: result[user_id] = await get_user_info(user_id, user) - return UsersInfoGet(users=result) \ No newline at end of file + return UsersInfoGet(users=result) From 7734ef477ff26bca583c7a726507a2fd75ee9d9b Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Tue, 16 Jul 2024 14:28:55 +0300 Subject: [PATCH 05/24] fix --- userdata_api/routes/user.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index 6a261d7..2e536d6 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -85,13 +85,14 @@ async def get_users_info( required_users = [] if users is None and categories is None: required_users = db.session.query(Info.owner_id).distinct().all() - else: + if users: for user_id in users: - required_users_users.append(user_id) + required_users.append(user_id) + if categories: for category in categories: - category_users_ids = get_users_ids_by_category(category, user) - required_users_users.extend(category_users_ids) - required_users_users = list(set(required_users_users)) + category_users_ids = await get_users_ids_by_category(category, user) + required_users.extend(category_users_ids) + required_users = list(set(required_users)) for user_id in required_users: result[user_id] = await get_user_info(user_id, user) return UsersInfoGet(users=result) From 91ed1cc1e1ee6917ce6b944fc10a2c8193e771ce Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Wed, 17 Jul 2024 12:07:02 +0300 Subject: [PATCH 06/24] request change --- userdata_api/routes/user.py | 20 +++------------ userdata_api/schemas/user.py | 2 +- userdata_api/utils/user.py | 49 +++++++++++++----------------------- 3 files changed, 23 insertions(+), 48 deletions(-) diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index 2e536d6..428c2d6 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -3,11 +3,10 @@ from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends, Query from fastapi_sqlalchemy import db -from userdata_api.models.db import Info +from userdata_api.models.db import Info, Category from userdata_api.schemas.response_model import StatusResponseModel from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate, UsersInfoGet from userdata_api.utils.user import get_user_info as get -from userdata_api.utils.user import get_users_ids_by_category from userdata_api.utils.user import patch_user_info as patch user = APIRouter(prefix="/user", tags=["User"]) @@ -82,17 +81,6 @@ async def get_users_info( Например: {users: {1: {}, 2: {}}} """ result = {} - required_users = [] - if users is None and categories is None: - required_users = db.session.query(Info.owner_id).distinct().all() - if users: - for user_id in users: - required_users.append(user_id) - if categories: - for category in categories: - category_users_ids = await get_users_ids_by_category(category, user) - required_users.extend(category_users_ids) - required_users = list(set(required_users)) - for user_id in required_users: - result[user_id] = await get_user_info(user_id, user) - return UsersInfoGet(users=result) + for user_id in users: + result[user_id] = await get(user_id, user, categories) + return UsersInfoGet(result) diff --git a/userdata_api/schemas/user.py b/userdata_api/schemas/user.py index 6cb0aa2..f251727 100644 --- a/userdata_api/schemas/user.py +++ b/userdata_api/schemas/user.py @@ -25,7 +25,7 @@ def unique_validator(cls, v): class UsersInfoGet(Base): - users: dict[int, list[int]] | None = None + users: dict[int, UserInfoGet] | None = None class UserInfoUpdate(UserInfoGet): diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index cf38dd9..afe6c66 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -101,7 +101,7 @@ async def patch_user_info(new: UserInfoUpdate, user_id: int, user: dict[str, int continue -async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | int]]]) -> UserInfoGet: +async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | int]]], categories: list[str] | None = None) -> UserInfoGet: """ Возвращает информауию о пользователе в соотетствии с переданным токеном. @@ -111,15 +111,25 @@ async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | :param user_id: Айди пользователя :param user: Сессия выполняющего запрос данных + :param categories: Список категорий, которые нужно вернуть(если не указать, вернутся все) :return: Список словарей содержащих категорию, параметр категории и значение этого параметра у польщователя """ - infos: list[Info] = ( - Info.query(session=db.session) - .join(Param) - .join(Category) - .filter(Info.owner_id == user_id, not_(Param.is_deleted), not_(Category.is_deleted)) - .all() - ) + if categories: + infos: list[Info] = ( + Info.query(session=db.session) + .join(Param) + .join(Category) + .filter(Info.owner_id == user_id, Param.category_id.in_(Category.query(session=db.session).filter(Category.name.in_(categories)).with_entities(Category.id)), not_(Param.is_deleted), not_(Category.is_deleted), not_(Info.is_deleted)) + .all() + ) + else: + infos: list[Info] = ( + Info.query(session=db.session) + .join(Param) + .join(Category) + .filter(Info.owner_id == user_id, not_(Param.is_deleted), not_(Category.is_deleted)) + .all() + ) if not infos: raise ObjectNotFound(Info, user_id) scope_names = [scope["name"] for scope in user["session_scopes"]] @@ -165,26 +175,3 @@ async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | else: result.append({"category": item.category.name, "param": item.param.name, "value": item.value}) return UserInfoGet(items=result) - - -async def get_users_ids_by_category(category_name: str, user: dict[str, int | list[dict[str, str | int]]]) -> list[int]: - """ - Возврщает список id всех юзеров в данной категории - :param category_name: Название категории - :param user: Сессия выполняющего запрос данных - :return: Лист айди юзеров в этой категории - """ - scope_names = [scope["name"] for scope in user["session_scopes"]] - category = Category.query(session=db.session).filter(Category.name == category_name).one_or_none() - if not category: - raise ObjectNotFound(Category, category_name) - if category.read_scope and category.read_scope not in scope_names: - raise Forbidden(f"Reading category {category_name} requires {category.read_scope} scope") - user_ids = ( - db.session.query(Info.owner_id) - .join(Param) - .filter(Param.category_id == category.id, not_(Param.is_deleted), not_(Info.is_deleted)) - .distinct() - .all() - ) - return [user_id for user_id in user_ids] From 473d18662346b78b0e0bce2fa761819dbd72904d Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Wed, 17 Jul 2024 12:27:58 +0300 Subject: [PATCH 07/24] fix --- userdata_api/routes/user.py | 6 ++++-- userdata_api/utils/user.py | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index 428c2d6..89afce0 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -3,12 +3,14 @@ from auth_lib.fastapi import UnionAuth from fastapi import APIRouter, Depends, Query from fastapi_sqlalchemy import db -from userdata_api.models.db import Info, Category + +from userdata_api.models.db import Category, Info from userdata_api.schemas.response_model import StatusResponseModel from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate, UsersInfoGet from userdata_api.utils.user import get_user_info as get from userdata_api.utils.user import patch_user_info as patch + user = APIRouter(prefix="/user", tags=["User"]) @@ -83,4 +85,4 @@ async def get_users_info( result = {} for user_id in users: result[user_id] = await get(user_id, user, categories) - return UsersInfoGet(result) + return UsersInfoGet(users=result) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index afe6c66..7c964be 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -2,6 +2,7 @@ from fastapi_sqlalchemy import db from sqlalchemy import not_ + from userdata_api.exceptions import Forbidden, ObjectNotFound from userdata_api.models.db import Category, Info, Param, Source, ViewType from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate @@ -101,7 +102,9 @@ async def patch_user_info(new: UserInfoUpdate, user_id: int, user: dict[str, int continue -async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | int]]], categories: list[str] | None = None) -> UserInfoGet: +async def get_user_info( + user_id: int, user: dict[str, int | list[dict[str, str | int]]], categories: list[str] | None = None +) -> UserInfoGet: """ Возвращает информауию о пользователе в соотетствии с переданным токеном. @@ -119,7 +122,15 @@ async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | Info.query(session=db.session) .join(Param) .join(Category) - .filter(Info.owner_id == user_id, Param.category_id.in_(Category.query(session=db.session).filter(Category.name.in_(categories)).with_entities(Category.id)), not_(Param.is_deleted), not_(Category.is_deleted), not_(Info.is_deleted)) + .filter( + Info.owner_id == user_id, + Param.category_id.in_( + Category.query(session=db.session).filter(Category.name.in_(categories)).with_entities(Category.id) + ), + not_(Param.is_deleted), + not_(Category.is_deleted), + not_(Info.is_deleted), + ) .all() ) else: From e694bd61dca223ddae03d2416e85c5783bfdacc6 Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Wed, 17 Jul 2024 13:52:23 +0300 Subject: [PATCH 08/24] fix --- userdata_api/routes/user.py | 17 ++++++++- userdata_api/utils/user.py | 73 +++++++++++++++++++++++++++++++++++-- 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index 89afce0..2921deb 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -8,6 +8,7 @@ from userdata_api.schemas.response_model import StatusResponseModel from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate, UsersInfoGet from userdata_api.utils.user import get_user_info as get +from userdata_api.utils.user import get_users_info as get_users from userdata_api.utils.user import patch_user_info as patch @@ -16,7 +17,8 @@ @user.get("/{id}", response_model=UserInfoGet) async def get_user_info( - id: int, user: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)) + id: int, + user: dict[str, Any] = Depends(UnionAuth(scopes=[], allow_none=False, auto_error=True)), ) -> UserInfoGet: """ Получить информацию о пользователе @@ -65,13 +67,22 @@ async def update_user( :return: """ await patch(new_info, id, user) +<<<<<<< Updated upstream return StatusResponseModel(status='Success', message='User patch succeeded', ru="Изменение успешно") +======= + return StatusResponseModel(status="Success", message="User patch succeeded", ru="Изменение успешно") +>>>>>>> Stashed changes @user.get("", response_model=UsersInfoGet, response_model_exclude_unset=True) async def get_users_info( +<<<<<<< Updated upstream users: list[int] = Query(None), categories: list[str] = Query(None), +======= + users: list[int] = Query(), + categories: list[str] = Query(), +>>>>>>> Stashed changes user: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.info.admin"], allow_none=False, auto_error=True)), ) -> UsersInfoGet: """ @@ -82,7 +93,11 @@ async def get_users_info( :return: словарь, где ключь - id пользователя, значение - информация. Например: {users: {1: {}, 2: {}}} """ +<<<<<<< Updated upstream result = {} for user_id in users: result[user_id] = await get(user_id, user, categories) return UsersInfoGet(users=result) +======= + return UsersInfoGet.model_validate(await get_users(users, categories, user)) +>>>>>>> Stashed changes diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 7c964be..60e09ea 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -5,7 +5,7 @@ from userdata_api.exceptions import Forbidden, ObjectNotFound from userdata_api.models.db import Category, Info, Param, Source, ViewType -from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate +from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate, UsersInfoGet async def patch_user_info(new: UserInfoUpdate, user_id: int, user: dict[str, int | list[dict[str, str | int]]]) -> None: @@ -68,7 +68,10 @@ async def patch_user_info(new: UserInfoUpdate, user_id: int, user: dict[str, int db.session.query(Info) .join(Source) .filter( - Info.param_id == param.id, Info.owner_id == user_id, Source.name == new.source, not_(Info.is_deleted) + Info.param_id == param.id, + Info.owner_id == user_id, + Source.name == new.source, + not_(Info.is_deleted), ) .one_or_none() ) @@ -181,8 +184,70 @@ async def get_user_info( for item in param_dict.values(): if isinstance(item, list): result.extend( - [{"category": _item.category.name, "param": _item.param.name, "value": _item.value} for _item in item] + [ + { + "category": _item.category.name, + "param": _item.param.name, + "value": _item.value, + } + for _item in item + ] ) else: - result.append({"category": item.category.name, "param": item.param.name, "value": item.value}) + result.append( + { + "category": item.category.name, + "param": item.param.name, + "value": item.value, + } + ) return UserInfoGet(items=result) + + +async def get_users_info( + users: list[int], + categories: list[str], + user: dict[str, int | list[dict[str, str | int]]], +) -> UsersInfoGet: + """ + Возвращает информацию о данных пользователей в указанных категориях + + :param users: Список айди юзеров + :param categories: Список необходимых категорий + :param user: Сессия выполняющего запрос данных + :return: Словарь, где ключи - айди юзеров, значение - словарь данных, как в get_user_info + """ + scope_names = [scope["name"] for scope in user["session_scopes"]] + infos: list[Info] = ( + Info.query(session=db.session) + .join(Param) + .join(Category) + .filter( + Info.owner_id.in_(users), + Param.category_id.in_( + Category.query(session=db.session).filter(Category.name.in_(categories)).with_entities(Category.id) + ), + not_(Param.is_deleted), + not_(Category.is_deleted), + not_(Info.is_deleted), + ) + .all() + ) + if not infos: + raise ObjectNotFound(Info, users) + result = {} + for info in infos: + if info.owner_id not in result: + + result[info.owner_id] = {"items": []} + if info.category.read_scope and info.category.read_scope not in scope_names and user["id"] != info.owner_id: + continue + result[info.owner_id]["items"].append( + { + "category": info.category.name, + "param": info.param.name, + "value": info.value, + } + ) + print(result) + return UsersInfoGet(users=result) From ba8861a91d3dd04270ffdf4ee98bdf3ad242bf2f Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Wed, 17 Jul 2024 13:56:24 +0300 Subject: [PATCH 09/24] fix --- userdata_api/routes/user.py | 16 ---------------- userdata_api/utils/user.py | 36 ++++++++---------------------------- 2 files changed, 8 insertions(+), 44 deletions(-) diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index 2921deb..8ab637e 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -67,22 +67,13 @@ async def update_user( :return: """ await patch(new_info, id, user) -<<<<<<< Updated upstream - return StatusResponseModel(status='Success', message='User patch succeeded', ru="Изменение успешно") -======= return StatusResponseModel(status="Success", message="User patch succeeded", ru="Изменение успешно") ->>>>>>> Stashed changes @user.get("", response_model=UsersInfoGet, response_model_exclude_unset=True) async def get_users_info( -<<<<<<< Updated upstream - users: list[int] = Query(None), - categories: list[str] = Query(None), -======= users: list[int] = Query(), categories: list[str] = Query(), ->>>>>>> Stashed changes user: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.info.admin"], allow_none=False, auto_error=True)), ) -> UsersInfoGet: """ @@ -93,11 +84,4 @@ async def get_users_info( :return: словарь, где ключь - id пользователя, значение - информация. Например: {users: {1: {}, 2: {}}} """ -<<<<<<< Updated upstream - result = {} - for user_id in users: - result[user_id] = await get(user_id, user, categories) - return UsersInfoGet(users=result) -======= return UsersInfoGet.model_validate(await get_users(users, categories, user)) ->>>>>>> Stashed changes diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 60e09ea..b0984b1 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -105,9 +105,7 @@ async def patch_user_info(new: UserInfoUpdate, user_id: int, user: dict[str, int continue -async def get_user_info( - user_id: int, user: dict[str, int | list[dict[str, str | int]]], categories: list[str] | None = None -) -> UserInfoGet: +async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | int]]]) -> UserInfoGet: """ Возвращает информауию о пользователе в соотетствии с переданным токеном. @@ -117,33 +115,15 @@ async def get_user_info( :param user_id: Айди пользователя :param user: Сессия выполняющего запрос данных - :param categories: Список категорий, которые нужно вернуть(если не указать, вернутся все) :return: Список словарей содержащих категорию, параметр категории и значение этого параметра у польщователя """ - if categories: - infos: list[Info] = ( - Info.query(session=db.session) - .join(Param) - .join(Category) - .filter( - Info.owner_id == user_id, - Param.category_id.in_( - Category.query(session=db.session).filter(Category.name.in_(categories)).with_entities(Category.id) - ), - not_(Param.is_deleted), - not_(Category.is_deleted), - not_(Info.is_deleted), - ) - .all() - ) - else: - infos: list[Info] = ( - Info.query(session=db.session) - .join(Param) - .join(Category) - .filter(Info.owner_id == user_id, not_(Param.is_deleted), not_(Category.is_deleted)) - .all() - ) + infos: list[Info] = ( + Info.query(session=db.session) + .join(Param) + .join(Category) + .filter(Info.owner_id == user_id, not_(Param.is_deleted), not_(Category.is_deleted)) + .all() + ) if not infos: raise ObjectNotFound(Info, user_id) scope_names = [scope["name"] for scope in user["session_scopes"]] From 3815d0fcca697a36a626d79e3d7298cd4ffa5c11 Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Wed, 17 Jul 2024 14:12:38 +0300 Subject: [PATCH 10/24] fix --- userdata_api/utils/user.py | 1 - 1 file changed, 1 deletion(-) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index b0984b1..48f8e89 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -229,5 +229,4 @@ async def get_users_info( "value": info.value, } ) - print(result) return UsersInfoGet(users=result) From 8898f710fca4f5a60180fb598b11ed274e4eb271 Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Thu, 18 Jul 2024 13:28:26 +0300 Subject: [PATCH 11/24] requested changes fix --- tests/test_routes/test_users_get.py | 0 userdata_api/routes/user.py | 7 +++---- userdata_api/utils/user.py | 18 ++++++++---------- 3 files changed, 11 insertions(+), 14 deletions(-) create mode 100644 tests/test_routes/test_users_get.py diff --git a/tests/test_routes/test_users_get.py b/tests/test_routes/test_users_get.py new file mode 100644 index 0000000..e69de29 diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index 8ab637e..6950657 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -73,15 +73,14 @@ async def update_user( @user.get("", response_model=UsersInfoGet, response_model_exclude_unset=True) async def get_users_info( users: list[int] = Query(), - categories: list[str] = Query(), + categories: list[int] = Query(), user: dict[str, Any] = Depends(UnionAuth(scopes=["userdata.info.admin"], allow_none=False, auto_error=True)), ) -> UsersInfoGet: """ Получить информацию о пользователях. - Если не указать users и categories, то вернется информация обо всех пользователях :param users: список id юзеров, про которых нужно вернуть информацию - :param categories: список категорий, про юзеров принажделащих которым нужно вернуть информацию - :return: словарь, где ключь - id пользователя, значение - информация. + :param categories: список id категорий, параметры которых нужно вернуть + :return: словарь, где ключ - id пользователя, значение - информация. Например: {users: {1: {}, 2: {}}} """ return UsersInfoGet.model_validate(await get_users(users, categories, user)) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 48f8e89..0d4678f 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -185,15 +185,15 @@ async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | async def get_users_info( - users: list[int], - categories: list[str], + user_ids: list[int], + category_ids: list[int], user: dict[str, int | list[dict[str, str | int]]], ) -> UsersInfoGet: """ Возвращает информацию о данных пользователей в указанных категориях - :param users: Список айди юзеров - :param categories: Список необходимых категорий + :param user_ids: Список айди юзеров + :param category_ids: Список айди необходимых категорий :param user: Сессия выполняющего запрос данных :return: Словарь, где ключи - айди юзеров, значение - словарь данных, как в get_user_info """ @@ -203,10 +203,8 @@ async def get_users_info( .join(Param) .join(Category) .filter( - Info.owner_id.in_(users), - Param.category_id.in_( - Category.query(session=db.session).filter(Category.name.in_(categories)).with_entities(Category.id) - ), + Info.owner_id.in_(user_ids), + Param.category_id.in_(category_ids), not_(Param.is_deleted), not_(Category.is_deleted), not_(Info.is_deleted), @@ -214,13 +212,13 @@ async def get_users_info( .all() ) if not infos: - raise ObjectNotFound(Info, users) + raise ObjectNotFound(Info, user_ids) result = {} for info in infos: if info.owner_id not in result: result[info.owner_id] = {"items": []} - if info.category.read_scope and info.category.read_scope not in scope_names and user["id"] != info.owner_id: + if info.category.read_scope and info.category.read_scope not in scope_names: continue result[info.owner_id]["items"].append( { From affcddcfd480b13510333db5815415a173f4606e Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Mon, 29 Jul 2024 12:09:46 +0300 Subject: [PATCH 12/24] Update test_users_get.py --- tests/test_routes/test_users_get.py | 146 ++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) diff --git a/tests/test_routes/test_users_get.py b/tests/test_routes/test_users_get.py index e69de29..4f0fafa 100644 --- a/tests/test_routes/test_users_get.py +++ b/tests/test_routes/test_users_get.py @@ -0,0 +1,146 @@ +from time import sleep + +import pytest + +from userdata_api.models.db import * +from userdata_api.utils.utils import random_string + + +@pytest.mark.authenticated("userdata.info.admin") +def test_get(client, dbsession, category_no_scopes, source): + source = source() + category1 = category_no_scopes() + category2 = category_no_scopes() + category3 = category_no_scopes() + param1 = Param( + name=f"test{random_string()}", category_id=category1.id, type="last", changeable=True, is_required=True + ) + param2 = Param( + name=f"test{random_string()}", category_id=category1.id, type="last", changeable=True, is_required=True + ) + param3 = Param( + name=f"test{random_string()}", category_id=category2.id, type="last", changeable=True, is_required=True + ) + param4 = Param( + name=f"test{random_string()}", category_id=category3.id, type="last", changeable=True, is_required=True + ) + dbsession.add_all([param1, param2, param3, param4]) + dbsession.flush() + info1 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param1.id, owner_id=0) + info2 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param2.id, owner_id=1) + info3 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param3.id, owner_id=0) + info4 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param4.id, owner_id=1) + dbsession.add_all([info1, info2, info3, info4]) + dbsession.commit() + response = client.get(f"/user", params={"users": [0, 1], "categories": [category1.id, category2.id, category3.id]}) + assert response.status_code == 200 + assert {"category": category1.name, "param": info2.param.name, "value": info2.value} in list( + response.json()["users"]["1"]["items"] + ) + assert {"category": category3.name, "param": info4.param.name, "value": info4.value} in list( + response.json()["users"]["1"]["items"] + ) + assert {"category": category1.name, "param": info1.param.name, "value": info1.value} in list( + response.json()["users"]["0"]["items"] + ) + assert {"category": category2.name, "param": info3.param.name, "value": info3.value} in list( + response.json()["users"]["0"]["items"] + ) + dbsession.delete(info1) + dbsession.delete(info2) + dbsession.delete(info3) + dbsession.delete(info4) + dbsession.flush() + dbsession.delete(param1) + dbsession.delete(param2) + dbsession.delete(param3) + dbsession.delete(param4) + dbsession.commit() + + +@pytest.mark.authenticated("userdata.info.admin") +def test_get_some_users(client, dbsession, category_no_scopes, source): + source = source() + category1 = category_no_scopes() + param1 = Param( + name=f"test{random_string()}", category_id=category1.id, type="last", changeable=True, is_required=True + ) + dbsession.add_all([param1]) + dbsession.flush() + info1 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param1.id, owner_id=1) + info2 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param1.id, owner_id=2) + info3 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param1.id, owner_id=3) + dbsession.add_all([info1, info2, info3]) + dbsession.commit() + response = client.get(f"/user", params={"users": [1, 2], "categories": [category1.id]}) + assert response.status_code == 200 + assert len(response.json()["users"]) == 2 + assert {"category": category1.name, "param": param1.name, "value": info1.value} in list( + response.json()["users"]["1"]["items"] + ) + assert {"category": category1.name, "param": param1.name, "value": info2.value} in list( + response.json()["users"]["2"]["items"] + ) + assert not "3" in list(response.json()["users"].keys()) + response = client.get(f"/user", params={"users": [3], "categories": [category1.id]}) + assert response.status_code == 200 + assert len(response.json()["users"]) == 1 + assert {"category": category1.name, "param": param1.name, "value": info3.value} in list( + response.json()["users"]["3"]["items"] + ) + dbsession.delete(info1) + dbsession.delete(info2) + dbsession.delete(info3) + dbsession.flush() + dbsession.delete(param1) + dbsession.commit() + + +@pytest.mark.authenticated("userdata.info.admin") +def test_get_some_categories(client, dbsession, category_no_scopes, source): + source = source() + category1 = category_no_scopes() + category2 = category_no_scopes() + category3 = category_no_scopes() + param1 = Param( + name=f"test{random_string()}", category_id=category1.id, type="last", changeable=True, is_required=True + ) + param2 = Param( + name=f"test{random_string()}", category_id=category2.id, type="last", changeable=True, is_required=True + ) + param3 = Param( + name=f"test{random_string()}", category_id=category3.id, type="last", changeable=True, is_required=True + ) + dbsession.add_all([param1, param2, param3]) + dbsession.flush() + info1 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param1.id, owner_id=1) + info2 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param2.id, owner_id=1) + info3 = Info(value=f"test{random_string()}", source_id=source.id, param_id=param3.id, owner_id=1) + dbsession.add_all([info1, info2, info3]) + dbsession.commit() + response = client.get(f"/user", params={"users": [1], "categories": [category1.id, category2.id]}) + assert response.status_code == 200 + assert {"category": category1.name, "param": info1.param.name, "value": info1.value} in list( + response.json()["users"]["1"]["items"] + ) + assert {"category": category2.name, "param": info2.param.name, "value": info2.value} in list( + response.json()["users"]["1"]["items"] + ) + assert not ( + {"category": category3.name, "param": info3.param.name, "value": info3.value} + in list(response.json()["users"]["1"]["items"]) + ) + + response = client.get(f"/user", params={"users": [1], "categories": [category3.id]}) + assert {"category": category3.name, "param": info3.param.name, "value": info3.value} in list( + response.json()["users"]["1"]["items"] + ) + + dbsession.delete(info1) + dbsession.delete(info2) + dbsession.delete(info3) + dbsession.flush() + dbsession.delete(param1) + dbsession.delete(param2) + dbsession.delete(param3) + dbsession.commit() From e07d4dd388b4e7a5b5cfcdf9672ae0153bf8084f Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Wed, 31 Jul 2024 12:44:05 +0300 Subject: [PATCH 13/24] redone response structure+tests --- tests/test_routes/test_users_get.py | 53 +++++++++++++++-------------- userdata_api/schemas/user.py | 9 ++++- userdata_api/utils/user.py | 10 +++--- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/tests/test_routes/test_users_get.py b/tests/test_routes/test_users_get.py index 4f0fafa..b83b318 100644 --- a/tests/test_routes/test_users_get.py +++ b/tests/test_routes/test_users_get.py @@ -34,17 +34,17 @@ def test_get(client, dbsession, category_no_scopes, source): dbsession.commit() response = client.get(f"/user", params={"users": [0, 1], "categories": [category1.id, category2.id, category3.id]}) assert response.status_code == 200 - assert {"category": category1.name, "param": info2.param.name, "value": info2.value} in list( - response.json()["users"]["1"]["items"] + assert {"user_id": 1, "category": category1.name, "param": info2.param.name, "value": info2.value} in list( + response.json()["items"] ) - assert {"category": category3.name, "param": info4.param.name, "value": info4.value} in list( - response.json()["users"]["1"]["items"] + assert {"user_id": 1, "category": category3.name, "param": info4.param.name, "value": info4.value} in list( + response.json()["items"] ) - assert {"category": category1.name, "param": info1.param.name, "value": info1.value} in list( - response.json()["users"]["0"]["items"] + assert {"user_id": 0, "category": category1.name, "param": info1.param.name, "value": info1.value} in list( + response.json()["items"] ) - assert {"category": category2.name, "param": info3.param.name, "value": info3.value} in list( - response.json()["users"]["0"]["items"] + assert {"user_id": 0, "category": category2.name, "param": info3.param.name, "value": info3.value} in list( + response.json()["items"] ) dbsession.delete(info1) dbsession.delete(info2) @@ -74,19 +74,21 @@ def test_get_some_users(client, dbsession, category_no_scopes, source): dbsession.commit() response = client.get(f"/user", params={"users": [1, 2], "categories": [category1.id]}) assert response.status_code == 200 - assert len(response.json()["users"]) == 2 - assert {"category": category1.name, "param": param1.name, "value": info1.value} in list( - response.json()["users"]["1"]["items"] + assert len(list(response.json()["items"])) == 2 + assert {"user_id": 1, "category": category1.name, "param": param1.name, "value": info1.value} in list( + response.json()["items"] ) - assert {"category": category1.name, "param": param1.name, "value": info2.value} in list( - response.json()["users"]["2"]["items"] + assert {"user_id": 2, "category": category1.name, "param": param1.name, "value": info2.value} in list( + response.json()["items"] + ) + assert {"user_id": 3, "category": category1.name, "param": param1.name, "value": info3.value} not in list( + response.json()["items"] ) - assert not "3" in list(response.json()["users"].keys()) response = client.get(f"/user", params={"users": [3], "categories": [category1.id]}) assert response.status_code == 200 - assert len(response.json()["users"]) == 1 - assert {"category": category1.name, "param": param1.name, "value": info3.value} in list( - response.json()["users"]["3"]["items"] + assert len(response.json()["items"]) == 1 + assert {"user_id": 3, "category": category1.name, "param": param1.name, "value": info3.value} in list( + response.json()["items"] ) dbsession.delete(info1) dbsession.delete(info2) @@ -120,20 +122,19 @@ def test_get_some_categories(client, dbsession, category_no_scopes, source): dbsession.commit() response = client.get(f"/user", params={"users": [1], "categories": [category1.id, category2.id]}) assert response.status_code == 200 - assert {"category": category1.name, "param": info1.param.name, "value": info1.value} in list( - response.json()["users"]["1"]["items"] + assert {"user_id": 1, "category": category1.name, "param": info1.param.name, "value": info1.value} in list( + response.json()["items"] ) - assert {"category": category2.name, "param": info2.param.name, "value": info2.value} in list( - response.json()["users"]["1"]["items"] + assert {"user_id": 1, "category": category2.name, "param": info2.param.name, "value": info2.value} in list( + response.json()["items"] ) - assert not ( - {"category": category3.name, "param": info3.param.name, "value": info3.value} - in list(response.json()["users"]["1"]["items"]) + assert {"user_id": 1, "category": category3.name, "param": info3.param.name, "value": info3.value} not in list( + response.json()["items"] ) response = client.get(f"/user", params={"users": [1], "categories": [category3.id]}) - assert {"category": category3.name, "param": info3.param.name, "value": info3.value} in list( - response.json()["users"]["1"]["items"] + assert {"user_id": 1, "category": category3.name, "param": info3.param.name, "value": info3.value} in list( + response.json()["items"] ) dbsession.delete(info1) diff --git a/userdata_api/schemas/user.py b/userdata_api/schemas/user.py index f251727..5a67c54 100644 --- a/userdata_api/schemas/user.py +++ b/userdata_api/schemas/user.py @@ -9,6 +9,13 @@ class UserInfo(Base): value: str | None = None +class ExtendedUserInfo(Base): + user_id: int + category: str + param: str + value: str | None = None + + class UserInfoGet(Base): items: list[UserInfo] @@ -25,7 +32,7 @@ def unique_validator(cls, v): class UsersInfoGet(Base): - users: dict[int, UserInfoGet] | None = None + items: list[ExtendedUserInfo] class UserInfoUpdate(UserInfoGet): diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 0d4678f..12f42cf 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -213,18 +213,16 @@ async def get_users_info( ) if not infos: raise ObjectNotFound(Info, user_ids) - result = {} + result = [] for info in infos: - if info.owner_id not in result: - - result[info.owner_id] = {"items": []} if info.category.read_scope and info.category.read_scope not in scope_names: continue - result[info.owner_id]["items"].append( + result.append( { + "user_id": info.owner_id, "category": info.category.name, "param": info.param.name, "value": info.value, } ) - return UsersInfoGet(users=result) + return UsersInfoGet(items=result) From c697333a302bb5981d0a190426b8e24c6fd98e90 Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Wed, 31 Jul 2024 12:48:36 +0300 Subject: [PATCH 14/24] Update test_users_get.py --- tests/test_routes/test_users_get.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_routes/test_users_get.py b/tests/test_routes/test_users_get.py index b83b318..0a8be2e 100644 --- a/tests/test_routes/test_users_get.py +++ b/tests/test_routes/test_users_get.py @@ -2,7 +2,7 @@ import pytest -from userdata_api.models.db import * +from userdata_api.models.db import Info, Param from userdata_api.utils.utils import random_string From 7bcd5a456ad3b733d5be46ef9f568402435a94ed Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Fri, 2 Aug 2024 10:51:46 +0300 Subject: [PATCH 15/24] response format change --- tests/test_routes/test_user_get.py | 10 ++ tests/test_routes/test_users_get.py | 17 ++- userdata_api/routes/user.py | 5 +- userdata_api/schemas/user.py | 6 +- userdata_api/utils/user.py | 156 ++++++++++++++++------------ 5 files changed, 117 insertions(+), 77 deletions(-) diff --git a/tests/test_routes/test_user_get.py b/tests/test_routes/test_user_get.py index a20ad96..4ceaf7f 100644 --- a/tests/test_routes/test_user_get.py +++ b/tests/test_routes/test_user_get.py @@ -79,6 +79,10 @@ def test_get_a_few(client, dbsession, category_no_scopes, source): dbsession.delete(param2) dbsession.delete(param3) dbsession.delete(param4) + dbsession.flush() + dbsession.delete(category1) + dbsession.delete(category2) + dbsession.delete(category3) dbsession.commit() @@ -147,6 +151,10 @@ def test_get_a_few_with_trust_level(client, dbsession, category_no_scopes, sourc dbsession.delete(param2) dbsession.delete(param3) dbsession.delete(param4) + dbsession.flush() + dbsession.delete(category1) + dbsession.delete(category2) + dbsession.delete(category3) dbsession.commit() @@ -181,4 +189,6 @@ def test_get_last_most_trusted(client, dbsession, category_no_scopes, source): dbsession.delete(info4) dbsession.flush() dbsession.delete(param1) + dbsession.flush() + dbsession.delete(category1) dbsession.commit() diff --git a/tests/test_routes/test_users_get.py b/tests/test_routes/test_users_get.py index 0a8be2e..8db92f5 100644 --- a/tests/test_routes/test_users_get.py +++ b/tests/test_routes/test_users_get.py @@ -55,6 +55,10 @@ def test_get(client, dbsession, category_no_scopes, source): dbsession.delete(param2) dbsession.delete(param3) dbsession.delete(param4) + dbsession.flush() + dbsession.delete(category1) + dbsession.delete(category2) + dbsession.delete(category3) dbsession.commit() @@ -73,8 +77,12 @@ def test_get_some_users(client, dbsession, category_no_scopes, source): dbsession.add_all([info1, info2, info3]) dbsession.commit() response = client.get(f"/user", params={"users": [1, 2], "categories": [category1.id]}) + print(client.get(f"/user", params={"users": [2], "categories": [category1.id]}).json()) + print(client.get(f"/user", params={"users": [1], "categories": [category1.id]}).json()) + print(client.get(f"/user", params={"users": [1, 2], "categories": [category1.id]}).json()) + print(client.get(f"/user", params={"users": [2, 1], "categories": [category1.id]}).json()) assert response.status_code == 200 - assert len(list(response.json()["items"])) == 2 + print(response.json()) assert {"user_id": 1, "category": category1.name, "param": param1.name, "value": info1.value} in list( response.json()["items"] ) @@ -95,6 +103,8 @@ def test_get_some_users(client, dbsession, category_no_scopes, source): dbsession.delete(info3) dbsession.flush() dbsession.delete(param1) + dbsession.flush() + dbsession.delete(category1) dbsession.commit() @@ -131,6 +141,7 @@ def test_get_some_categories(client, dbsession, category_no_scopes, source): assert {"user_id": 1, "category": category3.name, "param": info3.param.name, "value": info3.value} not in list( response.json()["items"] ) + response = client.get(f"/user", params={"users": [1], "categories": [category3.id]}) assert {"user_id": 1, "category": category3.name, "param": info3.param.name, "value": info3.value} in list( @@ -144,4 +155,8 @@ def test_get_some_categories(client, dbsession, category_no_scopes, source): dbsession.delete(param1) dbsession.delete(param2) dbsession.delete(param3) + dbsession.flush() + dbsession.delete(category1) + dbsession.delete(category2) + dbsession.delete(category3) dbsession.commit() diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index 6950657..d7a3ef2 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -8,7 +8,7 @@ from userdata_api.schemas.response_model import StatusResponseModel from userdata_api.schemas.user import UserInfoGet, UserInfoUpdate, UsersInfoGet from userdata_api.utils.user import get_user_info as get -from userdata_api.utils.user import get_users_info as get_users +from userdata_api.utils.user import get_users_info_batch as get_users from userdata_api.utils.user import patch_user_info as patch @@ -80,7 +80,6 @@ async def get_users_info( Получить информацию о пользователях. :param users: список id юзеров, про которых нужно вернуть информацию :param categories: список id категорий, параметры которых нужно вернуть - :return: словарь, где ключ - id пользователя, значение - информация. - Например: {users: {1: {}, 2: {}}} + :return: список данных о пользователях и данных категориях в формате {user_id: user_id, category: category_name, param: param_name, value: value} """ return UsersInfoGet.model_validate(await get_users(users, categories, user)) diff --git a/userdata_api/schemas/user.py b/userdata_api/schemas/user.py index 5a67c54..b5819c8 100644 --- a/userdata_api/schemas/user.py +++ b/userdata_api/schemas/user.py @@ -9,11 +9,9 @@ class UserInfo(Base): value: str | None = None -class ExtendedUserInfo(Base): +class ExtendedUserInfo(UserInfo): user_id: int - category: str - param: str - value: str | None = None + class UserInfoGet(Base): diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 12f42cf..8f6920e 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -99,52 +99,66 @@ async def patch_user_info(new: UserInfoUpdate, user_id: int, user: dict[str, int info.value = item.value db.session.flush() continue + if item.value is None: info.is_deleted = True db.session.flush() continue +async def get_users_info( + user_ids: list[int], + category_ids: list[int] | None, + user: dict[str, int | list[dict[str, str | int]]] +) -> list[dict]: + """. + Возвращает информацию о данных пользователей в указанных категориях -async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | int]]]) -> UserInfoGet: - """ - Возвращает информауию о пользователе в соотетствии с переданным токеном. - - Пользователь может прочитать любую информацию о себе - - Токен с доступом к read_scope категории может получить доступ к данным категории у любых пользователей - - :param user_id: Айди пользователя + :param user_ids: Список айди юзеров + :param category_ids: Список айди необходимых категорий, если None, то мы запрашиваем информацию только обо одном пользователе user_ids[0] обо всех досутпных категориях :param user: Сессия выполняющего запрос данных - :return: Список словарей содержащих категорию, параметр категории и значение этого параметра у польщователя + :return: Список словарей содержащих id пользователя(только если берем информацию о нескольких пользователях), категорию, параметр категории и значение этого параметра у пользователя """ - infos: list[Info] = ( + is_single_user = category_ids is None + scope_names = [scope["name"] for scope in user["session_scopes"]] + param_dict: dict[Param, list[Info] | Info | None] = {} + query: list[Info] = ( Info.query(session=db.session) .join(Param) .join(Category) - .filter(Info.owner_id == user_id, not_(Param.is_deleted), not_(Category.is_deleted)) - .all() + .filter( + Info.owner_id.in_(user_ids), + not_(Param.is_deleted), + not_(Category.is_deleted), + not_(Info.is_deleted), + ) ) + if not is_single_user: + query = query.filter(Param.category_id.in_(category_ids)) + infos = query.all() if not infos: - raise ObjectNotFound(Info, user_id) - scope_names = [scope["name"] for scope in user["session_scopes"]] - param_dict: dict[Param, list[Info] | Info | None] = {} + raise ObjectNotFound(Info, user_ids) + result = [] for info in infos: - ## Проверка доступов - нужен либо скоуп на категориию либо нужно быть овнером информации - if info.category.read_scope and info.category.read_scope not in scope_names and user["id"] != user_id: + is_many_values = info.param.pytype == list[str] + if ( + info.category.read_scope + and info.category.read_scope not in scope_names + and (info.owner_id != user_ids[0] if is_single_user else True) + ): continue - if info.param not in param_dict.keys(): - param_dict[info.param] = [] if info.param.pytype == list[str] else None + if (info.param, info.owner_id) not in param_dict.keys(): + param_dict[(info.param, info.owner_id)] = [] if is_many_values else None if info.param.type == ViewType.ALL: - param_dict[info.param].append(info) - elif (param_dict[info.param] is None) or ( - (info.param.type == ViewType.LAST and info.create_ts > param_dict[info.param].create_ts) + param_dict[(info.param, info.owner_id)].append(info) + elif (param_dict[(info.param,info.owner_id)] is None) or ( + (info.param.type == ViewType.LAST and info.create_ts > param_dict[(info.param,info.owner_id)].create_ts) or ( info.param.type == ViewType.MOST_TRUSTED and ( - param_dict[info.param].source.trust_level < info.source.trust_level + param_dict[(info.param,info.owner_id)].source.trust_level < info.source.trust_level or ( - param_dict[info.param].source.trust_level <= info.source.trust_level - and info.create_ts > param_dict[info.param].create_ts + param_dict[(info.param,info.owner_id)].source.trust_level <= info.source.trust_level + and info.create_ts > param_dict[(info.param,info.owner_id)].create_ts ) ) ) @@ -157,72 +171,76 @@ async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | - строго больше индекс доверия/такой же индекс доверия, но информация более поздняя по времени + Если у параметра отображение по времени то более релевантная - более позднаяя """ - param_dict[info.param] = info - result = [] + param_dict[(info.param,info.owner_id)] = info + for item in param_dict.values(): if isinstance(item, list): result.extend( [ - { - "category": _item.category.name, - "param": _item.param.name, - "value": _item.value, - } + ( + { + "user_id": _item.owner_id, + "category": _item.category.name, + "param": _item.param.name, + "value": _item.value, + } + if not is_single_user + else { + "category": _item.category.name, + "param": _item.param.name, + "value": _item.value, + } + ) for _item in item ] ) else: result.append( { + "user_id": item.owner_id, "category": item.category.name, "param": item.param.name, "value": item.value, } - ) - return UserInfoGet(items=result) - + if not is_single_user else + { + "category": item.category.name, + "param": item.param.name, + "value": item.value, + } + ) + return result -async def get_users_info( +async def get_users_info_batch( user_ids: list[int], category_ids: list[int], - user: dict[str, int | list[dict[str, str | int]]], + user: dict[str, int | list[dict[str, str | int]]] ) -> UsersInfoGet: - """ + """. Возвращает информацию о данных пользователей в указанных категориях :param user_ids: Список айди юзеров :param category_ids: Список айди необходимых категорий :param user: Сессия выполняющего запрос данных - :return: Словарь, где ключи - айди юзеров, значение - словарь данных, как в get_user_info + :return: Список словарей содержащих id пользователя, категорию, параметр категории и значение этого параметра у пользователя """ - scope_names = [scope["name"] for scope in user["session_scopes"]] - infos: list[Info] = ( - Info.query(session=db.session) - .join(Param) - .join(Category) - .filter( - Info.owner_id.in_(user_ids), - Param.category_id.in_(category_ids), - not_(Param.is_deleted), - not_(Category.is_deleted), - not_(Info.is_deleted), - ) - .all() - ) - if not infos: - raise ObjectNotFound(Info, user_ids) - result = [] - for info in infos: - if info.category.read_scope and info.category.read_scope not in scope_names: - continue - result.append( - { - "user_id": info.owner_id, - "category": info.category.name, - "param": info.param.name, - "value": info.value, - } - ) - return UsersInfoGet(items=result) + return UsersInfoGet(items=await get_users_info(user_ids, category_ids, user)) + +async def get_user_info( + user_id: int, + user: dict[str, int | list[dict[str, str | int]]] +) -> UserInfoGet: + """Возвращает информауию о пользователе в соотетствии с переданным токеном. + + Пользователь может прочитать любую информацию о себе + + Токен с доступом к read_scope категории может получить доступ к данным категории у любых пользователей + + :param user_id: Айди пользователя + :param user: Сессия выполняющего запрос данных + :return: Список словарей содержащих категорию, параметр категории и значение этого параметра у пользователя + """ + return UserInfoGet(items= await get_users_info([user_id], None, user)) \ No newline at end of file From 81d632216ee82529dfef515b0735d94f3740f45f Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Fri, 2 Aug 2024 11:15:37 +0300 Subject: [PATCH 16/24] fix --- tests/test_routes/test_user_get.py | 4 +-- tests/test_routes/test_users_get.py | 4 --- userdata_api/schemas/user.py | 1 - userdata_api/utils/user.py | 40 +++++++++++++---------------- 4 files changed, 19 insertions(+), 30 deletions(-) diff --git a/tests/test_routes/test_user_get.py b/tests/test_routes/test_user_get.py index 4ceaf7f..4a123fc 100644 --- a/tests/test_routes/test_user_get.py +++ b/tests/test_routes/test_user_get.py @@ -26,6 +26,7 @@ def test_get_no_all_scopes(client, dbsession, source, info_no_scopes): response = client.get(f"/user/{info1.owner_id}") assert response.status_code == 200 assert info1.category.name not in response.json() + dbsession.delete(info1) dbsession.commit() @@ -79,7 +80,6 @@ def test_get_a_few(client, dbsession, category_no_scopes, source): dbsession.delete(param2) dbsession.delete(param3) dbsession.delete(param4) - dbsession.flush() dbsession.delete(category1) dbsession.delete(category2) dbsession.delete(category3) @@ -151,7 +151,6 @@ def test_get_a_few_with_trust_level(client, dbsession, category_no_scopes, sourc dbsession.delete(param2) dbsession.delete(param3) dbsession.delete(param4) - dbsession.flush() dbsession.delete(category1) dbsession.delete(category2) dbsession.delete(category3) @@ -189,6 +188,5 @@ def test_get_last_most_trusted(client, dbsession, category_no_scopes, source): dbsession.delete(info4) dbsession.flush() dbsession.delete(param1) - dbsession.flush() dbsession.delete(category1) dbsession.commit() diff --git a/tests/test_routes/test_users_get.py b/tests/test_routes/test_users_get.py index 8db92f5..5ee5677 100644 --- a/tests/test_routes/test_users_get.py +++ b/tests/test_routes/test_users_get.py @@ -55,7 +55,6 @@ def test_get(client, dbsession, category_no_scopes, source): dbsession.delete(param2) dbsession.delete(param3) dbsession.delete(param4) - dbsession.flush() dbsession.delete(category1) dbsession.delete(category2) dbsession.delete(category3) @@ -103,7 +102,6 @@ def test_get_some_users(client, dbsession, category_no_scopes, source): dbsession.delete(info3) dbsession.flush() dbsession.delete(param1) - dbsession.flush() dbsession.delete(category1) dbsession.commit() @@ -141,7 +139,6 @@ def test_get_some_categories(client, dbsession, category_no_scopes, source): assert {"user_id": 1, "category": category3.name, "param": info3.param.name, "value": info3.value} not in list( response.json()["items"] ) - response = client.get(f"/user", params={"users": [1], "categories": [category3.id]}) assert {"user_id": 1, "category": category3.name, "param": info3.param.name, "value": info3.value} in list( @@ -155,7 +152,6 @@ def test_get_some_categories(client, dbsession, category_no_scopes, source): dbsession.delete(param1) dbsession.delete(param2) dbsession.delete(param3) - dbsession.flush() dbsession.delete(category1) dbsession.delete(category2) dbsession.delete(category3) diff --git a/userdata_api/schemas/user.py b/userdata_api/schemas/user.py index b5819c8..b48d27f 100644 --- a/userdata_api/schemas/user.py +++ b/userdata_api/schemas/user.py @@ -13,7 +13,6 @@ class ExtendedUserInfo(UserInfo): user_id: int - class UserInfoGet(Base): items: list[UserInfo] diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 8f6920e..7fa135a 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -105,10 +105,9 @@ async def patch_user_info(new: UserInfoUpdate, user_id: int, user: dict[str, int db.session.flush() continue + async def get_users_info( - user_ids: list[int], - category_ids: list[int] | None, - user: dict[str, int | list[dict[str, str | int]]] + user_ids: list[int], category_ids: list[int] | None, user: dict[str, int | list[dict[str, str | int]]] ) -> list[dict]: """. Возвращает информацию о данных пользователей в указанных категориях @@ -146,19 +145,19 @@ async def get_users_info( and (info.owner_id != user_ids[0] if is_single_user else True) ): continue - if (info.param, info.owner_id) not in param_dict.keys(): + if (info.param, info.owner_id) not in param_dict.keys(): param_dict[(info.param, info.owner_id)] = [] if is_many_values else None if info.param.type == ViewType.ALL: param_dict[(info.param, info.owner_id)].append(info) - elif (param_dict[(info.param,info.owner_id)] is None) or ( - (info.param.type == ViewType.LAST and info.create_ts > param_dict[(info.param,info.owner_id)].create_ts) + elif (param_dict[(info.param, info.owner_id)] is None) or ( + (info.param.type == ViewType.LAST and info.create_ts > param_dict[(info.param, info.owner_id)].create_ts) or ( info.param.type == ViewType.MOST_TRUSTED and ( - param_dict[(info.param,info.owner_id)].source.trust_level < info.source.trust_level + param_dict[(info.param, info.owner_id)].source.trust_level < info.source.trust_level or ( - param_dict[(info.param,info.owner_id)].source.trust_level <= info.source.trust_level - and info.create_ts > param_dict[(info.param,info.owner_id)].create_ts + param_dict[(info.param, info.owner_id)].source.trust_level <= info.source.trust_level + and info.create_ts > param_dict[(info.param, info.owner_id)].create_ts ) ) ) @@ -174,8 +173,8 @@ async def get_users_info( Если у параметра отображение по времени то более релевантная - более позднаяя """ - param_dict[(info.param,info.owner_id)] = info - + param_dict[(info.param, info.owner_id)] = info + for item in param_dict.values(): if isinstance(item, list): result.extend( @@ -205,19 +204,18 @@ async def get_users_info( "param": item.param.name, "value": item.value, } - if not is_single_user else - { + if not is_single_user + else { "category": item.category.name, "param": item.param.name, "value": item.value, } - ) + ) return result + async def get_users_info_batch( - user_ids: list[int], - category_ids: list[int], - user: dict[str, int | list[dict[str, str | int]]] + user_ids: list[int], category_ids: list[int], user: dict[str, int | list[dict[str, str | int]]] ) -> UsersInfoGet: """. Возвращает информацию о данных пользователей в указанных категориях @@ -229,10 +227,8 @@ async def get_users_info_batch( """ return UsersInfoGet(items=await get_users_info(user_ids, category_ids, user)) -async def get_user_info( - user_id: int, - user: dict[str, int | list[dict[str, str | int]]] -) -> UserInfoGet: + +async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | int]]]) -> UserInfoGet: """Возвращает информауию о пользователе в соотетствии с переданным токеном. Пользователь может прочитать любую информацию о себе @@ -243,4 +239,4 @@ async def get_user_info( :param user: Сессия выполняющего запрос данных :return: Список словарей содержащих категорию, параметр категории и значение этого параметра у пользователя """ - return UserInfoGet(items= await get_users_info([user_id], None, user)) \ No newline at end of file + return UserInfoGet(items=await get_users_info([user_id], None, user)) From 0276540a8a39853eaea03c2a5cc4e566e11969b8 Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Sat, 3 Aug 2024 11:49:07 +0300 Subject: [PATCH 17/24] deleting categories in tests --- tests/test_routes/test_user_get.py | 3 +++ tests/test_routes/test_users_get.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/test_routes/test_user_get.py b/tests/test_routes/test_user_get.py index 4a123fc..cf1b675 100644 --- a/tests/test_routes/test_user_get.py +++ b/tests/test_routes/test_user_get.py @@ -80,6 +80,7 @@ def test_get_a_few(client, dbsession, category_no_scopes, source): dbsession.delete(param2) dbsession.delete(param3) dbsession.delete(param4) + dbsession.flush() dbsession.delete(category1) dbsession.delete(category2) dbsession.delete(category3) @@ -151,6 +152,7 @@ def test_get_a_few_with_trust_level(client, dbsession, category_no_scopes, sourc dbsession.delete(param2) dbsession.delete(param3) dbsession.delete(param4) + dbsession.flush() dbsession.delete(category1) dbsession.delete(category2) dbsession.delete(category3) @@ -188,5 +190,6 @@ def test_get_last_most_trusted(client, dbsession, category_no_scopes, source): dbsession.delete(info4) dbsession.flush() dbsession.delete(param1) + dbsession.flush() dbsession.delete(category1) dbsession.commit() diff --git a/tests/test_routes/test_users_get.py b/tests/test_routes/test_users_get.py index 5ee5677..ca524f1 100644 --- a/tests/test_routes/test_users_get.py +++ b/tests/test_routes/test_users_get.py @@ -55,6 +55,7 @@ def test_get(client, dbsession, category_no_scopes, source): dbsession.delete(param2) dbsession.delete(param3) dbsession.delete(param4) + dbsession.flush() dbsession.delete(category1) dbsession.delete(category2) dbsession.delete(category3) @@ -102,6 +103,7 @@ def test_get_some_users(client, dbsession, category_no_scopes, source): dbsession.delete(info3) dbsession.flush() dbsession.delete(param1) + dbsession.flush() dbsession.delete(category1) dbsession.commit() @@ -152,6 +154,7 @@ def test_get_some_categories(client, dbsession, category_no_scopes, source): dbsession.delete(param1) dbsession.delete(param2) dbsession.delete(param3) + dbsession.flush() dbsession.delete(category1) dbsession.delete(category2) dbsession.delete(category3) From a434a5bfdc11dd1b5c954b75608607d774adbafe Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Sun, 11 Aug 2024 12:27:34 +0300 Subject: [PATCH 18/24] maybe fix --- tests/test_routes/test_user_get.py | 2 ++ userdata_api/utils/user.py | 19 +++---------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/tests/test_routes/test_user_get.py b/tests/test_routes/test_user_get.py index cf1b675..bc8c25e 100644 --- a/tests/test_routes/test_user_get.py +++ b/tests/test_routes/test_user_get.py @@ -16,6 +16,8 @@ def test_get(client, dbsession, source, info_no_scopes): assert info1.category.name == response.json()["items"][0]["category"] assert info1.param.name == response.json()["items"][0]["param"] assert info1.value == response.json()["items"][0]["value"] + dbsession.delete(info1) + dbsession.commit() @pytest.mark.authenticated(user_id=1) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 7fa135a..bc055ba 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -1,7 +1,7 @@ from __future__ import annotations from fastapi_sqlalchemy import db -from sqlalchemy import not_ +from sqlalchemy import not_, or_, and_ from userdata_api.exceptions import Forbidden, ObjectNotFound from userdata_api.models.db import Category, Info, Param, Source, ViewType @@ -138,13 +138,13 @@ async def get_users_info( raise ObjectNotFound(Info, user_ids) result = [] for info in infos: - is_many_values = info.param.pytype == list[str] if ( info.category.read_scope and info.category.read_scope not in scope_names and (info.owner_id != user_ids[0] if is_single_user else True) ): - continue + continue # skip if no read scope + is_many_values = info.param.pytype == list[str] if (info.param, info.owner_id) not in param_dict.keys(): param_dict[(info.param, info.owner_id)] = [] if is_many_values else None if info.param.type == ViewType.ALL: @@ -162,19 +162,7 @@ async def get_users_info( ) ) ): - """ - Сюда он зайдет либо если параметру не соответствует никакой информации, - либо если встретил более релевантную. - - Если у параметра отображение по доверию, то более релевантная - - строго больше индекс доверия/такой же индекс доверия, - но информация более поздняя по времени - - - Если у параметра отображение по времени то более релевантная - более позднаяя - """ param_dict[(info.param, info.owner_id)] = info - for item in param_dict.values(): if isinstance(item, list): result.extend( @@ -213,7 +201,6 @@ async def get_users_info( ) return result - async def get_users_info_batch( user_ids: list[int], category_ids: list[int], user: dict[str, int | list[dict[str, str | int]]] ) -> UsersInfoGet: From 6d833339c82ca185b549efe27f7daeba788a8edb Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Mon, 12 Aug 2024 10:28:21 +0300 Subject: [PATCH 19/24] get/{id} scopes bug fix --- userdata_api/utils/user.py | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index bc055ba..d16ebe5 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -1,7 +1,7 @@ from __future__ import annotations from fastapi_sqlalchemy import db -from sqlalchemy import not_, or_, and_ +from sqlalchemy import and_, not_, or_ from userdata_api.exceptions import Forbidden, ObjectNotFound from userdata_api.models.db import Category, Info, Param, Source, ViewType @@ -141,7 +141,7 @@ async def get_users_info( if ( info.category.read_scope and info.category.read_scope not in scope_names - and (info.owner_id != user_ids[0] if is_single_user else True) + and (info.owner_id != user["id"] if is_single_user else True) ): continue # skip if no read scope is_many_values = info.param.pytype == list[str] @@ -163,44 +163,30 @@ async def get_users_info( ) ): param_dict[(info.param, info.owner_id)] = info + result_format = lambda _item: { + "category": _item.category.name, + "param": _item.param.name, + "value": _item.value, + } for item in param_dict.values(): if isinstance(item, list): result.extend( [ ( - { - "user_id": _item.owner_id, - "category": _item.category.name, - "param": _item.param.name, - "value": _item.value, - } + {**result_format(_item), "user_id": _item.owner_id} if not is_single_user - else { - "category": _item.category.name, - "param": _item.param.name, - "value": _item.value, - } + else result_format(_item) ) for _item in item ] ) else: result.append( - { - "user_id": item.owner_id, - "category": item.category.name, - "param": item.param.name, - "value": item.value, - } - if not is_single_user - else { - "category": item.category.name, - "param": item.param.name, - "value": item.value, - } + {**result_format(item), "user_id": item.owner_id} if not is_single_user else {**result_format(item)} ) return result + async def get_users_info_batch( user_ids: list[int], category_ids: list[int], user: dict[str, int | list[dict[str, str | int]]] ) -> UsersInfoGet: From 9251663f7868077ad6696f64dc3bda8fb3289b6d Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Mon, 12 Aug 2024 11:01:00 +0300 Subject: [PATCH 20/24] fix --- userdata_api/utils/user.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index d16ebe5..49e2f1b 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -173,16 +173,24 @@ async def get_users_info( result.extend( [ ( - {**result_format(_item), "user_id": _item.owner_id} - if not is_single_user - else result_format(_item) + { + "user_id": _item.owner_id, + "category": _item.category.name, + "param": _item.param.name, + "value": _item.value, + } ) for _item in item ] ) else: result.append( - {**result_format(item), "user_id": item.owner_id} if not is_single_user else {**result_format(item)} + { + "user_id": item.owner_id, + "category": item.category.name, + "param": item.param.name, + "value": item.value, + } ) return result @@ -212,4 +220,7 @@ async def get_user_info(user_id: int, user: dict[str, int | list[dict[str, str | :param user: Сессия выполняющего запрос данных :return: Список словарей содержащих категорию, параметр категории и значение этого параметра у пользователя """ - return UserInfoGet(items=await get_users_info([user_id], None, user)) + result = await get_users_info([user_id], None, user) + for value in result: + del value["user_id"] + return UserInfoGet(items=result) From 20e90ad151801087905583de92b6b08a4d12635e Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Mon, 12 Aug 2024 11:48:49 +0300 Subject: [PATCH 21/24] {tuple : list | value} ->> {param : {owner_id : ...}} --- userdata_api/utils/user.py | 73 ++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 38 deletions(-) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 49e2f1b..15b0dd9 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -1,7 +1,7 @@ from __future__ import annotations from fastapi_sqlalchemy import db -from sqlalchemy import and_, not_, or_ +from sqlalchemy import not_ from userdata_api.exceptions import Forbidden, ObjectNotFound from userdata_api.models.db import Category, Info, Param, Source, ViewType @@ -119,7 +119,7 @@ async def get_users_info( """ is_single_user = category_ids is None scope_names = [scope["name"] for scope in user["session_scopes"]] - param_dict: dict[Param, list[Info] | Info | None] = {} + param_dict: dict[Param, dict[int, list[Info] | Info | None] | None] = {} query: list[Info] = ( Info.query(session=db.session) .join(Param) @@ -141,57 +141,54 @@ async def get_users_info( if ( info.category.read_scope and info.category.read_scope not in scope_names - and (info.owner_id != user["id"] if is_single_user else True) + and (info.owner_id != user["id"] or not is_single_user) ): - continue # skip if no read scope + continue is_many_values = info.param.pytype == list[str] - if (info.param, info.owner_id) not in param_dict.keys(): - param_dict[(info.param, info.owner_id)] = [] if is_many_values else None + if info.param not in param_dict: + param_dict[info.param] = {} + if info.owner_id not in param_dict[info.param]: + param_dict[info.param][info.owner_id] = [] if is_many_values else None if info.param.type == ViewType.ALL: - param_dict[(info.param, info.owner_id)].append(info) - elif (param_dict[(info.param, info.owner_id)] is None) or ( - (info.param.type == ViewType.LAST and info.create_ts > param_dict[(info.param, info.owner_id)].create_ts) + param_dict[info.param][info.owner_id].append(info) + elif param_dict[info.param][info.owner_id] is None or ( + (info.param.type == ViewType.LAST and info.create_ts > param_dict[info.param][info.owner_id].create_ts) or ( info.param.type == ViewType.MOST_TRUSTED and ( - param_dict[(info.param, info.owner_id)].source.trust_level < info.source.trust_level + param_dict[info.param][info.owner_id].source.trust_level < info.source.trust_level or ( - param_dict[(info.param, info.owner_id)].source.trust_level <= info.source.trust_level - and info.create_ts > param_dict[(info.param, info.owner_id)].create_ts + param_dict[info.param][info.owner_id].source.trust_level <= info.source.trust_level + and info.create_ts > param_dict[info.param][info.owner_id].create_ts ) ) ) ): - param_dict[(info.param, info.owner_id)] = info - result_format = lambda _item: { - "category": _item.category.name, - "param": _item.param.name, - "value": _item.value, - } - for item in param_dict.values(): - if isinstance(item, list): - result.extend( - [ - ( + param_dict[info.param][info.owner_id] = info + result = [] + for param, owner_dict in param_dict.items(): + for owner_id, item in owner_dict.items(): + if isinstance(item, list): + result.extend( + [ { - "user_id": _item.owner_id, + "user_id": owner_id, "category": _item.category.name, - "param": _item.param.name, + "param": param.name, "value": _item.value, } - ) - for _item in item - ] - ) - else: - result.append( - { - "user_id": item.owner_id, - "category": item.category.name, - "param": item.param.name, - "value": item.value, - } - ) + for _item in item + ] + ) + else: + result.append( + { + "user_id": owner_id, + "category": item.category.name, + "param": param.name, + "value": item.value, + } + ) return result From aa561e87969b3a2def142fdfd6ee6820499b5d2c Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Mon, 12 Aug 2024 12:41:21 +0300 Subject: [PATCH 22/24] minor fixes --- tests/test_routes/test_users_get.py | 5 ----- userdata_api/routes/user.py | 2 +- userdata_api/utils/user.py | 27 +++++++++++++++++---------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/tests/test_routes/test_users_get.py b/tests/test_routes/test_users_get.py index ca524f1..65d1753 100644 --- a/tests/test_routes/test_users_get.py +++ b/tests/test_routes/test_users_get.py @@ -77,12 +77,7 @@ def test_get_some_users(client, dbsession, category_no_scopes, source): dbsession.add_all([info1, info2, info3]) dbsession.commit() response = client.get(f"/user", params={"users": [1, 2], "categories": [category1.id]}) - print(client.get(f"/user", params={"users": [2], "categories": [category1.id]}).json()) - print(client.get(f"/user", params={"users": [1], "categories": [category1.id]}).json()) - print(client.get(f"/user", params={"users": [1, 2], "categories": [category1.id]}).json()) - print(client.get(f"/user", params={"users": [2, 1], "categories": [category1.id]}).json()) assert response.status_code == 200 - print(response.json()) assert {"user_id": 1, "category": category1.name, "param": param1.name, "value": info1.value} in list( response.json()["items"] ) diff --git a/userdata_api/routes/user.py b/userdata_api/routes/user.py index d7a3ef2..2fdec1b 100644 --- a/userdata_api/routes/user.py +++ b/userdata_api/routes/user.py @@ -80,6 +80,6 @@ async def get_users_info( Получить информацию о пользователях. :param users: список id юзеров, про которых нужно вернуть информацию :param categories: список id категорий, параметры которых нужно вернуть - :return: список данных о пользователях и данных категориях в формате {user_id: user_id, category: category_name, param: param_name, value: value} + :return: список данных о пользователях и данных категориях """ return UsersInfoGet.model_validate(await get_users(users, categories, user)) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 15b0dd9..1756205 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -108,14 +108,14 @@ async def patch_user_info(new: UserInfoUpdate, user_id: int, user: dict[str, int async def get_users_info( user_ids: list[int], category_ids: list[int] | None, user: dict[str, int | list[dict[str, str | int]]] -) -> list[dict]: +) -> list[dict[str, str | None]]: """. Возвращает информацию о данных пользователей в указанных категориях :param user_ids: Список айди юзеров :param category_ids: Список айди необходимых категорий, если None, то мы запрашиваем информацию только обо одном пользователе user_ids[0] обо всех досутпных категориях :param user: Сессия выполняющего запрос данных - :return: Список словарей содержащих id пользователя(только если берем информацию о нескольких пользователях), категорию, параметр категории и значение этого параметра у пользователя + :return: Список словарей содержащих id пользователя, категорию, параметр категории и значение этого параметра у пользователя """ is_single_user = category_ids is None scope_names = [scope["name"] for scope in user["session_scopes"]] @@ -138,17 +138,14 @@ async def get_users_info( raise ObjectNotFound(Info, user_ids) result = [] for info in infos: - if ( - info.category.read_scope - and info.category.read_scope not in scope_names - and (info.owner_id != user["id"] or not is_single_user) + if info.category.read_scope and ( + info.owner_id != user["id"] or not is_single_user and info.category.read_scope not in scope_names ): continue - is_many_values = info.param.pytype == list[str] if info.param not in param_dict: param_dict[info.param] = {} if info.owner_id not in param_dict[info.param]: - param_dict[info.param][info.owner_id] = [] if is_many_values else None + param_dict[info.param][info.owner_id] = [] if info.param.type == ViewType.ALL else None if info.param.type == ViewType.ALL: param_dict[info.param][info.owner_id].append(info) elif param_dict[info.param][info.owner_id] is None or ( @@ -164,10 +161,20 @@ async def get_users_info( ) ) ): + """ + Сюда он зайдет либо если параметру не соответствует никакой информации, + либо если встретил более релевантную. + + Если у параметра отображение по доверию, то более релевантная + - строго больше индекс доверия/такой же индекс доверия, + но информация более поздняя по времени + + Если у параметра отображение по времени то более релевантная - более позднаяя + """ param_dict[info.param][info.owner_id] = info result = [] - for param, owner_dict in param_dict.items(): - for owner_id, item in owner_dict.items(): + for param, user_dict in param_dict.items(): + for owner_id, item in user_dict.items(): if isinstance(item, list): result.extend( [ From 4c9d634ac66f8bcf1fc1f905c07f5770eb86c5c5 Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Mon, 12 Aug 2024 12:45:47 +0300 Subject: [PATCH 23/24] fix --- userdata_api/utils/user.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 1756205..6373a0e 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -138,8 +138,10 @@ async def get_users_info( raise ObjectNotFound(Info, user_ids) result = [] for info in infos: - if info.category.read_scope and ( - info.owner_id != user["id"] or not is_single_user and info.category.read_scope not in scope_names + if ( + info.category.read_scope + and (info.owner_id != user["id"] or not is_single_user) + and info.category.read_scope not in scope_names ): continue if info.param not in param_dict: From 7292fe3d860218a319b2d022c532bf03726578e2 Mon Sep 17 00:00:00 2001 From: Zimovchik Date: Mon, 12 Aug 2024 14:08:05 +0300 Subject: [PATCH 24/24] fix --- userdata_api/utils/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/userdata_api/utils/user.py b/userdata_api/utils/user.py index 6373a0e..f1e99ca 100644 --- a/userdata_api/utils/user.py +++ b/userdata_api/utils/user.py @@ -140,8 +140,8 @@ async def get_users_info( for info in infos: if ( info.category.read_scope - and (info.owner_id != user["id"] or not is_single_user) and info.category.read_scope not in scope_names + and (not is_single_user or info.owner_id != user["id"]) ): continue if info.param not in param_dict: