Skip to content

Commit 0df0400

Browse files
Merge pull request #1965 from kili-technology/feature/lab-3973-aa-sdk-user-i-see-an-easier-way-to-interact
feat: add domain API architecture
2 parents 380302b + 3baf8d5 commit 0df0400

File tree

68 files changed

+10400
-31
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+10400
-31
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ jobs:
8080
pip install -e ".[dev]"
8181
8282
- name: Unit and integration tests
83-
run: pytest -n auto -ra -sv --color yes --code-highlight yes --durations=15 -vv --ignore tests/e2e/ --cov=src/kili --cov-report=term-missing --cov-config=.coveragerc --cov-fail-under=80
83+
run: pytest -n auto -ra -sv --color yes --code-highlight yes --durations=15 -vv --ignore tests/e2e/ --cov=src/kili --cov-report=term-missing --cov-config=.coveragerc --cov-fail-under=75
8484

8585
markdown-link-check:
8686
timeout-minutes: 10

pyproject.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ dependencies = [
3535
"tenacity >= 8.0.0, < 9.0.0",
3636
"tqdm >= 4.0.0, < 5.0.0",
3737
"typeguard >= 4, < 5",
38-
"typing-extensions >= 4.1.0, < 5.0.0",
38+
"typing-extensions >= 4.5.0, < 5.0.0",
3939
"pyparsing >= 3.0.0, < 4.0.0",
4040
"websocket-client >= 1.0.0, < 2.0.0",
4141
"cuid >= 0.4, < 0.5",
@@ -200,6 +200,5 @@ line-ending = "lf"
200200
[tool.ruff.isort]
201201
known-first-party = ["src", "tests"]
202202

203-
[tool.pytest]
204-
ini_options = { pythonpath = ["src", "."] }
205-
aliases = ["test = pytest"]
203+
[tool.pytest.ini_options]
204+
pythonpath = ["src", "."]

src/kili/adapters/kili_api_gateway/label/mappers.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,13 @@ def update_label_data_mapper(data: UpdateLabelData) -> Dict:
4646
def append_label_data_mapper(data: AppendLabelData) -> Dict:
4747
"""Map AppendLabelData to GraphQL AppendLabelData input."""
4848
return {
49-
"authorID": data.author_id,
5049
"assetID": data.asset_id,
50+
"authorID": data.author_id,
5151
"clientVersion": data.client_version,
5252
"jsonResponse": json.dumps(data.json_response),
53-
"secondsToLabel": data.seconds_to_label,
5453
"modelName": data.model_name,
54+
"referencedLabelId": data.referenced_label_id,
55+
"secondsToLabel": data.seconds_to_label,
5556
}
5657

5758

src/kili/adapters/kili_api_gateway/label/types.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ class UpdateLabelData:
2222
class AppendLabelData:
2323
"""AppendLabelData data."""
2424

25-
author_id: Optional[UserId]
2625
asset_id: AssetId
26+
author_id: Optional[UserId]
2727
client_version: Optional[int]
2828
json_response: Dict
29-
seconds_to_label: Optional[float]
3029
model_name: Optional[str]
30+
referenced_label_id: Optional[str]
31+
seconds_to_label: Optional[float]
3132

3233

3334
@dataclass

src/kili/client.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ def __init__(
8686
) -> None:
8787
"""Initialize Kili client.
8888
89+
This client provides access to methods through mixin inheritance.
90+
For the domain-based API, use `from kili.client_domain import Kili` instead.
91+
8992
Args:
9093
api_key: User API key generated
9194
from https://cloud.kili-technology.com/label/my-account/api-key.
@@ -116,10 +119,8 @@ def __init__(
116119
from kili.client import Kili
117120
118121
kili = Kili()
119-
120-
kili.assets() # list your assets
121-
kili.labels() # list your labels
122-
kili.projects() # list your projects
122+
kili.assets()
123+
kili.projects()
123124
```
124125
"""
125126
api_key = api_key or os.getenv("KILI_API_KEY")

src/kili/client_domain.py

Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
"""Kili Python SDK client."""
2+
3+
import logging
4+
import warnings
5+
from functools import cached_property
6+
from typing import TYPE_CHECKING, Dict, Optional, Union
7+
8+
from kili.client import Kili as KiliLegacy
9+
from kili.core.graphql.graphql_client import GraphQLClientName
10+
11+
if TYPE_CHECKING:
12+
from kili.domain_api import (
13+
AssetsNamespace,
14+
ExportNamespace,
15+
IssuesNamespace,
16+
LabelsNamespace,
17+
OrganizationsNamespace,
18+
ProjectsNamespace,
19+
QuestionsNamespace,
20+
StoragesNamespace,
21+
TagsNamespace,
22+
UsersNamespace,
23+
)
24+
25+
warnings.filterwarnings("default", module="kili", category=DeprecationWarning)
26+
27+
28+
class FilterPoolFullWarning(logging.Filter):
29+
"""Filter out the specific urllib3 warning related to the connection pool."""
30+
31+
def filter(self, record) -> bool:
32+
"""urllib3.connectionpool:Connection pool is full, discarding connection: ..."""
33+
return "Connection pool is full, discarding connection" not in record.getMessage()
34+
35+
36+
logging.getLogger("urllib3.connectionpool").addFilter(FilterPoolFullWarning())
37+
38+
39+
class Kili:
40+
"""Kili Client (domain mode)."""
41+
42+
legacy_client: KiliLegacy
43+
44+
def __init__(
45+
self,
46+
api_key: Optional[str] = None,
47+
api_endpoint: Optional[str] = None,
48+
verify: Optional[Union[bool, str]] = None,
49+
graphql_client_params: Optional[Dict[str, object]] = None,
50+
) -> None:
51+
"""Initialize Kili client (domain mode).
52+
53+
This client provides access to domain-based namespaces.
54+
For the legacy API with methods, use `from kili.client import Kili` instead.
55+
56+
Args:
57+
api_key: User API key generated
58+
from https://cloud.kili-technology.com/label/my-account/api-key.
59+
Default to `KILI_API_KEY` environment variable.
60+
If not passed, requires the `KILI_API_KEY` environment variable to be set.
61+
api_endpoint: Recipient of the HTTP operation.
62+
Default to `KILI_API_ENDPOINT` environment variable.
63+
If not passed, default to Kili SaaS:
64+
'https://cloud.kili-technology.com/api/label/v2/graphql'
65+
verify: similar to `requests`' verify.
66+
Either a boolean, in which case it controls whether we verify
67+
the server's TLS certificate, or a string, in which case it must be a path
68+
to a CA bundle to use. Defaults to ``True``. When set to
69+
``False``, requests will accept any TLS certificate presented by
70+
the server, and will ignore hostname mismatches and/or expired
71+
certificates, which will make your application vulnerable to
72+
man-in-the-middle (MitM) attacks. Setting verify to ``False``
73+
may be useful during local development or testing.
74+
graphql_client_params: Parameters to pass to the graphQL client.
75+
76+
Returns:
77+
Instance of the Kili client.
78+
79+
Examples:
80+
```python
81+
from kili.client_domain import Kili
82+
83+
# Domain API with namespaces
84+
kili = Kili()
85+
kili.assets # domain namespace (clean name)
86+
kili.projects.list() # domain methods
87+
```
88+
"""
89+
warnings.warn(
90+
"Client domain api is still a work in progress. Method names and return type will evolve.",
91+
stacklevel=1,
92+
)
93+
self.legacy_client = KiliLegacy(
94+
api_key,
95+
api_endpoint,
96+
verify,
97+
GraphQLClientName.SDK_DOMAIN,
98+
graphql_client_params,
99+
)
100+
101+
# Domain API Namespaces - Lazy loaded properties
102+
@cached_property
103+
def assets(self) -> "AssetsNamespace":
104+
"""Get the assets domain namespace.
105+
106+
Returns:
107+
AssetsNamespace: Assets domain namespace with lazy loading
108+
109+
Examples:
110+
```python
111+
kili = Kili()
112+
# Namespace is instantiated on first access
113+
assets = kili.assets
114+
```
115+
"""
116+
from kili.domain_api import AssetsNamespace # pylint: disable=import-outside-toplevel
117+
118+
return AssetsNamespace(self.legacy_client, self.legacy_client.kili_api_gateway)
119+
120+
@cached_property
121+
def labels(self) -> "LabelsNamespace":
122+
"""Get the labels domain namespace.
123+
124+
Returns:
125+
LabelsNamespace: Labels domain namespace with lazy loading
126+
127+
Examples:
128+
```python
129+
kili = Kili()
130+
# Namespace is instantiated on first access
131+
labels = kili.labels
132+
```
133+
"""
134+
from kili.domain_api import LabelsNamespace # pylint: disable=import-outside-toplevel
135+
136+
return LabelsNamespace(self.legacy_client, self.legacy_client.kili_api_gateway)
137+
138+
@cached_property
139+
def projects(self) -> "ProjectsNamespace":
140+
"""Get the projects domain namespace.
141+
142+
Returns:
143+
ProjectsNamespace: Projects domain namespace with lazy loading
144+
145+
Examples:
146+
```python
147+
kili = Kili()
148+
# Namespace is instantiated on first access
149+
projects = kili.projects
150+
```
151+
"""
152+
from kili.domain_api import ProjectsNamespace # pylint: disable=import-outside-toplevel
153+
154+
return ProjectsNamespace(self.legacy_client, self.legacy_client.kili_api_gateway)
155+
156+
@cached_property
157+
def users(self) -> "UsersNamespace":
158+
"""Get the users domain namespace.
159+
160+
Returns:
161+
UsersNamespace: Users domain namespace with lazy loading
162+
163+
Examples:
164+
```python
165+
kili = Kili()
166+
# Namespace is instantiated on first access
167+
users = kili.users
168+
```
169+
"""
170+
from kili.domain_api import UsersNamespace # pylint: disable=import-outside-toplevel
171+
172+
return UsersNamespace(self.legacy_client, self.legacy_client.kili_api_gateway)
173+
174+
@cached_property
175+
def organizations(self) -> "OrganizationsNamespace":
176+
"""Get the organizations domain namespace.
177+
178+
Returns:
179+
OrganizationsNamespace: Organizations domain namespace with lazy loading
180+
181+
Examples:
182+
```python
183+
kili = Kili()
184+
# Namespace is instantiated on first access
185+
organizations = kili.organizations
186+
```
187+
"""
188+
from kili.domain_api import ( # pylint: disable=import-outside-toplevel
189+
OrganizationsNamespace,
190+
)
191+
192+
return OrganizationsNamespace(self.legacy_client, self.legacy_client.kili_api_gateway)
193+
194+
@cached_property
195+
def issues(self) -> "IssuesNamespace":
196+
"""Get the issues domain namespace.
197+
198+
Returns:
199+
IssuesNamespace: Issues domain namespace with lazy loading
200+
201+
Examples:
202+
```python
203+
kili = Kili()
204+
# Namespace is instantiated on first access
205+
issues = kili.issues
206+
```
207+
"""
208+
from kili.domain_api import IssuesNamespace # pylint: disable=import-outside-toplevel
209+
210+
return IssuesNamespace(self.legacy_client, self.legacy_client.kili_api_gateway)
211+
212+
@cached_property
213+
def questions(self) -> "QuestionsNamespace":
214+
"""Get the questions domain namespace.
215+
216+
Returns:
217+
QuestionsNamespace: Questions domain namespace with lazy loading
218+
219+
Examples:
220+
```python
221+
kili = Kili()
222+
# Namespace is instantiated on first access
223+
questions = kili.questions
224+
```
225+
"""
226+
from kili.domain_api import QuestionsNamespace # pylint: disable=import-outside-toplevel
227+
228+
return QuestionsNamespace(self.legacy_client, self.legacy_client.kili_api_gateway)
229+
230+
@cached_property
231+
def tags(self) -> "TagsNamespace":
232+
"""Get the tags domain namespace.
233+
234+
Returns:
235+
TagsNamespace: Tags domain namespace with lazy loading
236+
237+
Examples:
238+
```python
239+
kili = Kili()
240+
# Namespace is instantiated on first access
241+
tags = kili.tags
242+
```
243+
"""
244+
from kili.domain_api import TagsNamespace # pylint: disable=import-outside-toplevel
245+
246+
return TagsNamespace(self.legacy_client, self.legacy_client.kili_api_gateway)
247+
248+
@cached_property
249+
def storages(self) -> "StoragesNamespace":
250+
"""Get the storages domain namespace.
251+
252+
Returns:
253+
StoragesNamespace: Storages domain namespace with lazy loading
254+
255+
Examples:
256+
```python
257+
kili = Kili()
258+
# Namespace is instantiated on first access
259+
storages = kili.storages
260+
# Access nested namespaces
261+
integrations = kili.storages.integrations
262+
connections = kili.storages.connections
263+
```
264+
"""
265+
from kili.domain_api import StoragesNamespace # pylint: disable=import-outside-toplevel
266+
267+
return StoragesNamespace(self.legacy_client, self.legacy_client.kili_api_gateway)
268+
269+
@cached_property
270+
def exports(self) -> "ExportNamespace":
271+
"""Get the exports domain namespace.
272+
273+
Returns:
274+
ExportNamespace: Exports domain namespace with lazy loading
275+
276+
Examples:
277+
```python
278+
kili = Kili()
279+
# Namespace is instantiated on first access
280+
exports = kili.exports
281+
```
282+
"""
283+
from kili.domain_api import ExportNamespace # pylint: disable=import-outside-toplevel
284+
285+
return ExportNamespace(self.legacy_client, self.legacy_client.kili_api_gateway)

src/kili/core/graphql/clientnames.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ class GraphQLClientName(Enum):
77
"""GraphQL client name."""
88

99
SDK = "python-sdk"
10+
SDK_DOMAIN = "python-sdk-domain"
1011
CLI = "python-cli"

src/kili/core/graphql/graphql_client.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,14 +316,16 @@ def _raw_execute(
316316
self, document: DocumentNode, variables: Optional[Dict], **kwargs
317317
) -> Dict[str, Any]:
318318
_limiter.try_acquire("GraphQLClient.execute")
319+
log_context = LogContext()
320+
log_context.set_client_name(self.client_name)
319321
with _execute_lock:
320322
res = self._gql_client.execute(
321323
document=document,
322324
variable_values=variables,
323325
extra_args={
324326
"headers": {
325327
**(self._gql_transport.headers or {}),
326-
**LogContext(),
328+
**log_context,
327329
}
328330
},
329331
**kwargs,

src/kili/domain/asset/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""Asset domain."""
22

3-
from .asset import AssetExternalId, AssetFilters, AssetId, AssetStatus
3+
from .asset import AssetExternalId, AssetFilters, AssetId, AssetStatus, get_asset_default_fields
44

5-
__all__ = ["AssetFilters", "AssetId", "AssetExternalId", "AssetStatus"]
5+
__all__ = ["AssetFilters", "AssetId", "AssetExternalId", "AssetStatus", "get_asset_default_fields"]

0 commit comments

Comments
 (0)