diff --git a/.gitignore b/.gitignore index 0c12f0c..0aec924 100755 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,50 @@ dmypy.json .history/ .vscode/settings.json coverage.lcov +venv_3_11_6/lib64 +venv_3_11_6/pyvenv.cfg +venv_3_11_6/bin/activate +venv_3_11_6/bin/activate.csh +venv_3_11_6/bin/activate.fish +venv_3_11_6/bin/Activate.ps1 +venv_3_11_6/bin/beanie +venv_3_11_6/bin/chardetect +venv_3_11_6/bin/coverage +venv_3_11_6/bin/coverage-3.11 +venv_3_11_6/bin/coverage3 +venv_3_11_6/bin/dmypy +venv_3_11_6/bin/dotenv +venv_3_11_6/bin/email_validator +venv_3_11_6/bin/faker +venv_3_11_6/bin/flake8 +venv_3_11_6/bin/httpx +venv_3_11_6/bin/mypy +venv_3_11_6/bin/mypyc +venv_3_11_6/bin/pip +venv_3_11_6/bin/pip3 +venv_3_11_6/bin/pip3.11 +venv_3_11_6/bin/py.test +venv_3_11_6/bin/pycodestyle +venv_3_11_6/bin/pyflakes +venv_3_11_6/bin/pytest +venv_3_11_6/bin/python +venv_3_11_6/bin/python3 +venv_3_11_6/bin/python3.11 +venv_3_11_6/bin/stubgen +venv_3_11_6/bin/stubtest +venv_3_11_6/bin/tox +venv_3_11_6/bin/uvicorn +venv_3_11_6/bin/virtualenv +venv_3_11_6/bin/watchfiles +venv_3_12/lib64 +venv_3_12/pyvenv.cfg +venv_3_12/bin/activate +venv_3_12/bin/activate.csh +venv_3_12/bin/activate.fish +venv_3_12/bin/Activate.ps1 +venv_3_12/bin/pip +venv_3_12/bin/pip3 +venv_3_12/bin/pip3.12 +venv_3_12/bin/python +venv_3_12/bin/python3 +venv_3_12/bin/python3.12 diff --git a/pyproject.toml b/pyproject.toml index 1ddd34a..a2614d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,11 +15,12 @@ classifiers = [ "Programming Language :: Python :: 3", ] dependencies = [ - 'fastapi==0.98.0', - 'uvicorn[standard]==0.22.0', - 'fastapi-users[beanie]==11.0.0', - 'beanie==1.19.0', - 'colorama==0.4.6', + 'beanie', + 'colorama', + 'fastapi', + 'fastapi-users[beanie]', + 'pydantic-settings', + 'uvicorn[standard]', ] [project.scripts] @@ -94,4 +95,4 @@ warn_unreachable = true no_implicit_reexport = true plugins = [ "pydantic.mypy" -] \ No newline at end of file +] diff --git a/requirements.txt b/requirements.txt index 29ade27..4f8b7ab 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ -fastapi==0.98.0 -uvicorn[standard]==0.22.0 -fastapi-users[beanie]==11.0.0 -beanie==1.19.0 -colorama==0.4.6 \ No newline at end of file +beanie==1.22.6 +colorama==0.4.6 +fastapi-users[beanie]==12.1.2 +fastapi==0.103.2 +pydantic-settings==2.0.3 +uvicorn[standard]==0.23.2 diff --git a/src/unipoll_api/actions/group.py b/src/unipoll_api/actions/group.py index 6df56d3..e2f7b0f 100644 --- a/src/unipoll_api/actions/group.py +++ b/src/unipoll_api/actions/group.py @@ -15,7 +15,7 @@ # # Create a group list for output schema using the search results # for group in search_result: -# group_list.append(GroupSchemas.Group(**group.dict())) +# group_list.append(GroupSchemas.Group(**group.model_dump())) # return GroupSchemas.GroupList(groups=group_list) @@ -24,9 +24,9 @@ async def get_group(group: Group, include_members: bool = False, include_policies: bool = False) -> GroupSchemas.Group: members = (await get_group_members(group)).members if include_members else None policies = (await get_group_policies(group)).policies if include_policies else None - workspace = WorkspaceSchemas.Workspace(**group.workspace.dict(exclude={"members", # type: ignore - "policies", - "groups"})) + workspace = WorkspaceSchemas.Workspace(**group.workspace.model_dump(exclude={"members", # type: ignore + "policies", + "groups"})) # Return the workspace with the fetched resources return GroupSchemas.Group(id=group.id, name=group.name, @@ -62,7 +62,7 @@ async def update_group(group: Group, if save_changes: await Group.save(group) # Return the updated group - return GroupSchemas.Group(**group.dict()) + return GroupSchemas.Group(**group.model_dump()) # Delete a group @@ -88,7 +88,7 @@ async def get_group_members(group: Group) -> MemberSchemas.MemberList: req_permissions = Permissions.GroupPermissions["get_group_members"] # type: ignore if Permissions.check_permission(permissions, req_permissions): for member in group.members: # type: ignore - member_data = member.dict(include={'id', 'first_name', 'last_name', 'email'}) + member_data = member.model_dump(include={'id', 'first_name', 'last_name', 'email'}) member_scheme = MemberSchemas.Member(**member_data) member_list.append(member_scheme) # Return the list of members @@ -107,7 +107,7 @@ async def add_group_members(group: Group, member_data: MemberSchemas.AddMembers) await group.add_member(account, Permissions.GROUP_BASIC_PERMISSIONS) await Group.save(group) # Return the list of members added to the group - return MemberSchemas.MemberList(members=[MemberSchemas.Member(**account.dict()) for account in account_list]) + return MemberSchemas.MemberList(members=[MemberSchemas.Member(**account.model_dump()) for account in account_list]) # Remove a member from a workspace @@ -127,7 +127,7 @@ async def remove_group_member(group: Group, account_id: ResourceID | None): raise GroupExceptions.UserNotMember(group, account) # Remove the account from the group if await group.remove_member(account): - member_list = [MemberSchemas.Member(**account.dict()) for account in group.members] # type: ignore + member_list = [MemberSchemas.Member(**account.model_dump()) for account in group.members] # type: ignore return MemberSchemas.MemberList(members=member_list) raise GroupExceptions.ErrorWhileRemovingMember(group, account) @@ -153,11 +153,11 @@ async def get_group_policies(group: Group) -> PolicySchemas.PolicyList: # TODO: Replace with custom exception raise ResourceExceptions.InternalServerError("get_group_policies() => Policy holder not found") # Convert the policy_holder to a Member schema - policy_holder = MemberSchemas.Member(**policy_holder.dict()) # type: ignore + policy_holder = MemberSchemas.Member(**policy_holder.model_dump()) # type: ignore policy_list.append(PolicySchemas.PolicyShort(id=policy.id, policy_holder_type=policy.policy_holder_type, # Exclude unset fields(i.e. "description" for Account) - policy_holder=policy_holder.dict(exclude_unset=True), + policy_holder=policy_holder.model_dump(exclude_unset=True), permissions=permissions)) return PolicySchemas.PolicyList(policies=policy_list) @@ -183,7 +183,7 @@ async def get_group_policy(group: Group, account_id: ResourceID | None): # await group.fetch_link(Group.policies) user_permissions = await Permissions.get_all_permissions(group, account) res = {'permissions': Permissions.GroupPermissions(user_permissions).name.split('|'), # type: ignore - 'account': AccountSchemas.AccountShort(**account.dict())} + 'account': AccountSchemas.AccountShort(**account.model_dump())} return res @@ -240,4 +240,4 @@ async def set_group_policy(group: Group, # Return the updated policy return PolicySchemas.PolicyOutput( permissions=Permissions.GroupPermissions(policy.permissions).name.split('|'), # type: ignore - policy_holder=MemberSchemas.Member(**policy_holder.dict())) # type: ignore + policy_holder=MemberSchemas.Member(**policy_holder.model_dump())) # type: ignore diff --git a/src/unipoll_api/actions/policy.py b/src/unipoll_api/actions/policy.py index 0143e92..589dcdb 100644 --- a/src/unipoll_api/actions/policy.py +++ b/src/unipoll_api/actions/policy.py @@ -59,11 +59,11 @@ async def get_policy(policy: Policy) -> PolicySchemas.PolicyShort: if not policy_holder: raise PolicyExceptions.PolicyHolderNotFound(ph_ref) - policy_holder = MemberSchemas.Member(**policy_holder.dict()) # type: ignore + policy_holder = MemberSchemas.Member(**policy_holder.model_dump()) # type: ignore permissions = Permissions.WorkspacePermissions(policy.permissions).name.split('|') # type: ignore return PolicySchemas.PolicyShort(id=policy.id, policy_holder_type=policy.policy_holder_type, - policy_holder=policy_holder.dict(exclude_unset=True), + policy_holder=policy_holder.model_dump(exclude_unset=True), permissions=permissions) # if not account and account_id: @@ -76,4 +76,4 @@ async def get_policy(policy: Policy) -> PolicySchemas.PolicyShort: # user_permissions = await Permissions.get_all_permissions(workspace, account) # return PolicySchemas.PolicyOutput( # permissions=Permissions.WorkspacePermissions(user_permissions).name.split('|'), # type: ignore - # policy_holder=MemberSchemas.Member(**account.dict())) + # policy_holder=MemberSchemas.Member(**account.model_dump())) diff --git a/src/unipoll_api/actions/poll.py b/src/unipoll_api/actions/poll.py index 09aeed7..121fb2d 100644 --- a/src/unipoll_api/actions/poll.py +++ b/src/unipoll_api/actions/poll.py @@ -26,7 +26,7 @@ async def get_polls(workspace: Workspace | None = None) -> PollSchemas.PollList: poll_list.append(poll) # Build poll list and return the result for poll in poll_list: - poll_list.append(PollSchemas.PollShort(**poll.dict())) # type: ignore + poll_list.append(PollSchemas.PollShort(**poll.model_dump())) # type: ignore return PollSchemas.PollList(polls=poll_list) @@ -48,7 +48,7 @@ async def get_poll(poll: Poll, if Permissions.check_permission(permissions, req_permissions): policies = (await get_poll_policies(poll)).policies - workspace = WorkspaceSchemas.WorkspaceShort(**poll.workspace.dict()) # type: ignore + workspace = WorkspaceSchemas.WorkspaceShort(**poll.workspace.model_dump()) # type: ignore # Return the workspace with the fetched resources return PollSchemas.PollResponse(id=poll.id, @@ -65,7 +65,7 @@ async def get_poll_questions(poll: Poll) -> QuestionSchemas.QuestionList: print("Poll: ", poll.questions) question_list = [] for question in poll.questions: - # question_data = question.dict() + # question_data = question.model_dump() question_scheme = QuestionSchemas.Question(**question) question_list.append(question_scheme) # Return the list of questions @@ -88,11 +88,11 @@ async def get_poll_policies(poll: Poll) -> PolicySchemas.PolicyList: # TODO: Replace with custom exception raise ResourceExceptions.InternalServerError("get_poll_policies() => Policy holder not found") # Convert the policy_holder to a Member schema - policy_holder = MemberSchemas.Member(**policy_holder.dict()) # type: ignore + policy_holder = MemberSchemas.Member(**policy_holder.model_dump()) # type: ignore policy_list.append(PolicySchemas.PolicyShort(id=policy.id, policy_holder_type=policy.policy_holder_type, # Exclude unset fields(i.e. "description" for Account) - policy_holder=policy_holder.dict(exclude_unset=True), + policy_holder=policy_holder.model_dump(exclude_unset=True), permissions=permissions)) return PolicySchemas.PolicyList(policies=policy_list) diff --git a/src/unipoll_api/actions/superuser.py b/src/unipoll_api/actions/superuser.py index b09e8e4..d502c93 100644 --- a/src/unipoll_api/actions/superuser.py +++ b/src/unipoll_api/actions/superuser.py @@ -9,6 +9,6 @@ async def get_all_workspaces() -> WorkspaceSchemas.WorkspaceList: # Create a workspace list for output schema using the search results for workspace in search_result: - workspace_list.append(WorkspaceSchemas.Workspace(**workspace.dict())) + workspace_list.append(WorkspaceSchemas.Workspace(**workspace.model_dump())) return WorkspaceSchemas.WorkspaceList(workspaces=workspace_list) diff --git a/src/unipoll_api/actions/workspace.py b/src/unipoll_api/actions/workspace.py index 41ac7ce..0c3c462 100644 --- a/src/unipoll_api/actions/workspace.py +++ b/src/unipoll_api/actions/workspace.py @@ -21,7 +21,7 @@ async def get_workspaces() -> WorkspaceSchemas.WorkspaceList: # Create a workspace list for output schema using the search results for workspace in search_result: workspace_list.append(WorkspaceSchemas.WorkspaceShort( - **workspace.dict(exclude={'members', 'groups', 'permissions'}))) + **workspace.model_dump(exclude={'members', 'groups', 'permissions'}))) return WorkspaceSchemas.WorkspaceList(workspaces=workspace_list) @@ -54,7 +54,7 @@ async def create_workspace(input_data: WorkspaceSchemas.WorkspaceCreateInput) -> await Workspace.save(new_workspace, link_rule=WriteRules.WRITE) # Specify fields for output schema - return WorkspaceSchemas.WorkspaceCreateOutput(**new_workspace.dict()) + return WorkspaceSchemas.WorkspaceCreateOutput(**new_workspace.model_dump(include={'id', 'name', 'description'})) # Get a workspace @@ -96,7 +96,7 @@ async def update_workspace(workspace: Workspace, if save_changes: await Workspace.save(workspace) # Return the updated workspace - return WorkspaceSchemas.Workspace(**workspace.dict()) + return WorkspaceSchemas.Workspace(**workspace.model_dump()) # Delete a workspace @@ -120,7 +120,7 @@ async def get_workspace_members(workspace: Workspace) -> MemberSchemas.MemberLis req_permissions = Permissions.WorkspacePermissions["get_workspace_members"] # type: ignore if Permissions.check_permission(permissions, req_permissions): for member in workspace.members: # type: ignore - member_data = member.dict(include={'id', 'first_name', 'last_name', 'email'}) + member_data = member.model_dump(include={'id', 'first_name', 'last_name', 'email'}) member_scheme = MemberSchemas.Member(**member_data) member_list.append(member_scheme) # Return the list of members @@ -140,7 +140,7 @@ async def add_workspace_members(workspace: Workspace, await workspace.add_member(account, Permissions.WORKSPACE_BASIC_PERMISSIONS, save=False) await Workspace.save(workspace, link_rule=WriteRules.WRITE) # Return the list of members added to the group - return MemberSchemas.MemberList(members=[MemberSchemas.Member(**account.dict()) for account in account_list]) + return MemberSchemas.MemberList(members=[MemberSchemas.Member(**account.model_dump()) for account in account_list]) # Remove a member from a workspace @@ -159,7 +159,7 @@ async def remove_workspace_member(workspace: Workspace, account_id: ResourceID): # Remove the account from the workspace if await workspace.remove_member(account): # Return the list of members added to the group - member_list = [MemberSchemas.Member(**account.dict()) for account in workspace.members] # type: ignore + member_list = [MemberSchemas.Member(**account.model_dump()) for account in workspace.members] # type: ignore return MemberSchemas.MemberList(members=member_list) raise WorkspaceExceptions.ErrorWhileRemovingMember(workspace, account) @@ -171,7 +171,7 @@ async def get_groups(workspace: Workspace) -> GroupSchemas.GroupList: # Check if the user has permission to get all groups req_permissions = Permissions.WorkspacePermissions["get_groups"] # type: ignore if Permissions.check_permission(permissions, req_permissions): - groups = [GroupSchemas.GroupShort(**group.dict()) for group in workspace.groups] # type: ignore + groups = [GroupSchemas.GroupShort(**group.model_dump()) for group in workspace.groups] # type: ignore # Otherwise, return only the groups where the user has permission to get the group else: groups = [] @@ -180,7 +180,7 @@ async def get_groups(workspace: Workspace) -> GroupSchemas.GroupList: required_permission = Permissions.GroupPermissions['get_group'] if Permissions.check_permission(Permissions.GroupPermissions(user_permissions), # type: ignore required_permission): - groups.append(GroupSchemas.GroupShort(**group.dict())) # type: ignore + groups.append(GroupSchemas.GroupShort(**group.model_dump())) # type: ignore # Return the list of groups return GroupSchemas.GroupList(groups=groups) @@ -222,7 +222,7 @@ async def create_group(workspace: Workspace, await Workspace.save(workspace, link_rule=WriteRules.WRITE) # Return the new group - return GroupSchemas.GroupCreateOutput(**new_group.dict()) + return GroupSchemas.GroupCreateOutput(**new_group.model_dump(include={'id', 'name', 'description'})) # Get all policies of a workspace @@ -305,7 +305,7 @@ async def set_workspace_policy(workspace: Workspace, # Return the updated policy return PolicySchemas.PolicyOutput( permissions=Permissions.WorkspacePermissions(policy.permissions).name.split('|'), # type: ignore - policy_holder=MemberSchemas.Member(**policy_holder.dict())) # type: ignore + policy_holder=MemberSchemas.Member(**policy_holder.model_dump())) # type: ignore # Get a list of polls in a workspace @@ -339,4 +339,4 @@ async def create_poll(workspace: Workspace, input_data: PollSchemas.CreatePollRe await Workspace.save(workspace, link_rule=WriteRules.WRITE) # Return the new poll - return PollSchemas.PollResponse(**new_poll.dict()) + return PollSchemas.PollResponse(**new_poll.model_dump()) diff --git a/src/unipoll_api/app.py b/src/unipoll_api/app.py index fd60c24..3837242 100644 --- a/src/unipoll_api/app.py +++ b/src/unipoll_api/app.py @@ -24,7 +24,7 @@ app.include_router(router) # Add CORS middleware to allow cross-origin requests -origins = settings.origins +origins = settings.origins.split(",") # Middleware app.add_middleware( @@ -46,7 +46,7 @@ async def on_startup() -> None: route.operation_id = route.name await init_beanie( - database=mainDB, + database=mainDB, # type: ignore document_models=documentModels # type: ignore ) diff --git a/src/unipoll_api/config.py b/src/unipoll_api/config.py index 37c9e24..1716e5c 100644 --- a/src/unipoll_api/config.py +++ b/src/unipoll_api/config.py @@ -1,7 +1,8 @@ -from pydantic import BaseSettings, EmailStr, Field +from pydantic import EmailStr, Field from functools import lru_cache import importlib.metadata import unipoll_api.__version__ as version_file +from pydantic_settings import BaseSettings, SettingsConfigDict try: @@ -17,18 +18,16 @@ class Settings(BaseSettings): # type: ignore app_description: str = Field(default=("An Open Source API for creating surveys and polls " "to assist in university research."), title="App Description", description="A description of the API.") - admin_email: EmailStr = Field(default=EmailStr("admin@unipoll.cc"), - title="Admin Email", description="The email address of the admin of the API.") + admin_email: EmailStr = Field(default="admin@example.com", + title="Admin Email", description="The email address of the API administrator.") mongodb_url: str = Field(default="mongodb://localhost:27017", title="MongoDB URL", description="The URL of the MongoDB database.") secrete_key: str = Field(default="secret", title="Secrete Key", description="The secrete key of the API.") - origins = "*".split(",") - host = "0.0.0.0" - port = 9000 - reload = True - - class Config: - env_file = ".env" + origins: str = "*" + host: str = "0.0.0.0" + port: int = 9000 + reload: bool = True + model_config = SettingsConfigDict(env_file=".env") @lru_cache() diff --git a/src/unipoll_api/documents.py b/src/unipoll_api/documents.py index dbadab9..d489959 100644 --- a/src/unipoll_api/documents.py +++ b/src/unipoll_api/documents.py @@ -32,15 +32,10 @@ class AccessToken(BeanieBaseAccessToken, Document): # type: ignore class Resource(Document): id: ResourceID = Field(default_factory=ResourceID, alias="_id") - resource_type = "" + resource_type: Literal["workspace", "group", "poll"] name: str = Field(title="Name", description="Name of the resource", min_length=3, max_length=50) - # Field(default="", min_length=3, max_length=50, regex="^[A-Z][A-Za-z]{2,}([ ]([0-9]+|[A-Z][A-Za-z]*))*$") description: str = Field(default="", title="Description", max_length=1000) policies: list[Link["Policy"]] = [] - # If the resource is a root resource, all derived resources will be stored in the same collection - # However, the benefits of this are unclear - # class Settings: - # is_root = True @after_event(Insert) def create_group(self) -> None: @@ -67,7 +62,7 @@ async def remove_member(self, account, save: bool = True) -> bool: for i, member in enumerate(self.members): if account.id == member.id: # type: ignore self.members.remove(member) - Debug.info(f"Removed member {member.id} from {self.resource_type} {self.id}") # type: ignore + Debug.info(f"Removed member {member.id} from {self.resource_type} {self.id}") break # Remove the policy from the group @@ -91,32 +86,31 @@ class Account(BeanieBaseUser, Document): # type: ignore default_factory=str, max_length=20, min_length=2, - regex="^[A-Z][a-z]*$") + pattern="^[A-Z][a-z]*$") last_name: str = Field( default_factory=str, max_length=20, min_length=2, - regex="^[A-Z][a-z]*$") + pattern="^[A-Z][a-z]*$") class Workspace(Resource): - resource_type = "workspace" + resource_type: Literal["workspace"] = "workspace" members: list[Link["Account"]] = [] groups: list[Link["Group"]] = [] polls: list[Link["Poll"]] = [] class Group(Resource): - resource_type = "group" - workspace: Link["Workspace"] - # workspace: list[BackLink[Workspace]] = Field(original_field="groups") + resource_type: Literal["group"] = "group" + workspace: BackLink[Workspace] = Field(original_field="groups") members: list[Link["Account"]] = [] groups: list[Link["Group"]] = [] class Policy(Document): id: ResourceID = Field(default_factory=ResourceID, alias="_id") - parent_resource: BackLink["Workspace"] | BackLink["Group"] = Field(original_field="policies") + parent_resource: BackLink["Workspace"] | BackLink["Group"] | BackLink["Poll"] = Field(original_field="policies") policy_holder_type: Literal["account", "group"] policy_holder: Link["Group"] | Link["Account"] permissions: int @@ -124,18 +118,17 @@ class Policy(Document): class Poll(Resource): id: ResourceID = Field(default_factory=ResourceID, alias="_id") - workspace: Link["Workspace"] - resource_type = "poll" + workspace: BackLink["Workspace"] = Field(original_field="polls") + resource_type: Literal["poll"] = "poll" public: bool published: bool questions: list policies: list[Link["Policy"]] -# NOTE: ForwardRef is used to avoid circular imports -Resource.update_forward_refs() -Workspace.update_forward_refs() -Group.update_forward_refs() -Policy.update_forward_refs() -Poll.update_forward_refs() -# Question.update_forward_refs() +# NOTE: model_rebuild is used to avoid circular imports +Resource.model_rebuild() +Workspace.model_rebuild() +Group.model_rebuild() +Policy.model_rebuild() +Poll.model_rebuild() diff --git a/src/unipoll_api/routes/account.py b/src/unipoll_api/routes/account.py index 0bc805b..ef5c116 100644 --- a/src/unipoll_api/routes/account.py +++ b/src/unipoll_api/routes/account.py @@ -15,7 +15,7 @@ response_model=AccountSchemas.AccountList) async def get_all_accounts(): try: - accounts = [AccountSchemas.AccountShort(**account.dict()) for account in await Account.find_all().to_list()] + accounts = [AccountSchemas.AccountShort(**a.model_dump()) for a in await Account.find_all().to_list()] return AccountSchemas.AccountList(accounts=accounts) except APIException as e: raise HTTPException(status_code=e.code, detail=str(e)) diff --git a/src/unipoll_api/schemas/account.py b/src/unipoll_api/schemas/account.py index f48ff19..9055dd4 100644 --- a/src/unipoll_api/schemas/account.py +++ b/src/unipoll_api/schemas/account.py @@ -1,5 +1,5 @@ from fastapi_users import schemas -from pydantic import BaseModel, Field, EmailStr +from pydantic import ConfigDict, BaseModel, Field, EmailStr from unipoll_api.documents import ResourceID @@ -10,22 +10,20 @@ class Account(schemas.BaseUser[ResourceID]): default_factory=str, max_length=20, min_length=2, - regex="^[A-Z][a-z]*$") + pattern="^[A-Z][a-z]*$") last_name: str = Field( default_factory=str, max_length=20, min_length=2, - regex="^[A-Z][a-z]*$") - - class Config: - schema_extra = { - "example": { - "email": "user@example.com", - "password": "pass1234", - "first_name": "John", - "last_name": "Smith", - } + pattern="^[A-Z][a-z]*$") + model_config = ConfigDict(json_schema_extra={ + "example": { + "email": "user@example.com", + "password": "pass1234", + "first_name": "John", + "last_name": "Smith", } + }) class AccountShort(BaseModel): @@ -45,22 +43,20 @@ class CreateAccount(schemas.BaseUserCreate): default_factory=str, max_length=20, min_length=2, - regex="^[A-Z][a-z]*$") + pattern="^[A-Z][a-z]*$") last_name: str = Field( default_factory=str, max_length=20, min_length=2, - regex="^[A-Z][a-z]*$") - - class Config: - schema_extra = { - "example": { - "email": "user@example.com", - "password": "pass1234", - "first_name": "John", - "last_name": "Smith", - } + pattern="^[A-Z][a-z]*$") + model_config = ConfigDict(json_schema_extra={ + "example": { + "email": "user@example.com", + "password": "pass1234", + "first_name": "John", + "last_name": "Smith", } + }) class UpdateAccount(schemas.BaseUserUpdate): @@ -69,19 +65,17 @@ class UpdateAccount(schemas.BaseUserUpdate): default_factory=str, max_length=20, min_length=2, - regex="^[A-Z][a-z]*$") + pattern="^[A-Z][a-z]*$") last_name: str = Field( default_factory=str, max_length=20, min_length=2, - regex="^[A-Z][a-z]*$") - - class Config: - schema_extra = { - "example": { - "email": "user@example.com", - "password": "pass1234", - "first_name": "John", - "last_name": "Smith", - } + pattern="^[A-Z][a-z]*$") + model_config = ConfigDict(json_schema_extra={ + "example": { + "email": "user@example.com", + "password": "pass1234", + "first_name": "John", + "last_name": "Smith", } + }) diff --git a/src/unipoll_api/schemas/authentication.py b/src/unipoll_api/schemas/authentication.py index e331706..7ed3e97 100644 --- a/src/unipoll_api/schemas/authentication.py +++ b/src/unipoll_api/schemas/authentication.py @@ -5,8 +5,8 @@ class LoginResponse(BaseModel): access_token: str token_type: str = "Bearer" - scope: Optional[str] - client_id: Optional[str] + scope: Optional[str] = None + client_id: Optional[str] = None expires_in: int = 3600 refresh_token: str diff --git a/src/unipoll_api/schemas/group.py b/src/unipoll_api/schemas/group.py index 1e90fff..95c6a01 100644 --- a/src/unipoll_api/schemas/group.py +++ b/src/unipoll_api/schemas/group.py @@ -1,16 +1,16 @@ -from pydantic import BaseModel, Field +from pydantic import ConfigDict, BaseModel, Field from typing import Any, Optional from unipoll_api.documents import ResourceID class Group(BaseModel): - id: Optional[ResourceID] - name: Optional[str] - description: Optional[str] - workspace: Optional[Any] + id: Optional[ResourceID] = None + name: Optional[str] = None + description: Optional[str] = None + workspace: Optional[Any] = None # groups: Optional[list] - members: Optional[list] - policies: Optional[list] + members: Optional[list] = None + policies: Optional[list] = None # Schema for the response with basic group info after creation @@ -29,14 +29,12 @@ class GroupList(BaseModel): class GroupCreateInput(BaseModel): name: str = Field(default="", min_length=3, max_length=50) description: str = Field(default="", title="Description", max_length=300) - - class Config: - schema_extra = { - "example": { - "name": "Group 01", - "description": "My first Group", - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "name": "Group 01", + "description": "My first Group", } + }) # Schema for the response to a group creation request @@ -48,12 +46,10 @@ class GroupCreateOutput(BaseModel): # Schema for the request to add a user to a group class GroupUpdateRequest(BaseModel): - name: Optional[str] = Field(title="Name", min_length=3, max_length=50) + name: Optional[str] = Field(None, title="Name", min_length=3, max_length=50) description: Optional[str] = Field(default="", title="Description", max_length=300) - - class Config: - schema_extra = { - "example": { - "Description": "Updated description" - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "Description": "Updated description" } + }) diff --git a/src/unipoll_api/schemas/member.py b/src/unipoll_api/schemas/member.py index 76c7262..b4b8c56 100644 --- a/src/unipoll_api/schemas/member.py +++ b/src/unipoll_api/schemas/member.py @@ -1,64 +1,58 @@ from typing import Optional -from pydantic import BaseModel, EmailStr, Field +from pydantic import ConfigDict, BaseModel, EmailStr, Field from unipoll_api.documents import ResourceID # Schema for the response with basic member info class Member(BaseModel): id: ResourceID - email: Optional[EmailStr] - first_name: Optional[str] - last_name: Optional[str] - name: Optional[str] - description: Optional[str] - - class Config: - schema_extra = { - "example": { - "id": "1a2b3c4d5e6f7g8h9i0j", - "email": "user@example.com", - "first_name": "John", - "last_name": "Doe", - } + email: Optional[EmailStr] = None + first_name: Optional[str] = None + last_name: Optional[str] = None + name: Optional[str] = None + description: Optional[str] = None + model_config = ConfigDict(json_schema_extra={ + "example": { + "id": "1a2b3c4d5e6f7g8h9i0j", + "email": "user@example.com", + "first_name": "John", + "last_name": "Doe", } + }) # Schema for the request to add a member to a workspace class AddMembers(BaseModel): accounts: list[ResourceID] = Field(title="Accounts") - - class Config: - schema_extra = { - "example": { - "accounts": [ - "1a2b3c4d5e6f7g8h9i0j", - "2a3b4c5d6e7f8g9h0i1j", - "3a4b5c6d7e8f9g0h1i2j" - ] - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "accounts": [ + "1a2b3c4d5e6f7g8h9i0j", + "2a3b4c5d6e7f8g9h0i1j", + "3a4b5c6d7e8f9g0h1i2j" + ] } + }) # Schema for the response with a list of members and their info class MemberList(BaseModel): members: list[Member] - - class Config: - schema_extra = { - "example": { - "members": [ - { - "email": "jdoe@example.com", - "first_name": "John", - "last_name": "Doe", - "role": "admin" - }, - { - "email": "jsmith@example.com", - "first_name": "Jack", - "last_name": "Smith", - "role": "user" - } - ] - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "members": [ + { + "email": "jdoe@example.com", + "first_name": "John", + "last_name": "Doe", + "role": "admin" + }, + { + "email": "jsmith@example.com", + "first_name": "Jack", + "last_name": "Smith", + "role": "user" + } + ] } + }) diff --git a/src/unipoll_api/schemas/policy.py b/src/unipoll_api/schemas/policy.py index a9c954a..08e97e8 100644 --- a/src/unipoll_api/schemas/policy.py +++ b/src/unipoll_api/schemas/policy.py @@ -1,5 +1,5 @@ from typing import Literal, Any, Optional -from pydantic import BaseModel, Field +from pydantic import ConfigDict, BaseModel, Field from unipoll_api.documents import ResourceID, Account, Group from unipoll_api.utils.permissions import Permissions @@ -14,143 +14,133 @@ class Policy(BaseModel): class PolicyShort(BaseModel): id: ResourceID policy_holder_type: Literal["account", "group"] - policy_holder: Any - permissions: Optional[Any] + policy_holder: Any = None + permissions: Optional[Any] = None class PolicyInput(BaseModel): - policy_id: Optional[ResourceID] = Field(title="Policy ID") - account_id: Optional[ResourceID] = Field(title="Account ID") - group_id: Optional[ResourceID] = Field(title="Group ID") + policy_id: Optional[ResourceID] = Field(None, title="Policy ID") + account_id: Optional[ResourceID] = Field(None, title="Account ID") + group_id: Optional[ResourceID] = Field(None, title="Group ID") permissions: list[str] = Field(title="Permissions") - - class Config: - schema_extra = { - "example": { - "permissions": ["get_workspace_info", "list_members"], - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "permissions": ["get_workspace_info", "list_members"], } + }) class PolicyOutput(BaseModel): permissions: list[str] = Field(title="List of allowed actions") - policy_holder: Any - - class Config: - schema_extra = { - "example": { - "permissions": [ - "get_workspaces", - "create_workspace", - "get_workspace", - "update_workspace", - "delete_workspace", - "get_workspace_members", - "add_workspace_members", - "remove_workspace_member", - "get_groups", - "create_group", - "get_all_workspace_policies", - "get_workspace_policy", - "set_workspace_policy" - ], - "policy_holder": { - "id": "1a2b3c4d5e6f7g8h9i0j", - "email": "email@example.com", - "first_name": "Name", - "last_name": "Surname", - } + policy_holder: Any = None + model_config = ConfigDict(json_schema_extra={ + "example": { + "permissions": [ + "get_workspaces", + "create_workspace", + "get_workspace", + "update_workspace", + "delete_workspace", + "get_workspace_members", + "add_workspace_members", + "remove_workspace_member", + "get_groups", + "create_group", + "get_all_workspace_policies", + "get_workspace_policy", + "set_workspace_policy" + ], + "policy_holder": { + "id": "1a2b3c4d5e6f7g8h9i0j", + "email": "email@example.com", + "first_name": "Name", + "last_name": "Surname", } } + }) # Schema for listing all policies in a workspace class PolicyList(BaseModel): policies: list[PolicyShort] = Field(title="Policies") - - class Config: - schema_extra = { - "example": { - "policies": [ - { - "permissions": [ - "get_workspace", - "get_groups", - ], - "policy_holder": { - "id": "1a2b3c4d5e6f7g8h9i0j", - "email": "email@example.com", - "first_name": "Name", - "last_name": "Surname", - } - }, - { - "permissions": [ - "get_workspace", - "get_groups", - ], - "policy_holder": { - "id": "1a2b3c4d5e6f7g8h9i0j", - "email": "email@example.com", - "first_name": "Name", - "last_name": "Surname", - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "policies": [ + { + "permissions": [ + "get_workspace", + "get_groups", + ], + "policy_holder": { + "id": "1a2b3c4d5e6f7g8h9i0j", + "email": "email@example.com", + "first_name": "Name", + "last_name": "Surname", } - ] - } + }, + { + "permissions": [ + "get_workspace", + "get_groups", + ], + "policy_holder": { + "id": "1a2b3c4d5e6f7g8h9i0j", + "email": "email@example.com", + "first_name": "Name", + "last_name": "Surname", + } + } + ] } + }) # Schema for adding permissions to a group class AddPermission(BaseModel): permissions: list[Permissions] = Field(title="Permissions") - - class Config: - schema_extra = { - "example": { - "permissions": [ - { - "type": "account", - "id": "1a2b3c4d5e6f7g8h9i0j", - "permission": "eff", - }, - { - "type": "account", - "id": "2a3b4c5d6e7f8g9h0i1j", - "permission": "a3", - }, - { - "type": "group", - "id": "3a4b5c6d7e8f9g0h1i2j", - "permission": "1", - }, - ] - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "permissions": [ + { + "type": "account", + "id": "1a2b3c4d5e6f7g8h9i0j", + "permission": "eff", + }, + { + "type": "account", + "id": "2a3b4c5d6e7f8g9h0i1j", + "permission": "a3", + }, + { + "type": "group", + "id": "3a4b5c6d7e8f9g0h1i2j", + "permission": "1", + }, + ] } + }) # Schema for returning a list of permissions class PermissionList(BaseModel): permissions: list[str] = Field(title="Permissions") - - class Config: - schema_extra = { - "example": { - "permissions": [ - "get_workspaces", - "create_workspace", - "get_workspace", - "update_workspace", - "delete_workspace", - "get_workspace_members", - "add_workspace_members", - "remove_workspace_member", - "get_groups", - "create_group", - "get_workspace_policies", - "get_workspace_policy", - "set_workspace_policy", - "get_workspace_permissions" - ] - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "permissions": [ + "get_workspaces", + "create_workspace", + "get_workspace", + "update_workspace", + "delete_workspace", + "get_workspace_members", + "add_workspace_members", + "remove_workspace_member", + "get_groups", + "create_group", + "get_workspace_policies", + "get_workspace_policy", + "set_workspace_policy", + "get_workspace_permissions" + ] } + }) diff --git a/src/unipoll_api/schemas/poll.py b/src/unipoll_api/schemas/poll.py index 55de082..70b27ad 100644 --- a/src/unipoll_api/schemas/poll.py +++ b/src/unipoll_api/schemas/poll.py @@ -1,29 +1,27 @@ from typing import Optional, Any -from pydantic import BaseModel +from pydantic import ConfigDict, BaseModel from unipoll_api.documents import ResourceID from unipoll_api.schemas.question import Question class PollResponse(BaseModel): - id: Optional[ResourceID] + id: Optional[ResourceID] = None # workspace: Optional[Union['Workspace', 'WorkspaceShort']] - workspace: Optional[Any] + workspace: Optional[Any] = None name: str description: str public: bool published: bool - questions: Optional[list[Question]] - policies: Optional[list] - - class Config: - schema_extra = { - "example": { - "id": "1a2b3c4d5e6f7g8h9i0j", - "name": "Poll 01", - "description": "This is an example poll", - "published": True - } + questions: Optional[list[Question]] = None + policies: Optional[list] = None + model_config = ConfigDict(json_schema_extra={ + "example": { + "id": "1a2b3c4d5e6f7g8h9i0j", + "name": "Poll 01", + "description": "This is an example poll", + "published": True } + }) class PollShort(BaseModel): @@ -32,42 +30,38 @@ class PollShort(BaseModel): description: str public: bool published: bool - - class Config: - schema_extra = { - "example": { - "poll": { - "id": "1a2b3c4d5e6f7g8h9i0j", - "name": "Poll 01", - "description": "This is an example poll", - "published": True - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "poll": { + "id": "1a2b3c4d5e6f7g8h9i0j", + "name": "Poll 01", + "description": "This is an example poll", + "published": True } } + }) class PollList(BaseModel): polls: list[PollShort] - - class Config: - schema_extra = { - "example": { - "polls": [ - { - "id": "1a2b3c4d5e6f7g8h9i0j", - "name": "Poll 01", - "description": "This is an example poll", - "published": True - }, - { - "id": "1a2b3c4d5e6f7g8h9i0j", - "name": "Poll 02", - "description": "This is an example poll", - "published": True - } - ] - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "polls": [ + { + "id": "1a2b3c4d5e6f7g8h9i0j", + "name": "Poll 01", + "description": "This is an example poll", + "published": True + }, + { + "id": "1a2b3c4d5e6f7g8h9i0j", + "name": "Poll 02", + "description": "This is an example poll", + "published": True + } + ] } + }) class CreatePollRequest(BaseModel): @@ -79,23 +73,21 @@ class CreatePollRequest(BaseModel): class UpdatePollRequest(BaseModel): - name: Optional[str] - description: Optional[str] - public: Optional[bool] - published: Optional[bool] - questions: Optional[list[Question]] - - class Config: - schema_extra = { - "example": { - "name": "Poll 01", - "description": "This is an example poll", - "published": True - } + name: Optional[str] = None + description: Optional[str] = None + public: Optional[bool] = None + published: Optional[bool] = None + questions: Optional[list[Question]] = None + model_config = ConfigDict(json_schema_extra={ + "example": { + "name": "Poll 01", + "description": "This is an example poll", + "published": True } + }) # Forward references from unipoll_api.schemas.workspace import Workspace, WorkspaceShort # noqa: E402 -Workspace.update_forward_refs() -WorkspaceShort.update_forward_refs() +Workspace.model_rebuild() +WorkspaceShort.model_rebuild() diff --git a/src/unipoll_api/schemas/workspace.py b/src/unipoll_api/schemas/workspace.py index fc59cb6..8a0805d 100644 --- a/src/unipoll_api/schemas/workspace.py +++ b/src/unipoll_api/schemas/workspace.py @@ -1,26 +1,24 @@ -from pydantic import BaseModel, Field +from pydantic import ConfigDict, BaseModel, Field from typing import Optional from unipoll_api.documents import ResourceID # Schema for the response with basic workspace info (name and role) class Workspace(BaseModel): - id: Optional[ResourceID] - name: Optional[str] - description: Optional[str] - members: Optional[list] - groups: Optional[list] - policies: Optional[list] - polls: Optional[list] - - class Config: - schema_extra = { - "example": { - "id": "1a2b3c4d5e6f7g8h9i0j", - "name": "Workspace 01", - "description": "This is an example workspace", - } + id: Optional[ResourceID] = None + name: Optional[str] = None + description: Optional[str] = None + members: Optional[list] = None + groups: Optional[list] = None + policies: Optional[list] = None + polls: Optional[list] = None + model_config = ConfigDict(json_schema_extra={ + "example": { + "id": "1a2b3c4d5e6f7g8h9i0j", + "name": "Workspace 01", + "description": "This is an example workspace", } + }) class WorkspaceShort(BaseModel): @@ -33,52 +31,46 @@ class WorkspaceShort(BaseModel): # It can be used to return a list of workspaces with basic info or full info class WorkspaceList(BaseModel): workspaces: list[WorkspaceShort] | list[Workspace] - - class Config: - schema_extra = { - "example": { - "workspaces": [ - { - "name": "Workspace 01", - "description": "This is an example workspace", - "owner": "true", - }, - { - "name": "Workspace 02", - "description": "This is another example workspace", - "owner": "false", - }, - ] - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "workspaces": [ + { + "name": "Workspace 01", + "description": "This is an example workspace", + "owner": "true", + }, + { + "name": "Workspace 02", + "description": "This is another example workspace", + "owner": "false", + }, + ] } + }) # Schema for the request to create a workspace class WorkspaceCreateInput(BaseModel): name: str = Field(title="Name") description: str = Field(title="Description") - - class Config: - schema_extra = { - "example": { - "name": "Workspace 01", - "description": "This is an example workspace", - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "name": "Workspace 01", + "description": "This is an example workspace", } + }) # Schema for the request to update a workspace class WorkspaceUpdateRequest(BaseModel): - name: Optional[str] = Field(title="Name") - description: Optional[str] = Field(title="Description") - - class Config: - schema_extra = { - "example": { - "name": "Workspace 01", - "description": "This is an example workspace", - } + name: Optional[str] = Field(None, title="Name") + description: Optional[str] = Field(None, title="Description") + model_config = ConfigDict(json_schema_extra={ + "example": { + "name": "Workspace 01", + "description": "This is an example workspace", } + }) # Schema for the response when a workspace is created @@ -86,11 +78,9 @@ class WorkspaceCreateOutput(BaseModel): id: ResourceID = Field(title="ID") name: str = Field(title="Name") description: str = Field(title="Description") - - class Config: - schema_extra = { - "example": { - "name": "Workspace 01", - "description": "This is an example workspace", - } + model_config = ConfigDict(json_schema_extra={ + "example": { + "name": "Workspace 01", + "description": "This is an example workspace", } + }) diff --git a/src/unipoll_api/utils/auth_transport.py b/src/unipoll_api/utils/auth_transport.py index 70df4b6..b06200e 100644 --- a/src/unipoll_api/utils/auth_transport.py +++ b/src/unipoll_api/utils/auth_transport.py @@ -19,7 +19,7 @@ async def get_login_response(self, token) -> Response: bearer_response = LoginResponse(access_token=token.access_token, # type: ignore refresh_token=token.refresh_token, # type: ignore client_id=str(token.user_id)) # type: ignore - return JSONResponse(bearer_response.dict(exclude_none=True)) + return JSONResponse(bearer_response.model_dump(exclude_none=True)) async def get_logout_response(self) -> Response: raise TransportLogoutNotSupportedError() diff --git a/test-requirements-latest.txt b/test-requirements-latest.txt new file mode 100644 index 0000000..569a3b9 --- /dev/null +++ b/test-requirements-latest.txt @@ -0,0 +1,16 @@ +fastapi +uvicorn[standard] +fastapi-users[beanie] +beanie +colorama +pydantic-settings +pytest +pytest-cov +faker +flake8 +tox +mypy +tox-gh-actions +asgi-lifespan +httpx +pytest-asyncio \ No newline at end of file diff --git a/test-requirements.txt b/test-requirements.txt index 6effd95..9f7a2b4 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,15 +1,16 @@ -fastapi==0.98.0 -uvicorn[standard]==0.22.0 -fastapi-users[beanie]==11.0.0 -beanie==1.19.0 +asgi-lifespan==2.1.0 +beanie==1.22.6 colorama==0.4.6 -pytest==7.3.1 -pytest-cov==4.0.0 -faker==18.7.0 -flake8==6.0.0 -tox==4.5.1 +faker==19.6.2 +fastapi-users[beanie]==12.1.2 +fastapi==0.103.2 +flake8==6.1.0 +httpx==0.25.0 mypy==1.5.1 -tox-gh-actions==3.1.0 -asgi-lifespan==2.1.0 -httpx==0.24.0 -pytest-asyncio==0.21.0 \ No newline at end of file +pydantic-settings==2.0.3 +pytest-asyncio==0.21.1 +pytest-cov==4.1.0 +pytest==7.4.2 +tox-gh-actions==3.1.3 +tox==4.11.3 +uvicorn[standard]==0.23.2 diff --git a/tests/test_1_accounts.py b/tests/test_1_accounts.py index 9eccd3f..4c5f112 100644 --- a/tests/test_1_accounts.py +++ b/tests/test_1_accounts.py @@ -27,21 +27,14 @@ class TestAccount(BaseModel): is_verified: bool = False -# first_name: str = fake.first_name() -# last_name: str = fake.last_name() -# email: str = (first_name[0] + last_name + "@ucmerced.edu").lower() -# password: str = fake.password() -# new_user = Account(email=email, first_name=first_name, last_name=last_name, hashed_password=password) new_user = TestAccount() # Test to see if the user can create an account -# Check if the response is 201(Success) -# Check if the user information is correct async def test_register(client_test: AsyncClient, new_user: TestAccount = new_user): print("\n") colored_dbg.test_info("Registering new user: ", new_user.email) - response = await client_test.post("/auth/register", json=new_user.dict()) + response = await client_test.post("/auth/register", json=new_user.model_dump()) assert response.status_code == 201 response = response.json() assert response.get("id") is not None @@ -57,7 +50,7 @@ async def test_register(client_test: AsyncClient, new_user: TestAccount = new_us async def test_register_existing_email(client_test: AsyncClient): print("\n") colored_dbg.test_info("Attempting to register a new user with an existing email: ", new_user.email) - response = await client_test.post("/auth/register", json=new_user.dict()) + response = await client_test.post("/auth/register", json=new_user.model_dump()) assert response.status_code == status.HTTP_400_BAD_REQUEST response = response.json() assert response.get("detail") == "REGISTER_USER_ALREADY_EXISTS" diff --git a/tests/test_2_workspaces.py b/tests/test_2_workspaces.py index d86bd09..c031e0a 100644 --- a/tests/test_2_workspaces.py +++ b/tests/test_2_workspaces.py @@ -235,7 +235,7 @@ async def test_get_workspace_members(client_test: AsyncClient): response = response.json() assert len(response["members"]) == len(accounts) for acc in accounts: - assert acc.dict(include={"id", "email", "first_name", "last_name"}) in response["members"] + assert acc.model_dump(include={"id", "email", "first_name", "last_name"}) in response["members"] colored_dbg.test_success("The workspace returned the correct list of members") @@ -276,7 +276,7 @@ async def test_get_all_policies(client_test: AsyncClient): assert response.status_code == status.HTTP_200_OK response = response.json() assert len(response["policies"]) == len(accounts) - temp_acc_list = [acc.dict(include={"id", "email", "first_name", "last_name"}) for acc in accounts] + temp_acc_list = [acc.model_dump(include={"id", "email", "first_name", "last_name"}) for acc in accounts] for policy in response["policies"]: assert policy["policy_holder"] in temp_acc_list if policy["policy_holder"]["id"] == accounts[0].id: diff --git a/tests/test_3_groups.py b/tests/test_3_groups.py index c14e546..65e0fc6 100644 --- a/tests/test_3_groups.py +++ b/tests/test_3_groups.py @@ -254,7 +254,7 @@ async def test_get_group_members(client_test: AsyncClient): response = response.json() assert len(response["members"]) == 10 # 10 accounts were added to the group for acc in accounts[:10]: - assert acc.dict(include={"id", "email", "first_name", "last_name"}) in response["members"] + assert acc.model_dump(include={"id", "email", "first_name", "last_name"}) in response["members"] colored_dbg.test_success("The group returned the correct list of members") @@ -296,7 +296,7 @@ async def test_get_all_policies(client_test: AsyncClient): assert response.status_code == status.HTTP_200_OK response = response.json() assert len(response["policies"]) == 10 # 10 accounts were added to the group - temp_acc_list = [acc.dict(include={"id", "email", "first_name", "last_name"}) for acc in accounts] + temp_acc_list = [acc.model_dump(include={"id", "email", "first_name", "last_name"}) for acc in accounts] for policy in response["policies"]: assert policy["policy_holder"] in temp_acc_list if policy["policy_holder"]["id"] == accounts[0].id: