craft-ai-sdk 0.65.1rc1__tar.gz → 0.65.2rc1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of craft-ai-sdk might be problematic. Click here for more details.

Files changed (33) hide show
  1. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/PKG-INFO +1 -1
  2. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/__init__.py +1 -1
  3. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/core/deployments.py +2 -0
  4. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/core/endpoints.py +31 -28
  5. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/core/environment_variables.py +2 -1
  6. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/core/pipeline_executions.py +1 -2
  7. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/core/pipeline_metrics.py +3 -3
  8. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/core/resource_metrics.py +2 -0
  9. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/core/steps.py +7 -7
  10. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/core/users.py +1 -0
  11. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/core/vector_database.py +2 -1
  12. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/sdk.py +6 -5
  13. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/shared/logger.py +1 -1
  14. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/shared/request_response_handler.py +4 -3
  15. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/shared/warnings.py +2 -3
  16. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/utils/datetime_utils.py +2 -1
  17. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/utils/file_utils.py +5 -6
  18. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/documentation.pdf +0 -0
  19. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/pyproject.toml +13 -5
  20. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/LICENSE +0 -0
  21. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/README.md +0 -0
  22. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/constants.py +0 -0
  23. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/core/data_store.py +0 -0
  24. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/core/pipelines.py +2 -2
  25. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/exceptions.py +0 -0
  26. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/io.py +2 -2
  27. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/shared/authentication.py +0 -0
  28. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/shared/environments.py +0 -0
  29. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/shared/execution_context.py +0 -0
  30. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/shared/helpers.py +0 -0
  31. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/shared/types.py +0 -0
  32. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/utils/__init__.py +0 -0
  33. {craft_ai_sdk-0.65.1rc1 → craft_ai_sdk-0.65.2rc1}/craft_ai_sdk/utils/dict_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: craft-ai-sdk
3
- Version: 0.65.1rc1
3
+ Version: 0.65.2rc1
4
4
  Summary: Craft AI MLOps platform SDK
5
5
  License: Apache-2.0
6
6
  Author: Craft AI
@@ -13,4 +13,4 @@ from .io import ( # noqa: F401
13
13
  )
14
14
  from .sdk import CraftAiSdk # noqa: F401
15
15
 
16
- __version__ = "0.65.1rc1"
16
+ __version__ = "0.65.2rc1"
@@ -1,8 +1,10 @@
1
1
  from datetime import datetime
2
2
  from typing import Literal, TypedDict, Union
3
+
3
4
  from typing_extensions import NotRequired
4
5
 
5
6
  from craft_ai_sdk.shared.types import Log
7
+
6
8
  from ..constants import DEPLOYMENT_EXECUTION_RULES, DEPLOYMENT_MODES, DEPLOYMENT_STATUS
7
9
  from ..exceptions import SdkException
