Skip to content

Commit da43b98

Browse files
authored
Merge pull request #2007 from kili-technology/feature/lab-4126-aa-sdk-user-i-can-load-sdk-settings-using-a-dedicated-file
feat: Add configuration file
2 parents 1e3477e + 636f163 commit da43b98

File tree

17 files changed

+342
-10
lines changed

17 files changed

+342
-10
lines changed

kili-sdk-config.example.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"api_key": "your-api-key-here",
3+
"api_endpoint": "https://api-endpoint.com",
4+
"verify_ssl": true,
5+
"disable_tqdm": false
6+
}

src/kili/client.py

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from kili.adapters.authentification import is_api_key_valid
1212
from kili.adapters.http_client import HttpClient
1313
from kili.adapters.kili_api_gateway.kili_api_gateway import KiliAPIGateway
14+
from kili.core.config_loader import load_config_from_file
1415
from kili.core.graphql.graphql_client import GraphQLClient, GraphQLClientName
1516
from kili.entrypoints.mutations.asset import MutationsAsset
1617
from kili.entrypoints.mutations.issue import MutationsIssue
@@ -91,6 +92,7 @@ def __init__(
9192
verify: Optional[Union[bool, str]] = None,
9293
client_name: GraphQLClientName = GraphQLClientName.SDK,
9394
graphql_client_params: Optional[GraphQLClientParams] = None,
95+
disable_tqdm: bool | None = None,
9496
) -> None:
9597
"""Initialize Kili client.
9698
@@ -118,6 +120,10 @@ def __init__(
118120
client_name: For internal use only.
119121
Define the name of the graphQL client whith which graphQL calls will be sent.
120122
graphql_client_params: Parameters to pass to the graphQL client.
123+
disable_tqdm: Global setting to disable progress bars (tqdm) for all operations.
124+
Can be overridden by individual function calls.
125+
Default to `KILI_DISABLE_TQDM` environment variable.
126+
If not passed, default to `disable_tqdm` in config file or False.
121127
122128
Returns:
123129
Instance of the Kili client.
@@ -130,33 +136,57 @@ def __init__(
130136
kili.assets()
131137
kili.projects()
132138
```
139+
140+
Disable progress bars globally:
141+
```python
142+
kili = Kili(disable_tqdm=True)
143+
```
133144
"""
134-
api_key = api_key or os.getenv("KILI_API_KEY")
145+
config_file = load_config_from_file()
146+
147+
api_key = api_key or os.getenv("KILI_API_KEY") or config_file.get("api_key")
135148

136149
if not api_key and sys.stdin.isatty():
137150
api_key = getpass.getpass(
138151
"No `KILI_API_KEY` environment variable found.\nPlease enter your API key: "
139152
)
140153

