craft-ai-sdk 0.58.0rc1__tar.gz → 0.59.0__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 (22) hide show
  1. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/PKG-INFO +1 -1
  2. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/__init__.py +1 -1
  3. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/core/deployments.py +99 -33
  4. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/core/endpoints.py +22 -4
  5. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/core/pipeline_executions.py +8 -27
  6. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/io.py +23 -0
  7. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/sdk.py +17 -1
  8. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/documentation.pdf +0 -0
  9. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/pyproject.toml +1 -1
  10. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/LICENSE +0 -0
  11. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/README.md +0 -0
  12. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/constants.py +0 -0
  13. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/core/data_store.py +0 -0
  14. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/core/environment_variables.py +0 -0
  15. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/core/pipeline_metrics.py +0 -0
  16. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/core/pipelines.py +0 -0
  17. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/core/resource_metrics.py +0 -0
  18. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/core/steps.py +0 -0
  19. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/core/users.py +0 -0
  20. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/exceptions.py +0 -0
  21. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/utils.py +0 -0
  22. {craft_ai_sdk-0.58.0rc1 → craft_ai_sdk-0.59.0}/craft_ai_sdk/warnings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: craft-ai-sdk
3
- Version: 0.58.0rc1
3
+ Version: 0.59.0
4
4
  Summary: Craft AI MLOps platform SDK
5
5
  Home-page: https://www.craft.ai/
6
6
  License: Apache-2.0
@@ -14,4 +14,4 @@ from .io import ( # noqa: F401
14
14
  )
15
15
 
16
16
 
17
- __version__ = "0.58.0rc1"
17
+ __version__ = "0.59.0"
@@ -3,7 +3,10 @@ from ..constants import (
3
3
  DEPLOYMENT_MODES,
4
4
  DEPLOYMENT_STATUS,
5
5
  )
6
- from ..io import InputSource, OutputDestination
6
+ from ..io import (
7
+ _validate_inputs_mapping,
8
+ _validate_outputs_mapping,
9
+ )
7
10
  from ..sdk import BaseCraftAiSdk
8
11
  from ..exceptions import SdkException
