From cce6042a4ad1c35a073013115484fe750588cf44 Mon Sep 17 00:00:00 2001 From: theodu Date: Mon, 14 Aug 2023 18:12:04 +0200 Subject: [PATCH 01/24] feat: refactor issues mutations --- src/kili/client.py | 8 +- src/kili/core/graphql/graphql_gateway.py | 7 + .../core/graphql/operations/issue/__init__.py | 38 +++++ .../graphql/operations/issue/operations.py | 15 ++ .../core/graphql/operations/issue/types.py | 13 ++ src/kili/domain/issues.py | 3 + .../entrypoints/mutations/issue/__init__.py | 155 ++++++++---------- .../entrypoints/mutations/issue/fragments.py | 5 - .../entrypoints/mutations/issue/queries.py | 31 ---- src/kili/services/issues/__init__.py | 63 +++++++ src/kili/services/issues/types.py | 12 ++ 11 files changed, 226 insertions(+), 124 deletions(-) create mode 100644 src/kili/core/graphql/graphql_gateway.py create mode 100644 src/kili/core/graphql/operations/issue/operations.py create mode 100644 src/kili/core/graphql/operations/issue/types.py create mode 100644 src/kili/domain/issues.py delete mode 100644 src/kili/entrypoints/mutations/issue/fragments.py delete mode 100644 src/kili/entrypoints/mutations/issue/queries.py create mode 100644 src/kili/services/issues/__init__.py create mode 100644 src/kili/services/issues/types.py diff --git a/src/kili/client.py b/src/kili/client.py index c58eb9082..014307311 100644 --- a/src/kili/client.py +++ b/src/kili/client.py @@ -12,11 +12,12 @@ from kili import __version__ from kili.core.graphql import QueryOptions from kili.core.graphql.graphql_client import GraphQLClient, GraphQLClientName +from kili.core.graphql.graphql_gateway import GraphQLGateway from kili.core.graphql.operations.api_key.queries import APIKeyQuery, APIKeyWhere from kili.core.graphql.operations.user.queries import GQL_ME from kili.entrypoints.mutations.asset import MutationsAsset from kili.entrypoints.mutations.data_connection import MutationsDataConnection -from kili.entrypoints.mutations.issue import MutationsIssue +from kili.entrypoints.mutations.issue import IssueEntrypoints, MutationsIssue from kili.entrypoints.mutations.label import MutationsLabel from kili.entrypoints.mutations.notification import MutationsNotification from kili.entrypoints.mutations.plugins import MutationsPlugins @@ -56,7 +57,7 @@ def filter(self, record) -> bool: class Kili( # pylint: disable=too-many-ancestors,too-many-instance-attributes MutationsAsset, MutationsDataConnection, - MutationsIssue, + MutationsIssue, # deprecated MutationsLabel, MutationsNotification, MutationsPlugins, @@ -76,6 +77,7 @@ class Kili( # pylint: disable=too-many-ancestors,too-many-instance-attributes QueriesProjectVersion, QueriesUser, SubscriptionsLabel, + IssueEntrypoints, ): """Kili Client.""" @@ -165,6 +167,8 @@ def __init__( **(graphql_client_params or {}), # type: ignore ) + self.graphql_gateway = GraphQLGateway(self.graphql_client, http_client=self.http_client) + if not skip_checks: api_key_query = APIKeyQuery(self.graphql_client, self.http_client) self._check_expiry_of_key_is_close(api_key_query, self.api_key) diff --git a/src/kili/core/graphql/graphql_gateway.py b/src/kili/core/graphql/graphql_gateway.py new file mode 100644 index 000000000..c9f8aff12 --- /dev/null +++ b/src/kili/core/graphql/graphql_gateway.py @@ -0,0 +1,7 @@ +from kili.core.graphql.operations.issue import IssueOperationMixin + + +class GraphQLGateway(IssueOperationMixin): + def __init__(self, graphql_client, http_client): + self.graphql_client = graphql_client + self.http_client = http_client diff --git a/src/kili/core/graphql/operations/issue/__init__.py b/src/kili/core/graphql/operations/issue/__init__.py index e69de29bb..a52842d8c 100644 --- a/src/kili/core/graphql/operations/issue/__init__.py +++ b/src/kili/core/graphql/operations/issue/__init__.py @@ -0,0 +1,38 @@ +"""GraphQL Mixin extending GraphQL Gateway class with Issue related operations.""" + +from typing import List + +from kili.core.graphql.graphql_client import GraphQLClient +from kili.core.graphql.operations.issue.operations import GQL_CREATE_ISSUES +from kili.core.graphql.operations.issue.types import IssueToCreateGraphQLGatewayInput +from kili.core.utils.pagination import BatchIteratorBuilder +from kili.domain.issues import IssueType + + +class IssueOperationMixin: + """GraphQL Mixin extending GraphQL Gateway class with Issue related operations.""" + + graphql_client: GraphQLClient + + def create_issues(self, type_: IssueType, issues: List[IssueToCreateGraphQLGatewayInput]): + created_issues_ids = [] + for issues_batch in BatchIteratorBuilder(issues): + batch_targeted_asset_ids = [issue.asset_id for issue in issues_batch] + payload = { + "issues": [ + { + "issueNumber": issue.issue_number, + "labelID": issue.label_id, + "objectMid": issue.object_mid, + "type": type_, + "assetId": issue.asset_id, + "text": issue.text, + } + for issue in issues_batch + ], + "where": {"idIn": batch_targeted_asset_ids}, + } + result = self.graphql_client.execute(GQL_CREATE_ISSUES, payload) + batch_created_issues_ids = result["data"] + created_issues_ids.extend(batch_created_issues_ids) + return created_issues_ids diff --git a/src/kili/core/graphql/operations/issue/operations.py b/src/kili/core/graphql/operations/issue/operations.py new file mode 100644 index 000000000..db0ba8bf3 --- /dev/null +++ b/src/kili/core/graphql/operations/issue/operations.py @@ -0,0 +1,15 @@ +"""GraphQL Issues operations.""" + +GQL_CREATE_ISSUES = """ +mutation createIssues( + $issues: [IssueToCreate!]! + $where: AssetWhere! +) { + data: createIssues( + issues: $issues + where: $where + ) { + id + } +} +""" diff --git a/src/kili/core/graphql/operations/issue/types.py b/src/kili/core/graphql/operations/issue/types.py new file mode 100644 index 000000000..d137154f2 --- /dev/null +++ b/src/kili/core/graphql/operations/issue/types.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class IssueToCreateGraphQLGatewayInput: + """Data about an issue to create needed in graphql createIssue resolver.""" + + issue_number: int + label_id: Optional[str] + object_mid: Optional[str] + asset_id: str + text: Optional[str] diff --git a/src/kili/domain/issues.py b/src/kili/domain/issues.py new file mode 100644 index 000000000..895e8d3b9 --- /dev/null +++ b/src/kili/domain/issues.py @@ -0,0 +1,3 @@ +from typing import Literal + +IssueType = Literal["ISSUE", "QUESTION"] diff --git a/src/kili/entrypoints/mutations/issue/__init__.py b/src/kili/entrypoints/mutations/issue/__init__.py index 835ff49b7..8d61c9e0b 100644 --- a/src/kili/entrypoints/mutations/issue/__init__.py +++ b/src/kili/entrypoints/mutations/issue/__init__.py @@ -6,15 +6,81 @@ from typeguard import typechecked from kili.core.graphql import QueryOptions +from kili.core.graphql.graphql_gateway import GraphQLGateway +from kili.core.graphql.operations.issue.operations import GQL_CREATE_ISSUES from kili.core.graphql.operations.label.queries import LabelQuery, LabelWhere from kili.core.helpers import deprecate from kili.entrypoints.base import BaseOperationEntrypointMixin -from kili.entrypoints.mutations.asset.helpers import get_asset_ids_or_throw_error from kili.services.helpers import assert_all_arrays_have_same_size +from kili.services.issues import IssueUseCases +from kili.services.issues.types import IssueToCreateEntrypointInput from kili.utils.logcontext import for_all_methods, log_call -from .helpers import get_issue_numbers, get_labels_asset_ids_map -from .queries import GQL_CREATE_ISSUES +from .helpers import get_issue_numbers + + +@for_all_methods(log_call, exclude=["__init__"]) +class IssueEntrypoints: + """Set of Issue mutations.""" + + graphql_gateway: GraphQLGateway + + @typechecked + def create_issues( + self, + project_id: str, + label_id_array: List[str], + object_mid_array: Optional[List[Optional[str]]] = None, + text_array: Optional[List[Optional[str]]] = None, + ) -> List[Dict]: + """Create an issue. + + Args: + project_id: Id of the project. + label_id_array: List of Ids of the labels to add an issue to. + object_mid_array: List of mids of the objects in the labels to associate the issues to. + text_array: List of texts to associate to the issues. + + Returns: + A list of dictionary with the `id` key of the created issues. + """ + assert_all_arrays_have_same_size([label_id_array, object_mid_array, text_array]) + issues_entrypoint_input = [ + IssueToCreateEntrypointInput(label_id=label_id, object_mid=object_mid, text=text) + for (label_id, object_mid, text) in zip( + label_id_array, + object_mid_array or repeat(None), + text_array or repeat(None), + ) + ] + issue_use_cases = IssueUseCases(self.graphql_gateway) + return issue_use_cases.create_issues(project_id=project_id, issues=issues_entrypoint_input) + + @typechecked + def create_questions( + self, + project_id: str, + text_array: List[str], + asset_id_array: Optional[List[str]] = None, + asset_external_id_array: Optional[List[str]] = None, + ) -> List[Dict]: + # pylint:disable=line-too-long + """Create questions. + + Args: + project_id: Id of the project. + text_array: List of question strings. + asset_id_array: List of the assets to add the questions to. + asset_external_id_array: List of the assets to add the questions to. Used if `asset_id_array` is not given. + + Returns: + A list of dictionary with the `id` key of the created questions. + """ + assert_all_arrays_have_same_size([text_array, asset_id_array]) + issue_use_cases = IssueUseCases(self.graphql_gateway) + return issue_use_cases.create_questions( + project_id, text_array, asset_id_array, asset_external_id_array + ) @for_all_methods(log_call, exclude=["__init__"]) @@ -90,86 +156,3 @@ def append_to_issues( result = self.graphql_client.execute(GQL_CREATE_ISSUES, variables) return self.format_result("data", result)[0] - - @typechecked - def create_issues( - self, - project_id: str, - label_id_array: List[str], - object_mid_array: Optional[List[Optional[str]]] = None, - text_array: Optional[List[Optional[str]]] = None, - ) -> List[Dict]: - """Create an issue. - - Args: - project_id: Id of the project. - label_id_array: List of Ids of the labels to add an issue to. - object_mid_array: List of mids of the objects in the labels to associate the issues to. - text_array: List of texts to associate to the issues. - - Returns: - A list of dictionary with the `id` key of the created issues. - """ - assert_all_arrays_have_same_size([label_id_array, object_mid_array, text_array]) - issue_number_array = get_issue_numbers(self, project_id, "ISSUE", len(label_id_array)) - label_asset_ids_map = get_labels_asset_ids_map(self, project_id, label_id_array) - variables = { - "issues": [ - { - "issueNumber": issue_number, - "labelID": label_id, - "objectMid": object_mid, - "type": "ISSUE", - "assetId": label_asset_ids_map[label_id], - "text": text, - } - for (issue_number, label_id, object_mid, text) in zip( - issue_number_array, - label_id_array, - object_mid_array or repeat(None), - text_array or repeat(None), - ) - ], - "where": {"idIn": list(label_asset_ids_map.values())}, - } - - result = self.graphql_client.execute(GQL_CREATE_ISSUES, variables) - return self.format_result("data", result) - - @typechecked - def create_questions( - self, - project_id: str, - text_array: List[Optional[str]], - asset_id_array: Optional[List[str]] = None, - asset_external_id_array: Optional[List[str]] = None, - ) -> List[Dict]: - # pylint:disable=line-too-long - """Create questions. - - Args: - project_id: Id of the project. - text_array: List of question strings. - asset_id_array: List of the assets to add the questions to. - asset_external_id_array: List of the assets to add the questions to. Used if `asset_id_array` is not given. - - Returns: - A list of dictionary with the `id` key of the created questions. - """ - assert_all_arrays_have_same_size([text_array, asset_id_array]) - issue_number_array = get_issue_numbers(self, project_id, "QUESTION", len(text_array)) - asset_id_array = get_asset_ids_or_throw_error( - self, asset_id_array, asset_external_id_array, project_id - ) - variables = { - "issues": [ - {"issueNumber": issue_number, "type": "QUESTION", "assetId": asset_id, "text": text} - for (asset_id, text, issue_number) in zip( - asset_id_array, text_array, issue_number_array - ) - ], - "where": {"idIn": asset_id_array}, - } - - result = self.graphql_client.execute(GQL_CREATE_ISSUES, variables) - return self.format_result("data", result) diff --git a/src/kili/entrypoints/mutations/issue/fragments.py b/src/kili/entrypoints/mutations/issue/fragments.py deleted file mode 100644 index be33e997b..000000000 --- a/src/kili/entrypoints/mutations/issue/fragments.py +++ /dev/null @@ -1,5 +0,0 @@ -"""Fragments of issue mutations.""" - -ISSUE_FRAGMENT = """ -id -""" diff --git a/src/kili/entrypoints/mutations/issue/queries.py b/src/kili/entrypoints/mutations/issue/queries.py deleted file mode 100644 index 8381595fd..000000000 --- a/src/kili/entrypoints/mutations/issue/queries.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Queries of issue mutations.""" - -from .fragments import ISSUE_FRAGMENT - -GQL_APPEND_TO_ISSUES = f""" -mutation( - $data: AppendToIssuesData! - $where: AssetWhere! -) {{ - data: appendToIssues( - data: $data - where: $where - ) {{ - {ISSUE_FRAGMENT} - }} -}} -""" - -GQL_CREATE_ISSUES = f""" -mutation createIssues( - $issues: [IssueToCreate!]! - $where: AssetWhere! -) {{ - data: createIssues( - issues: $issues - where: $where - ) {{ - {ISSUE_FRAGMENT} - }} -}} -""" diff --git a/src/kili/services/issues/__init__.py b/src/kili/services/issues/__init__.py new file mode 100644 index 000000000..f75b31cb8 --- /dev/null +++ b/src/kili/services/issues/__init__.py @@ -0,0 +1,63 @@ +from typing import List, Optional + +from kili.core.graphql.graphql_gateway import GraphQLGateway +from kili.core.graphql.operations.issue.types import IssueToCreateGraphQLGatewayInput +from kili.entrypoints.mutations.asset.helpers import get_asset_ids_or_throw_error +from kili.entrypoints.mutations.issue.helpers import ( + get_issue_numbers, + get_labels_asset_ids_map, +) +from kili.services.issues.types import IssueToCreateEntrypointInput + + +class IssueUseCases: + def __init__(self, graphql_gateway: GraphQLGateway): + self._graphql_gateway = graphql_gateway + + def create_issues(self, project_id, issues: List[IssueToCreateEntrypointInput]): + issue_number_array = get_issue_numbers( + self._graphql_gateway, project_id, "ISSUE", len(issues) + ) + label_id_array = [issue.label_id for issue in issues] + label_asset_ids_map = get_labels_asset_ids_map( + self._graphql_gateway, project_id, label_id_array + ) # should be done in the backend + graphql_issues = [ + IssueToCreateGraphQLGatewayInput( + issue_number=issue_number, + label_id=issue.label_id, + object_mid=issue.object_mid, + asset_id=label_asset_ids_map[issue.label_id], + text=issue.text, + ) + for (issue_number, issue) in zip(issue_number_array, issues) + ] + issue_ids = self._graphql_gateway.create_issues(type_="ISSUE", issues=graphql_issues) + return issue_ids + + def create_questions( + self, + project_id: str, + text_array: List[str], + asset_id_array: Optional[List[str]], + asset_external_id_array: Optional[List[str]], + ): + issue_number_array = get_issue_numbers(self, project_id, "QUESTION", len(text_array)) + asset_id_array = get_asset_ids_or_throw_error( + self._graphql_gateway, asset_id_array, asset_external_id_array, project_id + ) # should be done in the backend + graphql_questions = [ + IssueToCreateGraphQLGatewayInput( + issue_number=issue_number, + asset_id=asset_id, + text=text, + label_id=None, + object_mid=None, + ) + for (issue_number, asset_id, text) in zip( + issue_number_array, asset_id_array, text_array + ) + ] + + issue_ids = self._graphql_gateway.create_issues(type_="QUESTION", issues=graphql_questions) + return issue_ids diff --git a/src/kili/services/issues/types.py b/src/kili/services/issues/types.py new file mode 100644 index 000000000..b9d77947b --- /dev/null +++ b/src/kili/services/issues/types.py @@ -0,0 +1,12 @@ +"""Types for Issue-related service.""" +from dataclasses import dataclass +from typing import Optional + + +@dataclass +class IssueToCreateEntrypointInput: + """Data about one Issue to create.""" + + label_id: str + object_mid: Optional[str] = None + text: Optional[str] = None From 0ca75c6a4386ed5fdedc3184bebc85a8df49176f Mon Sep 17 00:00:00 2001 From: theodu Date: Mon, 14 Aug 2023 18:44:05 +0200 Subject: [PATCH 02/24] feat: add issue count query --- .../core/graphql/operations/issue/__init__.py | 45 ++++++++++++++++++- .../graphql/operations/issue/operations.py | 18 ++++++++ src/kili/domain/issues.py | 1 + 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/kili/core/graphql/operations/issue/__init__.py b/src/kili/core/graphql/operations/issue/__init__.py index a52842d8c..3306f3c68 100644 --- a/src/kili/core/graphql/operations/issue/__init__.py +++ b/src/kili/core/graphql/operations/issue/__init__.py @@ -1,14 +1,40 @@ """GraphQL Mixin extending GraphQL Gateway class with Issue related operations.""" -from typing import List +from dataclasses import dataclass +from typing import List, Optional +from kili.core.enums import IssueStatus from kili.core.graphql.graphql_client import GraphQLClient -from kili.core.graphql.operations.issue.operations import GQL_CREATE_ISSUES +from kili.core.graphql.operations.issue.operations import ( + GQL_COUNT_ISSUES, + GQL_CREATE_ISSUES, +) from kili.core.graphql.operations.issue.types import IssueToCreateGraphQLGatewayInput from kili.core.utils.pagination import BatchIteratorBuilder from kili.domain.issues import IssueType +@dataclass +class IssueWhere: + """Tuple to be passed to the IssueQuery to restrict query.""" + + project_id: str + asset_id: Optional[str] = None + asset_id_in: Optional[List[str]] = None + issue_type: Optional[IssueType] = None + status: Optional[IssueStatus] = None + + def get_graphql_input(self): + """Build the GraphQL Where payload sent in the resolver from the SDK IssueWhere.""" + return { + "project": {"id": self.project_id}, + "asset": {"id": self.asset_id}, + "assetIn": self.asset_id_in, + "status": self.status, + "type": self.issue_type, + } + + class IssueOperationMixin: """GraphQL Mixin extending GraphQL Gateway class with Issue related operations.""" @@ -36,3 +62,18 @@ def create_issues(self, type_: IssueType, issues: List[IssueToCreateGraphQLGatew batch_created_issues_ids = result["data"] created_issues_ids.extend(batch_created_issues_ids) return created_issues_ids + + def count_issues( + self, + project_id: str, + asset_id: Optional[str] = None, + asset_id_in: Optional[List[str]] = None, + issue_type: Optional[IssueType] = None, + status: Optional[IssueStatus] = None, + ): + where = IssueWhere(project_id, asset_id, asset_id_in, issue_type, status) + payload = { + "where": where.get_graphql_input(), + } + count_result = self.graphql_client.execute(GQL_COUNT_ISSUES, payload) + return count_result["data"] diff --git a/src/kili/core/graphql/operations/issue/operations.py b/src/kili/core/graphql/operations/issue/operations.py index db0ba8bf3..a5eba05c3 100644 --- a/src/kili/core/graphql/operations/issue/operations.py +++ b/src/kili/core/graphql/operations/issue/operations.py @@ -13,3 +13,21 @@ } } """ + + +def get_gql_issues_query(fragment): + """Return the GraphQL issues query.""" + return f""" + query issues($where: IssueWhere!, $first: PageSize!, $skip: Int!) {{ + data: issues(where: $where, first: $first, skip: $skip) {{ + {fragment} + }} + }} + """ + + +GQL_COUNT_ISSUES = """ +query countIssues($where: IssueWhere!) { + data: countIssues(where: $where) +} +""" diff --git a/src/kili/domain/issues.py b/src/kili/domain/issues.py index 895e8d3b9..4b7e75b90 100644 --- a/src/kili/domain/issues.py +++ b/src/kili/domain/issues.py @@ -1,3 +1,4 @@ from typing import Literal IssueType = Literal["ISSUE", "QUESTION"] +IssueStatus = Literal["OPEN", "SOLVED"] From 43df2383ec7b8893ce076b29204a9c79fbc44b93 Mon Sep 17 00:00:00 2001 From: theodu Date: Thu, 17 Aug 2023 18:14:34 +0200 Subject: [PATCH 03/24] feat: update archi and add tests --- src/kili/client.py | 3 +- .../core/graphql/operations/issue/__init__.py | 10 ++- .../core/graphql/operations/issue/types.py | 2 +- src/kili/domain/issues.py | 4 +- .../entrypoints/mutations/issue/__init__.py | 71 +--------------- src/kili/entrypoints/sdk/issue.py | 81 +++++++++++++++++++ src/kili/services/issues/__init__.py | 29 ++++--- src/kili/services/issues/types.py | 2 +- tests/conftest.py | 16 ++++ tests/services/test_issues.py | 16 ++++ 10 files changed, 141 insertions(+), 93 deletions(-) create mode 100644 src/kili/entrypoints/sdk/issue.py create mode 100644 tests/conftest.py create mode 100644 tests/services/test_issues.py diff --git a/src/kili/client.py b/src/kili/client.py index 014307311..d4281620a 100644 --- a/src/kili/client.py +++ b/src/kili/client.py @@ -17,7 +17,7 @@ from kili.core.graphql.operations.user.queries import GQL_ME from kili.entrypoints.mutations.asset import MutationsAsset from kili.entrypoints.mutations.data_connection import MutationsDataConnection -from kili.entrypoints.mutations.issue import IssueEntrypoints, MutationsIssue +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 @@ -36,6 +36,7 @@ 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.sdk.issue import IssueEntrypoints from kili.entrypoints.subscriptions.label import SubscriptionsLabel from kili.exceptions import AuthenticationFailed, UserNotFoundError from kili.internal import KiliInternal diff --git a/src/kili/core/graphql/operations/issue/__init__.py b/src/kili/core/graphql/operations/issue/__init__.py index 3306f3c68..a2c309a5e 100644 --- a/src/kili/core/graphql/operations/issue/__init__.py +++ b/src/kili/core/graphql/operations/issue/__init__.py @@ -9,9 +9,9 @@ GQL_COUNT_ISSUES, GQL_CREATE_ISSUES, ) -from kili.core.graphql.operations.issue.types import IssueToCreateGraphQLGatewayInput +from kili.core.graphql.operations.issue.types import IssueToCreateGQLGatewayInput from kili.core.utils.pagination import BatchIteratorBuilder -from kili.domain.issues import IssueType +from kili.domain.issues import IssueId, IssueType @dataclass @@ -40,8 +40,10 @@ class IssueOperationMixin: graphql_client: GraphQLClient - def create_issues(self, type_: IssueType, issues: List[IssueToCreateGraphQLGatewayInput]): - created_issues_ids = [] + def create_issues( + self, type_: IssueType, issues: List[IssueToCreateGQLGatewayInput] + ) -> List[IssueId]: + created_issues_ids: List[IssueId] = [] for issues_batch in BatchIteratorBuilder(issues): batch_targeted_asset_ids = [issue.asset_id for issue in issues_batch] payload = { diff --git a/src/kili/core/graphql/operations/issue/types.py b/src/kili/core/graphql/operations/issue/types.py index d137154f2..c787f9ead 100644 --- a/src/kili/core/graphql/operations/issue/types.py +++ b/src/kili/core/graphql/operations/issue/types.py @@ -3,7 +3,7 @@ @dataclass -class IssueToCreateGraphQLGatewayInput: +class IssueToCreateGQLGatewayInput: """Data about an issue to create needed in graphql createIssue resolver.""" issue_number: int diff --git a/src/kili/domain/issues.py b/src/kili/domain/issues.py index 4b7e75b90..c4be23f01 100644 --- a/src/kili/domain/issues.py +++ b/src/kili/domain/issues.py @@ -1,4 +1,6 @@ -from typing import Literal +from typing import Literal, NewType IssueType = Literal["ISSUE", "QUESTION"] IssueStatus = Literal["OPEN", "SOLVED"] + +IssueId = NewType("IssueId", str) diff --git a/src/kili/entrypoints/mutations/issue/__init__.py b/src/kili/entrypoints/mutations/issue/__init__.py index 8d61c9e0b..7fff1d7ea 100644 --- a/src/kili/entrypoints/mutations/issue/__init__.py +++ b/src/kili/entrypoints/mutations/issue/__init__.py @@ -1,88 +1,19 @@ """Issue mutations.""" -from itertools import repeat -from typing import Dict, List, Literal, Optional +from typing import Dict, Literal, Optional from typeguard import typechecked from kili.core.graphql import QueryOptions -from kili.core.graphql.graphql_gateway import GraphQLGateway from kili.core.graphql.operations.issue.operations import GQL_CREATE_ISSUES from kili.core.graphql.operations.label.queries import LabelQuery, LabelWhere from kili.core.helpers import deprecate from kili.entrypoints.base import BaseOperationEntrypointMixin -from kili.services.helpers import assert_all_arrays_have_same_size -from kili.services.issues import IssueUseCases -from kili.services.issues.types import IssueToCreateEntrypointInput from kili.utils.logcontext import for_all_methods, log_call from .helpers import get_issue_numbers -@for_all_methods(log_call, exclude=["__init__"]) -class IssueEntrypoints: - """Set of Issue mutations.""" - - graphql_gateway: GraphQLGateway - - @typechecked - def create_issues( - self, - project_id: str, - label_id_array: List[str], - object_mid_array: Optional[List[Optional[str]]] = None, - text_array: Optional[List[Optional[str]]] = None, - ) -> List[Dict]: - """Create an issue. - - Args: - project_id: Id of the project. - label_id_array: List of Ids of the labels to add an issue to. - object_mid_array: List of mids of the objects in the labels to associate the issues to. - text_array: List of texts to associate to the issues. - - Returns: - A list of dictionary with the `id` key of the created issues. - """ - assert_all_arrays_have_same_size([label_id_array, object_mid_array, text_array]) - issues_entrypoint_input = [ - IssueToCreateEntrypointInput(label_id=label_id, object_mid=object_mid, text=text) - for (label_id, object_mid, text) in zip( - label_id_array, - object_mid_array or repeat(None), - text_array or repeat(None), - ) - ] - issue_use_cases = IssueUseCases(self.graphql_gateway) - return issue_use_cases.create_issues(project_id=project_id, issues=issues_entrypoint_input) - - @typechecked - def create_questions( - self, - project_id: str, - text_array: List[str], - asset_id_array: Optional[List[str]] = None, - asset_external_id_array: Optional[List[str]] = None, - ) -> List[Dict]: - # pylint:disable=line-too-long - """Create questions. - - Args: - project_id: Id of the project. - text_array: List of question strings. - asset_id_array: List of the assets to add the questions to. - asset_external_id_array: List of the assets to add the questions to. Used if `asset_id_array` is not given. - - Returns: - A list of dictionary with the `id` key of the created questions. - """ - assert_all_arrays_have_same_size([text_array, asset_id_array]) - issue_use_cases = IssueUseCases(self.graphql_gateway) - return issue_use_cases.create_questions( - project_id, text_array, asset_id_array, asset_external_id_array - ) - - @for_all_methods(log_call, exclude=["__init__"]) class MutationsIssue(BaseOperationEntrypointMixin): """Set of Issue mutations.""" diff --git a/src/kili/entrypoints/sdk/issue.py b/src/kili/entrypoints/sdk/issue.py new file mode 100644 index 000000000..4c1a81e0e --- /dev/null +++ b/src/kili/entrypoints/sdk/issue.py @@ -0,0 +1,81 @@ +"""Client entrypoints methods for issues.""" + +from itertools import repeat +from typing import Dict, List, Literal, Optional + +from typeguard import typechecked + +from kili.core.graphql.graphql_gateway import GraphQLGateway +from kili.domain.issues import IssueId +from kili.services.helpers import assert_all_arrays_have_same_size +from kili.services.issues import IssueUseCases +from kili.services.issues.types import IssueToCreateServiceInput +from kili.utils.logcontext import for_all_methods, log_call + + +@for_all_methods(log_call, exclude=["__init__"]) +class IssueEntrypoints: + """Set of Issue mutations.""" + + graphql_gateway: GraphQLGateway + + @typechecked + def create_issues( + self, + project_id: str, + label_id_array: List[str], + *, + object_mid_array: Optional[List[Optional[str]]] = None, + text_array: Optional[List[Optional[str]]] = None, + ) -> List[Dict[Literal["id"], IssueId]]: + """Create an issue. + + Args: + project_id: Id of the project. + label_id_array: List of Ids of the labels to add an issue to. + object_mid_array: List of mids of the objects in the labels to associate the issues to. + text_array: List of texts to associate to the issues. + + Returns: + A list of dictionary with the `id` key of the created issues. + """ + assert_all_arrays_have_same_size([label_id_array, object_mid_array, text_array]) + issues = [ + IssueToCreateServiceInput(label_id=label_id, object_mid=object_mid, text=text) + for (label_id, object_mid, text) in zip( + label_id_array, + object_mid_array or repeat(None), + text_array or repeat(None), + ) + ] + issue_use_cases = IssueUseCases(self.graphql_gateway) + created_issues_ids = issue_use_cases.create_issues(project_id=project_id, issues=issues) + return [{"id": issue_id} for issue_id in created_issues_ids] + + @typechecked + def create_questions( + self, + project_id: str, + text_array: List[str], + *, + asset_id_array: Optional[List[str]] = None, + asset_external_id_array: Optional[List[str]] = None, + ) -> List[Dict[Literal["id"], IssueId]]: + # pylint:disable=line-too-long + """Create questions. + + Args: + project_id: Id of the project. + text_array: List of question strings. + asset_id_array: List of the assets to add the questions to. + asset_external_id_array: List of the assets to add the questions to. Used if `asset_id_array` is not given. + + Returns: + A list of dictionary with the `id` key of the created questions. + """ + assert_all_arrays_have_same_size([text_array, asset_id_array]) + issue_use_cases = IssueUseCases(self.graphql_gateway) + created_questions_ids = issue_use_cases.create_questions( + project_id, text_array, asset_id_array, asset_external_id_array + ) + return [{"id": issue_id} for issue_id in created_questions_ids] diff --git a/src/kili/services/issues/__init__.py b/src/kili/services/issues/__init__.py index f75b31cb8..23be80ef8 100644 --- a/src/kili/services/issues/__init__.py +++ b/src/kili/services/issues/__init__.py @@ -1,29 +1,26 @@ +"""Issue use cases.""" + from typing import List, Optional from kili.core.graphql.graphql_gateway import GraphQLGateway -from kili.core.graphql.operations.issue.types import IssueToCreateGraphQLGatewayInput +from kili.core.graphql.operations.issue.types import IssueToCreateGQLGatewayInput from kili.entrypoints.mutations.asset.helpers import get_asset_ids_or_throw_error -from kili.entrypoints.mutations.issue.helpers import ( - get_issue_numbers, - get_labels_asset_ids_map, -) -from kili.services.issues.types import IssueToCreateEntrypointInput +from kili.entrypoints.mutations.issue.helpers import get_labels_asset_ids_map +from kili.services.issues.types import IssueToCreateServiceInput class IssueUseCases: def __init__(self, graphql_gateway: GraphQLGateway): self._graphql_gateway = graphql_gateway - def create_issues(self, project_id, issues: List[IssueToCreateEntrypointInput]): - issue_number_array = get_issue_numbers( - self._graphql_gateway, project_id, "ISSUE", len(issues) - ) + def create_issues(self, project_id, issues: List[IssueToCreateServiceInput]): + issue_number_array = [0] * len(issues) label_id_array = [issue.label_id for issue in issues] label_asset_ids_map = get_labels_asset_ids_map( self._graphql_gateway, project_id, label_id_array ) # should be done in the backend graphql_issues = [ - IssueToCreateGraphQLGatewayInput( + IssueToCreateGQLGatewayInput( issue_number=issue_number, label_id=issue.label_id, object_mid=issue.object_mid, @@ -42,12 +39,12 @@ def create_questions( asset_id_array: Optional[List[str]], asset_external_id_array: Optional[List[str]], ): - issue_number_array = get_issue_numbers(self, project_id, "QUESTION", len(text_array)) + issue_number_array = [0] * len(text_array) asset_id_array = get_asset_ids_or_throw_error( self._graphql_gateway, asset_id_array, asset_external_id_array, project_id ) # should be done in the backend graphql_questions = [ - IssueToCreateGraphQLGatewayInput( + IssueToCreateGQLGatewayInput( issue_number=issue_number, asset_id=asset_id, text=text, @@ -59,5 +56,7 @@ def create_questions( ) ] - issue_ids = self._graphql_gateway.create_issues(type_="QUESTION", issues=graphql_questions) - return issue_ids + question_ids = self._graphql_gateway.create_issues( + type_="QUESTION", issues=graphql_questions + ) + return question_ids diff --git a/src/kili/services/issues/types.py b/src/kili/services/issues/types.py index b9d77947b..311f3f4fc 100644 --- a/src/kili/services/issues/types.py +++ b/src/kili/services/issues/types.py @@ -4,7 +4,7 @@ @dataclass -class IssueToCreateEntrypointInput: +class IssueToCreateServiceInput: """Data about one Issue to create.""" label_id: str diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..474418d40 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,16 @@ +"""Common fixtures for tests.""" + +import pytest +import requests +from pytest_mock import MockerFixture + +from kili.core.graphql.graphql_client import GraphQLClient +from kili.core.graphql.graphql_gateway import GraphQLGateway + + +@pytest.fixture() +def graphql_gateway(mocker: MockerFixture): + mock = mocker.MagicMock(spec=GraphQLGateway) + mock.graphql_client = mocker.MagicMock(spec=GraphQLClient) + mock.http_client = mocker.MagicMock(spec=requests.Session) + return mock diff --git a/tests/services/test_issues.py b/tests/services/test_issues.py new file mode 100644 index 000000000..4ea8e339b --- /dev/null +++ b/tests/services/test_issues.py @@ -0,0 +1,16 @@ +from kili.services.issues import IssueUseCases +from kili.services.issues.types import IssueToCreateServiceInput + + +def test_create_one_issue(graphql_gateway): + issue_use_case = IssueUseCases(graphql_gateway) + + # given one issue to create + issues = [IssueToCreateServiceInput(label_id="label_id", text="text", object_mid="object_mid")] + graphql_gateway.create_issues.return_value(["issue_id"]) + + # when creating one issue + issues = issue_use_case.create_issues(project_id="project_id", issues=issues) + + # then + assert issues == ["issue_id"] From 6009d736149a4dc283dc5ec6a1a8c00392acf82f Mon Sep 17 00:00:00 2001 From: theodu Date: Fri, 18 Aug 2023 14:49:01 +0200 Subject: [PATCH 04/24] feat: add changes from brainstorming --- src/kili/client.py | 247 +----------------- .../core/graphql/operations/issue/__init__.py | 8 +- src/kili/domain/issues.py | 11 +- src/kili/entrypoints/client/__init__.py | 242 +++++++++++++++++ src/kili/entrypoints/{sdk => client}/issue.py | 13 +- src/kili/services/issues/__init__.py | 8 +- src/kili/services/plugins/model.py | 2 +- tests/e2e/test_client.py | 2 +- tests/e2e/test_json_response_validation.py | 2 +- tests/test_client.py | 2 +- 10 files changed, 275 insertions(+), 262 deletions(-) create mode 100644 src/kili/entrypoints/client/__init__.py rename src/kili/entrypoints/{sdk => client}/issue.py (86%) diff --git a/src/kili/client.py b/src/kili/client.py index d4281620a..af4105bc3 100644 --- a/src/kili/client.py +++ b/src/kili/client.py @@ -1,244 +1,9 @@ -"""This script permits to initialize the Kili Python SDK client.""" -import getpass -import logging -import os -import sys -import warnings -from datetime import datetime, timedelta -from typing import Callable, Dict, Optional, Union +"""Module with the Kili client. -import requests +The class definition is entrypoint/client/__init__.py +This is a shortcut module for the final user import +""" -from kili import __version__ -from kili.core.graphql import QueryOptions -from kili.core.graphql.graphql_client import GraphQLClient, GraphQLClientName -from kili.core.graphql.graphql_gateway import GraphQLGateway -from kili.core.graphql.operations.api_key.queries import APIKeyQuery, APIKeyWhere -from kili.core.graphql.operations.user.queries import GQL_ME -from kili.entrypoints.mutations.asset import MutationsAsset -from kili.entrypoints.mutations.data_connection import MutationsDataConnection -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.asset import QueriesAsset -from kili.entrypoints.queries.data_connection import QueriesDataConnection -from kili.entrypoints.queries.data_integration import QueriesDataIntegration -from kili.entrypoints.queries.issue import QueriesIssue -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 import QueriesProject -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.sdk.issue import IssueEntrypoints -from kili.entrypoints.subscriptions.label import SubscriptionsLabel -from kili.exceptions import AuthenticationFailed, UserNotFoundError -from kili.internal import KiliInternal +from kili.entrypoints.client import Kili -warnings.filterwarnings("default", module="kili", category=DeprecationWarning) - - -class FilterPoolFullWarning(logging.Filter): - """Filter out the specific urllib3 warning related to the connection pool.""" - - def filter(self, record) -> bool: - """urllib3.connectionpool:Connection pool is full, discarding connection: ...""" - return "Connection pool is full, discarding connection" not in record.getMessage() - - -logging.getLogger("urllib3.connectionpool").addFilter(FilterPoolFullWarning()) - - -class Kili( # pylint: disable=too-many-ancestors,too-many-instance-attributes - MutationsAsset, - MutationsDataConnection, - MutationsIssue, # deprecated - MutationsLabel, - MutationsNotification, - MutationsPlugins, - MutationsProject, - MutationsProjectVersion, - MutationsUser, - QueriesAsset, - QueriesDataConnection, - QueriesDataIntegration, - QueriesIssue, - QueriesLabel, - QueriesNotification, - QueriesOrganization, - QueriesPlugins, - QueriesProject, - QueriesProjectUser, - QueriesProjectVersion, - QueriesUser, - SubscriptionsLabel, - IssueEntrypoints, -): - """Kili Client.""" - - # pylint: disable=too-many-arguments - def __init__( - self, - api_key: Optional[str] = None, - api_endpoint: Optional[str] = None, - verify: Union[bool, str] = True, - client_name: GraphQLClientName = GraphQLClientName.SDK, - graphql_client_params: Optional[Dict[str, object]] = None, - ) -> None: - """Initialize Kili client. - - Args: - api_key: User API key generated - from https://cloud.kili-technology.com/label/my-account/api-key. - Default to `KILI_API_KEY` environment variable. - If not passed, requires the `KILI_API_KEY` environment variable to be set. - api_endpoint: Recipient of the HTTP operation. - Default to `KILI_API_ENDPOINT` environment variable. - If not passed, default to Kili SaaS: - 'https://cloud.kili-technology.com/api/label/v2/graphql' - verify: similar to `requests`' verify. - Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. When set to - ``False``, requests will accept any TLS certificate presented by - the server, and will ignore hostname mismatches and/or expired - certificates, which will make your application vulnerable to - man-in-the-middle (MitM) attacks. Setting verify to ``False`` - may be useful during local development or testing. - client_name: For internal use only. - Define the name of the graphQL client whith which graphQL calls will be sent. - graphql_client_params: Parameters to pass to the graphQL client. - - Returns: - Instance of the Kili client. - - Examples: - ```python - from kili.client import Kili - - kili = Kili() - - kili.assets() # list your assets - kili.labels() # list your labels - kili.projects() # list your projects - ``` - """ - api_key = api_key or os.getenv("KILI_API_KEY") - - if not api_key and sys.stdin.isatty(): - api_key = getpass.getpass( - "No `KILI_API_KEY` environment variable found.\nPlease enter your API key: " - ) - - if api_endpoint is None: - api_endpoint = os.getenv( - "KILI_API_ENDPOINT", - "https://cloud.kili-technology.com/api/label/v2/graphql", - ) - - if not api_key: - raise AuthenticationFailed(api_key, api_endpoint) - - self.api_key = api_key - self.api_endpoint = api_endpoint - self.verify = verify - self.client_name = client_name - self.graphql_client_params = graphql_client_params - - skip_checks = os.getenv("KILI_SDK_SKIP_CHECKS") is not None - - self.http_client = requests.Session() - self.http_client.verify = verify - - if not skip_checks: - self._check_api_key_valid() - - self.graphql_client = GraphQLClient( - endpoint=api_endpoint, - api_key=api_key, - client_name=client_name, - verify=self.verify, - http_client=self.http_client, - **(graphql_client_params or {}), # type: ignore - ) - - self.graphql_gateway = GraphQLGateway(self.graphql_client, http_client=self.http_client) - - if not skip_checks: - api_key_query = APIKeyQuery(self.graphql_client, self.http_client) - self._check_expiry_of_key_is_close(api_key_query, self.api_key) - - self.internal = KiliInternal(self) - - def _check_api_key_valid(self) -> None: - """Check that the api_key provided is valid.""" - response = self.http_client.post( - url=self.api_endpoint, - data='{"query":"{ me { id email } }"}', - timeout=30, - headers={ - "Authorization": f"X-API-Key: {self.api_key}", - "Accept": "application/json", - "Content-Type": "application/json", - "apollographql-client-name": self.client_name.value, - "apollographql-client-version": __version__, - }, - ) - if response.status_code == 200 and "email" in response.text and "id" in response.text: - return - - raise AuthenticationFailed( - api_key=self.api_key, - api_endpoint=self.api_endpoint, - error_msg=( - "Cannot check API key validity: status_code" - f" {response.status_code}\n\n{response.text}" - ), - ) - - def _get_kili_app_version(self) -> Optional[str]: - """Get the version of the Kili app server. - - Returns None if the version cannot be retrieved. - """ - url = self.api_endpoint.replace("/graphql", "/version") - response = self.http_client.get(url, timeout=30) - if response.status_code == 200 and '"version":' in response.text: - response_json = response.json() - version = response_json["version"] - return version - return None - - @staticmethod - def _check_expiry_of_key_is_close(api_key_query: Callable, api_key: str) -> None: - """Check that the expiration date of the api_key is not too close.""" - warn_days = 30 - - api_keys = api_key_query( - fields=["expiryDate"], - where=APIKeyWhere(api_key=api_key), - options=QueryOptions(disable_tqdm=True), - ) - - key_expiry = datetime.strptime(next(api_keys)["expiryDate"], r"%Y-%m-%dT%H:%M:%S.%fZ") - key_remaining_time = key_expiry - datetime.now() - key_soon_deprecated = key_remaining_time < timedelta(days=warn_days) - if key_soon_deprecated: - message = f""" - Your api key will be deprecated on {key_expiry:%Y-%m-%d}. - You should generate a new one on My account > API KEY.""" - warnings.warn(message, UserWarning, stacklevel=2) - - def get_user(self) -> Dict: - """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 +__all__ = ["Kili"] diff --git a/src/kili/core/graphql/operations/issue/__init__.py b/src/kili/core/graphql/operations/issue/__init__.py index a2c309a5e..c2a104cc7 100644 --- a/src/kili/core/graphql/operations/issue/__init__.py +++ b/src/kili/core/graphql/operations/issue/__init__.py @@ -11,7 +11,7 @@ ) from kili.core.graphql.operations.issue.types import IssueToCreateGQLGatewayInput from kili.core.utils.pagination import BatchIteratorBuilder -from kili.domain.issues import IssueId, IssueType +from kili.domain.issues import Issue, IssueType @dataclass @@ -42,8 +42,8 @@ class IssueOperationMixin: def create_issues( self, type_: IssueType, issues: List[IssueToCreateGQLGatewayInput] - ) -> List[IssueId]: - created_issues_ids: List[IssueId] = [] + ) -> List[Issue]: + created_issues_ids: List[str] = [] for issues_batch in BatchIteratorBuilder(issues): batch_targeted_asset_ids = [issue.asset_id for issue in issues_batch] payload = { @@ -63,7 +63,7 @@ def create_issues( result = self.graphql_client.execute(GQL_CREATE_ISSUES, payload) batch_created_issues_ids = result["data"] created_issues_ids.extend(batch_created_issues_ids) - return created_issues_ids + return [Issue(id=id) for id in created_issues_ids] def count_issues( self, diff --git a/src/kili/domain/issues.py b/src/kili/domain/issues.py index c4be23f01..753a85863 100644 --- a/src/kili/domain/issues.py +++ b/src/kili/domain/issues.py @@ -1,6 +1,13 @@ -from typing import Literal, NewType +"""Issue domain.""" +from dataclasses import dataclass +from typing import Literal IssueType = Literal["ISSUE", "QUESTION"] IssueStatus = Literal["OPEN", "SOLVED"] -IssueId = NewType("IssueId", str) + +@dataclass +class Issue: + """Issue Entity.""" + + id: str diff --git a/src/kili/entrypoints/client/__init__.py b/src/kili/entrypoints/client/__init__.py new file mode 100644 index 000000000..7cefa4eaa --- /dev/null +++ b/src/kili/entrypoints/client/__init__.py @@ -0,0 +1,242 @@ +"""This script permits to initialize the Kili Python SDK client.""" +import getpass +import logging +import os +import sys +import warnings +from datetime import datetime, timedelta +from typing import Callable, Dict, Optional, Union + +import requests + +from kili import __version__ +from kili.core.graphql import QueryOptions +from kili.core.graphql.gateway import GraphQLGateway +from kili.core.graphql.graphql_client import GraphQLClient, GraphQLClientName +from kili.core.graphql.operations.api_key.queries import APIKeyQuery, APIKeyWhere +from kili.core.graphql.operations.user.queries import GQL_ME +from kili.entrypoints.mutations.asset import MutationsAsset +from kili.entrypoints.mutations.data_connection import MutationsDataConnection +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.asset import QueriesAsset +from kili.entrypoints.queries.data_connection import QueriesDataConnection +from kili.entrypoints.queries.data_integration import QueriesDataIntegration +from kili.entrypoints.queries.issue import QueriesIssue +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 import QueriesProject +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.internal import KiliInternal + +warnings.filterwarnings("default", module="kili", category=DeprecationWarning) + + +class FilterPoolFullWarning(logging.Filter): + """Filter out the specific urllib3 warning related to the connection pool.""" + + def filter(self, record) -> bool: + """urllib3.connectionpool:Connection pool is full, discarding connection: ...""" + return "Connection pool is full, discarding connection" not in record.getMessage() + + +logging.getLogger("urllib3.connectionpool").addFilter(FilterPoolFullWarning()) + + +class Kili( # pylint: disable=too-many-ancestors,too-many-instance-attributes + MutationsAsset, + MutationsDataConnection, + MutationsIssue, + MutationsLabel, + MutationsNotification, + MutationsPlugins, + MutationsProject, + MutationsProjectVersion, + MutationsUser, + QueriesAsset, + QueriesDataConnection, + QueriesDataIntegration, + QueriesIssue, + QueriesLabel, + QueriesNotification, + QueriesOrganization, + QueriesPlugins, + QueriesProject, + QueriesProjectUser, + QueriesProjectVersion, + QueriesUser, + SubscriptionsLabel, +): + """Kili Client.""" + + # pylint: disable=too-many-arguments + def __init__( + self, + api_key: Optional[str] = None, + api_endpoint: Optional[str] = None, + verify: Union[bool, str] = True, + client_name: GraphQLClientName = GraphQLClientName.SDK, + graphql_client_params: Optional[Dict[str, object]] = None, + ) -> None: + """Initialize Kili client. + + Args: + api_key: User API key generated + from https://cloud.kili-technology.com/label/my-account/api-key. + Default to `KILI_API_KEY` environment variable. + If not passed, requires the `KILI_API_KEY` environment variable to be set. + api_endpoint: Recipient of the HTTP operation. + Default to `KILI_API_ENDPOINT` environment variable. + If not passed, default to Kili SaaS: + 'https://cloud.kili-technology.com/api/label/v2/graphql' + verify: similar to `requests`' verify. + Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. When set to + ``False``, requests will accept any TLS certificate presented by + the server, and will ignore hostname mismatches and/or expired + certificates, which will make your application vulnerable to + man-in-the-middle (MitM) attacks. Setting verify to ``False`` + may be useful during local development or testing. + client_name: For internal use only. + Define the name of the graphQL client whith which graphQL calls will be sent. + graphql_client_params: Parameters to pass to the graphQL client. + + Returns: + Instance of the Kili client. + + Examples: + ```python + from kili.client import Kili + + kili = Kili() + + kili.assets() # list your assets + kili.labels() # list your labels + kili.projects() # list your projects + ``` + """ + api_key = api_key or os.getenv("KILI_API_KEY") + + if not api_key and sys.stdin.isatty(): + api_key = getpass.getpass( + "No `KILI_API_KEY` environment variable found.\nPlease enter your API key: " + ) + + if api_endpoint is None: + api_endpoint = os.getenv( + "KILI_API_ENDPOINT", + "https://cloud.kili-technology.com/api/label/v2/graphql", + ) + + if not api_key: + raise AuthenticationFailed(api_key, api_endpoint) + + self.api_key = api_key + self.api_endpoint = api_endpoint + self.verify = verify + self.client_name = client_name + self.graphql_client_params = graphql_client_params + + skip_checks = os.getenv("KILI_SDK_SKIP_CHECKS") is not None + + self.http_client = requests.Session() + self.http_client.verify = verify + + if not skip_checks: + self._check_api_key_valid() + + self.graphql_client = GraphQLClient( + endpoint=api_endpoint, + api_key=api_key, + client_name=client_name, + verify=self.verify, + http_client=self.http_client, + **(graphql_client_params or {}), # type: ignore + ) + + self.graphql_gateway = GraphQLGateway(self.graphql_client, self.http_client) + + if not skip_checks: + api_key_query = APIKeyQuery(self.graphql_client, self.http_client) + self._check_expiry_of_key_is_close(api_key_query, self.api_key) + + self.internal = KiliInternal(self) + + def _check_api_key_valid(self) -> None: + """Check that the api_key provided is valid.""" + response = self.http_client.post( + url=self.api_endpoint, + data='{"query":"{ me { id email } }"}', + timeout=30, + headers={ + "Authorization": f"X-API-Key: {self.api_key}", + "Accept": "application/json", + "Content-Type": "application/json", + "apollographql-client-name": self.client_name.value, + "apollographql-client-version": __version__, + }, + ) + if response.status_code == 200 and "email" in response.text and "id" in response.text: + return + + raise AuthenticationFailed( + api_key=self.api_key, + api_endpoint=self.api_endpoint, + error_msg=( + "Cannot check API key validity: status_code" + f" {response.status_code}\n\n{response.text}" + ), + ) + + def _get_kili_app_version(self) -> Optional[str]: + """Get the version of the Kili app server. + + Returns None if the version cannot be retrieved. + """ + url = self.api_endpoint.replace("/graphql", "/version") + response = self.http_client.get(url, timeout=30) + if response.status_code == 200 and '"version":' in response.text: + response_json = response.json() + version = response_json["version"] + return version + return None + + @staticmethod + def _check_expiry_of_key_is_close(api_key_query: Callable, api_key: str) -> None: + """Check that the expiration date of the api_key is not too close.""" + warn_days = 30 + + api_keys = api_key_query( + fields=["expiryDate"], + where=APIKeyWhere(api_key=api_key), + options=QueryOptions(disable_tqdm=True), + ) + + key_expiry = datetime.strptime(next(api_keys)["expiryDate"], r"%Y-%m-%dT%H:%M:%S.%fZ") + key_remaining_time = key_expiry - datetime.now() + key_soon_deprecated = key_remaining_time < timedelta(days=warn_days) + if key_soon_deprecated: + message = f""" + Your api key will be deprecated on {key_expiry:%Y-%m-%d}. + You should generate a new one on My account > API KEY.""" + warnings.warn(message, UserWarning, stacklevel=2) + + def get_user(self) -> Dict: + """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 diff --git a/src/kili/entrypoints/sdk/issue.py b/src/kili/entrypoints/client/issue.py similarity index 86% rename from src/kili/entrypoints/sdk/issue.py rename to src/kili/entrypoints/client/issue.py index 4c1a81e0e..ad0449424 100644 --- a/src/kili/entrypoints/sdk/issue.py +++ b/src/kili/entrypoints/client/issue.py @@ -6,7 +6,6 @@ from typeguard import typechecked from kili.core.graphql.graphql_gateway import GraphQLGateway -from kili.domain.issues import IssueId from kili.services.helpers import assert_all_arrays_have_same_size from kili.services.issues import IssueUseCases from kili.services.issues.types import IssueToCreateServiceInput @@ -27,7 +26,7 @@ def create_issues( *, object_mid_array: Optional[List[Optional[str]]] = None, text_array: Optional[List[Optional[str]]] = None, - ) -> List[Dict[Literal["id"], IssueId]]: + ) -> List[Dict[Literal["id"], str]]: """Create an issue. Args: @@ -49,8 +48,8 @@ def create_issues( ) ] issue_use_cases = IssueUseCases(self.graphql_gateway) - created_issues_ids = issue_use_cases.create_issues(project_id=project_id, issues=issues) - return [{"id": issue_id} for issue_id in created_issues_ids] + issues_entities = issue_use_cases.create_issues(project_id=project_id, issues=issues) + return [{"id": issue.id} for issue in issues_entities] @typechecked def create_questions( @@ -60,7 +59,7 @@ def create_questions( *, asset_id_array: Optional[List[str]] = None, asset_external_id_array: Optional[List[str]] = None, - ) -> List[Dict[Literal["id"], IssueId]]: + ) -> List[Dict[Literal["id"], str]]: # pylint:disable=line-too-long """Create questions. @@ -75,7 +74,7 @@ def create_questions( """ assert_all_arrays_have_same_size([text_array, asset_id_array]) issue_use_cases = IssueUseCases(self.graphql_gateway) - created_questions_ids = issue_use_cases.create_questions( + created_questions = issue_use_cases.create_questions( project_id, text_array, asset_id_array, asset_external_id_array ) - return [{"id": issue_id} for issue_id in created_questions_ids] + return [{"id": question.id} for question in created_questions] diff --git a/src/kili/services/issues/__init__.py b/src/kili/services/issues/__init__.py index 23be80ef8..beb50ac56 100644 --- a/src/kili/services/issues/__init__.py +++ b/src/kili/services/issues/__init__.py @@ -29,8 +29,8 @@ def create_issues(self, project_id, issues: List[IssueToCreateServiceInput]): ) for (issue_number, issue) in zip(issue_number_array, issues) ] - issue_ids = self._graphql_gateway.create_issues(type_="ISSUE", issues=graphql_issues) - return issue_ids + created_issues = self._graphql_gateway.create_issues(type_="ISSUE", issues=graphql_issues) + return created_issues def create_questions( self, @@ -56,7 +56,7 @@ def create_questions( ) ] - question_ids = self._graphql_gateway.create_issues( + created_questions = self._graphql_gateway.create_issues( type_="QUESTION", issues=graphql_questions ) - return question_ids + return created_questions diff --git a/src/kili/services/plugins/model.py b/src/kili/services/plugins/model.py index eb43cd2d9..e97c9c37b 100644 --- a/src/kili/services/plugins/model.py +++ b/src/kili/services/plugins/model.py @@ -3,7 +3,7 @@ import logging from typing import Dict, List, Optional -from kili.client import Kili +from kili.entrypoints.client import Kili from kili.services.plugins.helpers import get_logger diff --git a/tests/e2e/test_client.py b/tests/e2e/test_client.py index b3f8e4adc..3ab359523 100644 --- a/tests/e2e/test_client.py +++ b/tests/e2e/test_client.py @@ -1,7 +1,7 @@ import pytest_mock from pyinstrument.profiler import Profiler -from kili.client import Kili +from kili.entrypoints.client import Kili def test_client_init_not_too_long_with_checks_enabled(): diff --git a/tests/e2e/test_json_response_validation.py b/tests/e2e/test_json_response_validation.py index 3a6c7004f..98196c959 100644 --- a/tests/e2e/test_json_response_validation.py +++ b/tests/e2e/test_json_response_validation.py @@ -4,7 +4,7 @@ import pytest from gql.transport import exceptions -from kili.client import Kili +from kili.entrypoints.client import Kili from kili.exceptions import GraphQLError diff --git a/tests/test_client.py b/tests/test_client.py index 711e11c69..549034ebf 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -7,7 +7,7 @@ import pytest import pytest_mock -from kili.client import Kili +from kili.entrypoints.client import Kili from kili.exceptions import AuthenticationFailed From 8e23aa2251170246b55a3fa153cd3ec01e3d1937 Mon Sep 17 00:00:00 2001 From: theodu Date: Mon, 21 Aug 2023 14:33:13 +0200 Subject: [PATCH 05/24] feat: clean code --- .../__init__.py} | 3 +- .../core/graphql/gateway/issue/__init__.py | 81 +++++++++++++++++++ .../issue/operations.py | 0 .../{operations => gateway}/issue/types.py | 0 .../core/graphql/operations/issue/__init__.py | 81 ------------------- src/kili/entrypoints/client/issue.py | 2 +- .../entrypoints/mutations/issue/__init__.py | 2 +- src/kili/services/issues/__init__.py | 4 +- tests/conftest.py | 2 +- 9 files changed, 88 insertions(+), 87 deletions(-) rename src/kili/core/graphql/{graphql_gateway.py => gateway/__init__.py} (65%) create mode 100644 src/kili/core/graphql/gateway/issue/__init__.py rename src/kili/core/graphql/{operations => gateway}/issue/operations.py (100%) rename src/kili/core/graphql/{operations => gateway}/issue/types.py (100%) diff --git a/src/kili/core/graphql/graphql_gateway.py b/src/kili/core/graphql/gateway/__init__.py similarity index 65% rename from src/kili/core/graphql/graphql_gateway.py rename to src/kili/core/graphql/gateway/__init__.py index c9f8aff12..1ef1aa294 100644 --- a/src/kili/core/graphql/graphql_gateway.py +++ b/src/kili/core/graphql/gateway/__init__.py @@ -1,4 +1,5 @@ -from kili.core.graphql.operations.issue import IssueOperationMixin +"""GraphQL gateway module.""" +from kili.core.graphql.gateway.issue import IssueOperationMixin class GraphQLGateway(IssueOperationMixin): diff --git a/src/kili/core/graphql/gateway/issue/__init__.py b/src/kili/core/graphql/gateway/issue/__init__.py new file mode 100644 index 000000000..903e5c1e0 --- /dev/null +++ b/src/kili/core/graphql/gateway/issue/__init__.py @@ -0,0 +1,81 @@ +"""GraphQL Mixin extending GraphQL Gateway class with Issue related operations.""" + +from dataclasses import dataclass +from typing import List, Optional + +from kili.core.enums import IssueStatus +from kili.core.graphql.gateway.issue.operations import ( + GQL_COUNT_ISSUES, + GQL_CREATE_ISSUES, +) +from kili.core.graphql.gateway.issue.types import IssueToCreateGQLGatewayInput +from kili.core.graphql.graphql_client import GraphQLClient +from kili.core.utils.pagination import BatchIteratorBuilder +from kili.domain.issues import Issue, IssueType + + +@dataclass +class IssueWhere: + """Tuple to be passed to the IssueQuery to restrict query.""" + + project_id: str + asset_id: Optional[str] = None + asset_id_in: Optional[List[str]] = None + issue_type: Optional[IssueType] = None + status: Optional[IssueStatus] = None + + def get_graphql_input(self): + """Build the GraphQL Where payload sent in the resolver from the SDK IssueWhere.""" + return { + "project": {"id": self.project_id}, + "asset": {"id": self.asset_id}, + "assetIn": self.asset_id_in, + "status": self.status, + "type": self.issue_type, + } + + +class IssueOperationMixin: + """GraphQL Mixin extending GraphQL Gateway class with Issue related operations.""" + + graphql_client: GraphQLClient + + def create_issues( + self, type_: IssueType, issues: List[IssueToCreateGQLGatewayInput] + ) -> List[Issue]: + created_issues_ids: List[str] = [] + for issues_batch in BatchIteratorBuilder(issues): + batch_targeted_asset_ids = [issue.asset_id for issue in issues_batch] + payload = { + "issues": [ + { + "issueNumber": issue.issue_number, + "labelID": issue.label_id, + "objectMid": issue.object_mid, + "type": type_, + "assetId": issue.asset_id, + "text": issue.text, + } + for issue in issues_batch + ], + "where": {"idIn": batch_targeted_asset_ids}, + } + result = self.graphql_client.execute(GQL_CREATE_ISSUES, payload) + batch_created_issues_ids = result["data"] + created_issues_ids.extend(batch_created_issues_ids) + return [Issue(id=id) for id in created_issues_ids] + + def count_issues( + self, + project_id: str, + asset_id: Optional[str] = None, + asset_id_in: Optional[List[str]] = None, + issue_type: Optional[IssueType] = None, + status: Optional[IssueStatus] = None, + ): + where = IssueWhere(project_id, asset_id, asset_id_in, issue_type, status) + payload = { + "where": where.get_graphql_input(), + } + count_result = self.graphql_client.execute(GQL_COUNT_ISSUES, payload) + return count_result["data"] diff --git a/src/kili/core/graphql/operations/issue/operations.py b/src/kili/core/graphql/gateway/issue/operations.py similarity index 100% rename from src/kili/core/graphql/operations/issue/operations.py rename to src/kili/core/graphql/gateway/issue/operations.py diff --git a/src/kili/core/graphql/operations/issue/types.py b/src/kili/core/graphql/gateway/issue/types.py similarity index 100% rename from src/kili/core/graphql/operations/issue/types.py rename to src/kili/core/graphql/gateway/issue/types.py diff --git a/src/kili/core/graphql/operations/issue/__init__.py b/src/kili/core/graphql/operations/issue/__init__.py index c2a104cc7..e69de29bb 100644 --- a/src/kili/core/graphql/operations/issue/__init__.py +++ b/src/kili/core/graphql/operations/issue/__init__.py @@ -1,81 +0,0 @@ -"""GraphQL Mixin extending GraphQL Gateway class with Issue related operations.""" - -from dataclasses import dataclass -from typing import List, Optional - -from kili.core.enums import IssueStatus -from kili.core.graphql.graphql_client import GraphQLClient -from kili.core.graphql.operations.issue.operations import ( - GQL_COUNT_ISSUES, - GQL_CREATE_ISSUES, -) -from kili.core.graphql.operations.issue.types import IssueToCreateGQLGatewayInput -from kili.core.utils.pagination import BatchIteratorBuilder -from kili.domain.issues import Issue, IssueType - - -@dataclass -class IssueWhere: - """Tuple to be passed to the IssueQuery to restrict query.""" - - project_id: str - asset_id: Optional[str] = None - asset_id_in: Optional[List[str]] = None - issue_type: Optional[IssueType] = None - status: Optional[IssueStatus] = None - - def get_graphql_input(self): - """Build the GraphQL Where payload sent in the resolver from the SDK IssueWhere.""" - return { - "project": {"id": self.project_id}, - "asset": {"id": self.asset_id}, - "assetIn": self.asset_id_in, - "status": self.status, - "type": self.issue_type, - } - - -class IssueOperationMixin: - """GraphQL Mixin extending GraphQL Gateway class with Issue related operations.""" - - graphql_client: GraphQLClient - - def create_issues( - self, type_: IssueType, issues: List[IssueToCreateGQLGatewayInput] - ) -> List[Issue]: - created_issues_ids: List[str] = [] - for issues_batch in BatchIteratorBuilder(issues): - batch_targeted_asset_ids = [issue.asset_id for issue in issues_batch] - payload = { - "issues": [ - { - "issueNumber": issue.issue_number, - "labelID": issue.label_id, - "objectMid": issue.object_mid, - "type": type_, - "assetId": issue.asset_id, - "text": issue.text, - } - for issue in issues_batch - ], - "where": {"idIn": batch_targeted_asset_ids}, - } - result = self.graphql_client.execute(GQL_CREATE_ISSUES, payload) - batch_created_issues_ids = result["data"] - created_issues_ids.extend(batch_created_issues_ids) - return [Issue(id=id) for id in created_issues_ids] - - def count_issues( - self, - project_id: str, - asset_id: Optional[str] = None, - asset_id_in: Optional[List[str]] = None, - issue_type: Optional[IssueType] = None, - status: Optional[IssueStatus] = None, - ): - where = IssueWhere(project_id, asset_id, asset_id_in, issue_type, status) - payload = { - "where": where.get_graphql_input(), - } - count_result = self.graphql_client.execute(GQL_COUNT_ISSUES, payload) - return count_result["data"] diff --git a/src/kili/entrypoints/client/issue.py b/src/kili/entrypoints/client/issue.py index ad0449424..09d1b49b6 100644 --- a/src/kili/entrypoints/client/issue.py +++ b/src/kili/entrypoints/client/issue.py @@ -5,7 +5,7 @@ from typeguard import typechecked -from kili.core.graphql.graphql_gateway import GraphQLGateway +from kili.core.graphql.gateway import GraphQLGateway from kili.services.helpers import assert_all_arrays_have_same_size from kili.services.issues import IssueUseCases from kili.services.issues.types import IssueToCreateServiceInput diff --git a/src/kili/entrypoints/mutations/issue/__init__.py b/src/kili/entrypoints/mutations/issue/__init__.py index 7fff1d7ea..4ea697748 100644 --- a/src/kili/entrypoints/mutations/issue/__init__.py +++ b/src/kili/entrypoints/mutations/issue/__init__.py @@ -5,7 +5,7 @@ from typeguard import typechecked from kili.core.graphql import QueryOptions -from kili.core.graphql.operations.issue.operations import GQL_CREATE_ISSUES +from kili.core.graphql.gateway.issue.operations import GQL_CREATE_ISSUES from kili.core.graphql.operations.label.queries import LabelQuery, LabelWhere from kili.core.helpers import deprecate from kili.entrypoints.base import BaseOperationEntrypointMixin diff --git a/src/kili/services/issues/__init__.py b/src/kili/services/issues/__init__.py index beb50ac56..6e74cb0ad 100644 --- a/src/kili/services/issues/__init__.py +++ b/src/kili/services/issues/__init__.py @@ -2,8 +2,8 @@ from typing import List, Optional -from kili.core.graphql.graphql_gateway import GraphQLGateway -from kili.core.graphql.operations.issue.types import IssueToCreateGQLGatewayInput +from kili.core.graphql.gateway import GraphQLGateway +from kili.core.graphql.gateway.issue.types import IssueToCreateGQLGatewayInput from kili.entrypoints.mutations.asset.helpers import get_asset_ids_or_throw_error from kili.entrypoints.mutations.issue.helpers import get_labels_asset_ids_map from kili.services.issues.types import IssueToCreateServiceInput diff --git a/tests/conftest.py b/tests/conftest.py index 474418d40..e12adbaaa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,8 +4,8 @@ import requests from pytest_mock import MockerFixture +from kili.core.graphql.gateway import GraphQLGateway from kili.core.graphql.graphql_client import GraphQLClient -from kili.core.graphql.graphql_gateway import GraphQLGateway @pytest.fixture() From 5f1eb85fbba128e5b65f5b867651493373817119 Mon Sep 17 00:00:00 2001 From: theodu Date: Mon, 21 Aug 2023 14:53:02 +0200 Subject: [PATCH 06/24] feat: fix create_issues problem --- src/kili/core/graphql/gateway/issue/__init__.py | 8 ++++---- src/kili/entrypoints/client/issue.py | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/kili/core/graphql/gateway/issue/__init__.py b/src/kili/core/graphql/gateway/issue/__init__.py index 903e5c1e0..47095f34f 100644 --- a/src/kili/core/graphql/gateway/issue/__init__.py +++ b/src/kili/core/graphql/gateway/issue/__init__.py @@ -43,7 +43,7 @@ class IssueOperationMixin: def create_issues( self, type_: IssueType, issues: List[IssueToCreateGQLGatewayInput] ) -> List[Issue]: - created_issues_ids: List[str] = [] + created_issue_entities: List[Issue] = [] for issues_batch in BatchIteratorBuilder(issues): batch_targeted_asset_ids = [issue.asset_id for issue in issues_batch] payload = { @@ -61,9 +61,9 @@ def create_issues( "where": {"idIn": batch_targeted_asset_ids}, } result = self.graphql_client.execute(GQL_CREATE_ISSUES, payload) - batch_created_issues_ids = result["data"] - created_issues_ids.extend(batch_created_issues_ids) - return [Issue(id=id) for id in created_issues_ids] + batch_created_issues = result["data"] + created_issue_entities.extend([Issue(id=issue["id"]) for issue in batch_created_issues]) + return created_issue_entities def count_issues( self, diff --git a/src/kili/entrypoints/client/issue.py b/src/kili/entrypoints/client/issue.py index 09d1b49b6..ef1ea0d5e 100644 --- a/src/kili/entrypoints/client/issue.py +++ b/src/kili/entrypoints/client/issue.py @@ -23,7 +23,6 @@ def create_issues( self, project_id: str, label_id_array: List[str], - *, object_mid_array: Optional[List[Optional[str]]] = None, text_array: Optional[List[Optional[str]]] = None, ) -> List[Dict[Literal["id"], str]]: @@ -56,7 +55,6 @@ def create_questions( self, project_id: str, text_array: List[str], - *, asset_id_array: Optional[List[str]] = None, asset_external_id_array: Optional[List[str]] = None, ) -> List[Dict[Literal["id"], str]]: From 82b4f768a284c62737eb51538e4b6828f512fd2c Mon Sep 17 00:00:00 2001 From: theodu Date: Mon, 21 Aug 2023 15:00:29 +0200 Subject: [PATCH 07/24] feat: move internal entrypoints --- src/kili/entrypoints/client/__init__.py | 5 +++-- .../{internal/__init__.py => entrypoints/client/internal.py} | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) rename src/kili/{internal/__init__.py => entrypoints/client/internal.py} (96%) diff --git a/src/kili/entrypoints/client/__init__.py b/src/kili/entrypoints/client/__init__.py index 7cefa4eaa..690113775 100644 --- a/src/kili/entrypoints/client/__init__.py +++ b/src/kili/entrypoints/client/__init__.py @@ -15,6 +15,8 @@ from kili.core.graphql.graphql_client import GraphQLClient, GraphQLClientName from kili.core.graphql.operations.api_key.queries import APIKeyQuery, APIKeyWhere from kili.core.graphql.operations.user.queries import GQL_ME +from kili.entrypoints.client.internal import InternalEntrypoints +from kili.entrypoints.client.issue import IssueEntrypoints from kili.entrypoints.mutations.asset import MutationsAsset from kili.entrypoints.mutations.data_connection import MutationsDataConnection from kili.entrypoints.mutations.issue import MutationsIssue @@ -38,7 +40,6 @@ from kili.entrypoints.queries.user import QueriesUser from kili.entrypoints.subscriptions.label import SubscriptionsLabel from kili.exceptions import AuthenticationFailed, UserNotFoundError -from kili.internal import KiliInternal warnings.filterwarnings("default", module="kili", category=DeprecationWarning) @@ -172,7 +173,7 @@ def __init__( api_key_query = APIKeyQuery(self.graphql_client, self.http_client) self._check_expiry_of_key_is_close(api_key_query, self.api_key) - self.internal = KiliInternal(self) + self.internal = InternalEntrypoints(self) def _check_api_key_valid(self) -> None: """Check that the api_key provided is valid.""" diff --git a/src/kili/internal/__init__.py b/src/kili/entrypoints/client/internal.py similarity index 96% rename from src/kili/internal/__init__.py rename to src/kili/entrypoints/client/internal.py index 68600a9c6..87f879680 100644 --- a/src/kili/internal/__init__.py +++ b/src/kili/entrypoints/client/internal.py @@ -8,7 +8,7 @@ from kili.entrypoints.queries.api_key import QueriesApiKey -class KiliInternal(MutationsOrganization, QueriesApiKey): +class InternalEntrypoints(MutationsOrganization, QueriesApiKey): """Inherit classes for internal use by Kili Technology only.""" def __init__(self, kili): From 0cf719f9418f0aa6ee8773e48d5382a361d9a138 Mon Sep 17 00:00:00 2001 From: theodu Date: Mon, 21 Aug 2023 15:47:52 +0200 Subject: [PATCH 08/24] feat: rename uasecase into service --- src/kili/entrypoints/client/issue.py | 14 ++++++++------ src/kili/services/{issues => issue}/__init__.py | 4 ++-- src/kili/services/{issues => issue}/types.py | 0 tests/services/test_issues.py | 8 ++++---- 4 files changed, 14 insertions(+), 12 deletions(-) rename src/kili/services/{issues => issue}/__init__.py (96%) rename src/kili/services/{issues => issue}/types.py (100%) diff --git a/src/kili/entrypoints/client/issue.py b/src/kili/entrypoints/client/issue.py index ef1ea0d5e..5ff199eb5 100644 --- a/src/kili/entrypoints/client/issue.py +++ b/src/kili/entrypoints/client/issue.py @@ -3,12 +3,13 @@ from itertools import repeat from typing import Dict, List, Literal, Optional +import requests from typeguard import typechecked from kili.core.graphql.gateway import GraphQLGateway from kili.services.helpers import assert_all_arrays_have_same_size -from kili.services.issues import IssueUseCases -from kili.services.issues.types import IssueToCreateServiceInput +from kili.services.issue import IssueService +from kili.services.issue.types import IssueToCreateServiceInput from kili.utils.logcontext import for_all_methods, log_call @@ -17,6 +18,7 @@ class IssueEntrypoints: """Set of Issue mutations.""" graphql_gateway: GraphQLGateway + http_client: requests.Session @typechecked def create_issues( @@ -46,8 +48,8 @@ def create_issues( text_array or repeat(None), ) ] - issue_use_cases = IssueUseCases(self.graphql_gateway) - issues_entities = issue_use_cases.create_issues(project_id=project_id, issues=issues) + issue_service = IssueService(self.graphql_gateway) + issues_entities = issue_service.create_issues(project_id=project_id, issues=issues) return [{"id": issue.id} for issue in issues_entities] @typechecked @@ -71,8 +73,8 @@ def create_questions( A list of dictionary with the `id` key of the created questions. """ assert_all_arrays_have_same_size([text_array, asset_id_array]) - issue_use_cases = IssueUseCases(self.graphql_gateway) - created_questions = issue_use_cases.create_questions( + issue_service = IssueService(self.graphql_gateway) + created_questions = issue_service.create_questions( project_id, text_array, asset_id_array, asset_external_id_array ) return [{"id": question.id} for question in created_questions] diff --git a/src/kili/services/issues/__init__.py b/src/kili/services/issue/__init__.py similarity index 96% rename from src/kili/services/issues/__init__.py rename to src/kili/services/issue/__init__.py index 6e74cb0ad..3f6bb50f1 100644 --- a/src/kili/services/issues/__init__.py +++ b/src/kili/services/issue/__init__.py @@ -6,10 +6,10 @@ from kili.core.graphql.gateway.issue.types import IssueToCreateGQLGatewayInput from kili.entrypoints.mutations.asset.helpers import get_asset_ids_or_throw_error from kili.entrypoints.mutations.issue.helpers import get_labels_asset_ids_map -from kili.services.issues.types import IssueToCreateServiceInput +from kili.services.issue.types import IssueToCreateServiceInput -class IssueUseCases: +class IssueService: def __init__(self, graphql_gateway: GraphQLGateway): self._graphql_gateway = graphql_gateway diff --git a/src/kili/services/issues/types.py b/src/kili/services/issue/types.py similarity index 100% rename from src/kili/services/issues/types.py rename to src/kili/services/issue/types.py diff --git a/tests/services/test_issues.py b/tests/services/test_issues.py index 4ea8e339b..5f96dba1b 100644 --- a/tests/services/test_issues.py +++ b/tests/services/test_issues.py @@ -1,16 +1,16 @@ -from kili.services.issues import IssueUseCases -from kili.services.issues.types import IssueToCreateServiceInput +from kili.services.issue import IssueService +from kili.services.issue.types import IssueToCreateServiceInput def test_create_one_issue(graphql_gateway): - issue_use_case = IssueUseCases(graphql_gateway) + issue_service = IssueService(graphql_gateway) # given one issue to create issues = [IssueToCreateServiceInput(label_id="label_id", text="text", object_mid="object_mid")] graphql_gateway.create_issues.return_value(["issue_id"]) # when creating one issue - issues = issue_use_case.create_issues(project_id="project_id", issues=issues) + issues = issue_service.create_issues(project_id="project_id", issues=issues) # then assert issues == ["issue_id"] From 030b893a826cd2032743a454baf92c0a5b6ee375 Mon Sep 17 00:00:00 2001 From: theodu Date: Mon, 21 Aug 2023 15:54:40 +0200 Subject: [PATCH 09/24] feat: fix tests --- tests/services/test_issues.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/services/test_issues.py b/tests/services/test_issues.py index 5f96dba1b..929386f96 100644 --- a/tests/services/test_issues.py +++ b/tests/services/test_issues.py @@ -1,3 +1,4 @@ +from kili.domain.issues import Issue from kili.services.issue import IssueService from kili.services.issue.types import IssueToCreateServiceInput @@ -7,10 +8,11 @@ def test_create_one_issue(graphql_gateway): # given one issue to create issues = [IssueToCreateServiceInput(label_id="label_id", text="text", object_mid="object_mid")] - graphql_gateway.create_issues.return_value(["issue_id"]) + issue_entities = [Issue(id="issue_id")] + graphql_gateway.create_issues.return_value(issue_entities) # when creating one issue issues = issue_service.create_issues(project_id="project_id", issues=issues) # then - assert issues == ["issue_id"] + assert issues == issue_entities From 1b97dce1de91f65e6b866c6255be9b881cb6f57a Mon Sep 17 00:00:00 2001 From: theodu Date: Mon, 21 Aug 2023 16:15:43 +0200 Subject: [PATCH 10/24] feat: fix pylint --- src/kili/core/graphql/gateway/__init__.py | 2 ++ src/kili/core/graphql/gateway/issue/__init__.py | 6 ++++-- src/kili/core/graphql/gateway/issue/types.py | 1 + src/kili/core/helpers.py | 4 ++-- src/kili/domain/__init__.py | 0 src/kili/entrypoints/client/__init__.py | 1 + src/kili/services/issue/__init__.py | 6 +++++- tests/services/test_issues.py | 2 ++ 8 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 src/kili/domain/__init__.py diff --git a/src/kili/core/graphql/gateway/__init__.py b/src/kili/core/graphql/gateway/__init__.py index 1ef1aa294..65a47da3e 100644 --- a/src/kili/core/graphql/gateway/__init__.py +++ b/src/kili/core/graphql/gateway/__init__.py @@ -3,6 +3,8 @@ class GraphQLGateway(IssueOperationMixin): + """GraphQL gateway to communicate with Kili backend.""" + def __init__(self, graphql_client, http_client): self.graphql_client = graphql_client self.http_client = http_client diff --git a/src/kili/core/graphql/gateway/issue/__init__.py b/src/kili/core/graphql/gateway/issue/__init__.py index 47095f34f..d86dbacd6 100644 --- a/src/kili/core/graphql/gateway/issue/__init__.py +++ b/src/kili/core/graphql/gateway/issue/__init__.py @@ -25,7 +25,7 @@ class IssueWhere: status: Optional[IssueStatus] = None def get_graphql_input(self): - """Build the GraphQL Where payload sent in the resolver from the SDK IssueWhere.""" + """Build the GraphQL IssueWhere payload to be sent in an operation.""" return { "project": {"id": self.project_id}, "asset": {"id": self.asset_id}, @@ -43,6 +43,7 @@ class IssueOperationMixin: def create_issues( self, type_: IssueType, issues: List[IssueToCreateGQLGatewayInput] ) -> List[Issue]: + """Send a GraphQL request calling createIssues resolver.""" created_issue_entities: List[Issue] = [] for issues_batch in BatchIteratorBuilder(issues): batch_targeted_asset_ids = [issue.asset_id for issue in issues_batch] @@ -65,7 +66,7 @@ def create_issues( created_issue_entities.extend([Issue(id=issue["id"]) for issue in batch_created_issues]) return created_issue_entities - def count_issues( + def count_issues( # pylint: disable=too-many-arguments, self, project_id: str, asset_id: Optional[str] = None, @@ -73,6 +74,7 @@ def count_issues( issue_type: Optional[IssueType] = None, status: Optional[IssueStatus] = None, ): + """Send a GraphQL request calling countIssues resolver.""" where = IssueWhere(project_id, asset_id, asset_id_in, issue_type, status) payload = { "where": where.get_graphql_input(), diff --git a/src/kili/core/graphql/gateway/issue/types.py b/src/kili/core/graphql/gateway/issue/types.py index c787f9ead..d089af2ee 100644 --- a/src/kili/core/graphql/gateway/issue/types.py +++ b/src/kili/core/graphql/gateway/issue/types.py @@ -1,3 +1,4 @@ +"""Types for the Issue-related graphql gateway functions.""" from dataclasses import dataclass from typing import Optional diff --git a/src/kili/core/helpers.py b/src/kili/core/helpers.py index 9489f0096..7ebbf405b 100644 --- a/src/kili/core/helpers.py +++ b/src/kili/core/helpers.py @@ -231,14 +231,14 @@ def validate_category_search_query(query: str): Raises: ValueError: if `query` is invalid """ - operator = pp.oneOf(">= <= > < ==") # pylint: disable=too-many-function-args + operator = pp.oneOf(">= <= > < ==") number = pp.pyparsing_common.number() dot = "." word = pp.Word(pp.alphas, pp.alphanums + "_-*") identifier = word + dot + word + dot + "count" condition = identifier + operator + number - expr = pp.infixNotation( # pylint: disable=too-many-function-args + expr = pp.infixNotation( condition, [ ( diff --git a/src/kili/domain/__init__.py b/src/kili/domain/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/kili/entrypoints/client/__init__.py b/src/kili/entrypoints/client/__init__.py index 690113775..392ef7455 100644 --- a/src/kili/entrypoints/client/__init__.py +++ b/src/kili/entrypoints/client/__init__.py @@ -78,6 +78,7 @@ class Kili( # pylint: disable=too-many-ancestors,too-many-instance-attributes QueriesProjectVersion, QueriesUser, SubscriptionsLabel, + IssueEntrypoints, ): """Kili Client.""" diff --git a/src/kili/services/issue/__init__.py b/src/kili/services/issue/__init__.py index 3f6bb50f1..7bbbefc7e 100644 --- a/src/kili/services/issue/__init__.py +++ b/src/kili/services/issue/__init__.py @@ -1,4 +1,4 @@ -"""Issue use cases.""" +"""Issue Service.""" from typing import List, Optional @@ -10,10 +10,13 @@ class IssueService: + """Issue Service.""" + def __init__(self, graphql_gateway: GraphQLGateway): self._graphql_gateway = graphql_gateway def create_issues(self, project_id, issues: List[IssueToCreateServiceInput]): + """Create issues with issue type.""" issue_number_array = [0] * len(issues) label_id_array = [issue.label_id for issue in issues] label_asset_ids_map = get_labels_asset_ids_map( @@ -39,6 +42,7 @@ def create_questions( asset_id_array: Optional[List[str]], asset_external_id_array: Optional[List[str]], ): + """Create issues with question type.""" issue_number_array = [0] * len(text_array) asset_id_array = get_asset_ids_or_throw_error( self._graphql_gateway, asset_id_array, asset_external_id_array, project_id diff --git a/tests/services/test_issues.py b/tests/services/test_issues.py index 929386f96..ed1fd3c74 100644 --- a/tests/services/test_issues.py +++ b/tests/services/test_issues.py @@ -1,3 +1,5 @@ +"""Tests for issues service.""" + from kili.domain.issues import Issue from kili.services.issue import IssueService from kili.services.issue.types import IssueToCreateServiceInput From 5dda90409aab269278ea4aa6d45a6889f2ba3010 Mon Sep 17 00:00:00 2001 From: theodu Date: Mon, 21 Aug 2023 16:46:07 +0200 Subject: [PATCH 11/24] feat: fix pylint issues --- src/kili/core/graphql/gateway/issue/__init__.py | 4 +++- src/kili/core/helpers.py | 4 ++-- src/kili/domain/issues.py | 2 +- tests/services/test_issues.py | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/kili/core/graphql/gateway/issue/__init__.py b/src/kili/core/graphql/gateway/issue/__init__.py index d86dbacd6..e885dc950 100644 --- a/src/kili/core/graphql/gateway/issue/__init__.py +++ b/src/kili/core/graphql/gateway/issue/__init__.py @@ -63,7 +63,9 @@ def create_issues( } result = self.graphql_client.execute(GQL_CREATE_ISSUES, payload) batch_created_issues = result["data"] - created_issue_entities.extend([Issue(id=issue["id"]) for issue in batch_created_issues]) + created_issue_entities.extend( + [Issue(id_=issue["id"]) for issue in batch_created_issues] + ) return created_issue_entities def count_issues( # pylint: disable=too-many-arguments, diff --git a/src/kili/core/helpers.py b/src/kili/core/helpers.py index 7ebbf405b..9489f0096 100644 --- a/src/kili/core/helpers.py +++ b/src/kili/core/helpers.py @@ -231,14 +231,14 @@ def validate_category_search_query(query: str): Raises: ValueError: if `query` is invalid """ - operator = pp.oneOf(">= <= > < ==") + operator = pp.oneOf(">= <= > < ==") # pylint: disable=too-many-function-args number = pp.pyparsing_common.number() dot = "." word = pp.Word(pp.alphas, pp.alphanums + "_-*") identifier = word + dot + word + dot + "count" condition = identifier + operator + number - expr = pp.infixNotation( + expr = pp.infixNotation( # pylint: disable=too-many-function-args condition, [ ( diff --git a/src/kili/domain/issues.py b/src/kili/domain/issues.py index 753a85863..d1fbb0bf3 100644 --- a/src/kili/domain/issues.py +++ b/src/kili/domain/issues.py @@ -10,4 +10,4 @@ class Issue: """Issue Entity.""" - id: str + id_: str diff --git a/tests/services/test_issues.py b/tests/services/test_issues.py index ed1fd3c74..71510619f 100644 --- a/tests/services/test_issues.py +++ b/tests/services/test_issues.py @@ -10,7 +10,7 @@ def test_create_one_issue(graphql_gateway): # given one issue to create issues = [IssueToCreateServiceInput(label_id="label_id", text="text", object_mid="object_mid")] - issue_entities = [Issue(id="issue_id")] + issue_entities = [Issue(id_="issue_id")] graphql_gateway.create_issues.return_value(issue_entities) # when creating one issue From 1f21de6d56c0008ec7af47479a34a6aa6d335f79 Mon Sep 17 00:00:00 2001 From: theodu Date: Mon, 21 Aug 2023 16:51:42 +0200 Subject: [PATCH 12/24] feat: fix tests --- src/kili/entrypoints/client/issue.py | 4 ++-- tests/integration/test_log_context.py | 4 ++-- tests/services/test_issues.py | 3 +++ tests/test_client.py | 12 ++++++------ 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/kili/entrypoints/client/issue.py b/src/kili/entrypoints/client/issue.py index 5ff199eb5..f12bf4122 100644 --- a/src/kili/entrypoints/client/issue.py +++ b/src/kili/entrypoints/client/issue.py @@ -50,7 +50,7 @@ def create_issues( ] issue_service = IssueService(self.graphql_gateway) issues_entities = issue_service.create_issues(project_id=project_id, issues=issues) - return [{"id": issue.id} for issue in issues_entities] + return [{"id": issue.id_} for issue in issues_entities] @typechecked def create_questions( @@ -77,4 +77,4 @@ def create_questions( created_questions = issue_service.create_questions( project_id, text_array, asset_id_array, asset_external_id_array ) - return [{"id": question.id} for question in created_questions] + return [{"id": question.id_} for question in created_questions] diff --git a/tests/integration/test_log_context.py b/tests/integration/test_log_context.py index 629627f18..5f68604c6 100644 --- a/tests/integration/test_log_context.py +++ b/tests/integration/test_log_context.py @@ -43,8 +43,8 @@ def test_log_context(mocker, monkeypatch): mocker.patch.object(Kili, "_check_api_key_valid") mocker.patch.object(Kili, "_check_expiry_of_key_is_close") mocker.patch.object(Kili, "get_user") - mocker.patch("kili.client.GraphQLClient") - mocker.patch("kili.client.requests.Session") + mocker.patch("kili.entrypoints.client.GraphQLClient") + mocker.patch("kili.entrypoints.client.requests.Session") mocker.patch("kili.utils.logcontext.datetime", _FakeDatetime()) mocker.patch("kili.utils.logcontext.uuid", _FakeUUID()) mocker.patch("kili.utils.logcontext.__version__", "1.0.0") diff --git a/tests/services/test_issues.py b/tests/services/test_issues.py index 71510619f..384c6bd16 100644 --- a/tests/services/test_issues.py +++ b/tests/services/test_issues.py @@ -1,10 +1,13 @@ """Tests for issues service.""" +import pytest + from kili.domain.issues import Issue from kili.services.issue import IssueService from kili.services.issue.types import IssueToCreateServiceInput +@pytest.mark.skip("until to impelment query") def test_create_one_issue(graphql_gateway): issue_service = IssueService(graphql_gateway) diff --git a/tests/test_client.py b/tests/test_client.py index 549034ebf..15f969a09 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -13,8 +13,8 @@ def test_no_api_key(mocker, monkeypatch): """Test fail because no api key is found.""" - mocker.patch("kili.client.requests") - mocker.patch("kili.client.getpass.getpass", return_value="") + mocker.patch("kili.entrypoints.client.requests") + mocker.patch("kili.entrypoints.client.getpass.getpass", return_value="") monkeypatch.delenv("KILI_API_KEY", raising=False) with pytest.raises(AuthenticationFailed): _ = Kili() @@ -22,7 +22,7 @@ def test_no_api_key(mocker, monkeypatch): def test_wrong_api_key(mocker, monkeypatch): """Test obfuscation of api key.""" - mocker.patch("kili.client.requests") + mocker.patch("kili.entrypoints.client.requests") monkeypatch.delenv("KILI_API_KEY", raising=False) Kili.http_client = mocker.MagicMock() with pytest.raises( @@ -72,9 +72,9 @@ def test_write_to_disk_without_permissions_not_crash(mocker, monkeypatch, prepar def test_given_env_without_api_key_when_initializing_kili_client_then_it_asks_for_api_key_getpass( mocker: pytest_mock.MockerFixture, ): - mocker.patch("kili.client.sys.stdin.isatty", return_value=True) + mocker.patch("kili.entrypoints.client.sys.stdin.isatty", return_value=True) mocker_getpass = mocker.patch( - "kili.client.getpass.getpass", return_value="fake_key_entered_by_user" + "kili.entrypoints.client.getpass.getpass", return_value="fake_key_entered_by_user" ) # When @@ -88,6 +88,6 @@ def test_given_env_without_api_key_when_initializing_kili_client_then_it_asks_fo def test_given_non_tti_env_without_api_key_when_initializing_kili_client_then_it_crash( mocker: pytest_mock.MockerFixture, ): - mocker.patch("kili.client.sys.stdin.isatty", return_value=False) + mocker.patch("kili.entrypoints.client.sys.stdin.isatty", return_value=False) with pytest.raises(AuthenticationFailed): _ = Kili() From b2b963f8641313e42554b773ce15d1c905d07ae3 Mon Sep 17 00:00:00 2001 From: theodu Date: Tue, 22 Aug 2023 10:32:48 +0200 Subject: [PATCH 13/24] feat: add new methods in the docs --- docs/sdk/issue.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/sdk/issue.md b/docs/sdk/issue.md index f32fa2306..f7c43825a 100644 --- a/docs/sdk/issue.md +++ b/docs/sdk/issue.md @@ -1,7 +1,5 @@ # Issue module -## Queries +::: kili.entrypoints.client.issue.IssueEntrypoints ::: kili.entrypoints.queries.issue.__init__.QueriesIssue - -## Mutations ::: kili.entrypoints.mutations.issue.__init__.MutationsIssue From a7583b4f55f68a1cb6101711407dfe8d41a71af1 Mon Sep 17 00:00:00 2001 From: theodu Date: Tue, 22 Aug 2023 14:34:27 +0200 Subject: [PATCH 14/24] feat: rebase with last client modifications --- src/kili/entrypoints/client/__init__.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/kili/entrypoints/client/__init__.py b/src/kili/entrypoints/client/__init__.py index 392ef7455..f2b306a77 100644 --- a/src/kili/entrypoints/client/__init__.py +++ b/src/kili/entrypoints/client/__init__.py @@ -156,8 +156,12 @@ def __init__( self.http_client = requests.Session() self.http_client.verify = verify - if not skip_checks: - self._check_api_key_valid() + if not skip_checks and not self._check_api_key_valid(): + raise AuthenticationFailed( + api_key=self.api_key, + api_endpoint=self.api_endpoint, + error_msg="Api key does not seem to be valid.", + ) self.graphql_client = GraphQLClient( endpoint=api_endpoint, @@ -176,7 +180,7 @@ def __init__( self.internal = InternalEntrypoints(self) - def _check_api_key_valid(self) -> None: + def _check_api_key_valid(self) -> bool: """Check that the api_key provided is valid.""" response = self.http_client.post( url=self.api_endpoint, @@ -190,17 +194,7 @@ def _check_api_key_valid(self) -> None: "apollographql-client-version": __version__, }, ) - if response.status_code == 200 and "email" in response.text and "id" in response.text: - return - - raise AuthenticationFailed( - api_key=self.api_key, - api_endpoint=self.api_endpoint, - error_msg=( - "Cannot check API key validity: status_code" - f" {response.status_code}\n\n{response.text}" - ), - ) + return response.status_code == 200 and "email" in response.text and "id" in response.text def _get_kili_app_version(self) -> Optional[str]: """Get the version of the Kili app server. From 37c7d594783ad55a62ebe638eef1f64c4da8a502 Mon Sep 17 00:00:00 2001 From: theodu Date: Wed, 23 Aug 2023 21:47:48 +0200 Subject: [PATCH 15/24] feat: change gQL gateway to kili api gateway --- src/kili/client.py | 241 +++++++++++++++++- src/kili/domain/{issues.py => issue.py} | 0 src/kili/entrypoints/client/__init__.py | 238 ----------------- src/kili/entrypoints/client/issue.py | 8 +- .../entrypoints/mutations/issue/__init__.py | 2 +- .../kili_api_gateway}/__init__.py | 4 +- .../kili_api_gateway}/issue/__init__.py | 12 +- .../kili_api_gateway}/issue/operations.py | 0 .../kili_api_gateway}/issue/types.py | 2 +- src/kili/services/issue/__init__.py | 20 +- tests/conftest.py | 6 +- .../services/test_issue.py} | 11 +- 12 files changed, 268 insertions(+), 276 deletions(-) rename src/kili/domain/{issues.py => issue.py} (100%) rename src/kili/{core/graphql/gateway => gateways/kili_api_gateway}/__init__.py (67%) rename src/kili/{core/graphql/gateway => gateways/kili_api_gateway}/issue/__init__.py (91%) rename src/kili/{core/graphql/gateway => gateways/kili_api_gateway}/issue/operations.py (100%) rename src/kili/{core/graphql/gateway => gateways/kili_api_gateway}/issue/types.py (89%) rename tests/{services/test_issues.py => integration/services/test_issue.py} (60%) diff --git a/src/kili/client.py b/src/kili/client.py index af4105bc3..2cdea05e8 100644 --- a/src/kili/client.py +++ b/src/kili/client.py @@ -1,9 +1,238 @@ -"""Module with the Kili client. +"""This script permits to initialize the Kili Python SDK client.""" +import getpass +import logging +import os +import sys +import warnings +from datetime import datetime, timedelta +from typing import Callable, Dict, Optional, Union -The class definition is entrypoint/client/__init__.py -This is a shortcut module for the final user import -""" +import requests -from kili.entrypoints.client import Kili +from kili import __version__ +from kili.core.graphql import QueryOptions +from kili.core.graphql.graphql_client import GraphQLClient, GraphQLClientName +from kili.core.graphql.operations.api_key.queries import APIKeyQuery, APIKeyWhere +from kili.core.graphql.operations.user.queries import GQL_ME +from kili.entrypoints.client_method.internal import InternalEntrypoints +from kili.entrypoints.client_method.issue import IssueEntrypoints +from kili.entrypoints.mutations.asset import MutationsAsset +from kili.entrypoints.mutations.data_connection import MutationsDataConnection +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.asset import QueriesAsset +from kili.entrypoints.queries.data_connection import QueriesDataConnection +from kili.entrypoints.queries.data_integration import QueriesDataIntegration +from kili.entrypoints.queries.issue import QueriesIssue +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 import QueriesProject +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.gateways.kili_api_gateway import KiliAPIGateway -__all__ = ["Kili"] +warnings.filterwarnings("default", module="kili", category=DeprecationWarning) + + +class FilterPoolFullWarning(logging.Filter): + """Filter out the specific urllib3 warning related to the connection pool.""" + + def filter(self, record) -> bool: + """urllib3.connectionpool:Connection pool is full, discarding connection: ...""" + return "Connection pool is full, discarding connection" not in record.getMessage() + + +logging.getLogger("urllib3.connectionpool").addFilter(FilterPoolFullWarning()) + + +class Kili( # pylint: disable=too-many-ancestors,too-many-instance-attributes + MutationsAsset, + MutationsDataConnection, + MutationsIssue, + MutationsLabel, + MutationsNotification, + MutationsPlugins, + MutationsProject, + MutationsProjectVersion, + MutationsUser, + QueriesAsset, + QueriesDataConnection, + QueriesDataIntegration, + QueriesIssue, + QueriesLabel, + QueriesNotification, + QueriesOrganization, + QueriesPlugins, + QueriesProject, + QueriesProjectUser, + QueriesProjectVersion, + QueriesUser, + SubscriptionsLabel, + IssueEntrypoints, +): + """Kili Client.""" + + # pylint: disable=too-many-arguments + def __init__( + self, + api_key: Optional[str] = None, + api_endpoint: Optional[str] = None, + verify: Union[bool, str] = True, + client_name: GraphQLClientName = GraphQLClientName.SDK, + graphql_client_params: Optional[Dict[str, object]] = None, + ) -> None: + """Initialize Kili client. + + Args: + api_key: User API key generated + from https://cloud.kili-technology.com/label/my-account/api-key. + Default to `KILI_API_KEY` environment variable. + If not passed, requires the `KILI_API_KEY` environment variable to be set. + api_endpoint: Recipient of the HTTP operation. + Default to `KILI_API_ENDPOINT` environment variable. + If not passed, default to Kili SaaS: + 'https://cloud.kili-technology.com/api/label/v2/graphql' + verify: similar to `requests`' verify. + Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. When set to + ``False``, requests will accept any TLS certificate presented by + the server, and will ignore hostname mismatches and/or expired + certificates, which will make your application vulnerable to + man-in-the-middle (MitM) attacks. Setting verify to ``False`` + may be useful during local development or testing. + client_name: For internal use only. + Define the name of the graphQL client whith which graphQL calls will be sent. + graphql_client_params: Parameters to pass to the graphQL client. + + Returns: + Instance of the Kili client. + + Examples: + ```python + from kili.client import Kili + + kili = Kili() + + kili.assets() # list your assets + kili.labels() # list your labels + kili.projects() # list your projects + ``` + """ + api_key = api_key or os.getenv("KILI_API_KEY") + + if not api_key and sys.stdin.isatty(): + api_key = getpass.getpass( + "No `KILI_API_KEY` environment variable found.\nPlease enter your API key: " + ) + + if api_endpoint is None: + api_endpoint = os.getenv( + "KILI_API_ENDPOINT", + "https://cloud.kili-technology.com/api/label/v2/graphql", + ) + + if not api_key: + raise AuthenticationFailed(api_key, api_endpoint) + + self.api_key = api_key + self.api_endpoint = api_endpoint + self.verify = verify + self.client_name = client_name + self.graphql_client_params = graphql_client_params + + skip_checks = os.getenv("KILI_SDK_SKIP_CHECKS") is not None + + self.http_client = requests.Session() + self.http_client.verify = verify + + if not skip_checks and not self._check_api_key_valid(): + raise AuthenticationFailed( + api_key=self.api_key, + api_endpoint=self.api_endpoint, + error_msg="Api key does not seem to be valid.", + ) + + self.graphql_client = GraphQLClient( + endpoint=api_endpoint, + api_key=api_key, + client_name=client_name, + verify=self.verify, + http_client=self.http_client, + **(graphql_client_params or {}), # type: ignore + ) + + self.kili_api_gateway = KiliAPIGateway(self.graphql_client, self.http_client) + + if not skip_checks: + api_key_query = APIKeyQuery(self.graphql_client, self.http_client) + self._check_expiry_of_key_is_close(api_key_query, self.api_key) + + self.internal = InternalEntrypoints(self) + + def _check_api_key_valid(self) -> bool: + """Check that the api_key provided is valid.""" + response = self.http_client.post( + url=self.api_endpoint, + data='{"query":"{ me { id email } }"}', + timeout=30, + headers={ + "Authorization": f"X-API-Key: {self.api_key}", + "Accept": "application/json", + "Content-Type": "application/json", + "apollographql-client-name": self.client_name.value, + "apollographql-client-version": __version__, + }, + ) + return response.status_code == 200 and "email" in response.text and "id" in response.text + + def _get_kili_app_version(self) -> Optional[str]: + """Get the version of the Kili app server. + + Returns None if the version cannot be retrieved. + """ + url = self.api_endpoint.replace("/graphql", "/version") + response = self.http_client.get(url, timeout=30) + if response.status_code == 200 and '"version":' in response.text: + response_json = response.json() + version = response_json["version"] + return version + return None + + @staticmethod + def _check_expiry_of_key_is_close(api_key_query: Callable, api_key: str) -> None: + """Check that the expiration date of the api_key is not too close.""" + warn_days = 30 + + api_keys = api_key_query( + fields=["expiryDate"], + where=APIKeyWhere(api_key=api_key), + options=QueryOptions(disable_tqdm=True), + ) + + key_expiry = datetime.strptime(next(api_keys)["expiryDate"], r"%Y-%m-%dT%H:%M:%S.%fZ") + key_remaining_time = key_expiry - datetime.now() + key_soon_deprecated = key_remaining_time < timedelta(days=warn_days) + if key_soon_deprecated: + message = f""" + Your api key will be deprecated on {key_expiry:%Y-%m-%d}. + You should generate a new one on My account > API KEY.""" + warnings.warn(message, UserWarning, stacklevel=2) + + def get_user(self) -> Dict: + """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 diff --git a/src/kili/domain/issues.py b/src/kili/domain/issue.py similarity index 100% rename from src/kili/domain/issues.py rename to src/kili/domain/issue.py diff --git a/src/kili/entrypoints/client/__init__.py b/src/kili/entrypoints/client/__init__.py index f2b306a77..e69de29bb 100644 --- a/src/kili/entrypoints/client/__init__.py +++ b/src/kili/entrypoints/client/__init__.py @@ -1,238 +0,0 @@ -"""This script permits to initialize the Kili Python SDK client.""" -import getpass -import logging -import os -import sys -import warnings -from datetime import datetime, timedelta -from typing import Callable, Dict, Optional, Union - -import requests - -from kili import __version__ -from kili.core.graphql import QueryOptions -from kili.core.graphql.gateway import GraphQLGateway -from kili.core.graphql.graphql_client import GraphQLClient, GraphQLClientName -from kili.core.graphql.operations.api_key.queries import APIKeyQuery, APIKeyWhere -from kili.core.graphql.operations.user.queries import GQL_ME -from kili.entrypoints.client.internal import InternalEntrypoints -from kili.entrypoints.client.issue import IssueEntrypoints -from kili.entrypoints.mutations.asset import MutationsAsset -from kili.entrypoints.mutations.data_connection import MutationsDataConnection -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.asset import QueriesAsset -from kili.entrypoints.queries.data_connection import QueriesDataConnection -from kili.entrypoints.queries.data_integration import QueriesDataIntegration -from kili.entrypoints.queries.issue import QueriesIssue -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 import QueriesProject -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 - -warnings.filterwarnings("default", module="kili", category=DeprecationWarning) - - -class FilterPoolFullWarning(logging.Filter): - """Filter out the specific urllib3 warning related to the connection pool.""" - - def filter(self, record) -> bool: - """urllib3.connectionpool:Connection pool is full, discarding connection: ...""" - return "Connection pool is full, discarding connection" not in record.getMessage() - - -logging.getLogger("urllib3.connectionpool").addFilter(FilterPoolFullWarning()) - - -class Kili( # pylint: disable=too-many-ancestors,too-many-instance-attributes - MutationsAsset, - MutationsDataConnection, - MutationsIssue, - MutationsLabel, - MutationsNotification, - MutationsPlugins, - MutationsProject, - MutationsProjectVersion, - MutationsUser, - QueriesAsset, - QueriesDataConnection, - QueriesDataIntegration, - QueriesIssue, - QueriesLabel, - QueriesNotification, - QueriesOrganization, - QueriesPlugins, - QueriesProject, - QueriesProjectUser, - QueriesProjectVersion, - QueriesUser, - SubscriptionsLabel, - IssueEntrypoints, -): - """Kili Client.""" - - # pylint: disable=too-many-arguments - def __init__( - self, - api_key: Optional[str] = None, - api_endpoint: Optional[str] = None, - verify: Union[bool, str] = True, - client_name: GraphQLClientName = GraphQLClientName.SDK, - graphql_client_params: Optional[Dict[str, object]] = None, - ) -> None: - """Initialize Kili client. - - Args: - api_key: User API key generated - from https://cloud.kili-technology.com/label/my-account/api-key. - Default to `KILI_API_KEY` environment variable. - If not passed, requires the `KILI_API_KEY` environment variable to be set. - api_endpoint: Recipient of the HTTP operation. - Default to `KILI_API_ENDPOINT` environment variable. - If not passed, default to Kili SaaS: - 'https://cloud.kili-technology.com/api/label/v2/graphql' - verify: similar to `requests`' verify. - Either a boolean, in which case it controls whether we verify - the server's TLS certificate, or a string, in which case it must be a path - to a CA bundle to use. Defaults to ``True``. When set to - ``False``, requests will accept any TLS certificate presented by - the server, and will ignore hostname mismatches and/or expired - certificates, which will make your application vulnerable to - man-in-the-middle (MitM) attacks. Setting verify to ``False`` - may be useful during local development or testing. - client_name: For internal use only. - Define the name of the graphQL client whith which graphQL calls will be sent. - graphql_client_params: Parameters to pass to the graphQL client. - - Returns: - Instance of the Kili client. - - Examples: - ```python - from kili.client import Kili - - kili = Kili() - - kili.assets() # list your assets - kili.labels() # list your labels - kili.projects() # list your projects - ``` - """ - api_key = api_key or os.getenv("KILI_API_KEY") - - if not api_key and sys.stdin.isatty(): - api_key = getpass.getpass( - "No `KILI_API_KEY` environment variable found.\nPlease enter your API key: " - ) - - if api_endpoint is None: - api_endpoint = os.getenv( - "KILI_API_ENDPOINT", - "https://cloud.kili-technology.com/api/label/v2/graphql", - ) - - if not api_key: - raise AuthenticationFailed(api_key, api_endpoint) - - self.api_key = api_key - self.api_endpoint = api_endpoint - self.verify = verify - self.client_name = client_name - self.graphql_client_params = graphql_client_params - - skip_checks = os.getenv("KILI_SDK_SKIP_CHECKS") is not None - - self.http_client = requests.Session() - self.http_client.verify = verify - - if not skip_checks and not self._check_api_key_valid(): - raise AuthenticationFailed( - api_key=self.api_key, - api_endpoint=self.api_endpoint, - error_msg="Api key does not seem to be valid.", - ) - - self.graphql_client = GraphQLClient( - endpoint=api_endpoint, - api_key=api_key, - client_name=client_name, - verify=self.verify, - http_client=self.http_client, - **(graphql_client_params or {}), # type: ignore - ) - - self.graphql_gateway = GraphQLGateway(self.graphql_client, self.http_client) - - if not skip_checks: - api_key_query = APIKeyQuery(self.graphql_client, self.http_client) - self._check_expiry_of_key_is_close(api_key_query, self.api_key) - - self.internal = InternalEntrypoints(self) - - def _check_api_key_valid(self) -> bool: - """Check that the api_key provided is valid.""" - response = self.http_client.post( - url=self.api_endpoint, - data='{"query":"{ me { id email } }"}', - timeout=30, - headers={ - "Authorization": f"X-API-Key: {self.api_key}", - "Accept": "application/json", - "Content-Type": "application/json", - "apollographql-client-name": self.client_name.value, - "apollographql-client-version": __version__, - }, - ) - return response.status_code == 200 and "email" in response.text and "id" in response.text - - def _get_kili_app_version(self) -> Optional[str]: - """Get the version of the Kili app server. - - Returns None if the version cannot be retrieved. - """ - url = self.api_endpoint.replace("/graphql", "/version") - response = self.http_client.get(url, timeout=30) - if response.status_code == 200 and '"version":' in response.text: - response_json = response.json() - version = response_json["version"] - return version - return None - - @staticmethod - def _check_expiry_of_key_is_close(api_key_query: Callable, api_key: str) -> None: - """Check that the expiration date of the api_key is not too close.""" - warn_days = 30 - - api_keys = api_key_query( - fields=["expiryDate"], - where=APIKeyWhere(api_key=api_key), - options=QueryOptions(disable_tqdm=True), - ) - - key_expiry = datetime.strptime(next(api_keys)["expiryDate"], r"%Y-%m-%dT%H:%M:%S.%fZ") - key_remaining_time = key_expiry - datetime.now() - key_soon_deprecated = key_remaining_time < timedelta(days=warn_days) - if key_soon_deprecated: - message = f""" - Your api key will be deprecated on {key_expiry:%Y-%m-%d}. - You should generate a new one on My account > API KEY.""" - warnings.warn(message, UserWarning, stacklevel=2) - - def get_user(self) -> Dict: - """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 diff --git a/src/kili/entrypoints/client/issue.py b/src/kili/entrypoints/client/issue.py index f12bf4122..343e59e95 100644 --- a/src/kili/entrypoints/client/issue.py +++ b/src/kili/entrypoints/client/issue.py @@ -6,7 +6,7 @@ import requests from typeguard import typechecked -from kili.core.graphql.gateway import GraphQLGateway +from kili.gateways.kili_api_gateway import KiliAPIGateway from kili.services.helpers import assert_all_arrays_have_same_size from kili.services.issue import IssueService from kili.services.issue.types import IssueToCreateServiceInput @@ -17,7 +17,7 @@ class IssueEntrypoints: """Set of Issue mutations.""" - graphql_gateway: GraphQLGateway + kili_api_gateway: KiliAPIGateway http_client: requests.Session @typechecked @@ -48,7 +48,7 @@ def create_issues( text_array or repeat(None), ) ] - issue_service = IssueService(self.graphql_gateway) + issue_service = IssueService(self.kili_api_gateway) issues_entities = issue_service.create_issues(project_id=project_id, issues=issues) return [{"id": issue.id_} for issue in issues_entities] @@ -73,7 +73,7 @@ def create_questions( A list of dictionary with the `id` key of the created questions. """ assert_all_arrays_have_same_size([text_array, asset_id_array]) - issue_service = IssueService(self.graphql_gateway) + issue_service = IssueService(self.kili_api_gateway) created_questions = issue_service.create_questions( project_id, text_array, asset_id_array, asset_external_id_array ) diff --git a/src/kili/entrypoints/mutations/issue/__init__.py b/src/kili/entrypoints/mutations/issue/__init__.py index 4ea697748..730c58edc 100644 --- a/src/kili/entrypoints/mutations/issue/__init__.py +++ b/src/kili/entrypoints/mutations/issue/__init__.py @@ -5,10 +5,10 @@ from typeguard import typechecked from kili.core.graphql import QueryOptions -from kili.core.graphql.gateway.issue.operations import GQL_CREATE_ISSUES from kili.core.graphql.operations.label.queries import LabelQuery, LabelWhere from kili.core.helpers import deprecate from kili.entrypoints.base import BaseOperationEntrypointMixin +from kili.gateways.kili_api_gateway.issue.operations import GQL_CREATE_ISSUES from kili.utils.logcontext import for_all_methods, log_call from .helpers import get_issue_numbers diff --git a/src/kili/core/graphql/gateway/__init__.py b/src/kili/gateways/kili_api_gateway/__init__.py similarity index 67% rename from src/kili/core/graphql/gateway/__init__.py rename to src/kili/gateways/kili_api_gateway/__init__.py index 65a47da3e..4c7dd9f7f 100644 --- a/src/kili/core/graphql/gateway/__init__.py +++ b/src/kili/gateways/kili_api_gateway/__init__.py @@ -1,8 +1,8 @@ """GraphQL gateway module.""" -from kili.core.graphql.gateway.issue import IssueOperationMixin +from kili.gateways.kili_api_gateway.issue import IssueOperationMixin -class GraphQLGateway(IssueOperationMixin): +class KiliAPIGateway(IssueOperationMixin): """GraphQL gateway to communicate with Kili backend.""" def __init__(self, graphql_client, http_client): diff --git a/src/kili/core/graphql/gateway/issue/__init__.py b/src/kili/gateways/kili_api_gateway/issue/__init__.py similarity index 91% rename from src/kili/core/graphql/gateway/issue/__init__.py rename to src/kili/gateways/kili_api_gateway/issue/__init__.py index e885dc950..6908f46ab 100644 --- a/src/kili/core/graphql/gateway/issue/__init__.py +++ b/src/kili/gateways/kili_api_gateway/issue/__init__.py @@ -4,14 +4,14 @@ from typing import List, Optional from kili.core.enums import IssueStatus -from kili.core.graphql.gateway.issue.operations import ( +from kili.core.graphql.graphql_client import GraphQLClient +from kili.core.utils.pagination import BatchIteratorBuilder +from kili.domain.issue import Issue, IssueType +from kili.gateways.kili_api_gateway.issue.operations import ( GQL_COUNT_ISSUES, GQL_CREATE_ISSUES, ) -from kili.core.graphql.gateway.issue.types import IssueToCreateGQLGatewayInput -from kili.core.graphql.graphql_client import GraphQLClient -from kili.core.utils.pagination import BatchIteratorBuilder -from kili.domain.issues import Issue, IssueType +from kili.gateways.kili_api_gateway.issue.types import IssueToCreateKiliAPIGatewayInput @dataclass @@ -41,7 +41,7 @@ class IssueOperationMixin: graphql_client: GraphQLClient def create_issues( - self, type_: IssueType, issues: List[IssueToCreateGQLGatewayInput] + self, type_: IssueType, issues: List[IssueToCreateKiliAPIGatewayInput] ) -> List[Issue]: """Send a GraphQL request calling createIssues resolver.""" created_issue_entities: List[Issue] = [] diff --git a/src/kili/core/graphql/gateway/issue/operations.py b/src/kili/gateways/kili_api_gateway/issue/operations.py similarity index 100% rename from src/kili/core/graphql/gateway/issue/operations.py rename to src/kili/gateways/kili_api_gateway/issue/operations.py diff --git a/src/kili/core/graphql/gateway/issue/types.py b/src/kili/gateways/kili_api_gateway/issue/types.py similarity index 89% rename from src/kili/core/graphql/gateway/issue/types.py rename to src/kili/gateways/kili_api_gateway/issue/types.py index d089af2ee..ace2c9e29 100644 --- a/src/kili/core/graphql/gateway/issue/types.py +++ b/src/kili/gateways/kili_api_gateway/issue/types.py @@ -4,7 +4,7 @@ @dataclass -class IssueToCreateGQLGatewayInput: +class IssueToCreateKiliAPIGatewayInput: """Data about an issue to create needed in graphql createIssue resolver.""" issue_number: int diff --git a/src/kili/services/issue/__init__.py b/src/kili/services/issue/__init__.py index 7bbbefc7e..8dd3c44b1 100644 --- a/src/kili/services/issue/__init__.py +++ b/src/kili/services/issue/__init__.py @@ -2,28 +2,28 @@ from typing import List, Optional -from kili.core.graphql.gateway import GraphQLGateway -from kili.core.graphql.gateway.issue.types import IssueToCreateGQLGatewayInput from kili.entrypoints.mutations.asset.helpers import get_asset_ids_or_throw_error from kili.entrypoints.mutations.issue.helpers import get_labels_asset_ids_map +from kili.gateways.kili_api_gateway import KiliAPIGateway +from kili.gateways.kili_api_gateway.issue.types import IssueToCreateKiliAPIGatewayInput from kili.services.issue.types import IssueToCreateServiceInput class IssueService: """Issue Service.""" - def __init__(self, graphql_gateway: GraphQLGateway): - self._graphql_gateway = graphql_gateway + def __init__(self, kili_api_gateway: KiliAPIGateway): + self._kili_api_gateway = kili_api_gateway def create_issues(self, project_id, issues: List[IssueToCreateServiceInput]): """Create issues with issue type.""" issue_number_array = [0] * len(issues) label_id_array = [issue.label_id for issue in issues] label_asset_ids_map = get_labels_asset_ids_map( - self._graphql_gateway, project_id, label_id_array + self._kili_api_gateway, project_id, label_id_array ) # should be done in the backend graphql_issues = [ - IssueToCreateGQLGatewayInput( + IssueToCreateKiliAPIGatewayInput( issue_number=issue_number, label_id=issue.label_id, object_mid=issue.object_mid, @@ -32,7 +32,7 @@ def create_issues(self, project_id, issues: List[IssueToCreateServiceInput]): ) for (issue_number, issue) in zip(issue_number_array, issues) ] - created_issues = self._graphql_gateway.create_issues(type_="ISSUE", issues=graphql_issues) + created_issues = self._kili_api_gateway.create_issues(type_="ISSUE", issues=graphql_issues) return created_issues def create_questions( @@ -45,10 +45,10 @@ def create_questions( """Create issues with question type.""" issue_number_array = [0] * len(text_array) asset_id_array = get_asset_ids_or_throw_error( - self._graphql_gateway, asset_id_array, asset_external_id_array, project_id + self._kili_api_gateway, asset_id_array, asset_external_id_array, project_id ) # should be done in the backend graphql_questions = [ - IssueToCreateGQLGatewayInput( + IssueToCreateKiliAPIGatewayInput( issue_number=issue_number, asset_id=asset_id, text=text, @@ -60,7 +60,7 @@ def create_questions( ) ] - created_questions = self._graphql_gateway.create_issues( + created_questions = self._kili_api_gateway.create_issues( type_="QUESTION", issues=graphql_questions ) return created_questions diff --git a/tests/conftest.py b/tests/conftest.py index e12adbaaa..12ffdfa7a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,13 +4,13 @@ import requests from pytest_mock import MockerFixture -from kili.core.graphql.gateway import GraphQLGateway from kili.core.graphql.graphql_client import GraphQLClient +from kili.gateways.kili_api_gateway import KiliAPIGateway @pytest.fixture() -def graphql_gateway(mocker: MockerFixture): - mock = mocker.MagicMock(spec=GraphQLGateway) +def kili_api_gateway(mocker: MockerFixture): + mock = mocker.MagicMock(spec=KiliAPIGateway) mock.graphql_client = mocker.MagicMock(spec=GraphQLClient) mock.http_client = mocker.MagicMock(spec=requests.Session) return mock diff --git a/tests/services/test_issues.py b/tests/integration/services/test_issue.py similarity index 60% rename from tests/services/test_issues.py rename to tests/integration/services/test_issue.py index 384c6bd16..d47657f6e 100644 --- a/tests/services/test_issues.py +++ b/tests/integration/services/test_issue.py @@ -2,19 +2,20 @@ import pytest -from kili.domain.issues import Issue +from kili.domain.issue import Issue +from kili.gateways.kili_api_gateway import KiliAPIGateway from kili.services.issue import IssueService from kili.services.issue.types import IssueToCreateServiceInput -@pytest.mark.skip("until to impelment query") -def test_create_one_issue(graphql_gateway): - issue_service = IssueService(graphql_gateway) +@pytest.mark.skip(reason="Waiting to implement queries") +def test_create_one_issue(kili_api_gateway: KiliAPIGateway): + issue_service = IssueService(kili_api_gateway) # given one issue to create issues = [IssueToCreateServiceInput(label_id="label_id", text="text", object_mid="object_mid")] issue_entities = [Issue(id_="issue_id")] - graphql_gateway.create_issues.return_value(issue_entities) + kili_api_gateway.create_issues.return_value(issue_entities) # when creating one issue issues = issue_service.create_issues(project_id="project_id", issues=issues) From f2ff829f7c900ed3df1af33bca5fbdf6667c8e13 Mon Sep 17 00:00:00 2001 From: theodu Date: Wed, 23 Aug 2023 23:27:00 +0200 Subject: [PATCH 16/24] feat: update the question service --- src/kili/client.py | 4 +-- src/kili/entrypoints/client/issue.py | 23 ++++++++++--- src/kili/services/issue/__init__.py | 51 ++++++++++++++-------------- src/kili/services/issue/types.py | 9 +++++ 4 files changed, 55 insertions(+), 32 deletions(-) diff --git a/src/kili/client.py b/src/kili/client.py index 2cdea05e8..bf050e565 100644 --- a/src/kili/client.py +++ b/src/kili/client.py @@ -14,8 +14,8 @@ from kili.core.graphql.graphql_client import GraphQLClient, GraphQLClientName from kili.core.graphql.operations.api_key.queries import APIKeyQuery, APIKeyWhere from kili.core.graphql.operations.user.queries import GQL_ME -from kili.entrypoints.client_method.internal import InternalEntrypoints -from kili.entrypoints.client_method.issue import IssueEntrypoints +from kili.entrypoints.client.internal import InternalEntrypoints +from kili.entrypoints.client.issue import IssueEntrypoints from kili.entrypoints.mutations.asset import MutationsAsset from kili.entrypoints.mutations.data_connection import MutationsDataConnection from kili.entrypoints.mutations.issue import MutationsIssue diff --git a/src/kili/entrypoints/client/issue.py b/src/kili/entrypoints/client/issue.py index 343e59e95..02b0f1cce 100644 --- a/src/kili/entrypoints/client/issue.py +++ b/src/kili/entrypoints/client/issue.py @@ -9,7 +9,10 @@ from kili.gateways.kili_api_gateway import KiliAPIGateway from kili.services.helpers import assert_all_arrays_have_same_size from kili.services.issue import IssueService -from kili.services.issue.types import IssueToCreateServiceInput +from kili.services.issue.types import ( + IssueToCreateServiceInput, + QuestionToCreateServiceInput, +) from kili.utils.logcontext import for_all_methods, log_call @@ -73,8 +76,20 @@ def create_questions( A list of dictionary with the `id` key of the created questions. """ assert_all_arrays_have_same_size([text_array, asset_id_array]) + if asset_id_array is not None and asset_external_id_array is not None: + raise ValueError( + "Only one of asset_id_array and asset_external_id_array should be given" + ) issue_service = IssueService(self.kili_api_gateway) - created_questions = issue_service.create_questions( - project_id, text_array, asset_id_array, asset_external_id_array - ) + questions = [ + QuestionToCreateServiceInput( + asset_id=asset_id, asset_external_id=asset_external_id, text=text + ) + for (asset_id, asset_external_id, text) in zip( + asset_id_array or repeat(None), + asset_external_id_array or repeat(None), + text_array, + ) + ] + created_questions = issue_service.create_questions(project_id, questions) return [{"id": question.id_} for question in created_questions] diff --git a/src/kili/services/issue/__init__.py b/src/kili/services/issue/__init__.py index 8dd3c44b1..4514e3cf7 100644 --- a/src/kili/services/issue/__init__.py +++ b/src/kili/services/issue/__init__.py @@ -1,12 +1,15 @@ """Issue Service.""" -from typing import List, Optional +from typing import List, cast from kili.entrypoints.mutations.asset.helpers import get_asset_ids_or_throw_error from kili.entrypoints.mutations.issue.helpers import get_labels_asset_ids_map from kili.gateways.kili_api_gateway import KiliAPIGateway from kili.gateways.kili_api_gateway.issue.types import IssueToCreateKiliAPIGatewayInput -from kili.services.issue.types import IssueToCreateServiceInput +from kili.services.issue.types import ( + IssueToCreateServiceInput, + QuestionToCreateServiceInput, +) class IssueService: @@ -17,50 +20,46 @@ def __init__(self, kili_api_gateway: KiliAPIGateway): def create_issues(self, project_id, issues: List[IssueToCreateServiceInput]): """Create issues with issue type.""" - issue_number_array = [0] * len(issues) label_id_array = [issue.label_id for issue in issues] label_asset_ids_map = get_labels_asset_ids_map( self._kili_api_gateway, project_id, label_id_array ) # should be done in the backend - graphql_issues = [ + gateway_issues = [ IssueToCreateKiliAPIGatewayInput( - issue_number=issue_number, + issue_number=0, label_id=issue.label_id, object_mid=issue.object_mid, asset_id=label_asset_ids_map[issue.label_id], text=issue.text, ) - for (issue_number, issue) in zip(issue_number_array, issues) + for issue in issues ] - created_issues = self._kili_api_gateway.create_issues(type_="ISSUE", issues=graphql_issues) + created_issues = self._kili_api_gateway.create_issues(type_="ISSUE", issues=gateway_issues) return created_issues - def create_questions( - self, - project_id: str, - text_array: List[str], - asset_id_array: Optional[List[str]], - asset_external_id_array: Optional[List[str]], - ): + def create_questions(self, project_id: str, questions: List[QuestionToCreateServiceInput]): """Create issues with question type.""" - issue_number_array = [0] * len(text_array) - asset_id_array = get_asset_ids_or_throw_error( - self._kili_api_gateway, asset_id_array, asset_external_id_array, project_id - ) # should be done in the backend - graphql_questions = [ + if questions[0].asset_id is None: + asset_id_array = get_asset_ids_or_throw_error( + self._kili_api_gateway, + None, + cast(List[str], [question.asset_external_id for question in questions]), + project_id, + ) # should be done in the backend + for i, question in enumerate(questions): + question.asset_id = asset_id_array[i] + gateway_questions = [ IssueToCreateKiliAPIGatewayInput( - issue_number=issue_number, - asset_id=asset_id, - text=text, + issue_number=0, + asset_id=cast(str, question.asset_id), + text=question.text, label_id=None, object_mid=None, ) - for (issue_number, asset_id, text) in zip( - issue_number_array, asset_id_array, text_array - ) + for question in questions ] created_questions = self._kili_api_gateway.create_issues( - type_="QUESTION", issues=graphql_questions + type_="QUESTION", issues=gateway_questions ) return created_questions diff --git a/src/kili/services/issue/types.py b/src/kili/services/issue/types.py index 311f3f4fc..1af6c7fd8 100644 --- a/src/kili/services/issue/types.py +++ b/src/kili/services/issue/types.py @@ -10,3 +10,12 @@ class IssueToCreateServiceInput: label_id: str object_mid: Optional[str] = None text: Optional[str] = None + + +@dataclass +class QuestionToCreateServiceInput: + """Data about one Issue to create.""" + + asset_id: Optional[str] + asset_external_id: Optional[str] + text: str From ee55de8fe9e7cd15c06a70ac97fd4f55877513b5 Mon Sep 17 00:00:00 2001 From: theodu Date: Thu, 24 Aug 2023 11:10:11 +0200 Subject: [PATCH 17/24] feat: rename service into use cases --- src/kili/entrypoints/client/issue.py | 50 ++------------ .../entrypoints/mutations/issue/__init__.py | 42 +++++++++++- src/kili/services/issue/__init__.py | 65 ------------------- src/kili/use_cases/issue/__init__.py | 34 ++++++++++ .../{services => use_cases}/issue/types.py | 11 +--- .../{services => use_cases}/test_issue.py | 10 +-- 6 files changed, 85 insertions(+), 127 deletions(-) delete mode 100644 src/kili/services/issue/__init__.py create mode 100644 src/kili/use_cases/issue/__init__.py rename src/kili/{services => use_cases}/issue/types.py (54%) rename tests/integration/{services => use_cases}/test_issue.py (62%) diff --git a/src/kili/entrypoints/client/issue.py b/src/kili/entrypoints/client/issue.py index 02b0f1cce..033a5ac4f 100644 --- a/src/kili/entrypoints/client/issue.py +++ b/src/kili/entrypoints/client/issue.py @@ -8,11 +8,8 @@ from kili.gateways.kili_api_gateway import KiliAPIGateway from kili.services.helpers import assert_all_arrays_have_same_size -from kili.services.issue import IssueService -from kili.services.issue.types import ( - IssueToCreateServiceInput, - QuestionToCreateServiceInput, -) +from kili.use_cases.issue import IssueUseCases +from kili.use_cases.issue.types import IssueToCreateUseCaseInput from kili.utils.logcontext import for_all_methods, log_call @@ -44,52 +41,13 @@ def create_issues( """ assert_all_arrays_have_same_size([label_id_array, object_mid_array, text_array]) issues = [ - IssueToCreateServiceInput(label_id=label_id, object_mid=object_mid, text=text) + IssueToCreateUseCaseInput(label_id=label_id, object_mid=object_mid, text=text) for (label_id, object_mid, text) in zip( label_id_array, object_mid_array or repeat(None), text_array or repeat(None), ) ] - issue_service = IssueService(self.kili_api_gateway) + issue_service = IssueUseCases(self.kili_api_gateway) issues_entities = issue_service.create_issues(project_id=project_id, issues=issues) return [{"id": issue.id_} for issue in issues_entities] - - @typechecked - def create_questions( - self, - project_id: str, - text_array: List[str], - asset_id_array: Optional[List[str]] = None, - asset_external_id_array: Optional[List[str]] = None, - ) -> List[Dict[Literal["id"], str]]: - # pylint:disable=line-too-long - """Create questions. - - Args: - project_id: Id of the project. - text_array: List of question strings. - asset_id_array: List of the assets to add the questions to. - asset_external_id_array: List of the assets to add the questions to. Used if `asset_id_array` is not given. - - Returns: - A list of dictionary with the `id` key of the created questions. - """ - assert_all_arrays_have_same_size([text_array, asset_id_array]) - if asset_id_array is not None and asset_external_id_array is not None: - raise ValueError( - "Only one of asset_id_array and asset_external_id_array should be given" - ) - issue_service = IssueService(self.kili_api_gateway) - questions = [ - QuestionToCreateServiceInput( - asset_id=asset_id, asset_external_id=asset_external_id, text=text - ) - for (asset_id, asset_external_id, text) in zip( - asset_id_array or repeat(None), - asset_external_id_array or repeat(None), - text_array, - ) - ] - created_questions = issue_service.create_questions(project_id, questions) - return [{"id": question.id_} for question in created_questions] diff --git a/src/kili/entrypoints/mutations/issue/__init__.py b/src/kili/entrypoints/mutations/issue/__init__.py index 730c58edc..b2529c2dd 100644 --- a/src/kili/entrypoints/mutations/issue/__init__.py +++ b/src/kili/entrypoints/mutations/issue/__init__.py @@ -1,6 +1,6 @@ """Issue mutations.""" -from typing import Dict, Literal, Optional +from typing import Dict, List, Literal, Optional from typeguard import typechecked @@ -8,7 +8,9 @@ from kili.core.graphql.operations.label.queries import LabelQuery, LabelWhere from kili.core.helpers import deprecate from kili.entrypoints.base import BaseOperationEntrypointMixin +from kili.entrypoints.mutations.asset.helpers import get_asset_ids_or_throw_error from kili.gateways.kili_api_gateway.issue.operations import GQL_CREATE_ISSUES +from kili.services.helpers import assert_all_arrays_have_same_size from kili.utils.logcontext import for_all_methods, log_call from .helpers import get_issue_numbers @@ -87,3 +89,41 @@ def append_to_issues( result = self.graphql_client.execute(GQL_CREATE_ISSUES, variables) return self.format_result("data", result)[0] + + @typechecked + def create_questions( + self, + project_id: str, + text_array: List[Optional[str]], + asset_id_array: Optional[List[str]] = None, + asset_external_id_array: Optional[List[str]] = None, + ) -> List[Dict]: + # pylint:disable=line-too-long + """Create questions. + + Args: + project_id: Id of the project. + text_array: List of question strings. + asset_id_array: List of the assets to add the questions to. + asset_external_id_array: List of the assets to add the questions to. Used if `asset_id_array` is not given. + + Returns: + A list of dictionary with the `id` key of the created questions. + """ + assert_all_arrays_have_same_size([text_array, asset_id_array]) + issue_number_array = get_issue_numbers(self, project_id, "QUESTION", len(text_array)) + asset_id_array = get_asset_ids_or_throw_error( + self, asset_id_array, asset_external_id_array, project_id + ) + variables = { + "issues": [ + {"issueNumber": issue_number, "type": "QUESTION", "assetId": asset_id, "text": text} + for (asset_id, text, issue_number) in zip( + asset_id_array, text_array, issue_number_array + ) + ], + "where": {"idIn": asset_id_array}, + } + + result = self.graphql_client.execute(GQL_CREATE_ISSUES, variables) + return self.format_result("data", result) diff --git a/src/kili/services/issue/__init__.py b/src/kili/services/issue/__init__.py deleted file mode 100644 index 4514e3cf7..000000000 --- a/src/kili/services/issue/__init__.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Issue Service.""" - -from typing import List, cast - -from kili.entrypoints.mutations.asset.helpers import get_asset_ids_or_throw_error -from kili.entrypoints.mutations.issue.helpers import get_labels_asset_ids_map -from kili.gateways.kili_api_gateway import KiliAPIGateway -from kili.gateways.kili_api_gateway.issue.types import IssueToCreateKiliAPIGatewayInput -from kili.services.issue.types import ( - IssueToCreateServiceInput, - QuestionToCreateServiceInput, -) - - -class IssueService: - """Issue Service.""" - - def __init__(self, kili_api_gateway: KiliAPIGateway): - self._kili_api_gateway = kili_api_gateway - - def create_issues(self, project_id, issues: List[IssueToCreateServiceInput]): - """Create issues with issue type.""" - label_id_array = [issue.label_id for issue in issues] - label_asset_ids_map = get_labels_asset_ids_map( - self._kili_api_gateway, project_id, label_id_array - ) # should be done in the backend - gateway_issues = [ - IssueToCreateKiliAPIGatewayInput( - issue_number=0, - label_id=issue.label_id, - object_mid=issue.object_mid, - asset_id=label_asset_ids_map[issue.label_id], - text=issue.text, - ) - for issue in issues - ] - created_issues = self._kili_api_gateway.create_issues(type_="ISSUE", issues=gateway_issues) - return created_issues - - def create_questions(self, project_id: str, questions: List[QuestionToCreateServiceInput]): - """Create issues with question type.""" - if questions[0].asset_id is None: - asset_id_array = get_asset_ids_or_throw_error( - self._kili_api_gateway, - None, - cast(List[str], [question.asset_external_id for question in questions]), - project_id, - ) # should be done in the backend - for i, question in enumerate(questions): - question.asset_id = asset_id_array[i] - gateway_questions = [ - IssueToCreateKiliAPIGatewayInput( - issue_number=0, - asset_id=cast(str, question.asset_id), - text=question.text, - label_id=None, - object_mid=None, - ) - for question in questions - ] - - created_questions = self._kili_api_gateway.create_issues( - type_="QUESTION", issues=gateway_questions - ) - return created_questions diff --git a/src/kili/use_cases/issue/__init__.py b/src/kili/use_cases/issue/__init__.py new file mode 100644 index 000000000..61fcd4766 --- /dev/null +++ b/src/kili/use_cases/issue/__init__.py @@ -0,0 +1,34 @@ +"""Issue Service.""" + +from typing import List + +from kili.entrypoints.mutations.issue.helpers import get_labels_asset_ids_map +from kili.gateways.kili_api_gateway import KiliAPIGateway +from kili.gateways.kili_api_gateway.issue.types import IssueToCreateKiliAPIGatewayInput +from kili.use_cases.issue.types import IssueToCreateUseCaseInput + + +class IssueUseCases: + """Issue Service.""" + + def __init__(self, kili_api_gateway: KiliAPIGateway): + self._kili_api_gateway = kili_api_gateway + + def create_issues(self, project_id, issues: List[IssueToCreateUseCaseInput]): + """Create issues with issue type.""" + label_id_array = [issue.label_id for issue in issues] + label_asset_ids_map = get_labels_asset_ids_map( + self._kili_api_gateway, project_id, label_id_array + ) # should be done in the backend + gateway_issues = [ + IssueToCreateKiliAPIGatewayInput( + issue_number=0, + label_id=issue.label_id, + object_mid=issue.object_mid, + asset_id=label_asset_ids_map[issue.label_id], + text=issue.text, + ) + for issue in issues + ] + created_issues = self._kili_api_gateway.create_issues(type_="ISSUE", issues=gateway_issues) + return created_issues diff --git a/src/kili/services/issue/types.py b/src/kili/use_cases/issue/types.py similarity index 54% rename from src/kili/services/issue/types.py rename to src/kili/use_cases/issue/types.py index 1af6c7fd8..fe986c78d 100644 --- a/src/kili/services/issue/types.py +++ b/src/kili/use_cases/issue/types.py @@ -4,18 +4,9 @@ @dataclass -class IssueToCreateServiceInput: +class IssueToCreateUseCaseInput: """Data about one Issue to create.""" label_id: str object_mid: Optional[str] = None text: Optional[str] = None - - -@dataclass -class QuestionToCreateServiceInput: - """Data about one Issue to create.""" - - asset_id: Optional[str] - asset_external_id: Optional[str] - text: str diff --git a/tests/integration/services/test_issue.py b/tests/integration/use_cases/test_issue.py similarity index 62% rename from tests/integration/services/test_issue.py rename to tests/integration/use_cases/test_issue.py index d47657f6e..6789cd95d 100644 --- a/tests/integration/services/test_issue.py +++ b/tests/integration/use_cases/test_issue.py @@ -4,21 +4,21 @@ from kili.domain.issue import Issue from kili.gateways.kili_api_gateway import KiliAPIGateway -from kili.services.issue import IssueService -from kili.services.issue.types import IssueToCreateServiceInput +from kili.use_cases.issue import IssueUseCases +from kili.use_cases.issue.types import IssueToCreateUseCaseInput @pytest.mark.skip(reason="Waiting to implement queries") def test_create_one_issue(kili_api_gateway: KiliAPIGateway): - issue_service = IssueService(kili_api_gateway) + issue_use_cases = IssueUseCases(kili_api_gateway) # given one issue to create - issues = [IssueToCreateServiceInput(label_id="label_id", text="text", object_mid="object_mid")] + issues = [IssueToCreateUseCaseInput(label_id="label_id", text="text", object_mid="object_mid")] issue_entities = [Issue(id_="issue_id")] kili_api_gateway.create_issues.return_value(issue_entities) # when creating one issue - issues = issue_service.create_issues(project_id="project_id", issues=issues) + issues = issue_use_cases.create_issues(project_id="project_id", issues=issues) # then assert issues == issue_entities From d822c4033c723bdb15f5023ff58415a5cf923356 Mon Sep 17 00:00:00 2001 From: theodu Date: Thu, 24 Aug 2023 11:24:24 +0200 Subject: [PATCH 18/24] chore: add presentation layer --- docs/sdk/issue.md | 2 +- src/kili/client.py | 8 ++++---- src/kili/{entrypoints => presentation}/client/__init__.py | 0 src/kili/{entrypoints => presentation}/client/internal.py | 2 +- src/kili/{entrypoints => presentation}/client/issue.py | 2 +- src/kili/services/plugins/model.py | 2 +- tests/e2e/test_e2e_client.py | 2 +- tests/e2e/test_json_response_validation.py | 2 +- tests/integration/entrypoints/client/test_client.py | 2 +- 9 files changed, 11 insertions(+), 11 deletions(-) rename src/kili/{entrypoints => presentation}/client/__init__.py (100%) rename src/kili/{entrypoints => presentation}/client/internal.py (96%) rename src/kili/{entrypoints => presentation}/client/issue.py (98%) diff --git a/docs/sdk/issue.md b/docs/sdk/issue.md index f7c43825a..59d28c60d 100644 --- a/docs/sdk/issue.md +++ b/docs/sdk/issue.md @@ -1,5 +1,5 @@ # Issue module -::: kili.entrypoints.client.issue.IssueEntrypoints +::: kili.presentation.client.issue.IssueClientMethods ::: kili.entrypoints.queries.issue.__init__.QueriesIssue ::: kili.entrypoints.mutations.issue.__init__.MutationsIssue diff --git a/src/kili/client.py b/src/kili/client.py index bf050e565..dbb9631dc 100644 --- a/src/kili/client.py +++ b/src/kili/client.py @@ -14,8 +14,6 @@ from kili.core.graphql.graphql_client import GraphQLClient, GraphQLClientName from kili.core.graphql.operations.api_key.queries import APIKeyQuery, APIKeyWhere from kili.core.graphql.operations.user.queries import GQL_ME -from kili.entrypoints.client.internal import InternalEntrypoints -from kili.entrypoints.client.issue import IssueEntrypoints from kili.entrypoints.mutations.asset import MutationsAsset from kili.entrypoints.mutations.data_connection import MutationsDataConnection from kili.entrypoints.mutations.issue import MutationsIssue @@ -40,6 +38,8 @@ from kili.entrypoints.subscriptions.label import SubscriptionsLabel from kili.exceptions import AuthenticationFailed, UserNotFoundError from kili.gateways.kili_api_gateway import KiliAPIGateway +from kili.presentation.client.internal import InternalClientMethods +from kili.presentation.client.issue import IssueClientMethods warnings.filterwarnings("default", module="kili", category=DeprecationWarning) @@ -78,7 +78,7 @@ class Kili( # pylint: disable=too-many-ancestors,too-many-instance-attributes QueriesProjectVersion, QueriesUser, SubscriptionsLabel, - IssueEntrypoints, + IssueClientMethods, ): """Kili Client.""" @@ -178,7 +178,7 @@ def __init__( api_key_query = APIKeyQuery(self.graphql_client, self.http_client) self._check_expiry_of_key_is_close(api_key_query, self.api_key) - self.internal = InternalEntrypoints(self) + self.internal = InternalClientMethods(self) def _check_api_key_valid(self) -> bool: """Check that the api_key provided is valid.""" diff --git a/src/kili/entrypoints/client/__init__.py b/src/kili/presentation/client/__init__.py similarity index 100% rename from src/kili/entrypoints/client/__init__.py rename to src/kili/presentation/client/__init__.py diff --git a/src/kili/entrypoints/client/internal.py b/src/kili/presentation/client/internal.py similarity index 96% rename from src/kili/entrypoints/client/internal.py rename to src/kili/presentation/client/internal.py index 87f879680..c4c7c5ce9 100644 --- a/src/kili/entrypoints/client/internal.py +++ b/src/kili/presentation/client/internal.py @@ -8,7 +8,7 @@ from kili.entrypoints.queries.api_key import QueriesApiKey -class InternalEntrypoints(MutationsOrganization, QueriesApiKey): +class InternalClientMethods(MutationsOrganization, QueriesApiKey): """Inherit classes for internal use by Kili Technology only.""" def __init__(self, kili): diff --git a/src/kili/entrypoints/client/issue.py b/src/kili/presentation/client/issue.py similarity index 98% rename from src/kili/entrypoints/client/issue.py rename to src/kili/presentation/client/issue.py index 033a5ac4f..ed837c97a 100644 --- a/src/kili/entrypoints/client/issue.py +++ b/src/kili/presentation/client/issue.py @@ -14,7 +14,7 @@ @for_all_methods(log_call, exclude=["__init__"]) -class IssueEntrypoints: +class IssueClientMethods: """Set of Issue mutations.""" kili_api_gateway: KiliAPIGateway diff --git a/src/kili/services/plugins/model.py b/src/kili/services/plugins/model.py index e97c9c37b..eb43cd2d9 100644 --- a/src/kili/services/plugins/model.py +++ b/src/kili/services/plugins/model.py @@ -3,7 +3,7 @@ import logging from typing import Dict, List, Optional -from kili.entrypoints.client import Kili +from kili.client import Kili from kili.services.plugins.helpers import get_logger diff --git a/tests/e2e/test_e2e_client.py b/tests/e2e/test_e2e_client.py index 3ab359523..b3f8e4adc 100644 --- a/tests/e2e/test_e2e_client.py +++ b/tests/e2e/test_e2e_client.py @@ -1,7 +1,7 @@ import pytest_mock from pyinstrument.profiler import Profiler -from kili.entrypoints.client import Kili +from kili.client import Kili def test_client_init_not_too_long_with_checks_enabled(): diff --git a/tests/e2e/test_json_response_validation.py b/tests/e2e/test_json_response_validation.py index 98196c959..3a6c7004f 100644 --- a/tests/e2e/test_json_response_validation.py +++ b/tests/e2e/test_json_response_validation.py @@ -4,7 +4,7 @@ import pytest from gql.transport import exceptions -from kili.entrypoints.client import Kili +from kili.client import Kili from kili.exceptions import GraphQLError diff --git a/tests/integration/entrypoints/client/test_client.py b/tests/integration/entrypoints/client/test_client.py index b78fcb3e3..93d27533f 100644 --- a/tests/integration/entrypoints/client/test_client.py +++ b/tests/integration/entrypoints/client/test_client.py @@ -6,8 +6,8 @@ import pytest_mock from filelock import FileLock +from kili.client import Kili from kili.core.graphql.graphql_client import DEFAULT_GRAPHQL_SCHEMA_CACHE_DIR -from kili.entrypoints.client import Kili from kili.exceptions import AuthenticationFailed From 789ce5ac167f0db409db1a8a13c978cb03f8019b Mon Sep 17 00:00:00 2001 From: theodu Date: Thu, 24 Aug 2023 11:49:22 +0200 Subject: [PATCH 19/24] feat: add tqdm to create_issues --- .../kili_api_gateway/issue/__init__.py | 45 ++++++++++--------- .../entrypoints/client/test_client.py | 13 +++--- tests/integration/utils/test_log_context.py | 4 +- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/kili/gateways/kili_api_gateway/issue/__init__.py b/src/kili/gateways/kili_api_gateway/issue/__init__.py index 6908f46ab..4c0cfbc46 100644 --- a/src/kili/gateways/kili_api_gateway/issue/__init__.py +++ b/src/kili/gateways/kili_api_gateway/issue/__init__.py @@ -12,6 +12,7 @@ GQL_CREATE_ISSUES, ) from kili.gateways.kili_api_gateway.issue.types import IssueToCreateKiliAPIGatewayInput +from kili.utils import tqdm @dataclass @@ -45,27 +46,29 @@ def create_issues( ) -> List[Issue]: """Send a GraphQL request calling createIssues resolver.""" created_issue_entities: List[Issue] = [] - for issues_batch in BatchIteratorBuilder(issues): - batch_targeted_asset_ids = [issue.asset_id for issue in issues_batch] - payload = { - "issues": [ - { - "issueNumber": issue.issue_number, - "labelID": issue.label_id, - "objectMid": issue.object_mid, - "type": type_, - "assetId": issue.asset_id, - "text": issue.text, - } - for issue in issues_batch - ], - "where": {"idIn": batch_targeted_asset_ids}, - } - result = self.graphql_client.execute(GQL_CREATE_ISSUES, payload) - batch_created_issues = result["data"] - created_issue_entities.extend( - [Issue(id_=issue["id"]) for issue in batch_created_issues] - ) + with tqdm.tqdm(total=len(issues)) as pbar: + for issues_batch in BatchIteratorBuilder(issues): + batch_targeted_asset_ids = [issue.asset_id for issue in issues_batch] + payload = { + "issues": [ + { + "issueNumber": issue.issue_number, + "labelID": issue.label_id, + "objectMid": issue.object_mid, + "type": type_, + "assetId": issue.asset_id, + "text": issue.text, + } + for issue in issues_batch + ], + "where": {"idIn": batch_targeted_asset_ids}, + } + result = self.graphql_client.execute(GQL_CREATE_ISSUES, payload) + batch_created_issues = result["data"] + created_issue_entities.extend( + [Issue(id_=issue["id"]) for issue in batch_created_issues] + ) + pbar.update(len(issues_batch)) return created_issue_entities def count_issues( # pylint: disable=too-many-arguments, diff --git a/tests/integration/entrypoints/client/test_client.py b/tests/integration/entrypoints/client/test_client.py index 93d27533f..df36223bb 100644 --- a/tests/integration/entrypoints/client/test_client.py +++ b/tests/integration/entrypoints/client/test_client.py @@ -1,4 +1,5 @@ import os +import shutil from pathlib import Path from unittest.mock import patch @@ -14,8 +15,8 @@ @patch.dict(os.environ, {"KILI_API_KEY": "", "KILI_SDK_SKIP_CHECKS": "True"}) def test_no_api_key(mocker: pytest_mock.MockerFixture): """Test fail because no api key is found.""" - mocker.patch("kili.entrypoints.client.requests") - mocker.patch("kili.entrypoints.client.getpass.getpass", return_value="") + mocker.patch("kili.client.requests") + mocker.patch("kili.client.getpass.getpass", return_value="") with pytest.raises(AuthenticationFailed): _ = Kili() @@ -23,7 +24,7 @@ def test_no_api_key(mocker: pytest_mock.MockerFixture): @patch.dict(os.environ, {"KILI_API_KEY": "wrong_api_key"}) def test_wrong_api_key_is_obfuscated(mocker: pytest_mock.MockerFixture): """Test obfuscation of api key.""" - mocker.patch("kili.entrypoints.client.requests") + mocker.patch.object(Kili, "_check_api_key_valid", return_value=False) with pytest.raises( AuthenticationFailed, match=r"failed with API key: \*{9}_key" # 9 stars for "wrong_api" ): @@ -78,9 +79,9 @@ def test_write_to_disk_without_permissions_not_crash( def test_given_env_without_api_key_when_initializing_kili_client_then_it_asks_for_api_key_getpass( mocker: pytest_mock.MockerFixture, ): - mocker.patch("kili.entrypoints.client.sys.stdin.isatty", return_value=True) + mocker.patch("kili.client.sys.stdin.isatty", return_value=True) mocker_getpass = mocker.patch( - "kili.entrypoints.client.getpass.getpass", return_value="fake_key_entered_by_user" + "kili.client.getpass.getpass", return_value="fake_key_entered_by_user" ) # When @@ -94,6 +95,6 @@ def test_given_env_without_api_key_when_initializing_kili_client_then_it_asks_fo def test_given_non_tti_env_without_api_key_when_initializing_kili_client_then_it_crash( mocker: pytest_mock.MockerFixture, ): - mocker.patch("kili.entrypoints.client.sys.stdin.isatty", return_value=False) + mocker.patch("kili.client.sys.stdin.isatty", return_value=False) with pytest.raises(AuthenticationFailed): _ = Kili() diff --git a/tests/integration/utils/test_log_context.py b/tests/integration/utils/test_log_context.py index 5f68604c6..629627f18 100644 --- a/tests/integration/utils/test_log_context.py +++ b/tests/integration/utils/test_log_context.py @@ -43,8 +43,8 @@ def test_log_context(mocker, monkeypatch): mocker.patch.object(Kili, "_check_api_key_valid") mocker.patch.object(Kili, "_check_expiry_of_key_is_close") mocker.patch.object(Kili, "get_user") - mocker.patch("kili.entrypoints.client.GraphQLClient") - mocker.patch("kili.entrypoints.client.requests.Session") + mocker.patch("kili.client.GraphQLClient") + mocker.patch("kili.client.requests.Session") mocker.patch("kili.utils.logcontext.datetime", _FakeDatetime()) mocker.patch("kili.utils.logcontext.uuid", _FakeUUID()) mocker.patch("kili.utils.logcontext.__version__", "1.0.0") From 21e91ee9d8c8ffe075cd34be6df52a1c5d126985 Mon Sep 17 00:00:00 2001 From: theodu Date: Thu, 24 Aug 2023 11:56:35 +0200 Subject: [PATCH 20/24] feat: add missing init files --- src/kili/gateways/__init__.py | 0 src/kili/presentation/__init__.py | 0 src/kili/use_cases/__init__.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/kili/gateways/__init__.py create mode 100644 src/kili/presentation/__init__.py create mode 100644 src/kili/use_cases/__init__.py diff --git a/src/kili/gateways/__init__.py b/src/kili/gateways/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/kili/presentation/__init__.py b/src/kili/presentation/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/kili/use_cases/__init__.py b/src/kili/use_cases/__init__.py new file mode 100644 index 000000000..e69de29bb From 632a8a6efd1a3d935c3d85fa5b9d7059714b3034 Mon Sep 17 00:00:00 2001 From: theodu Date: Thu, 24 Aug 2023 16:45:56 +0200 Subject: [PATCH 21/24] feat: take comments --- .pylintrc | 1 + src/kili/gateways/kili_api_gateway/__init__.py | 5 ++++- src/kili/gateways/kili_api_gateway/issue/__init__.py | 2 +- .../gateways/kili_api_gateway/issue/operations.py | 12 ------------ src/kili/use_cases/issue/__init__.py | 2 +- 5 files changed, 7 insertions(+), 15 deletions(-) diff --git a/.pylintrc b/.pylintrc index 5722c1d10..e9aabe068 100644 --- a/.pylintrc +++ b/.pylintrc @@ -72,6 +72,7 @@ disable=raw-checker-failed, logging-fstring-interpolation, cyclic-import, too-few-public-methods, + fixme, # Enable the message, report, category or checker with the given id(s). You can diff --git a/src/kili/gateways/kili_api_gateway/__init__.py b/src/kili/gateways/kili_api_gateway/__init__.py index 4c7dd9f7f..b68a2d4e0 100644 --- a/src/kili/gateways/kili_api_gateway/__init__.py +++ b/src/kili/gateways/kili_api_gateway/__init__.py @@ -1,10 +1,13 @@ """GraphQL gateway module.""" +import requests + +from kili.core.graphql.graphql_client import GraphQLClient from kili.gateways.kili_api_gateway.issue import IssueOperationMixin class KiliAPIGateway(IssueOperationMixin): """GraphQL gateway to communicate with Kili backend.""" - def __init__(self, graphql_client, http_client): + def __init__(self, graphql_client: GraphQLClient, http_client: requests.Session): self.graphql_client = graphql_client self.http_client = http_client diff --git a/src/kili/gateways/kili_api_gateway/issue/__init__.py b/src/kili/gateways/kili_api_gateway/issue/__init__.py index 4c0cfbc46..5cc1815d0 100644 --- a/src/kili/gateways/kili_api_gateway/issue/__init__.py +++ b/src/kili/gateways/kili_api_gateway/issue/__init__.py @@ -78,7 +78,7 @@ def count_issues( # pylint: disable=too-many-arguments, asset_id_in: Optional[List[str]] = None, issue_type: Optional[IssueType] = None, status: Optional[IssueStatus] = None, - ): + ) -> int: """Send a GraphQL request calling countIssues resolver.""" where = IssueWhere(project_id, asset_id, asset_id_in, issue_type, status) payload = { diff --git a/src/kili/gateways/kili_api_gateway/issue/operations.py b/src/kili/gateways/kili_api_gateway/issue/operations.py index a5eba05c3..1f14caad6 100644 --- a/src/kili/gateways/kili_api_gateway/issue/operations.py +++ b/src/kili/gateways/kili_api_gateway/issue/operations.py @@ -14,18 +14,6 @@ } """ - -def get_gql_issues_query(fragment): - """Return the GraphQL issues query.""" - return f""" - query issues($where: IssueWhere!, $first: PageSize!, $skip: Int!) {{ - data: issues(where: $where, first: $first, skip: $skip) {{ - {fragment} - }} - }} - """ - - GQL_COUNT_ISSUES = """ query countIssues($where: IssueWhere!) { data: countIssues(where: $where) diff --git a/src/kili/use_cases/issue/__init__.py b/src/kili/use_cases/issue/__init__.py index 61fcd4766..f16c955ed 100644 --- a/src/kili/use_cases/issue/__init__.py +++ b/src/kili/use_cases/issue/__init__.py @@ -19,7 +19,7 @@ def create_issues(self, project_id, issues: List[IssueToCreateUseCaseInput]): label_id_array = [issue.label_id for issue in issues] label_asset_ids_map = get_labels_asset_ids_map( self._kili_api_gateway, project_id, label_id_array - ) # should be done in the backend + ) # TODO: should be done in the backend gateway_issues = [ IssueToCreateKiliAPIGatewayInput( issue_number=0, From e119835cd4b421f1ada287db79b2a27c333e7873 Mon Sep 17 00:00:00 2001 From: theodu Date: Thu, 24 Aug 2023 17:44:50 +0200 Subject: [PATCH 22/24] feat: update doctrings --- src/kili/gateways/kili_api_gateway/__init__.py | 2 +- src/kili/gateways/kili_api_gateway/issue/types.py | 2 +- src/kili/presentation/client/internal.py | 4 ++-- src/kili/presentation/client/issue.py | 2 +- src/kili/use_cases/issue/__init__.py | 4 ++-- src/kili/use_cases/issue/types.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/kili/gateways/kili_api_gateway/__init__.py b/src/kili/gateways/kili_api_gateway/__init__.py index b68a2d4e0..cec59a0a7 100644 --- a/src/kili/gateways/kili_api_gateway/__init__.py +++ b/src/kili/gateways/kili_api_gateway/__init__.py @@ -1,4 +1,4 @@ -"""GraphQL gateway module.""" +"""Kili API Gateway module for interacting with Kili.""" import requests from kili.core.graphql.graphql_client import GraphQLClient diff --git a/src/kili/gateways/kili_api_gateway/issue/types.py b/src/kili/gateways/kili_api_gateway/issue/types.py index ace2c9e29..d59fbaf51 100644 --- a/src/kili/gateways/kili_api_gateway/issue/types.py +++ b/src/kili/gateways/kili_api_gateway/issue/types.py @@ -1,4 +1,4 @@ -"""Types for the Issue-related graphql gateway functions.""" +"""Types for the Issue-related Kili API gateway functions.""" from dataclasses import dataclass from typing import Optional diff --git a/src/kili/presentation/client/internal.py b/src/kili/presentation/client/internal.py index c4c7c5ce9..6c566df2a 100644 --- a/src/kili/presentation/client/internal.py +++ b/src/kili/presentation/client/internal.py @@ -1,4 +1,4 @@ -"""Module for methods and classes that are for internal use by Kili Technology only.""" +"""Module for methods that are for internal use by Kili Technology only.""" from typeguard import typechecked @@ -9,7 +9,7 @@ class InternalClientMethods(MutationsOrganization, QueriesApiKey): - """Inherit classes for internal use by Kili Technology only.""" + """Kili client methods for internal use by Kili Technology only.""" def __init__(self, kili): """Initializes the class. diff --git a/src/kili/presentation/client/issue.py b/src/kili/presentation/client/issue.py index ed837c97a..fbe62f9e2 100644 --- a/src/kili/presentation/client/issue.py +++ b/src/kili/presentation/client/issue.py @@ -15,7 +15,7 @@ @for_all_methods(log_call, exclude=["__init__"]) class IssueClientMethods: - """Set of Issue mutations.""" + """Methods attached to the Kili client, to run actions on issues.""" kili_api_gateway: KiliAPIGateway http_client: requests.Session diff --git a/src/kili/use_cases/issue/__init__.py b/src/kili/use_cases/issue/__init__.py index f16c955ed..8e6db2dc2 100644 --- a/src/kili/use_cases/issue/__init__.py +++ b/src/kili/use_cases/issue/__init__.py @@ -1,4 +1,4 @@ -"""Issue Service.""" +"""Issue use cases.""" from typing import List @@ -9,7 +9,7 @@ class IssueUseCases: - """Issue Service.""" + """Issue use cases.""" def __init__(self, kili_api_gateway: KiliAPIGateway): self._kili_api_gateway = kili_api_gateway diff --git a/src/kili/use_cases/issue/types.py b/src/kili/use_cases/issue/types.py index fe986c78d..1f440fa32 100644 --- a/src/kili/use_cases/issue/types.py +++ b/src/kili/use_cases/issue/types.py @@ -1,4 +1,4 @@ -"""Types for Issue-related service.""" +"""Types for Issue-related use cases.""" from dataclasses import dataclass from typing import Optional From b18ce3125fac93dd6a929caa48ceb2fbe90701a2 Mon Sep 17 00:00:00 2001 From: theodu Date: Fri, 25 Aug 2023 16:20:00 +0200 Subject: [PATCH 23/24] feat: small fixes --- src/kili/core/enums.py | 13 ------- .../kili_api_gateway/issue/__init__.py | 34 ++++--------------- .../gateways/kili_api_gateway/issue/types.py | 25 +++++++++++++- src/kili/types.py | 2 +- 4 files changed, 32 insertions(+), 42 deletions(-) diff --git a/src/kili/core/enums.py b/src/kili/core/enums.py index 8e0d8ff0b..eac48e312 100644 --- a/src/kili/core/enums.py +++ b/src/kili/core/enums.py @@ -39,19 +39,6 @@ "VIDEO_LEGACY", ] - -IssueStatus = Literal[ - "OPEN", - "SOLVED", -] - - -IssueType = Literal[ - "ISSUE", - "QUESTION", -] - - LabelFormat = Literal[ "RAW", "SIMPLE", diff --git a/src/kili/gateways/kili_api_gateway/issue/__init__.py b/src/kili/gateways/kili_api_gateway/issue/__init__.py index 5cc1815d0..e4e0aa77b 100644 --- a/src/kili/gateways/kili_api_gateway/issue/__init__.py +++ b/src/kili/gateways/kili_api_gateway/issue/__init__.py @@ -1,41 +1,21 @@ -"""GraphQL Mixin extending GraphQL Gateway class with Issue related operations.""" +"""Mixin extending Kili API Gateway class with Issue related operations.""" -from dataclasses import dataclass from typing import List, Optional -from kili.core.enums import IssueStatus from kili.core.graphql.graphql_client import GraphQLClient from kili.core.utils.pagination import BatchIteratorBuilder -from kili.domain.issue import Issue, IssueType +from kili.domain.issue import Issue, IssueStatus, IssueType from kili.gateways.kili_api_gateway.issue.operations import ( GQL_COUNT_ISSUES, GQL_CREATE_ISSUES, ) -from kili.gateways.kili_api_gateway.issue.types import IssueToCreateKiliAPIGatewayInput +from kili.gateways.kili_api_gateway.issue.types import ( + IssueToCreateKiliAPIGatewayInput, + IssueWhere, +) from kili.utils import tqdm -@dataclass -class IssueWhere: - """Tuple to be passed to the IssueQuery to restrict query.""" - - project_id: str - asset_id: Optional[str] = None - asset_id_in: Optional[List[str]] = None - issue_type: Optional[IssueType] = None - status: Optional[IssueStatus] = None - - def get_graphql_input(self): - """Build the GraphQL IssueWhere payload to be sent in an operation.""" - return { - "project": {"id": self.project_id}, - "asset": {"id": self.asset_id}, - "assetIn": self.asset_id_in, - "status": self.status, - "type": self.issue_type, - } - - class IssueOperationMixin: """GraphQL Mixin extending GraphQL Gateway class with Issue related operations.""" @@ -82,7 +62,7 @@ def count_issues( # pylint: disable=too-many-arguments, """Send a GraphQL request calling countIssues resolver.""" where = IssueWhere(project_id, asset_id, asset_id_in, issue_type, status) payload = { - "where": where.get_graphql_input(), + "where": where.get_graphql_where_value(), } count_result = self.graphql_client.execute(GQL_COUNT_ISSUES, payload) return count_result["data"] diff --git a/src/kili/gateways/kili_api_gateway/issue/types.py b/src/kili/gateways/kili_api_gateway/issue/types.py index d59fbaf51..a23e0a16d 100644 --- a/src/kili/gateways/kili_api_gateway/issue/types.py +++ b/src/kili/gateways/kili_api_gateway/issue/types.py @@ -1,6 +1,8 @@ """Types for the Issue-related Kili API gateway functions.""" from dataclasses import dataclass -from typing import Optional +from typing import List, Optional + +from kili.domain.issue import IssueStatus, IssueType @dataclass @@ -12,3 +14,24 @@ class IssueToCreateKiliAPIGatewayInput: object_mid: Optional[str] asset_id: str text: Optional[str] + + +@dataclass +class IssueWhere: + """Tuple to be passed to the IssueQuery to restrict query.""" + + project_id: str + asset_id: Optional[str] = None + asset_id_in: Optional[List[str]] = None + issue_type: Optional[IssueType] = None + status: Optional[IssueStatus] = None + + def get_graphql_where_value(self): + """Build the GraphQL IssueWhere variable value to be sent in an operation.""" + return { + "project": {"id": self.project_id}, + "asset": {"id": self.asset_id}, + "assetIn": self.asset_id_in, + "status": self.status, + "type": self.issue_type, + } diff --git a/src/kili/types.py b/src/kili/types.py index 0c351b764..00461a79d 100644 --- a/src/kili/types.py +++ b/src/kili/types.py @@ -6,7 +6,6 @@ from kili.core.enums import ( InputType, - IssueStatus, LabelType, LicenseType, LockType, @@ -17,6 +16,7 @@ Status, ) from kili.core.helpers import deprecate +from kili.domain.issue import IssueStatus ####### From 03182f84a08daafca264f55c23223327237a219d Mon Sep 17 00:00:00 2001 From: theodu Date: Mon, 28 Aug 2023 16:38:14 +0200 Subject: [PATCH 24/24] feat: take last comments --- src/kili/gateways/kili_api_gateway/issue/__init__.py | 2 +- src/kili/gateways/kili_api_gateway/issue/types.py | 1 - src/kili/presentation/client/issue.py | 2 +- src/kili/use_cases/issue/__init__.py | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/kili/gateways/kili_api_gateway/issue/__init__.py b/src/kili/gateways/kili_api_gateway/issue/__init__.py index e4e0aa77b..e6d82a7d8 100644 --- a/src/kili/gateways/kili_api_gateway/issue/__init__.py +++ b/src/kili/gateways/kili_api_gateway/issue/__init__.py @@ -32,7 +32,7 @@ def create_issues( payload = { "issues": [ { - "issueNumber": issue.issue_number, + "issueNumber": 0, "labelID": issue.label_id, "objectMid": issue.object_mid, "type": type_, diff --git a/src/kili/gateways/kili_api_gateway/issue/types.py b/src/kili/gateways/kili_api_gateway/issue/types.py index a23e0a16d..fec001fe4 100644 --- a/src/kili/gateways/kili_api_gateway/issue/types.py +++ b/src/kili/gateways/kili_api_gateway/issue/types.py @@ -9,7 +9,6 @@ class IssueToCreateKiliAPIGatewayInput: """Data about an issue to create needed in graphql createIssue resolver.""" - issue_number: int label_id: Optional[str] object_mid: Optional[str] asset_id: str diff --git a/src/kili/presentation/client/issue.py b/src/kili/presentation/client/issue.py index fbe62f9e2..d792a9d9b 100644 --- a/src/kili/presentation/client/issue.py +++ b/src/kili/presentation/client/issue.py @@ -1,4 +1,4 @@ -"""Client entrypoints methods for issues.""" +"""Client presentation methods for issues.""" from itertools import repeat from typing import Dict, List, Literal, Optional diff --git a/src/kili/use_cases/issue/__init__.py b/src/kili/use_cases/issue/__init__.py index 8e6db2dc2..663069034 100644 --- a/src/kili/use_cases/issue/__init__.py +++ b/src/kili/use_cases/issue/__init__.py @@ -22,7 +22,6 @@ def create_issues(self, project_id, issues: List[IssueToCreateUseCaseInput]): ) # TODO: should be done in the backend gateway_issues = [ IssueToCreateKiliAPIGatewayInput( - issue_number=0, label_id=issue.label_id, object_mid=issue.object_mid, asset_id=label_asset_ids_map[issue.label_id],