141154
if api_endpoint is None:
142-
api_endpoint = os.getenv(
143-
"KILI_API_ENDPOINT",
144-
"https://cloud.kili-technology.com/api/label/v2/graphql",
155+
api_endpoint = (
156+
os.getenv("KILI_API_ENDPOINT")
157+
or config_file.get("api_endpoint")
158+
or "https://cloud.kili-technology.com/api/label/v2/graphql"
145159
)
146160

161+
if verify is None:
162+
verify_env = os.getenv("KILI_VERIFY")
163+
if verify_env is not None:
164+
verify = verify_env.lower() in ("true", "1", "yes")
165+
elif "verify_ssl" in config_file:
166+
verify = config_file["verify_ssl"]
167+
else:
168+
verify = True
169+
170+
# Load disable_tqdm from env or config if not explicitly provided
171+
if disable_tqdm is None:
172+
disable_tqdm_env = os.getenv("KILI_DISABLE_TQDM")
173+
if disable_tqdm_env is not None:
174+
disable_tqdm = disable_tqdm_env.lower() in ("true", "1", "yes")
175+
elif "disable_tqdm" in config_file:
176+
disable_tqdm = config_file["disable_tqdm"]
177+
# Otherwise keep as None to let individual functions use their own defaults
178+
179+
assert api_endpoint is not None
180+
assert verify is not None
181+
147182
if not api_key:
148183
raise AuthenticationFailed(api_key, api_endpoint)
149184

150-
if verify is None:
151-
verify = os.getenv(
152-
"KILI_VERIFY",
153-
"True",
154-
).lower() in ("true", "1", "yes")
155-
156185
self.api_key = api_key
157186
self.api_endpoint = api_endpoint
158187
self.verify = verify
159188
self.client_name = client_name
189+
self.disable_tqdm = disable_tqdm
160190
self.http_client = HttpClient(kili_endpoint=api_endpoint, verify=verify, api_key=api_key)
161191
skip_checks = os.getenv("KILI_SDK_SKIP_CHECKS") is not None
162192
if not skip_checks and not is_api_key_valid(

src/kili/core/config_loader.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""Configuration file loader for the Kili SDK."""
2+
3+
import json
4+
import warnings
5+
from pathlib import Path
6+
from typing import Any, Optional
7+
8+
9+
def load_config_from_file(
10+
filename: str = "kili-sdk-config.json",
11+
search_paths: Optional[list[Path]] = None,
12+
) -> dict[str, Any]:
13+
"""Load configuration from a JSON file.
14+
15+
Searches for the configuration file in the provided search paths.
16+
If no search paths are provided, searches in the current working directory
17+
and the user's home directory.
18+
19+
Args:
20+
filename: Name of the configuration file. Defaults to "kili-sdk-config.json".
21+
search_paths: List of directories to search for the configuration file.
22+
If None, defaults to [cwd, home].
23+
24+
Returns:
25+
A dictionary containing the configuration, or an empty dict if no file is found.
26+
"""
27+
if search_paths is None:
28+
search_paths = [
29+
Path.cwd(),
30+
Path.home(),
31+
]
32+
33+
for search_path in search_paths:
34+
config_path = search_path / filename
35+
if config_path.exists():
36+
try:
37+
with open(config_path, encoding="utf-8") as f:
38+
return json.load(f)
39+
except json.JSONDecodeError as e:
40+
warnings.warn(
41+
f"Invalid JSON in configuration file '{config_path}': {e}. Skipping this file",
42+
UserWarning,
43+
stacklevel=2,
44+
)
45+
continue
46+
except OSError as e:
47+
warnings.warn(
48+
f"Could not read configuration file '{config_path}': {e}. Skipping this file.",
49+
UserWarning,
50+
stacklevel=2,
51+
)
52+
continue
53+
54+
return {}

src/kili/presentation/client/asset.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from kili.domain.types import ListOrTuple
3030
from kili.presentation.client.helpers.common_validators import (
3131
disable_tqdm_if_as_generator,
32+
resolve_disable_tqdm,
3233
)
3334
from kili.presentation.client.helpers.filter_conversion import (
3435
extract_step_ids_from_project_steps,
@@ -499,6 +500,9 @@ def assets(
499500
step_name_in=step_name_not_in,
500501
)
501502

503+
# Resolve disable_tqdm: function parameter > client global setting > function default
504+
disable_tqdm = resolve_disable_tqdm(disable_tqdm, getattr(self, "disable_tqdm", None))
505+
502506
asset_use_cases = AssetUseCases(self.kili_api_gateway)
503507
filters = AssetFilters(
504508
project_id=ProjectId(project_id),

src/kili/presentation/client/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Base class for all client methods classes."""
22

33
from abc import ABC
4+
from typing import Optional
45

56
from kili.adapters.kili_api_gateway.kili_api_gateway import KiliAPIGateway
67

@@ -14,3 +15,4 @@ class BaseClientMethods(ABC):
1415
"""
1516

1617
kili_api_gateway: KiliAPIGateway # instantiated in the Kili client child class
18+
disable_tqdm: Optional[bool] # instantiated in the Kili client child class

src/kili/presentation/client/cloud_storage.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from kili.domain.types import ListOrTuple
2121
from kili.presentation.client.helpers.common_validators import (
2222
disable_tqdm_if_as_generator,
23+
resolve_disable_tqdm,
2324
)
2425
from kili.use_cases.cloud_storage import CloudStorageUseCases
2526
from kili.utils.logcontext import for_all_methods, log_call
@@ -127,6 +128,7 @@ def cloud_storage_connections(
127128
" project_id must be specified"
128129
)
129130

131+
disable_tqdm = resolve_disable_tqdm(disable_tqdm, getattr(self, "disable_tqdm", None))
130132
disable_tqdm = disable_tqdm_if_as_generator(as_generator, disable_tqdm)
131133

132134
cloud_storage_use_cases = CloudStorageUseCases(self.kili_api_gateway)
@@ -230,6 +232,7 @@ def cloud_storage_integrations(
230232
>>> kili.cloud_storage_integrations()
231233
[{'name': 'My bucket', 'id': '123456789', 'platform': 'AWS', 'status': 'CONNECTED'}]
232234
"""
235+
disable_tqdm = resolve_disable_tqdm(disable_tqdm, getattr(self, "disable_tqdm", None))
233236
disable_tqdm = disable_tqdm_if_as_generator(as_generator, disable_tqdm)
234237
options = QueryOptions(disable_tqdm, first, skip)
235238
data_integrations_gen = CloudStorageUseCases(self.kili_api_gateway).list_data_integrations(

src/kili/presentation/client/helpers/common_validators.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,32 @@ def disable_tqdm_if_as_generator(
2020
return disable_tqdm
2121

2222

23+
def resolve_disable_tqdm(
24+
disable_tqdm: bool | None, client_disable_tqdm: bool | None
25+
) -> bool | None:
26+
"""Resolve the disable_tqdm parameter with priority: function param > client global setting.
27+
28+
Args:
29+
disable_tqdm: The disable_tqdm parameter passed to the function.
30+
client_disable_tqdm: The global disable_tqdm setting from the client.
31+
Can be None if the client doesn't have this attribute set (e.g., in tests).
32+
33+
Returns:
34+
The resolved disable_tqdm value, or None if both are None (to use function default).
35+
36+
Examples:
37+
>>> resolve_disable_tqdm(True, False) # Function param takes priority
38+
True
39+
>>> resolve_disable_tqdm(None, True) # Use client global setting
40+
True
41+
>>> resolve_disable_tqdm(None, None) # Use function default
42+
None
43+
"""
44+
if disable_tqdm is not None:
45+
return disable_tqdm
46+
return client_disable_tqdm
47+
48+
2349
def assert_all_arrays_have_same_size(
2450
arrays: list[Optional[ListOrTuple[Any]]], raise_error: bool = True
2551
) -> bool:

src/kili/presentation/client/internal.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from kili.domain.api_key import ApiKeyFilters
1212
from kili.domain.types import ListOrTuple
1313
from kili.entrypoints.mutations.project.queries import GQL_DELETE_PROJECT
14+
from kili.presentation.client.helpers.common_validators import resolve_disable_tqdm
1415
from kili.presentation.client.organization import InternalOrganizationClientMethods
1516
from kili.use_cases.api_key import ApiKeyUseCases
1617

@@ -97,6 +98,7 @@ def api_keys(
9798
A result object which contains the query if it was successful,
9899
or an error message.
99100
"""
101+
disable_tqdm = resolve_disable_tqdm(disable_tqdm, getattr(self, "disable_tqdm", None))
100102
api_key_use_cases = ApiKeyUseCases(self.kili_api_gateway)
101103
filters = ApiKeyFilters(api_key_id=api_key_id, user_id=user_id, api_key=api_key)
102104
api_keys_gen = api_key_use_cases.list_api_keys(

src/kili/presentation/client/issue.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from kili.presentation.client.helpers.common_validators import (
1616
assert_all_arrays_have_same_size,
1717
disable_tqdm_if_as_generator,
18+
resolve_disable_tqdm,
1819
)
1920
from kili.use_cases.issue import IssueUseCases
2021
from kili.use_cases.issue.types import IssueToCreateUseCaseInput
@@ -195,6 +196,7 @@ def issues(
195196
"You cannot provide both `asset_id` and `asset_id_in` at the same time."
196197
)
197198

199+
disable_tqdm = resolve_disable_tqdm(disable_tqdm, getattr(self, "disable_tqdm", None))
198200
disable_tqdm = disable_tqdm_if_as_generator(as_generator, disable_tqdm)
199201
options = QueryOptions(disable_tqdm=disable_tqdm, first=first, skip=skip)
200202
issues_gen = IssueUseCases(self.kili_api_gateway).list_issues(

src/kili/presentation/client/label.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
from kili.presentation.client.helpers.common_validators import (
3838
assert_all_arrays_have_same_size,
3939
disable_tqdm_if_as_generator,
40+
resolve_disable_tqdm,
4041
)
4142
from kili.presentation.client.helpers.filter_conversion import (
4243
extract_step_ids_from_project_steps,
@@ -454,6 +455,7 @@ def labels(
454455
if category_search:
455456
validate_category_search_query(category_search)
456457

458+
disable_tqdm = resolve_disable_tqdm(disable_tqdm, getattr(self, "disable_tqdm", None))
457459
disable_tqdm = disable_tqdm_if_as_generator(as_generator, disable_tqdm)
458460
options = QueryOptions(disable_tqdm, first, skip)
459461

0 commit comments

Comments
 (0)