9
12
  from ..utils import (
@@ -11,6 +14,7 @@ from ..utils import (
11
14
  log_func_result,
12
15
  log_action,
13
16
  remove_keys_from_dict,
17
+ remove_none_values,
14
18
  )
15
19
 
16
20
 
@@ -22,6 +26,7 @@ def create_deployment(
22
26
  execution_rule,
23
27
  mode=DEPLOYMENT_MODES.ELASTIC,
24
28
  schedule=None,
29
+ endpoint_url_path=None,
25
30
  inputs_mapping=None,
26
31
  outputs_mapping=None,
27
32
  description=None,
@@ -89,6 +94,12 @@ def create_deployment(
89
94
  * ``"0 0 5 * *"`` will execute the deployment every 5th day of
90
95
  the month at midnight.
91
96
 
97
+ endpoint_url_path (:obj:`str`, optional): Applicable only if ``execution_rule``
98
+ is ``"endpoint"``. The last part of the URL (address) used to trigger the
99
+ endpoint deployment. See the note below on how to trigger the endpoint. By
100
+ default the value of ``deployment_name`` is used. This can be used to
101
+ customize the URL independently of ``deployment_name``.
102
+
92
103
  inputs_mapping(:obj:`list` of instances of :class:`InputSource`):
93
104
  List of input mappings, to map pipeline inputs to different
94
105
  sources (such as constant values, endpoint inputs, or environment
@@ -252,8 +263,9 @@ def create_deployment(
252
263
  When ``execution_rule`` is ``"endpoint"``:
253
264
 
254
265
  * When the endpoint deployment is created, it creates an HTTP endpoint which can
255
- be called at ``POST {environment_url}/endpoints/{endpoint_name}``. You can get
256
- `environment_url` with `sdk.base_environment_url`.
266
+ be called at ``POST {environment_url}/endpoints/{endpoint_url_path}``. You can
267
+ get `environment_url` with `sdk.base_environment_url`. `endpoint_url_path` is
268
+ the `endpoint_name` if `enpoint_url_path` is not provided.
257
269
  * Only one step input can be mapped to an endpoint as an
258
270
  ``"endpoint_input_name"`` if it is of type ``"file"``.
259
271
  * Only one step output can be mapped to an endpoint as an
@@ -353,34 +365,19 @@ def create_deployment(
353
365
  else:
354
366
  data["schedule"] = schedule
355
367
 
356
- if inputs_mapping is not None:
357
- if any(
358
- [
359
- not isinstance(input_mapping_, InputSource)
360
- for input_mapping_ in inputs_mapping
361
- ]
362
- ):
363
- raise ValueError("'inputs' must be a list of instances of InputSource.")
364
- data["inputs_mapping"] = [
365
- input_mapping_.to_dict() for input_mapping_ in inputs_mapping
366
- ]
367
-
368
- if outputs_mapping is not None:
369
- if any(
370
- [
371
- not isinstance(output_mapping_, OutputDestination)
372
- for output_mapping_ in outputs_mapping
373
- ]
374
- ):
368
+ if endpoint_url_path is not None:
369
+ if execution_rule != "endpoint":
375
370
  raise ValueError(
376
- "'outputs' must be a list of instances of OutputDestination."
371
+ "'endpoint_url_path' can only be specified if 'execution_rule' is \
372
+ 'endpoint'."
377
373
  )
378
- data["outputs_mapping"] = [
379
- output_mapping_.to_dict() for output_mapping_ in outputs_mapping
380
- ]
374
+ else:
375
+ data["endpoint_url_path"] = endpoint_url_path
381
376
 
382
- # filter optional parameters
383
- data = {k: v for k, v in data.items() if v is not None}
377
+ data["inputs_mapping"] = _validate_inputs_mapping(inputs_mapping)
378
+ data["outputs_mapping"] = _validate_outputs_mapping(outputs_mapping)
379
+
380
+ data = remove_none_values(data)
384
381
 
385
382
  log_action(
386
383
  sdk,
@@ -397,6 +394,14 @@ def create_deployment(
397
394
  )
398
395
 
399
396
 
397
+ FINAL_DEPLOYMENT_STATUSES = [
398
+ DEPLOYMENT_STATUS.UP,
399
+ DEPLOYMENT_STATUS.CREATION_FAILED,
400
+ DEPLOYMENT_STATUS.DISABLED,
401
+ DEPLOYMENT_STATUS.STANDBY,
402
+ ]
403
+
404
+
400
405
  def get_deployment(
401
406
  sdk: BaseCraftAiSdk, deployment_name, wait_for_completion=False, timeout_s=None
402
407
  ):
@@ -521,10 +526,9 @@ def get_deployment(
521
526
  start_time = sdk._get_time()
522
527
  elapsed_time = 0
523
528
  deployment_status = DEPLOYMENT_STATUS.CREATION_PENDING
524
- while deployment_status not in [
525
- DEPLOYMENT_STATUS.UP,
526
- DEPLOYMENT_STATUS.CREATION_FAILED,
527
- ] and (timeout_s is None or elapsed_time < timeout_s):
529
+ while deployment_status not in FINAL_DEPLOYMENT_STATUSES and (
530
+ timeout_s is None or elapsed_time < timeout_s
531
+ ):
528
532
  deployment = sdk._get(
529
533
  f"{base_url}?wait_for_completion=true", allow_redirects=False
530
534
  )
@@ -539,7 +543,7 @@ details.",
539
543
  name="CreationFailedException",
540
544
  )
541
545
 
542
- if deployment_status != DEPLOYMENT_STATUS.UP:
546
+ if deployment_status not in FINAL_DEPLOYMENT_STATUSES:
543
547
  raise SdkException(
544
548
  'The deployment was not ready in time. It is still being created but \
545
549
  this function stopped trying. Please check its status with "get_deployment" with \
@@ -562,6 +566,68 @@ wait_for_completion parameter set to false.',
562
566
  return remove_keys_from_dict(deployment, {"is_archived", "id", "pipeline.id"})
563
567
 
564
568
 
569
+ @log_func_result("Deployment update")
570
+ def update_deployment(
571
+ sdk: BaseCraftAiSdk,
572
+ deployment_name,
573
+ is_enabled=None,
574
+ inputs_mapping=None,
575
+ outputs_mapping=None,
576
+ schedule=None,
577
+ wait_for_completion=True,
578
+ timeout_s=None,
579
+ ):
580
+ """Update the specified properties of a deployment. The properties that can be
581
+ updated include enabling/disabling the deployment, updating input/output values,
582
+ and changing the deployment schedule. Only one property can be updated at a time.
583
+
584
+ Args:
585
+ deployment_name (:obj:`str`): Name of the deployment to update.
586
+ is_enabled (:obj:`bool`, optional): Whether the deployment should be enabled
587
+ or disabled. Disabling a deployment prevents new executions from being
588
+ triggered. It also frees up computing resources associated to a low-latency
589
+ deployment. Defaults to `None`.
590
+ inputs_mapping(:obj:`list` of instances of :class:`InputSource`):
591
+ List of inputs mapping to update with keys `pipeline_input_name`, and
592
+ `constant_value` or `datastore_path`. The mapping types can not be changed;
593
+ only the values of constant values (`constant_value`) and data store paths
594
+ (`datastore_path`) can be updated. See :class:`InputSource` for more details.
595
+ outputs_mapping(:obj:`list` of instances of :class:`OutputDestination`):
596
+ List of outputs mapping to update with keys `pipeline_output_name` and
597
+ `datastore_path`. The mapping types can not be changed;
598
+ only the values of data store paths (`datastore_path`) can be updated.
599
+ See :class:`OutputDestination` for more details.
600
+ schedule (:obj:`str`, optional): New schedule to be assigned to the periodic
601
+ deployment. Must be a valid CRON expression. Defaults to `None`.
602
+
603
+ Returns:
604
+ :obj:`dict`: Deployment information represented as :obj:`dict` as described
605
+ in :func:`get_deployment`.
606
+ """
607
+ if timeout_s is not None and timeout_s <= 0:
608
+ raise ValueError("'timeout_s' must be greater than 0 or None.")
609
+
610
+ url = f"{sdk.base_environment_api_url}/deployments/{deployment_name}"
611
+
612
+ data = remove_none_values(
613
+ {
614
+ "is_enabled": is_enabled,
615
+ "schedule": schedule,
616
+ "inputs_mapping": _validate_inputs_mapping(inputs_mapping),
617
+ "outputs_mapping": _validate_outputs_mapping(outputs_mapping),
618
+ }
619
+ )
620
+
621
+ sdk._patch(url, json=data, allow_redirects=False)
622
+
623
+ return get_deployment(
624
+ sdk,
625
+ deployment_name,
626
+ wait_for_completion=wait_for_completion,
627
+ timeout_s=timeout_s,
628
+ )
629
+
630
+
565
631
  def get_deployment_logs(
566
632
  sdk: BaseCraftAiSdk,
567
633
  deployment_name,
@@ -4,6 +4,16 @@ from urllib.parse import urlencode
4
4
 
5
5
  from ..utils import log_func_result, handle_http_response
6
6
  from ..sdk import BaseCraftAiSdk
7
+ from .deployments import get_deployment
8
+
9
+
10
+ def _get_endpoint_url_path(sdk: BaseCraftAiSdk, endpoint_name):
11
+ deployment = get_deployment(sdk, endpoint_name)
12
+
13
+ if deployment.get("execution_rule", "") != "endpoint":
14
+ raise ValueError(f"Deployment {endpoint_name} is not an endpoint deployment")
15
+
16
+ return deployment.get("endpoint_url_path", "")
7
17
 
8
18
 
9
19
  @log_func_result("Endpoint trigger")
@@ -38,6 +48,7 @@ def trigger_endpoint(
38
48
  output names as keys and corresponding values as values. Note that this
39
49
  key is only returned if ``wait_for_completion`` is `True`.
40
50
  """
51
+ endpoint_url_path = _get_endpoint_url_path(sdk, endpoint_name)
41
52
 
42
53
  body = {}
43
54
  files = {}
@@ -47,7 +58,7 @@ def trigger_endpoint(
47
58
  else:
48
59
  body[input_name] = input_value
49
60
 
50
- url = f"{sdk.base_environment_url}/endpoints/{endpoint_name}"
61
+ url = f"{sdk.base_environment_url}/endpoints/{endpoint_url_path}"
51
62
  post_result = requests.post(
52
63
  url,
53
64
  headers={
@@ -62,14 +73,20 @@ def trigger_endpoint(
62
73
  execution_id = post_result.headers.get("Craft-Ai-Execution-Id", "")
63
74
  if wait_for_completion and 200 <= post_result.status_code < 400:
64
75
  return retrieve_endpoint_results(
65
- sdk, endpoint_name, execution_id, endpoint_token
76
+ sdk,
77
+ endpoint_name,
78
+ execution_id,
79
+ endpoint_token,
66
80
  )
67
81
  return {"execution_id": execution_id}
68
82
 
69
83
 
70
84
  @log_func_result("Endpoint result retrieval")
71
85
  def retrieve_endpoint_results(
72
- sdk: BaseCraftAiSdk, endpoint_name, execution_id, endpoint_token
86
+ sdk: BaseCraftAiSdk,
87
+ endpoint_name,
88
+ execution_id,
89
+ endpoint_token,
73
90
  ):
74
91
  """Get the results of an endpoint execution.
75
92
 
@@ -86,10 +103,11 @@ def retrieve_endpoint_results(
86
103
  * ``"outputs"`` (:obj:`dict`): Dictionary of outputs of the pipeline with
87
104
  output names as keys and corresponding values as values.
88
105
  """
106
+ endpoint_url_path = _get_endpoint_url_path(sdk, endpoint_name)
89
107
 
90
108
  url = (
91
109
  f"{sdk.base_environment_url}"
92
- f"/endpoints/{endpoint_name}/executions/{execution_id}"
110
+ f"/endpoints/{endpoint_url_path}/executions/{execution_id}"
93
111
  )
94
112
  query = urlencode({"token": endpoint_token})
95
113
  response = requests.get(f"{url}?{query}")
@@ -5,10 +5,10 @@ import io
5
5
  from ..exceptions import SdkException
6
6
  from ..io import (
7
7
  INPUT_OUTPUT_TYPES,
8
- InputSource,
9
- OutputDestination,
10
8
  _format_execution_output,
11
9
  _format_execution_input,
10
+ _validate_inputs_mapping,
11
+ _validate_outputs_mapping,
12
12
  )
13
13
  from ..sdk import BaseCraftAiSdk
14
14
  from ..utils import (
@@ -85,33 +85,14 @@ value is not a string or bytes"
85
85
  elif input_types.get(input_name) != INPUT_OUTPUT_TYPES.FILE:
86
86
  data["json_inputs"][input_name] = input_value
87
87
  data["json_inputs"] = json.dumps(data["json_inputs"])
88
+
89
+ _inputs_mapping = _validate_inputs_mapping(inputs_mapping)
88
90
  if inputs_mapping is not None:
89
- if any(
90
- [
91
- not isinstance(input_mapping_, InputSource)
92
- for input_mapping_ in inputs_mapping
93
- ]
94
- ):
95
- raise ValueError(
96
- "'inputs_mapping' must be a list of instances of InputSource."
97
- )
98
- data["inputs_mapping"] = json.dumps(
99
- [input_mapping_.to_dict() for input_mapping_ in inputs_mapping]
100
- )
91
+ data["inputs_mapping"] = json.dumps(_inputs_mapping)
92
+
93
+ _outputs_mapping = _validate_outputs_mapping(outputs_mapping)
101
94
  if outputs_mapping is not None:
102
- if any(
103
- [
104
- not isinstance(output_mapping_, OutputDestination)
105
- for output_mapping_ in outputs_mapping
106
- ]
107
- ):
108
- raise ValueError(
109
- "'outputs_mapping' must be a list of instances of \
110
- OutputDestination."
111
- )
112
- data["outputs_mapping"] = json.dumps(
113
- [output_mapping_.to_dict() for output_mapping_ in outputs_mapping]
114
- )
95
+ data["outputs_mapping"] = json.dumps(_outputs_mapping)
115
96
 
116
97
  # Execute pipeline
117
98
  url = f"{sdk.base_environment_api_url}/pipelines/{pipeline_name}/run"
@@ -288,3 +288,26 @@ def _format_execution_input(name, input):
288
288
  "source": input["mapping_type"],
289
289
  "value": input["value"],
290
290
  }
291
+
292
+
293
+ def _validate_inputs_mapping(inputs_mapping):
294
+ if inputs_mapping is None:
295
+ return None
296
+ if any(
297
+ not isinstance(input_mapping_, InputSource) for input_mapping_ in inputs_mapping
298
+ ):
299
+ raise ValueError("'inputs_mapping' must be a list of instances of InputSource.")
300
+ return [input_mapping_.to_dict() for input_mapping_ in inputs_mapping]
301
+
302
+
303
+ def _validate_outputs_mapping(outputs_mapping):
304
+ if outputs_mapping is None:
305
+ return None
306
+ if any(
307
+ not isinstance(output_mapping_, OutputDestination)
308
+ for output_mapping_ in outputs_mapping
309
+ ):
310
+ raise ValueError(
311
+ "'outputs_mapping' must be a list of instances of OutputDestination."
312
+ )
313
+ return [output_mapping_.to_dict() for output_mapping_ in outputs_mapping]
@@ -34,6 +34,10 @@ class BaseCraftAiSdk(ABC):
34
34
  def _put(self, url, data=None, params=None, files=None, **kwargs):
35
35
  pass
36
36
 
37
+ @abstractmethod
38
+ def _patch(self, url, data=None, params=None, files=None, **kwargs):
39
+ pass
40
+
37
41
  @abstractmethod
38
42
  def _delete(self, url, **kwargs):
39
43
  pass
@@ -83,6 +87,7 @@ class CraftAiSdk(BaseCraftAiSdk):
83
87
  get_deployment,
84
88
  get_deployment_logs,
85
89
  list_deployments,
90
+ update_deployment,
86
91
  delete_deployment,
87
92
  )
88
93
  from .core.endpoints import (
@@ -130,7 +135,7 @@ class CraftAiSdk(BaseCraftAiSdk):
130
135
  os.environ.get("CRAFT_AI__MULTIPART_PART_SIZE__B", str(38 * 256 * 1024))
131
136
  )
132
137
  _access_token_margin = timedelta(seconds=30)
133
- _version = "0.58.0rc1" # Would be better to share it somewhere
138
+ _version = "0.59.0" # Would be better to share it somewhere
134
139
 
135
140
  def __init__(
136
141
  self,
@@ -249,6 +254,17 @@ class CraftAiSdk(BaseCraftAiSdk):
249
254
  **kwargs,
250
255
  )
251
256
 
257
+ @handle_http_request
258
+ @use_authentication
259
+ def _patch(self, url, data=None, params=None, files=None, **kwargs):
260
+ return self._session.patch(
261
+ url,
262
+ data=data,
263
+ params=params,
264
+ files=files,
265
+ **kwargs,
266
+ )
267
+
252
268
  @handle_http_request
253
269
  @use_authentication
254
270
  def _delete(self, url, **kwargs):
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "craft-ai-sdk"
3
- version = "0.58.0rc1"
3
+ version = "0.59.0"
4
4
  description = "Craft AI MLOps platform SDK"
5
5
  license = "Apache-2.0"
6
6
  authors = ["Craft AI <contact@craft.ai>"]
File without changes