8
10
  from ..io import (
@@ -5,10 +5,10 @@ from urllib.parse import urlencode
5
5
  import requests
6
6
 
7
7
  from ..sdk import BaseCraftAiSdk
8
+ from ..shared.authentication import use_authentication
8
9
  from ..shared.logger import log_func_result
9
10
  from ..shared.request_response_handler import handle_http_response
10
11
  from .deployments import get_deployment
11
- from ..shared.authentication import use_authentication
12
12
 
13
13
 
14
14
  def _get_endpoint_url_path(sdk: BaseCraftAiSdk, endpoint_name: str):
@@ -36,9 +36,9 @@ class EndpointNewToken(TypedDict):
36
36
  def trigger_endpoint(
37
37
  sdk: BaseCraftAiSdk,
38
38
  endpoint_name: str,
39
- endpoint_token: str,
40
- inputs: dict[str, Any],
41
- wait_for_completion: Literal[True],
39
+ endpoint_token: Union[str, None] = None,
40
+ inputs: Union[dict[str, Any], None] = None,
41
+ wait_for_completion: Literal[True] = True,
42
42
  ) -> EndpointTriggerWithOutputs: ...
43
43
 
44
44
 
@@ -46,9 +46,9 @@ def trigger_endpoint(
46
46
  def trigger_endpoint(
47
47
  sdk: BaseCraftAiSdk,
48
48
  endpoint_name: str,
49
- endpoint_token: str,
50
- inputs: dict[str, Any],
51
- wait_for_completion: Literal[False],
49
+ endpoint_token: Union[str, None] = None,
50
+ inputs: Union[dict[str, Any], None] = None,
51
+ wait_for_completion: Literal[False] = False,
52
52
  ) -> EndpointTriggerBase: ...
53
53
 
54
54
 
@@ -97,9 +97,7 @@ def trigger_endpoint(
97
97
  body[input_name] = input_value
98
98
 
99
99
  if endpoint_token is None:
100
- url = (
101
- f"{sdk.base_environment_api_url}" f"/deployments/{endpoint_name}/executions"
102
- )
100
+ url = f"{sdk.base_environment_api_url}/deployments/{endpoint_name}/executions"
103
101
  do_post = use_authentication(
104
102
  lambda sdk, *args, **kwargs: sdk._session.post(*args, **kwargs)
105
103
  )
@@ -167,7 +165,7 @@ def retrieve_endpoint_results(
167
165
 
168
166
  url = (
169
167
  f"{sdk.base_environment_url}"
170
- f"/endpoints/{endpoint_url_path}/executions/{execution_id}"
168
+ f"/endpoints/v1/{endpoint_url_path}/executions/{execution_id}"
171
169
  )
172
170
  query = urlencode({"token": endpoint_token})
173
171
  response = requests.get(f"{url}?{query}")
@@ -182,19 +180,27 @@ def retrieve_endpoint_results(
182
180
  except KeyError:
183
181
  return response.json()
184
182
 
185
- if "application/octet-stream" in response.headers.get("Content-Type", ""):
186
- content_disposition = response.headers.get("Content-Disposition", "")
187
- output_name = content_disposition.split(f"_{execution_id}_")[1]
188
- return {
189
- "outputs": {output_name: handled_response},
190
- "execution_id": execution_id,
191
- }
192
- else:
193
- response_data = handle_http_response(response)
194
- return {
195
- "outputs": response_data.get("outputs", []),
196
- "execution_id": execution_id,
197
- }
183
+ response_data = handle_http_response(response)
184
+ outputs = response_data.get("outputs", {})
185
+
186
+ for key, value in outputs.items():
187
+ if isinstance(value, dict) and "value" in value:
188
+ outputs[key] = value["value"]
189
+ if isinstance(value, dict) and "url" in value:
190
+ # If the output is a file, we need to download it
191
+ url = f"{value['url']}?token={endpoint_token}"
192
+ file_response = requests.get(url)
193
+ if file_response.status_code == 200:
194
+ outputs[key] = file_response.content
195
+ else:
196
+ raise ValueError(
197
+ f"Failed to download file output {key}: {file_response.text}"
198
+ )
199
+
200
+ return {
201
+ "outputs": response_data.get("outputs", []),
202
+ "execution_id": execution_id,
203
+ }
198
204
 
199
205
 
200
206
  def generate_new_endpoint_token(
@@ -211,8 +217,5 @@ def generate_new_endpoint_token(
211
217
 
212
218
  * ``"endpoint_token"`` (:obj:`str`): New endpoint token.
213
219
  """
214
- url = (
215
- f"{sdk.base_environment_api_url}"
216
- f"/endpoints/{endpoint_name}/generate-new-token"
217
- )
220
+ url = f"{sdk.base_environment_api_url}/endpoints/{endpoint_name}/generate-new-token"
218
221
  return sdk._post(url)
@@ -1,4 +1,5 @@
1
1
  from typing import TypedDict
2
+
2
3
  from ..sdk import BaseCraftAiSdk
3
4
  from ..shared.logger import log_func_result
4
5
 
@@ -24,7 +25,7 @@ def create_or_update_environment_variable(
24
25
  Returns:
25
26
  None
26
27
  """
27
- url = f"{sdk.base_environment_api_url}" f"/environment-variables"
28
+ url = f"{sdk.base_environment_api_url}/environment-variables"
28
29
  data = {
29
30
  "environment_variables": {
30
31
  environment_variable_name: environment_variable_value
@@ -177,8 +177,7 @@ def _retrieve_pipeline_execution_input_value(
177
177
  sdk: BaseCraftAiSdk, execution_id, input_name
178
178
  ):
179
179
  url = (
180
- f"{sdk.base_environment_api_url}"
181
- f"/executions/{execution_id}/inputs/{input_name}"
180
+ f"{sdk.base_environment_api_url}/executions/{execution_id}/inputs/{input_name}"
182
181
  )
183
182
  response = sdk._get(url)
184
183
  return response
@@ -1,5 +1,5 @@
1
- from typing import TypedDict, Union
2
1
  import warnings
2
+ from typing import TypedDict, Union
3
3
 
4
4
  from ..sdk import BaseCraftAiSdk
5
5
  from ..shared.execution_context import get_execution_id
@@ -46,7 +46,7 @@ been sent",
46
46
  stacklevel=2,
47
47
  )
48
48
  return False
49
- url = f"{sdk.base_environment_api_url}" f"/metrics/single-value/{name}"
49
+ url = f"{sdk.base_environment_api_url}/metrics/single-value/{name}"
50
50
  data = {"value": value, "execution_id": get_execution_id()}
51
51
  sdk._put(url, json=data)
52
52
  return True
@@ -81,7 +81,7 @@ been sent",
81
81
 
82
82
  BATCH_SIZE = 10000
83
83
  for i in range(0, len(values), BATCH_SIZE):
84
- url = f"{sdk.base_environment_api_url}" f"/metrics/list-values/{name}"
84
+ url = f"{sdk.base_environment_api_url}/metrics/list-values/{name}"
85
85
  data = {
86
86
  "values": values[i : i + BATCH_SIZE],
87
87
  "execution_id": get_execution_id(),
@@ -1,6 +1,8 @@
1
1
  from datetime import datetime
2
2
  from typing import Literal, TypedDict, Union, overload
3
+
3
4
  from typing_extensions import NotRequired
5
+
4
6
  from ..sdk import BaseCraftAiSdk
5
7
  from ..utils import datetime_to_timestamp_in_ms
6
8
 
@@ -1,11 +1,11 @@
1
- from datetime import datetime
2
1
  import io
3
2
  import os
4
3
  import tarfile
5
- from typing import TypeVar, TypedDict, Union, cast
6
- from typing_extensions import NotRequired
4
+ from datetime import datetime
5
+ from typing import TypedDict, TypeVar, Union, cast
7
6
 
8
7
  import requests
8
+ from typing_extensions import NotRequired
9
9
 
10
10
  from craft_ai_sdk.shared.types import Log
11
11
 
@@ -97,11 +97,11 @@ def _validate_create_step_parameters(
97
97
  raise ValueError("The timeout must be greater than 0 or None.")
98
98
 
99
99
  if inputs is not None:
100
- if any([not isinstance(input_, Input) for input_ in inputs]):
100
+ if any(not isinstance(input_, Input) for input_ in inputs):
101
101
  raise ValueError("'inputs' must be a list of instances of Input.")
102
102
 
103
103
  if outputs is not None:
104
- if any([not isinstance(output_, Output) for output_ in outputs]):
104
+ if any(not isinstance(output_, Output) for output_ in outputs):
105
105
  raise ValueError("'outputs' must be a list of instances of Output.")
106
106
 
107
107
 
@@ -191,12 +191,12 @@ def _add_inputs_outputs_in_message(message, inputs, outputs):
191
191
  message += "\n Inputs: "
192
192
  for inp in inputs:
193
193
  required_str = ", required" if inp.get("is_required", False) else ""
194
- message += f'\n - {inp.get("name", inp.get("input_name"))} ({inp["data_type"]}{required_str})' # noqa: E501
194
+ message += f"\n - {inp.get('name', inp.get('input_name'))} ({inp['data_type']}{required_str})" # noqa: E501
195
195
 
196
196
  if outputs:
197
197
  message += "\n Outputs: "
198
198
  for output in outputs:
199
- message += f'\n - {output.get("name", output.get("output_name"))} ({output["data_type"]})' # noqa: E501
199
+ message += f"\n - {output.get('name', output.get('output_name'))} ({output['data_type']})" # noqa: E501
200
200
  return message
201
201
 
202
202
 
@@ -1,4 +1,5 @@
1
1
  from typing import TypedDict
2
+
2
3
  from ..sdk import BaseCraftAiSdk
3
4
 
4
5
 
@@ -1,4 +1,5 @@
1
1
  from typing import TypedDict
2
+
2
3
  from craft_ai_sdk.shared.environments import get_environment_id
3
4
 
4
5
  from ..sdk import BaseCraftAiSdk
@@ -41,7 +42,7 @@ def get_weaviate_client(sdk: BaseCraftAiSdk):
41
42
  raise ModuleNotFoundError(
42
43
  "The 'weaviate' package is required to use the vector database. "
43
44
  "You can install it with 'pip install weaviate-client'."
44
- )
45
+ ) from None
45
46
  credentials = get_vector_database_credentials(sdk)
46
47
 
47
48
  is_secure = credentials["vector_database_url"].startswith("https://")
@@ -1,10 +1,10 @@
1
1
  import os
2
2
  import sys
3
3
  import time
4
- from typing import Any
5
4
  import warnings
6
5
  from abc import ABC, abstractmethod
7
6
  from datetime import timedelta
7
+ from typing import Any
8
8
 
9
9
  import jwt
10
10
  import requests
@@ -92,8 +92,8 @@ class CraftAiSdk(BaseCraftAiSdk):
92
92
  get_pipeline_execution_logs,
93
93
  get_pipeline_execution_output,
94
94
  list_pipeline_executions,
95
- run_pipeline,
96
95
  retrieve_pipeline_execution_outputs,
96
+ run_pipeline,
97
97
  )
98
98
  from .core.pipeline_metrics import (
99
99
  get_list_metrics,
@@ -119,7 +119,6 @@ class CraftAiSdk(BaseCraftAiSdk):
119
119
  list_steps,
120
120
  )
121
121
  from .core.users import get_user
122
-
123
122
  from .core.vector_database import (
124
123
  get_vector_database_credentials,
125
124
  get_weaviate_client,
@@ -139,7 +138,7 @@ class CraftAiSdk(BaseCraftAiSdk):
139
138
  os.environ.get("CRAFT_AI__MULTIPART_PART_SIZE__B", str(38 * 256 * 1024))
140
139
  )
141
140
  _access_token_margin = timedelta(seconds=30)
142
- _version = "0.65.1rc1" # Would be better to share it somewhere
141
+ _version = "0.65.2rc1" # Would be better to share it somewhere
143
142
 
144
143
  def __init__(
145
144
  self,
@@ -216,7 +215,9 @@ class CraftAiSdk(BaseCraftAiSdk):
216
215
  verbose_log = (
217
216
  True
218
217
  if env_verbose_log == "true"
219
- else False if env_verbose_log == "false" else hasattr(sys, "ps1")
218
+ else False
219
+ if env_verbose_log == "false"
220
+ else hasattr(sys, "ps1")
220
221
  )
221
222
  self.verbose_log = verbose_log
222
223
 
@@ -7,7 +7,7 @@ from craft_ai_sdk.exceptions import SdkException
7
7
 
8
8
  def log_action(sdk, message: str, should_log: Union[bool, Callable[[], bool]] = True):
9
9
  if sdk.verbose_log and (should_log() if callable(should_log) else should_log):
10
- print(message, file=sys.stderr)
10
+ print(message, file=sys.stderr) # noqa: T201
11
11
 
12
12
 
13
13
  def log_func_result(message: str, should_log: Union[bool, Callable[[], bool]] = True):
@@ -1,6 +1,6 @@
1
- from typing import Any, Callable
2
1
  import xml.etree.ElementTree as ET
3
2
  from json import JSONDecodeError
3
+ from typing import Any, Callable
4
4
 
5
5
  from requests import RequestException, Response
6
6
 
@@ -20,6 +20,7 @@ def handle_data_store_response(response: Response):
20
20
 
21
21
  Returns:
22
22
  :obj:`str`: Content of the response.
23
+
23
24
  """
24
25
  if 200 <= response.status_code < 300:
25
26
  return response.content
@@ -36,12 +37,12 @@ def handle_data_store_response(response: Response):
36
37
  name=error_code,
37
38
  additional_data=error_infos,
38
39
  )
39
- except ET.ParseError:
40
+ except ET.ParseError as error:
40
41
  raise SdkException(
41
42
  "Unable to decode response from the data store: "
42
43
  f"Content being:\n'{response.text}'",
43
44
  status_code=response.status_code,
44
- )
45
+ ) from error
45
46
 
46
47
 
47
48
  def _parse_json_response(response: Response):
@@ -13,8 +13,7 @@ MULTIPLE_EXPERIMENTAL_WARNING_JOINER = "\n * "
13
13
  def _documentation_function_warning_decorator(
14
14
  func_or_message, default_message, warning_category
15
15
  ):
16
- """
17
- A general-purpose decorator for issuing function warnings. This function is
16
+ """A general-purpose decorator for issuing function warnings. This function is
18
17
  designed to be used within other decorators to add warning functionality.
19
18
 
20
19
  Args:
@@ -26,6 +25,7 @@ def _documentation_function_warning_decorator(
26
25
  warning_category (class): The category of warning to be used.
27
26
  This should be a class derived from the Warning class,
28
27
  such as DeprecationWarning, FutureWarning, etc.
28
+
29
29
  """
30
30
 
31
31
  def _get_wrapper(func, warning_message):
@@ -161,7 +161,6 @@ def deprecated(func_or_message):
161
161
  (i.e. `@deprecated`), a default deprecation warning message will be used.
162
162
 
163
163
  """
164
-
165
164
  DEFAULT_DEPRECATION_WARNING_MESSAGE = (
166
165
  "This function is deprecated and its usage is not supported."
167
166
  )
@@ -9,7 +9,7 @@ def datetime_to_timestamp_in_ms(dt: datetime) -> int:
9
9
 
10
10
 
11
11
  def parse_isodate(date_string: str):
12
- """_summary_
12
+ """Parse a date string in ISO 8601 format and return a `datetime` object.
13
13
 
14
14
  Args:
15
15
  date_string (str): date in ISO 8601 format potentially ending with
@@ -17,6 +17,7 @@ def parse_isodate(date_string: str):
17
17
 
18
18
  Returns:
19
19
  :obj:`datetime.datetime`: A `datetime` corresponding to `date_string`.
20
+
20
21
  """
21
22
  if date_string[-1] == "Z":
22
23
  date_string = date_string.rstrip("Z")
@@ -39,9 +39,7 @@ def chunk_buffer(buffer: IOBase, size: int) -> Iterable[ChunkedIO]:
39
39
 
40
40
 
41
41
  def convert_size(size_in_bytes: Union[int, float]):
42
- """
43
- Convert a size in bytes to a human readable string.
44
- """
42
+ """Convert a size in bytes to a human readable string."""
45
43
  units = ["B", "KB", "MB", "GB", "TB"]
46
44
  for unit in units: # noqa: B007
47
45
  if size_in_bytes < 1024.0:
@@ -53,9 +51,9 @@ def convert_size(size_in_bytes: Union[int, float]):
53
51
  # Adapted from
54
52
  # https://gist.github.com/kazqvaizer/4cebebe5db654a414132809f9f88067b#file-multipartify-py-L13-L33
55
53
  def multipartify(data, parent_key: Union[str, None] = None) -> dict:
56
- """
57
- Convert a nested dictionary or list into a format suitable for multipart/form-data.
58
- This is useful for triggering endpoints that require multipart/form-data payloads.
54
+ """Convert a nested dictionary or list into a format suitable for
55
+ multipart/form-data. This is useful for triggering endpoints that require
56
+ multipart/form-data payloads.
59
57
 
60
58
  Args:
61
59
  data (:obj:`any`):
@@ -65,6 +63,7 @@ def multipartify(data, parent_key: Union[str, None] = None) -> dict:
65
63
 
66
64
  Returns:
67
65
  :obj:`dict`: A dictionary where keys are formatted for multipart/form-data.
66
+
68
67
  """
69
68
 
70
69
  def formatter(v):
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "craft-ai-sdk"
3
- version = "0.65.1rc1"
3
+ version = "0.65.2rc1"
4
4
  description = "Craft AI MLOps platform SDK"
5
5
  license = "Apache-2.0"
6
6
  authors = ["Craft AI <contact@craft.ai>"]
@@ -11,6 +11,7 @@ include = ["documentation.pdf"]
11
11
 
12
12
  [tool.poetry.scripts]
13
13
  lint = "scripts.scripts:lint"
14
+ lintfix = "scripts.scripts:lintfix"
14
15
  format = "scripts.scripts:format"
15
16
  reformat = "scripts.scripts:reformat"
16
17
  test = "scripts.scripts:test"
@@ -34,22 +35,29 @@ weaviate-client = "^4.10.4"
34
35
  [tool.poetry.group.dev.dependencies]
35
36
  ipython = "^8.18.1"
36
37
  ipykernel = "^6.29.5"
37
- black = "^25.1.0"
38
- flake8 = "^7.1.2"
39
- flake8-bugbear = "^24.12.12"
40
- pylint = "^3.3.4"
38
+ pylint = "^3.3.7"
41
39
  python-dotenv = "^1.0.1"
42
40
  pytest = "^8.3.4"
43
41
  requests-mock = { extras = ["fixture"], version = "^1.9.3" }
44
42
  pytest-watch = "^4.2.0"
45
43
  pyarrow = "^19.0.1"
46
44
  pandas = "^2.2.3"
45
+ ruff = "^0.12.2"
47
46
 
48
47
  [tool.poetry.group.docs.dependencies]
49
48
  doc8 = "^0.10.1"
50
49
  Sphinx = "^5.0.1"
51
50
  sphinxcontrib-restbuilder = "^0.3"
52
51
 
52
+ [tool.ruff.lint]
53
+ extend-select = ["B", "C", "E", "F", "I", "T", "W"]
54
+ extend-ignore = ["C401", "C901"]
55
+
56
+ [tool.ruff.lint.per-file-ignores]
57
+ "**/docs/**/*.py" = ["T"]
58
+ "**/tests/**/*.py" = ["C", "T"]
59
+ "**/scripts/**/*.py" = ["T"]
60
+
53
61
  [build-system]
54
62
  requires = ["poetry-core>=1.0.0"]
55
63
  build-backend = "poetry.core.masonry.api"
@@ -1,9 +1,9 @@
1
1
  import os
2
- from typing import TypedDict, Union, cast
3
- from typing_extensions import NotRequired
4
2
  import warnings
3
+ from typing import TypedDict, Union, cast
5
4
 
6
5
  import requests
6
+ from typing_extensions import NotRequired
7
7
 
8
8
  from craft_ai_sdk.io import Input, Output
9
9
  from craft_ai_sdk.shared.types import Log
@@ -1,8 +1,8 @@
1
- from typing import Any, TypedDict, cast
2
- from typing_extensions import NotRequired
3
1
  import warnings
2
+ from typing import Any, TypedDict, cast
4
3
 
5
4
  from strenum import LowercaseStrEnum
5
+ from typing_extensions import NotRequired
6
6
 
7
7
  from craft_ai_sdk.utils import remove_none_values
8
8