Skip to content

Commit a4387c9

Browse files
authored
feat: Add list events method (#1831)
1 parent 622d760 commit a4387c9

File tree

12 files changed

+332
-0
lines changed

12 files changed

+332
-0
lines changed

docs/sdk/event.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Event module
2+
3+
::: kili.event.presentation.client.event.EventClientMethods

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ nav:
1616
- Reference:
1717
- Asset: sdk/asset.md
1818
- Cloud Storage: sdk/cloud_storage.md
19+
- Event: sdk/event.md
1920
- Issue: sdk/issue.md
2021
- Label: sdk/label.md
2122
- Label Utils: sdk/label_utils.md
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Api key Kili Gateway module."""
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""GraphQL payload data mappers for api keys operations."""
2+
3+
from typing import Dict
4+
5+
from kili.domain.event import EventFilters, QueryOptions
6+
7+
8+
def event_where_wrapper(filter: EventFilters) -> Dict:
9+
"""Build the GraphQL EventMapperWhere variable to be sent in an operation."""
10+
return {
11+
"projectId": filter.project_id,
12+
"createdAtGte": filter.created_at_gte,
13+
"createdAtLte": filter.created_at_lte,
14+
"userId": filter.user_id,
15+
"event": filter.event,
16+
}
17+
18+
19+
def event_pagination_wrapper(options: QueryOptions) -> Dict:
20+
"""Build the GraphQL EventMapperPagination variable to be sent in an operation."""
21+
return {
22+
"fromEventId": options.from_event_id,
23+
"untilEventId": options.until_event_id,
24+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""GraphQL Event operations."""
2+
3+
4+
def get_events_query(fragment: str) -> str:
5+
"""Return the GraphQL events query."""
6+
return f"""
7+
query Events($where: EventsWhere!, $pagination: EventPagination!, $order: Order) {{
8+
data: events(where: $where, pagination: $pagination, order: $order) {{
9+
{fragment}
10+
}}
11+
}}
12+
"""
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""Mixin extending Kili API Gateway class with Api Keys related operations."""
2+
3+
from typing import List, Optional, cast
4+
5+
from kili.adapters.kili_api_gateway.base import BaseOperationMixin
6+
from kili.adapters.kili_api_gateway.event.mappers import (
7+
event_pagination_wrapper,
8+
event_where_wrapper,
9+
)
10+
from kili.adapters.kili_api_gateway.event.operations import get_events_query
11+
from kili.adapters.kili_api_gateway.event.queries import (
12+
PaginatedGraphQLQuery,
13+
)
14+
from kili.adapters.kili_api_gateway.helpers.queries import fragment_builder
15+
from kili.domain.event import EventDict, EventFilters, QueryOptions
16+
from kili.domain.types import ListOrTuple
17+
18+
DEFAULT_EVENT_FIELDS = [
19+
"createdAt",
20+
"event",
21+
"id",
22+
"organizationId",
23+
"payload",
24+
"projectId",
25+
"userId",
26+
]
27+
28+
29+
class EventOperationMixin(BaseOperationMixin):
30+
"""Mixin extending Kili API Gateway class with event related operations."""
31+
32+
def list_events(
33+
self,
34+
filters: EventFilters,
35+
options: QueryOptions,
36+
fields: Optional[ListOrTuple[str]] = None,
37+
) -> List[EventDict]:
38+
"""List events with given options."""
39+
fragment = fragment_builder(fields or DEFAULT_EVENT_FIELDS)
40+
query = get_events_query(fragment)
41+
where = event_where_wrapper(filters)
42+
pagination = event_pagination_wrapper(options)
43+
44+
return [
45+
cast(EventDict, item)
46+
for item in PaginatedGraphQLQuery(
47+
self.graphql_client
48+
).execute_query_from_paginated_call(
49+
query,
50+
where,
51+
pagination,
52+
options,
53+
)
54+
]
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""GraphQL module."""
2+
3+
from typing import Any, Dict, Generator
4+
5+
from kili.core.graphql.graphql_client import GraphQLClient
6+
from kili.domain.event import QueryOptions
7+
8+
9+
class PaginatedGraphQLQuery:
10+
"""Query class for querying Kili objects.
11+
12+
It factorizes code for executing paginated queries.
13+
"""
14+
15+
def __init__(self, graphql_client: GraphQLClient) -> None:
16+
"""Initialize the paginator."""
17+
self._graphql_client = graphql_client
18+
19+
# pylint: disable=too-many-arguments
20+
def execute_query_from_paginated_call(
21+
self,
22+
query: str,
23+
where: Dict[str, Any],
24+
pagination: Dict[str, Any],
25+
options: QueryOptions,
26+
) -> Generator[Dict, None, None]:
27+
"""Build a row generator from paginated query calls with the first and skip pattern.
28+
29+
Args:
30+
query: The object query to execute and to send to graphQL, in string format
31+
where: The where payload to send in the graphQL query
32+
pagination: The where pagination payload to send in the graphQL query
33+
options: The query options with skip and first and disable_tqdm
34+
"""
35+
count_elements_retrieved = 0
36+
while True:
37+
skip = count_elements_retrieved + options.skip
38+
first = options.batch_size
39+
order = options.order
40+
41+
payload = {
42+
"where": where,
43+
"pagination": {"skip": skip, "first": first, **pagination},
44+
"order": order,
45+
}
46+
elements = self._graphql_client.execute(query, payload)["data"]
47+
if not isinstance(elements, list):
48+
raise TypeError(
49+
"PaginatedGraphQLQuery only support operations returning a list of objects"
50+
)
51+
52+
if len(elements) == 0:
53+
break
54+
55+
yield from elements
56+
57+
count_elements_retrieved += len(elements)
58+
59+
if len(elements) < first:
60+
break

src/kili/adapters/kili_api_gateway/kili_api_gateway.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from kili.adapters.kili_api_gateway.api_key.operations_mixin import ApiKeyOperationMixin
55
from kili.adapters.kili_api_gateway.asset.operations_mixin import AssetOperationMixin
66
from kili.adapters.kili_api_gateway.cloud_storage import CloudStorageOperationMixin
7+
from kili.adapters.kili_api_gateway.event.operations_mixin import EventOperationMixin
78
from kili.adapters.kili_api_gateway.issue import IssueOperationMixin
89
from kili.adapters.kili_api_gateway.label.operations_mixin import LabelOperationMixin
910
from kili.adapters.kili_api_gateway.llm.operations_mixin import (
@@ -33,6 +34,7 @@ class KiliAPIGateway(
3334
ProjectOperationMixin,
3435
TagOperationMixin,
3536
UserOperationMixin,
37+
EventOperationMixin,
3638
):
3739
"""GraphQL gateway to communicate with Kili backend."""
3840

src/kili/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from kili.entrypoints.queries.project_user import QueriesProjectUser
2222
from kili.entrypoints.queries.project_version import QueriesProjectVersion
2323
from kili.entrypoints.subscriptions.label import SubscriptionsLabel
24+
from kili.event.presentation.client.event import EventClientMethods
2425
from kili.exceptions import AuthenticationFailed
2526
from kili.llm.presentation.client.llm import LlmClientMethods
2627
from kili.presentation.client.asset import AssetClientMethods
@@ -167,6 +168,7 @@ def __init__(
167168
self.kili_api_gateway = KiliAPIGateway(self.graphql_client, self.http_client)
168169
self.internal = InternalClientMethods(self.kili_api_gateway)
169170
self.llm = LlmClientMethods(self.kili_api_gateway)
171+
self.events = EventClientMethods(self.kili_api_gateway)
170172

171173
if not skip_checks:
172174
api_key_use_cases = ApiKeyUseCases(self.kili_api_gateway)

src/kili/domain/event.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Event domain."""
2+
3+
from dataclasses import dataclass
4+
from typing import TYPE_CHECKING, Literal, NamedTuple, Optional
5+
6+
from typing_extensions import TypedDict
7+
8+
from kili.core.constants import QUERY_BATCH_SIZE
9+
10+
if TYPE_CHECKING:
11+
from .project import ProjectId
12+
from .user import UserId
13+
14+
OrderType = Literal["asc", "desc"]
15+
16+
17+
@dataclass
18+
# pylint: disable=too-many-instance-attributes
19+
class EventFilters:
20+
"""Event filters for running an event search."""
21+
22+
project_id: "ProjectId"
23+
created_at_gte: Optional[str] = None
24+
created_at_lte: Optional[str] = None
25+
user_id: Optional["UserId"] = None
26+
event: Optional[str] = None
27+
28+
29+
class EventDict(TypedDict):
30+
"""Dict that represents an Event."""
31+
32+
created_at: str
33+
event: str
34+
id: str
35+
organization_id: str
36+
payload: dict
37+
project_id: str
38+
user_id: str
39+
40+
41+
class QueryOptions(NamedTuple):
42+
"""Options when calling GraphQLQuery from the SDK."""
43+
44+
first: Optional[int] = None
45+
skip: int = 0
46+
batch_size: int = QUERY_BATCH_SIZE
47+
from_event_id: Optional[str] = None
48+
until_event_id: Optional[str] = None
49+
order: Optional[OrderType] = "asc"

0 commit comments

Comments
 (0)