Skip to content

Commit

Permalink
refactor: user methods (#1535)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonas1312 authored Oct 12, 2023
1 parent 04277dc commit 48950a8
Show file tree
Hide file tree
Showing 26 changed files with 841 additions and 429 deletions.
5 changes: 1 addition & 4 deletions docs/sdk/user.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# User module

## Queries
::: kili.entrypoints.queries.user.__init__.QueriesUser
## Mutations
::: kili.entrypoints.mutations.user.__init__.MutationsUser
::: kili.presentation.client.user.UserClientMethods
2 changes: 2 additions & 0 deletions src/kili/adapters/kili_api_gateway/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from kili.adapters.kili_api_gateway.issue import IssueOperationMixin
from kili.adapters.kili_api_gateway.project import ProjectOperationMixin
from kili.adapters.kili_api_gateway.tag import TagOperationMixin
from kili.adapters.kili_api_gateway.user.operation_mixin import UserOperationMixin
from kili.core.graphql.graphql_client import GraphQLClient


Expand All @@ -17,6 +18,7 @@ class KiliAPIGateway(
ProjectOperationMixin,
TagOperationMixin,
ApiKeyOperationMixin,
UserOperationMixin,
CloudStorageOperationMixin,
):
"""GraphQL gateway to communicate with Kili backend."""
Expand Down
47 changes: 47 additions & 0 deletions src/kili/adapters/kili_api_gateway/user/mappers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""GraphQL payload data mappers for user operations."""

from typing import Dict

from kili.domain.user import UserFilter

from .types import CreateUserDataKiliGatewayInput, UserDataKiliGatewayInput


def user_where_mapper(filters: UserFilter) -> Dict:
"""Build the GraphQL UserWhere variable to be sent in an operation."""
return {
"activated": filters.activated,
"apiKey": filters.api_key,
"email": filters.email,
"id": filters.id,
"idIn": filters.id_in,
"organization": {"id": filters.organization_id},
}


def create_user_data_mapper(data: CreateUserDataKiliGatewayInput) -> Dict:
"""Build the CreateUserDataKiliGatewayInput data variable to be sent in an operation."""
return {
"email": data.email,
"firstname": data.firstname,
"lastname": data.lastname,
"password": data.password,
"organizationRole": data.organization_role,
}


def update_user_data_mapper(data: UserDataKiliGatewayInput) -> Dict:
"""Build the UserDataKiliGatewayInput data variable to be sent in an operation."""
return {
"activated": data.activated,
"apiKey": data.api_key,
# "auth0Id": data.auth0_id, # refused by the backend: only used for service account # noqa: ERA001 # pylint: disable=line-too-long
"email": data.email,
"firstname": data.firstname,
"hasCompletedLabelingTour": data.has_completed_labeling_tour,
"hubspotSubscriptionStatus": data.hubspot_subscription_status,
"lastname": data.lastname,
"organization": data.organization,
"organizationId": data.organization_id,
"organizationRole": data.organization_role,
}
93 changes: 93 additions & 0 deletions src/kili/adapters/kili_api_gateway/user/operation_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Mixin extending Kili API Gateway class with User related operations."""

from typing import Dict, Generator

from kili.adapters.kili_api_gateway.base import BaseOperationMixin
from kili.adapters.kili_api_gateway.helpers.queries import (
PaginatedGraphQLQuery,
QueryOptions,
fragment_builder,
)
from kili.domain.types import ListOrTuple
from kili.domain.user import UserFilter

from .mappers import create_user_data_mapper, update_user_data_mapper, user_where_mapper
from .operations import (
GQL_COUNT_USERS,
get_create_user_mutation,
get_current_user_query,
get_update_password_mutation,
get_update_user_mutation,
get_users_query,
)
from .types import CreateUserDataKiliGatewayInput, UserDataKiliGatewayInput


class UserOperationMixin(BaseOperationMixin):
"""GraphQL Mixin extending GraphQL Gateway class with User related operations."""

def list_users(
self, user_filters: UserFilter, fields: ListOrTuple[str], options: QueryOptions
) -> Generator[Dict, None, None]:
"""Return a generator of users that match the filter."""
fragment = fragment_builder(fields)
query = get_users_query(fragment)
where = user_where_mapper(filters=user_filters)
return PaginatedGraphQLQuery(self.graphql_client).execute_query_from_paginated_call(
query, where, options, "Retrieving users", GQL_COUNT_USERS
)

def count_users(self, user_filters: UserFilter) -> int:
"""Return the number of users that match the filter."""
result = self.graphql_client.execute(GQL_COUNT_USERS, user_where_mapper(user_filters))
return result["data"]

def get_current_user(self, fields: ListOrTuple[str]) -> Dict:
"""Return the current user."""
fragment = fragment_builder(fields)
query = get_current_user_query(fragment=fragment)
result = self.graphql_client.execute(query)
return result["data"]

def create_user(self, data: CreateUserDataKiliGatewayInput, fields: ListOrTuple[str]) -> Dict:
"""Create a user."""
fragment = fragment_builder(fields)
query = get_create_user_mutation(fragment)
variables = create_user_data_mapper(data)
result = self.graphql_client.execute(query, variables)
return result["data"]

def update_password(
self,
old_password: str,
new_password_1: str,
new_password_2: str,
user_filter: UserFilter,
fields: ListOrTuple[str],
) -> Dict:
"""Update user password."""
fragment = fragment_builder(fields)
query = get_update_password_mutation(fragment)
variables = {
"data": {
"oldPassword": old_password,
"newPassword1": new_password_1,
"newPassword2": new_password_2,
},
"where": user_where_mapper(filters=user_filter),
}
result = self.graphql_client.execute(query, variables)
return result["data"]

def update_user(
self, user_filter: UserFilter, data: UserDataKiliGatewayInput, fields: ListOrTuple[str]
) -> Dict:
"""Update a user."""
fragment = fragment_builder(fields)
query = get_update_user_mutation(fragment)
variables = {
"data": update_user_data_mapper(data),
"where": user_where_mapper(filters=user_filter),
}
result = self.graphql_client.execute(query, variables)
return result["data"]
84 changes: 84 additions & 0 deletions src/kili/adapters/kili_api_gateway/user/operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""GraphQL User operations."""


def get_users_query(fragment: str) -> str:
"""Return the GraphQL users query."""
return f"""
query users($where: UserWhere!, $first: PageSize!, $skip: Int!) {{
data: users(where: $where, first: $first, skip: $skip) {{
{fragment}
}}
}}
"""


def get_current_user_query(fragment: str) -> str:
"""Return the GraphQL current user query."""
return f"""
query me {{
data: me {{
{fragment}
}}
}}
"""


def get_create_user_mutation(fragment: str) -> str:
"""Return the GraphQL create user mutation."""
return f"""
mutation(
$data: CreateUserData!
) {{
data: createUser(
data: $data
) {{
{fragment}
}}
}}
"""


def get_update_password_mutation(fragment: str) -> str:
"""Return the GraphQL update password mutation."""
return f"""
mutation(
$data: UpdatePasswordData!
$where: UserWhere!
) {{
data: updatePassword(
data: $data
where: $where
) {{
{fragment}
}}
}}
"""


def get_update_user_mutation(fragment: str) -> str:
"""Return the GraphQL update user mutation."""
return f"""
mutation updatePropertiesInUser( $data: UserData!, $where: UserWhere!) {{
data: updatePropertiesInUser( data: $data, where: $where) {{
{fragment}
}}
}}
"""


GQL_COUNT_USERS = """
query countUsers($where: UserWhere!) {
data: countUsers(where: $where)
}
"""


def get_reset_password_mutation(fragment: str) -> str:
"""Return the GraphQL reset password mutation."""
return f"""
mutation($where: UserWhere!) {{
data: resetPassword(where: $where) {{
{fragment}
}}
}}
"""
35 changes: 35 additions & 0 deletions src/kili/adapters/kili_api_gateway/user/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Types for the user-related Kili API gateway functions."""
from dataclasses import dataclass
from typing import Optional

from kili.core.enums import OrganizationRole
from kili.domain.organization import OrganizationId
from kili.domain.user import HubspotSubscriptionStatus


@dataclass
class CreateUserDataKiliGatewayInput:
"""Input type for creating a user in Kili Gateway."""

email: str
firstname: Optional[str]
lastname: Optional[str]
password: Optional[str]
organization_role: OrganizationRole


@dataclass
class UserDataKiliGatewayInput:
"""Input type for updating a user in Kili Gateway."""

activated: Optional[bool] = None
api_key: Optional[str] = None
# auth0_id: Optional[str] = None # refused by the backend: only used for service account # noqa: ERA001 # pylint: disable=line-too-long
email: Optional[str] = None
firstname: Optional[str] = None
has_completed_labeling_tour: Optional[bool] = None
hubspot_subscription_status: Optional[HubspotSubscriptionStatus] = None
lastname: Optional[str] = None
organization: Optional[str] = None
organization_id: Optional[OrganizationId] = None
organization_role: Optional[OrganizationRole] = None
18 changes: 3 additions & 15 deletions src/kili/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,28 @@
from kili.adapters.http_client import HttpClient
from kili.adapters.kili_api_gateway import KiliAPIGateway
from kili.core.graphql.graphql_client import GraphQLClient, GraphQLClientName
from kili.core.graphql.operations.user.queries import GQL_ME
from kili.entrypoints.mutations.asset import MutationsAsset
from kili.entrypoints.mutations.issue import MutationsIssue
from kili.entrypoints.mutations.label import MutationsLabel
from kili.entrypoints.mutations.notification import MutationsNotification
from kili.entrypoints.mutations.plugins import MutationsPlugins
from kili.entrypoints.mutations.project import MutationsProject
from kili.entrypoints.mutations.project_version import MutationsProjectVersion
from kili.entrypoints.mutations.user import MutationsUser
from kili.entrypoints.queries.label import QueriesLabel
from kili.entrypoints.queries.notification import QueriesNotification
from kili.entrypoints.queries.organization import QueriesOrganization
from kili.entrypoints.queries.plugins import QueriesPlugins
from kili.entrypoints.queries.project_user import QueriesProjectUser
from kili.entrypoints.queries.project_version import QueriesProjectVersion
from kili.entrypoints.queries.user import QueriesUser
from kili.entrypoints.subscriptions.label import SubscriptionsLabel
from kili.exceptions import AuthenticationFailed, UserNotFoundError
from kili.exceptions import AuthenticationFailed
from kili.presentation.client.asset import AssetClientMethods
from kili.presentation.client.cloud_storage import CloudStorageClientMethods
from kili.presentation.client.internal import InternalClientMethods
from kili.presentation.client.issue import IssueClientMethods
from kili.presentation.client.project import ProjectClientMethods
from kili.presentation.client.tag import TagClientMethods
from kili.presentation.client.user import UserClientMethods
from kili.use_cases.api_key import ApiKeyUseCases

warnings.filterwarnings("default", module="kili", category=DeprecationWarning)
Expand All @@ -58,20 +56,19 @@ class Kili( # pylint: disable=too-many-ancestors,too-many-instance-attributes
MutationsPlugins,
MutationsProject,
MutationsProjectVersion,
MutationsUser,
QueriesLabel,
QueriesNotification,
QueriesOrganization,
QueriesPlugins,
QueriesProjectUser,
QueriesProjectVersion,
QueriesUser,
SubscriptionsLabel,
IssueClientMethods,
AssetClientMethods,
TagClientMethods,
ProjectClientMethods,
CloudStorageClientMethods,
UserClientMethods,
):
"""Kili Client."""

Expand Down Expand Up @@ -168,12 +165,3 @@ def __init__(
if not skip_checks:
api_key_use_cases = ApiKeyUseCases(self.kili_api_gateway)
api_key_use_cases.check_expiry_of_key_is_close(api_key)

def get_user(self) -> Dict:
# TODO: move this method
"""Get the current user from the api_key provided."""
result = self.graphql_client.execute(GQL_ME)
user = self.format_result("data", result)
if user is None or user["id"] is None or user["email"] is None:
raise UserNotFoundError("No user attached to the API key was found")
return user
8 changes: 6 additions & 2 deletions src/kili/core/graphql/graphql_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,13 @@ def _get_kili_app_version(self) -> Optional[str]:
return response_json["version"]
return None

@staticmethod
def _remove_nullable_inputs(variables: Dict) -> Dict:
@classmethod
def _remove_nullable_inputs(cls, variables: Dict) -> Dict:
"""Remove nullable inputs from the variables."""
if "data" in variables and isinstance(variables["data"], dict):
variables["data"] = cls._remove_nullable_inputs(variables["data"])
if "where" in variables and isinstance(variables["where"], dict):
variables["where"] = cls._remove_nullable_inputs(variables["where"])
return {k: v for k, v in variables.items() if v is not None}

def execute(
Expand Down
Loading

0 comments on commit 48950a8

Please sign in to comment.