snowflake-ml-python 1.8.5__py3-none-any.whl → 1.9.0__py3-none-any.whl
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.
- snowflake/ml/_internal/telemetry.py +6 -9
- snowflake/ml/_internal/utils/connection_params.py +196 -0
- snowflake/ml/_internal/utils/identifier.py +1 -1
- snowflake/ml/_internal/utils/mixins.py +61 -0
- snowflake/ml/jobs/__init__.py +2 -0
- snowflake/ml/jobs/_utils/constants.py +3 -2
- snowflake/ml/jobs/_utils/function_payload_utils.py +43 -0
- snowflake/ml/jobs/_utils/interop_utils.py +63 -4
- snowflake/ml/jobs/_utils/payload_utils.py +89 -40
- snowflake/ml/jobs/_utils/query_helper.py +9 -0
- snowflake/ml/jobs/_utils/scripts/constants.py +19 -3
- snowflake/ml/jobs/_utils/scripts/mljob_launcher.py +8 -26
- snowflake/ml/jobs/_utils/spec_utils.py +29 -5
- snowflake/ml/jobs/_utils/stage_utils.py +119 -0
- snowflake/ml/jobs/_utils/types.py +5 -1
- snowflake/ml/jobs/decorators.py +20 -28
- snowflake/ml/jobs/job.py +197 -61
- snowflake/ml/jobs/manager.py +253 -121
- snowflake/ml/model/_client/model/model_impl.py +58 -0
- snowflake/ml/model/_client/model/model_version_impl.py +90 -0
- snowflake/ml/model/_client/ops/model_ops.py +18 -6
- snowflake/ml/model/_client/ops/service_ops.py +23 -6
- snowflake/ml/model/_client/service/model_deployment_spec_schema.py +2 -0
- snowflake/ml/model/_client/sql/service.py +68 -20
- snowflake/ml/model/_client/sql/stage.py +5 -2
- snowflake/ml/model/_model_composer/model_manifest/model_manifest.py +38 -10
- snowflake/ml/model/_packager/model_env/model_env.py +35 -27
- snowflake/ml/model/_packager/model_handlers/pytorch.py +5 -1
- snowflake/ml/model/_packager/model_handlers/snowmlmodel.py +103 -73
- snowflake/ml/model/_packager/model_meta/model_meta.py +3 -1
- snowflake/ml/model/_signatures/core.py +24 -0
- snowflake/ml/model/_signatures/snowpark_handler.py +55 -3
- snowflake/ml/model/target_platform.py +11 -0
- snowflake/ml/model/task.py +9 -0
- snowflake/ml/model/type_hints.py +5 -13
- snowflake/ml/modeling/metrics/metrics_utils.py +2 -0
- snowflake/ml/monitoring/explain_visualize.py +2 -2
- snowflake/ml/monitoring/model_monitor.py +0 -4
- snowflake/ml/registry/_manager/model_manager.py +30 -15
- snowflake/ml/registry/registry.py +144 -47
- snowflake/ml/utils/connection_params.py +1 -1
- snowflake/ml/utils/html_utils.py +263 -0
- snowflake/ml/version.py +1 -1
- {snowflake_ml_python-1.8.5.dist-info → snowflake_ml_python-1.9.0.dist-info}/METADATA +64 -19
- {snowflake_ml_python-1.8.5.dist-info → snowflake_ml_python-1.9.0.dist-info}/RECORD +48 -41
- snowflake/ml/monitoring/model_monitor_version.py +0 -1
- {snowflake_ml_python-1.8.5.dist-info → snowflake_ml_python-1.9.0.dist-info}/WHEEL +0 -0
- {snowflake_ml_python-1.8.5.dist-info → snowflake_ml_python-1.9.0.dist-info}/licenses/LICENSE.txt +0 -0
- {snowflake_ml_python-1.8.5.dist-info → snowflake_ml_python-1.9.0.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,5 @@
|
|
1
|
+
import logging
|
2
|
+
import os
|
1
3
|
import warnings
|
2
4
|
from types import ModuleType
|
3
5
|
from typing import Any, Optional, Union, overload
|
@@ -11,10 +13,11 @@ from snowflake.ml.model import (
|
|
11
13
|
Model,
|
12
14
|
ModelVersion,
|
13
15
|
model_signature,
|
16
|
+
task,
|
14
17
|
type_hints as model_types,
|
15
18
|
)
|
16
19
|
from snowflake.ml.model._client.model import model_version_impl
|
17
|
-
from snowflake.ml.monitoring import model_monitor
|
20
|
+
from snowflake.ml.monitoring import model_monitor
|
18
21
|
from snowflake.ml.monitoring._manager import model_monitor_manager
|
19
22
|
from snowflake.ml.monitoring.entities import model_monitor_config
|
20
23
|
from snowflake.ml.registry._manager import model_manager
|
@@ -29,7 +32,54 @@ _MODEL_MONITORING_DISABLED_ERROR = (
|
|
29
32
|
)
|
30
33
|
|
31
34
|
|
35
|
+
class _NullStatusContext:
|
36
|
+
"""A fallback context manager that logs status updates."""
|
37
|
+
|
38
|
+
def __init__(self, label: str) -> None:
|
39
|
+
self._label = label
|
40
|
+
|
41
|
+
def __enter__(self) -> "_NullStatusContext":
|
42
|
+
logging.info(f"Starting: {self._label}")
|
43
|
+
return self
|
44
|
+
|
45
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
46
|
+
pass
|
47
|
+
|
48
|
+
def update(self, label: str, *, state: str = "running", expanded: bool = True) -> None:
|
49
|
+
"""Update the status by logging the message."""
|
50
|
+
logging.info(f"Status update: {label} (state: {state})")
|
51
|
+
|
52
|
+
|
53
|
+
class RegistryEventHandler:
|
54
|
+
def __init__(self) -> None:
|
55
|
+
try:
|
56
|
+
import streamlit as st
|
57
|
+
|
58
|
+
if not st.runtime.exists():
|
59
|
+
self._streamlit = None
|
60
|
+
else:
|
61
|
+
self._streamlit = st
|
62
|
+
USE_STREAMLIT_WIDGETS = os.getenv("USE_STREAMLIT_WIDGETS", "1") == "1"
|
63
|
+
if not USE_STREAMLIT_WIDGETS:
|
64
|
+
self._streamlit = None
|
65
|
+
except ImportError:
|
66
|
+
self._streamlit = None
|
67
|
+
|
68
|
+
def update(self, message: str) -> None:
|
69
|
+
"""Write a message using streamlit if available, otherwise do nothing."""
|
70
|
+
if self._streamlit is not None:
|
71
|
+
self._streamlit.write(message)
|
72
|
+
|
73
|
+
def status(self, label: str, *, state: str = "running", expanded: bool = True) -> Any:
|
74
|
+
"""Context manager that provides status updates with optional enhanced display capabilities."""
|
75
|
+
if self._streamlit is None:
|
76
|
+
return _NullStatusContext(label)
|
77
|
+
else:
|
78
|
+
return self._streamlit.status(label, state=state, expanded=expanded)
|
79
|
+
|
80
|
+
|
32
81
|
class Registry:
|
82
|
+
@telemetry.send_api_usage_telemetry(project=_TELEMETRY_PROJECT, subproject=_MODEL_TELEMETRY_SUBPROJECT)
|
33
83
|
def __init__(
|
34
84
|
self,
|
35
85
|
session: session.Session,
|
@@ -74,6 +124,22 @@ class Registry:
|
|
74
124
|
else sql_identifier.SqlIdentifier("PUBLIC")
|
75
125
|
)
|
76
126
|
|
127
|
+
database_exists = session.sql(
|
128
|
+
f"""SELECT 1 FROM INFORMATION_SCHEMA.DATABASES WHERE DATABASE_NAME = '{self._database_name.resolved()}';"""
|
129
|
+
).collect()
|
130
|
+
|
131
|
+
if not database_exists:
|
132
|
+
raise ValueError(f"Database {self._database_name} does not exist.")
|
133
|
+
|
134
|
+
schema_exists = session.sql(
|
135
|
+
f"""
|
136
|
+
SELECT 1 FROM {self._database_name.identifier()}.INFORMATION_SCHEMA.SCHEMATA
|
137
|
+
WHERE SCHEMA_NAME = '{self._schema_name.resolved()}';"""
|
138
|
+
).collect()
|
139
|
+
|
140
|
+
if not schema_exists:
|
141
|
+
raise ValueError(f"Schema {self._schema_name} does not exist.")
|
142
|
+
|
77
143
|
self._model_manager = model_manager.ModelManager(
|
78
144
|
session,
|
79
145
|
database_name=self._database_name,
|
@@ -119,7 +185,7 @@ class Registry:
|
|
119
185
|
user_files: Optional[dict[str, list[str]]] = None,
|
120
186
|
code_paths: Optional[list[str]] = None,
|
121
187
|
ext_modules: Optional[list[ModuleType]] = None,
|
122
|
-
task: model_types.Task =
|
188
|
+
task: model_types.Task = task.Task.UNKNOWN,
|
123
189
|
options: Optional[model_types.ModelSaveOption] = None,
|
124
190
|
) -> ModelVersion:
|
125
191
|
"""
|
@@ -142,12 +208,12 @@ class Registry:
|
|
142
208
|
to specify a dependency. It is a recommended way to specify your dependencies using conda. When channel
|
143
209
|
is not specified, Snowflake Anaconda Channel will be used. Defaults to None.
|
144
210
|
pip_requirements: List of Pip package specifications. Defaults to None.
|
145
|
-
Models
|
146
|
-
|
147
|
-
|
148
|
-
|
211
|
+
Models running in a Snowflake Warehouse must also specify a pip artifact repository (see
|
212
|
+
`artifact_repository_map`). Otherwise, models with pip requirements are runnable only in Snowpark
|
213
|
+
Container Services. See
|
214
|
+
https://docs.snowflake.com/en/developer-guide/snowflake-ml/model-registry/container for more.
|
149
215
|
artifact_repository_map: Specifies a mapping of package channels or platforms to custom artifact
|
150
|
-
repositories. Defaults to None. Currently, the mapping applies only to
|
216
|
+
repositories. Defaults to None. Currently, the mapping applies only to Warehouse execution.
|
151
217
|
Note : This feature is currently in Public Preview.
|
152
218
|
Format: {channel_name: artifact_repository_name}, where:
|
153
219
|
- channel_name: Currently must be 'pip'.
|
@@ -155,7 +221,14 @@ class Registry:
|
|
155
221
|
`snowflake.snowpark.pypi_shared_repository`.
|
156
222
|
resource_constraint: Mapping of resource constraint keys and values, e.g. {"architecture": "x86"}.
|
157
223
|
target_platforms: List of target platforms to run the model. The only acceptable inputs are a combination of
|
158
|
-
|
224
|
+
"WAREHOUSE" and "SNOWPARK_CONTAINER_SERVICES", or a target platform constant:
|
225
|
+
- ["WAREHOUSE"] or snowflake.ml.model.target_platform.WAREHOUSE_ONLY (Warehouse only)
|
226
|
+
- ["SNOWPARK_CONTAINER_SERVICES"] or
|
227
|
+
snowflake.ml.model.target_platform.SNOWPARK_CONTAINER_SERVICES_ONLY
|
228
|
+
(Snowpark Container Services only)
|
229
|
+
- ["WAREHOUSE", "SNOWPARK_CONTAINER_SERVICES"] or
|
230
|
+
snowflake.ml.model.target_platform.BOTH_WAREHOUSE_AND_SNOWPARK_CONTAINER_SERVICES (Both)
|
231
|
+
Defaults to None. When None, the target platforms will be both.
|
159
232
|
python_version: Python version in which the model is run. Defaults to None.
|
160
233
|
signatures: Model data signatures for inputs and outputs for various target methods. If it is None,
|
161
234
|
sample_input_data would be used to infer the signatures for those models that cannot automatically
|
@@ -259,7 +332,7 @@ class Registry:
|
|
259
332
|
user_files: Optional[dict[str, list[str]]] = None,
|
260
333
|
code_paths: Optional[list[str]] = None,
|
261
334
|
ext_modules: Optional[list[ModuleType]] = None,
|
262
|
-
task: model_types.Task =
|
335
|
+
task: model_types.Task = task.Task.UNKNOWN,
|
263
336
|
options: Optional[model_types.ModelSaveOption] = None,
|
264
337
|
) -> ModelVersion:
|
265
338
|
"""
|
@@ -282,12 +355,12 @@ class Registry:
|
|
282
355
|
to specify a dependency. It is a recommended way to specify your dependencies using conda. When channel
|
283
356
|
is not specified, Snowflake Anaconda Channel will be used. Defaults to None.
|
284
357
|
pip_requirements: List of Pip package specifications. Defaults to None.
|
285
|
-
Models
|
286
|
-
|
287
|
-
|
288
|
-
|
358
|
+
Models running in a Snowflake Warehouse must also specify a pip artifact repository (see
|
359
|
+
`artifact_repository_map`). Otherwise, models with pip requirements are runnable only in Snowpark
|
360
|
+
Container Services. See
|
361
|
+
https://docs.snowflake.com/en/developer-guide/snowflake-ml/model-registry/container for more.
|
289
362
|
artifact_repository_map: Specifies a mapping of package channels or platforms to custom artifact
|
290
|
-
repositories. Defaults to None. Currently, the mapping applies only to
|
363
|
+
repositories. Defaults to None. Currently, the mapping applies only to Warehouse execution.
|
291
364
|
Note : This feature is currently in Public Preview.
|
292
365
|
Format: {channel_name: artifact_repository_name}, where:
|
293
366
|
- channel_name: Currently must be 'pip'.
|
@@ -295,8 +368,14 @@ class Registry:
|
|
295
368
|
`snowflake.snowpark.pypi_shared_repository`.
|
296
369
|
resource_constraint: Mapping of resource constraint keys and values, e.g. {"architecture": "x86"}.
|
297
370
|
target_platforms: List of target platforms to run the model. The only acceptable inputs are a combination of
|
298
|
-
|
299
|
-
|
371
|
+
"WAREHOUSE" and "SNOWPARK_CONTAINER_SERVICES", or a target platform constant:
|
372
|
+
- ["WAREHOUSE"] or snowflake.ml.model.target_platform.WAREHOUSE_ONLY (Warehouse only)
|
373
|
+
- ["SNOWPARK_CONTAINER_SERVICES"] or
|
374
|
+
snowflake.ml.model.target_platform.SNOWPARK_CONTAINER_SERVICES_ONLY
|
375
|
+
(Snowpark Container Services only)
|
376
|
+
- ["WAREHOUSE", "SNOWPARK_CONTAINER_SERVICES"] or
|
377
|
+
snowflake.ml.model.target_platform.BOTH_WAREHOUSE_AND_SNOWPARK_CONTAINER_SERVICES (Both)
|
378
|
+
Defaults to None. When None, the target platforms will be both.
|
300
379
|
python_version: Python version in which the model is run. Defaults to None.
|
301
380
|
signatures: Model data signatures for inputs and outputs for various target methods. If it is None,
|
302
381
|
sample_input_data would be used to infer the signatures for those models that cannot automatically
|
@@ -342,6 +421,7 @@ class Registry:
|
|
342
421
|
|
343
422
|
Raises:
|
344
423
|
ValueError: If extra arguments are specified ModelVersion is provided.
|
424
|
+
Exception: If the model logging fails.
|
345
425
|
|
346
426
|
Returns:
|
347
427
|
ModelVersion: ModelVersion object corresponding to the model just logged.
|
@@ -397,35 +477,47 @@ class Registry:
|
|
397
477
|
if task is not model_types.Task.UNKNOWN:
|
398
478
|
raise ValueError("`task` cannot be specified when calling log_model with a ModelVersion.")
|
399
479
|
|
400
|
-
if pip_requirements:
|
480
|
+
if pip_requirements and not artifact_repository_map and self._targets_warehouse(target_platforms):
|
401
481
|
warnings.warn(
|
402
|
-
"Models logged specifying `pip_requirements`
|
403
|
-
"
|
404
|
-
"
|
482
|
+
"Models logged specifying `pip_requirements` cannot be executed in a Snowflake Warehouse "
|
483
|
+
"without specifying `artifact_repository_map`. This model can be run in Snowpark Container "
|
484
|
+
"Services. See https://docs.snowflake.com/en/developer-guide/snowflake-ml/model-registry/container.",
|
405
485
|
category=UserWarning,
|
406
486
|
stacklevel=1,
|
407
487
|
)
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
488
|
+
|
489
|
+
event_handler = RegistryEventHandler()
|
490
|
+
with event_handler.status("Logging model to registry...") as status:
|
491
|
+
# Perform the actual model logging
|
492
|
+
try:
|
493
|
+
result = self._model_manager.log_model(
|
494
|
+
model=model,
|
495
|
+
model_name=model_name,
|
496
|
+
version_name=version_name,
|
497
|
+
comment=comment,
|
498
|
+
metrics=metrics,
|
499
|
+
conda_dependencies=conda_dependencies,
|
500
|
+
pip_requirements=pip_requirements,
|
501
|
+
artifact_repository_map=artifact_repository_map,
|
502
|
+
resource_constraint=resource_constraint,
|
503
|
+
target_platforms=target_platforms,
|
504
|
+
python_version=python_version,
|
505
|
+
signatures=signatures,
|
506
|
+
sample_input_data=sample_input_data,
|
507
|
+
user_files=user_files,
|
508
|
+
code_paths=code_paths,
|
509
|
+
ext_modules=ext_modules,
|
510
|
+
task=task,
|
511
|
+
options=options,
|
512
|
+
statement_params=statement_params,
|
513
|
+
event_handler=event_handler,
|
514
|
+
)
|
515
|
+
status.update(label="Model logged successfully!", state="complete", expanded=False)
|
516
|
+
return result
|
517
|
+
except Exception as e:
|
518
|
+
event_handler.update("❌ Model logging failed!")
|
519
|
+
status.update(label="Model logging failed!", state="error", expanded=False)
|
520
|
+
raise e
|
429
521
|
|
430
522
|
@telemetry.send_api_usage_telemetry(
|
431
523
|
project=_TELEMETRY_PROJECT,
|
@@ -500,7 +592,6 @@ class Registry:
|
|
500
592
|
project=telemetry.TelemetryProject.MLOPS.value,
|
501
593
|
subproject=telemetry.TelemetrySubProject.MONITORING.value,
|
502
594
|
)
|
503
|
-
@snowpark._internal.utils.private_preview(version=model_monitor_version.SNOWFLAKE_ML_MONITORING_MIN_VERSION)
|
504
595
|
def add_monitor(
|
505
596
|
self,
|
506
597
|
name: str,
|
@@ -525,7 +616,7 @@ class Registry:
|
|
525
616
|
return self._model_monitor_manager.add_monitor(name, source_config, model_monitor_config)
|
526
617
|
|
527
618
|
@overload
|
528
|
-
def get_monitor(self, model_version: model_version_impl.ModelVersion) -> model_monitor.ModelMonitor:
|
619
|
+
def get_monitor(self, *, model_version: model_version_impl.ModelVersion) -> model_monitor.ModelMonitor:
|
529
620
|
"""Get a Model Monitor on a Model Version from the Registry.
|
530
621
|
|
531
622
|
Args:
|
@@ -534,7 +625,7 @@ class Registry:
|
|
534
625
|
...
|
535
626
|
|
536
627
|
@overload
|
537
|
-
def get_monitor(self, name: str) -> model_monitor.ModelMonitor:
|
628
|
+
def get_monitor(self, *, name: str) -> model_monitor.ModelMonitor:
|
538
629
|
"""Get a Model Monitor by name from the Registry.
|
539
630
|
|
540
631
|
Args:
|
@@ -546,7 +637,6 @@ class Registry:
|
|
546
637
|
project=telemetry.TelemetryProject.MLOPS.value,
|
547
638
|
subproject=telemetry.TelemetrySubProject.MONITORING.value,
|
548
639
|
)
|
549
|
-
@snowpark._internal.utils.private_preview(version=model_monitor_version.SNOWFLAKE_ML_MONITORING_MIN_VERSION)
|
550
640
|
def get_monitor(
|
551
641
|
self, *, name: Optional[str] = None, model_version: Optional[model_version_impl.ModelVersion] = None
|
552
642
|
) -> model_monitor.ModelMonitor:
|
@@ -575,7 +665,6 @@ class Registry:
|
|
575
665
|
project=telemetry.TelemetryProject.MLOPS.value,
|
576
666
|
subproject=telemetry.TelemetrySubProject.MONITORING.value,
|
577
667
|
)
|
578
|
-
@snowpark._internal.utils.private_preview(version=model_monitor_version.SNOWFLAKE_ML_MONITORING_MIN_VERSION)
|
579
668
|
def show_model_monitors(self) -> list[snowpark.Row]:
|
580
669
|
"""Show all model monitors in the registry.
|
581
670
|
|
@@ -593,7 +682,6 @@ class Registry:
|
|
593
682
|
project=telemetry.TelemetryProject.MLOPS.value,
|
594
683
|
subproject=telemetry.TelemetrySubProject.MONITORING.value,
|
595
684
|
)
|
596
|
-
@snowpark._internal.utils.private_preview(version=model_monitor_version.SNOWFLAKE_ML_MONITORING_MIN_VERSION)
|
597
685
|
def delete_monitor(self, name: str) -> None:
|
598
686
|
"""Delete a Model Monitor by name from the Registry.
|
599
687
|
|
@@ -606,3 +694,12 @@ class Registry:
|
|
606
694
|
if not self.enable_monitoring:
|
607
695
|
raise ValueError(_MODEL_MONITORING_DISABLED_ERROR)
|
608
696
|
self._model_monitor_manager.delete_monitor(name)
|
697
|
+
|
698
|
+
@staticmethod
|
699
|
+
def _targets_warehouse(target_platforms: Optional[list[model_types.SupportedTargetPlatformType]]) -> bool:
|
700
|
+
"""Returns True if warehouse is a target platform (None defaults to True)."""
|
701
|
+
return (
|
702
|
+
target_platforms is None
|
703
|
+
or model_types.TargetPlatform.WAREHOUSE in target_platforms
|
704
|
+
or "WAREHOUSE" in target_platforms
|
705
|
+
)
|
@@ -136,7 +136,7 @@ def _load_from_snowsql_config_file(connection_name: str, login_file: str = "") -
|
|
136
136
|
return conn_params
|
137
137
|
|
138
138
|
|
139
|
-
@snowpark._internal.utils.
|
139
|
+
@snowpark._internal.utils.deprecated(version="1.8.5")
|
140
140
|
def SnowflakeLoginOptions(connection_name: str = "", login_file: Optional[str] = None) -> dict[str, Union[str, bytes]]:
|
141
141
|
"""Returns a dict that can be used directly into snowflake python connector or Snowpark session config.
|
142
142
|
|
@@ -0,0 +1,263 @@
|
|
1
|
+
"""Utilities for generating consistent HTML representations across model classes."""
|
2
|
+
|
3
|
+
from typing import Any, Sequence
|
4
|
+
|
5
|
+
# Common CSS styles used across model classes
|
6
|
+
BASE_CONTAINER_STYLE = (
|
7
|
+
"font-family: Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.5; "
|
8
|
+
"color: #333; background-color: #f9f9f9; border: 1px solid #ddd; "
|
9
|
+
"border-radius: 4px; padding: 15px; margin-bottom: 15px;"
|
10
|
+
)
|
11
|
+
|
12
|
+
HEADER_STYLE = "margin-top: 0; margin-bottom: 10px; font-size: 16px; color: #007bff;"
|
13
|
+
|
14
|
+
SECTION_HEADER_STYLE = "margin: 15px 0 10px; color: #007bff;"
|
15
|
+
|
16
|
+
GRID_CONTAINER_STYLE = "display: grid; grid-template-columns: 150px 1fr; gap: 10px;"
|
17
|
+
|
18
|
+
GRID_LABEL_STYLE = "font-weight: bold;"
|
19
|
+
|
20
|
+
DETAILS_STYLE = "margin: 5px 0; border: 1px solid #e0e0e0; border-radius: 4px;"
|
21
|
+
|
22
|
+
SUMMARY_STYLE = "padding: 8px; cursor: pointer; background-color: #f5f5f5; border-radius: 3px;"
|
23
|
+
|
24
|
+
SUMMARY_LABEL_STYLE = "font-weight: bold; color: #007bff;"
|
25
|
+
|
26
|
+
CONTENT_SECTION_STYLE = "margin-left: 10px;"
|
27
|
+
|
28
|
+
VERSION_ITEM_STYLE = "margin: 5px 0; padding: 5px; background-color: #f5f5f5; border-radius: 3px;"
|
29
|
+
|
30
|
+
NESTED_GROUP_STYLE = "border-left: 2px solid #e0e0e0; margin-left: 8px;"
|
31
|
+
|
32
|
+
ERROR_STYLE = "color: #888; font-style: italic;"
|
33
|
+
|
34
|
+
|
35
|
+
def create_base_container(title: str, content: str) -> str:
|
36
|
+
"""Create a base HTML container with consistent styling.
|
37
|
+
|
38
|
+
Args:
|
39
|
+
title: The main title for the container.
|
40
|
+
content: The HTML content to include in the container.
|
41
|
+
|
42
|
+
Returns:
|
43
|
+
HTML string with the base container structure.
|
44
|
+
"""
|
45
|
+
return f"""
|
46
|
+
<div style="{BASE_CONTAINER_STYLE}">
|
47
|
+
<h3 style="{HEADER_STYLE}">
|
48
|
+
{title}
|
49
|
+
</h3>
|
50
|
+
{content}
|
51
|
+
</div>
|
52
|
+
"""
|
53
|
+
|
54
|
+
|
55
|
+
def create_grid_section(items: list[tuple[str, str]]) -> str:
|
56
|
+
"""Create a grid layout section for key-value pairs.
|
57
|
+
|
58
|
+
Args:
|
59
|
+
items: List of (label, value) tuples to display in the grid.
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
HTML string with grid layout.
|
63
|
+
"""
|
64
|
+
grid_items = ""
|
65
|
+
for label, value in items:
|
66
|
+
grid_items += f"""
|
67
|
+
<div style="{GRID_LABEL_STYLE}">{label}:</div>
|
68
|
+
<div>{value}</div>
|
69
|
+
"""
|
70
|
+
|
71
|
+
return f"""
|
72
|
+
<div style="{GRID_CONTAINER_STYLE}">
|
73
|
+
{grid_items}
|
74
|
+
</div>
|
75
|
+
"""
|
76
|
+
|
77
|
+
|
78
|
+
def create_section_header(title: str) -> str:
|
79
|
+
"""Create a section header with consistent styling.
|
80
|
+
|
81
|
+
Args:
|
82
|
+
title: The section title.
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
HTML string for the section header.
|
86
|
+
"""
|
87
|
+
return f'<h4 style="{SECTION_HEADER_STYLE}">{title}</h4>'
|
88
|
+
|
89
|
+
|
90
|
+
def create_content_section(content: str) -> str:
|
91
|
+
"""Create a content section with consistent indentation.
|
92
|
+
|
93
|
+
Args:
|
94
|
+
content: The content to include in the section.
|
95
|
+
|
96
|
+
Returns:
|
97
|
+
HTML string with content section styling.
|
98
|
+
"""
|
99
|
+
return f"""
|
100
|
+
<div style="{CONTENT_SECTION_STYLE}">
|
101
|
+
{content}
|
102
|
+
</div>
|
103
|
+
"""
|
104
|
+
|
105
|
+
|
106
|
+
def create_collapsible_section(title: str, content: str, open_by_default: bool = True) -> str:
|
107
|
+
"""Create a collapsible section with consistent styling.
|
108
|
+
|
109
|
+
Args:
|
110
|
+
title: The title for the collapsible section.
|
111
|
+
content: The content to include in the collapsible section.
|
112
|
+
open_by_default: Whether the section should be open by default.
|
113
|
+
|
114
|
+
Returns:
|
115
|
+
HTML string for the collapsible section.
|
116
|
+
"""
|
117
|
+
open_attr = "open" if open_by_default else ""
|
118
|
+
|
119
|
+
return f"""
|
120
|
+
<details style="{DETAILS_STYLE}" {open_attr}>
|
121
|
+
<summary style="{SUMMARY_STYLE}">
|
122
|
+
<span style="{SUMMARY_LABEL_STYLE}">{title}</span>
|
123
|
+
</summary>
|
124
|
+
<div style="padding: 10px; border-top: 1px solid #e0e0e0;">
|
125
|
+
{content}
|
126
|
+
</div>
|
127
|
+
</details>
|
128
|
+
"""
|
129
|
+
|
130
|
+
|
131
|
+
def create_version_item(version_name: str, created_on: str, comment: str, is_default: bool = False) -> str:
|
132
|
+
"""Create a version item with consistent styling.
|
133
|
+
|
134
|
+
Args:
|
135
|
+
version_name: The name of the version.
|
136
|
+
created_on: The creation timestamp.
|
137
|
+
comment: The version comment.
|
138
|
+
is_default: Whether this is the default version.
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
HTML string for the version item.
|
142
|
+
"""
|
143
|
+
default_text = " (Default)" if is_default else ""
|
144
|
+
|
145
|
+
return f"""
|
146
|
+
<div style="{VERSION_ITEM_STYLE}">
|
147
|
+
<strong>Version:</strong> {version_name}{default_text}<br/>
|
148
|
+
<strong>Created:</strong> {created_on}<br/>
|
149
|
+
<strong>Comment:</strong> {comment}<br/>
|
150
|
+
</div>
|
151
|
+
"""
|
152
|
+
|
153
|
+
|
154
|
+
def create_tag_item(tag_name: str, tag_value: str) -> str:
|
155
|
+
"""Create a tag item with consistent styling.
|
156
|
+
|
157
|
+
Args:
|
158
|
+
tag_name: The name of the tag.
|
159
|
+
tag_value: The value of the tag.
|
160
|
+
|
161
|
+
Returns:
|
162
|
+
HTML string for the tag item.
|
163
|
+
"""
|
164
|
+
return f"""
|
165
|
+
<div style="margin: 5px 0;">
|
166
|
+
<strong>{tag_name}:</strong> {tag_value}
|
167
|
+
</div>
|
168
|
+
"""
|
169
|
+
|
170
|
+
|
171
|
+
def create_metric_item(metric_name: str, value: Any) -> str:
|
172
|
+
"""Create a metric item with consistent styling.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
metric_name: The name of the metric.
|
176
|
+
value: The value of the metric.
|
177
|
+
|
178
|
+
Returns:
|
179
|
+
HTML string for the metric item.
|
180
|
+
"""
|
181
|
+
value_str = "N/A" if value is None else str(value)
|
182
|
+
return f"""
|
183
|
+
<div style="margin: 5px 0;">
|
184
|
+
<strong>{metric_name}:</strong> {value_str}
|
185
|
+
</div>
|
186
|
+
"""
|
187
|
+
|
188
|
+
|
189
|
+
def create_error_message(message: str) -> str:
|
190
|
+
"""Create an error message with consistent styling.
|
191
|
+
|
192
|
+
Args:
|
193
|
+
message: The error message to display.
|
194
|
+
|
195
|
+
Returns:
|
196
|
+
HTML string for the error message.
|
197
|
+
"""
|
198
|
+
return f'<em style="{ERROR_STYLE}">{message}</em>'
|
199
|
+
|
200
|
+
|
201
|
+
def create_feature_spec_html(spec: Any, indent: int = 0) -> str:
|
202
|
+
"""Create HTML representation for a feature specification.
|
203
|
+
|
204
|
+
Args:
|
205
|
+
spec: The feature specification (FeatureSpec or FeatureGroupSpec).
|
206
|
+
indent: The indentation level for nested features.
|
207
|
+
|
208
|
+
Returns:
|
209
|
+
HTML string for the feature specification.
|
210
|
+
"""
|
211
|
+
# Import here to avoid circular imports
|
212
|
+
from snowflake.ml.model._signatures import core
|
213
|
+
|
214
|
+
if isinstance(spec, core.FeatureSpec):
|
215
|
+
shape_str = f" shape={spec._shape}" if spec._shape else ""
|
216
|
+
nullable_str = "" if spec._nullable else ", not nullable"
|
217
|
+
return f"""
|
218
|
+
<div style="margin: 3px 0; padding-left: {indent * 16}px;">
|
219
|
+
<strong>{spec.name}</strong>: {spec._dtype}{shape_str}{nullable_str}
|
220
|
+
</div>
|
221
|
+
"""
|
222
|
+
elif isinstance(spec, core.FeatureGroupSpec):
|
223
|
+
group_html = f"""
|
224
|
+
<div style="margin: 8px 0;">
|
225
|
+
<div style="margin: 3px 0; padding-left: {indent * 16}px;">
|
226
|
+
<strong>{spec.name}</strong> <span style="color: #666;">(group)</span>
|
227
|
+
</div>
|
228
|
+
<div style="border-left: 2px solid #e0e0e0; margin-left: {indent * 16 + 8}px;">
|
229
|
+
"""
|
230
|
+
for sub_spec in spec._specs:
|
231
|
+
group_html += create_feature_spec_html(sub_spec, indent + 1)
|
232
|
+
group_html += """
|
233
|
+
</div>
|
234
|
+
</div>
|
235
|
+
"""
|
236
|
+
return group_html
|
237
|
+
return ""
|
238
|
+
|
239
|
+
|
240
|
+
def create_features_html(features: Sequence[Any], title: str) -> str:
|
241
|
+
"""Create HTML representation for a collection of features.
|
242
|
+
|
243
|
+
Args:
|
244
|
+
features: The sequence of feature specifications.
|
245
|
+
title: The title for the feature collection.
|
246
|
+
|
247
|
+
Returns:
|
248
|
+
HTML string for the features collection.
|
249
|
+
"""
|
250
|
+
if not features:
|
251
|
+
return f"""
|
252
|
+
<div style="margin: 5px 0; padding: 5px;">
|
253
|
+
<em>No {title.lower()} features defined</em>
|
254
|
+
</div>
|
255
|
+
"""
|
256
|
+
|
257
|
+
html = """
|
258
|
+
<div style="margin: 5px 0; padding: 5px;">
|
259
|
+
"""
|
260
|
+
for feature in features:
|
261
|
+
html += create_feature_spec_html(feature)
|
262
|
+
html += "</div>"
|
263
|
+
return html
|
snowflake/ml/version.py
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
# This is parsed by regex in conda recipe meta file. Make sure not to break it.
|
2
|
-
VERSION = "1.
|
2
|
+
VERSION = "1.9.0"
|