snowflake-ml-python 1.0.1__py3-none-any.whl → 1.0.3__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.
Files changed (196) hide show
  1. snowflake/ml/_internal/env_utils.py +2 -1
  2. snowflake/ml/_internal/file_utils.py +35 -40
  3. snowflake/ml/_internal/telemetry.py +5 -8
  4. snowflake/ml/_internal/utils/identifier.py +74 -7
  5. snowflake/ml/_internal/utils/uri.py +7 -2
  6. snowflake/ml/model/_core_requirements.py +1 -1
  7. snowflake/ml/model/_deploy_client/image_builds/base_image_builder.py +15 -0
  8. snowflake/ml/model/_deploy_client/image_builds/client_image_builder.py +259 -0
  9. snowflake/ml/model/_deploy_client/image_builds/docker_context.py +89 -0
  10. snowflake/ml/model/_deploy_client/image_builds/gunicorn_run.sh +24 -0
  11. snowflake/ml/model/_deploy_client/image_builds/inference_server/main.py +118 -0
  12. snowflake/ml/model/_deploy_client/image_builds/templates/dockerfile_template +40 -0
  13. snowflake/ml/model/_deploy_client/snowservice/deploy.py +199 -0
  14. snowflake/ml/model/_deploy_client/snowservice/deploy_options.py +88 -0
  15. snowflake/ml/model/_deploy_client/snowservice/templates/service_spec_template +24 -0
  16. snowflake/ml/model/_deploy_client/utils/constants.py +47 -0
  17. snowflake/ml/model/_deploy_client/utils/snowservice_client.py +178 -0
  18. snowflake/ml/model/_deploy_client/warehouse/deploy.py +25 -28
  19. snowflake/ml/model/_deploy_client/warehouse/infer_template.py +7 -4
  20. snowflake/ml/model/_deployer.py +14 -27
  21. snowflake/ml/model/_env.py +4 -4
  22. snowflake/ml/model/_handlers/_base.py +3 -1
  23. snowflake/ml/model/_handlers/custom.py +14 -2
  24. snowflake/ml/model/_handlers/pytorch.py +186 -0
  25. snowflake/ml/model/_handlers/sklearn.py +14 -8
  26. snowflake/ml/model/_handlers/snowmlmodel.py +14 -9
  27. snowflake/ml/model/_handlers/torchscript.py +180 -0
  28. snowflake/ml/model/_handlers/xgboost.py +19 -9
  29. snowflake/ml/model/_model.py +27 -21
  30. snowflake/ml/model/_model_meta.py +33 -19
  31. snowflake/ml/model/model_signature.py +446 -66
  32. snowflake/ml/model/type_hints.py +28 -15
  33. snowflake/ml/modeling/calibration/calibrated_classifier_cv.py +79 -43
  34. snowflake/ml/modeling/cluster/affinity_propagation.py +79 -43
  35. snowflake/ml/modeling/cluster/agglomerative_clustering.py +79 -43
  36. snowflake/ml/modeling/cluster/birch.py +79 -43
  37. snowflake/ml/modeling/cluster/bisecting_k_means.py +79 -43
  38. snowflake/ml/modeling/cluster/dbscan.py +79 -43
  39. snowflake/ml/modeling/cluster/feature_agglomeration.py +79 -43
  40. snowflake/ml/modeling/cluster/k_means.py +79 -43
  41. snowflake/ml/modeling/cluster/mean_shift.py +79 -43
  42. snowflake/ml/modeling/cluster/mini_batch_k_means.py +79 -43
  43. snowflake/ml/modeling/cluster/optics.py +79 -43
  44. snowflake/ml/modeling/cluster/spectral_biclustering.py +79 -43
  45. snowflake/ml/modeling/cluster/spectral_clustering.py +79 -43
  46. snowflake/ml/modeling/cluster/spectral_coclustering.py +79 -43
  47. snowflake/ml/modeling/compose/column_transformer.py +79 -43
  48. snowflake/ml/modeling/compose/transformed_target_regressor.py +79 -43
  49. snowflake/ml/modeling/covariance/elliptic_envelope.py +79 -43
  50. snowflake/ml/modeling/covariance/empirical_covariance.py +79 -43
  51. snowflake/ml/modeling/covariance/graphical_lasso.py +79 -43
  52. snowflake/ml/modeling/covariance/graphical_lasso_cv.py +79 -43
  53. snowflake/ml/modeling/covariance/ledoit_wolf.py +79 -43
  54. snowflake/ml/modeling/covariance/min_cov_det.py +79 -43
  55. snowflake/ml/modeling/covariance/oas.py +79 -43
  56. snowflake/ml/modeling/covariance/shrunk_covariance.py +79 -43
  57. snowflake/ml/modeling/decomposition/dictionary_learning.py +79 -43
  58. snowflake/ml/modeling/decomposition/factor_analysis.py +79 -43
  59. snowflake/ml/modeling/decomposition/fast_ica.py +79 -43
  60. snowflake/ml/modeling/decomposition/incremental_pca.py +79 -43
  61. snowflake/ml/modeling/decomposition/kernel_pca.py +79 -43
  62. snowflake/ml/modeling/decomposition/mini_batch_dictionary_learning.py +79 -43
  63. snowflake/ml/modeling/decomposition/mini_batch_sparse_pca.py +79 -43
  64. snowflake/ml/modeling/decomposition/pca.py +79 -43
  65. snowflake/ml/modeling/decomposition/sparse_pca.py +79 -43
  66. snowflake/ml/modeling/decomposition/truncated_svd.py +79 -43
  67. snowflake/ml/modeling/discriminant_analysis/linear_discriminant_analysis.py +79 -43
  68. snowflake/ml/modeling/discriminant_analysis/quadratic_discriminant_analysis.py +79 -43
  69. snowflake/ml/modeling/ensemble/ada_boost_classifier.py +79 -43
  70. snowflake/ml/modeling/ensemble/ada_boost_regressor.py +79 -43
  71. snowflake/ml/modeling/ensemble/bagging_classifier.py +79 -43
  72. snowflake/ml/modeling/ensemble/bagging_regressor.py +79 -43
  73. snowflake/ml/modeling/ensemble/extra_trees_classifier.py +79 -43
  74. snowflake/ml/modeling/ensemble/extra_trees_regressor.py +79 -43
  75. snowflake/ml/modeling/ensemble/gradient_boosting_classifier.py +79 -43
  76. snowflake/ml/modeling/ensemble/gradient_boosting_regressor.py +79 -43
  77. snowflake/ml/modeling/ensemble/hist_gradient_boosting_classifier.py +79 -43
  78. snowflake/ml/modeling/ensemble/hist_gradient_boosting_regressor.py +79 -43
  79. snowflake/ml/modeling/ensemble/isolation_forest.py +79 -43
  80. snowflake/ml/modeling/ensemble/random_forest_classifier.py +79 -43
  81. snowflake/ml/modeling/ensemble/random_forest_regressor.py +79 -43
  82. snowflake/ml/modeling/ensemble/stacking_regressor.py +79 -43
  83. snowflake/ml/modeling/ensemble/voting_classifier.py +79 -43
  84. snowflake/ml/modeling/ensemble/voting_regressor.py +79 -43
  85. snowflake/ml/modeling/feature_selection/generic_univariate_select.py +79 -43
  86. snowflake/ml/modeling/feature_selection/select_fdr.py +79 -43
  87. snowflake/ml/modeling/feature_selection/select_fpr.py +79 -43
  88. snowflake/ml/modeling/feature_selection/select_fwe.py +79 -43
  89. snowflake/ml/modeling/feature_selection/select_k_best.py +79 -43
  90. snowflake/ml/modeling/feature_selection/select_percentile.py +79 -43
  91. snowflake/ml/modeling/feature_selection/sequential_feature_selector.py +79 -43
  92. snowflake/ml/modeling/feature_selection/variance_threshold.py +79 -43
  93. snowflake/ml/modeling/gaussian_process/gaussian_process_classifier.py +79 -43
  94. snowflake/ml/modeling/gaussian_process/gaussian_process_regressor.py +79 -43
  95. snowflake/ml/modeling/impute/iterative_imputer.py +79 -43
  96. snowflake/ml/modeling/impute/knn_imputer.py +79 -43
  97. snowflake/ml/modeling/impute/missing_indicator.py +79 -43
  98. snowflake/ml/modeling/kernel_approximation/additive_chi2_sampler.py +79 -43
  99. snowflake/ml/modeling/kernel_approximation/nystroem.py +79 -43
  100. snowflake/ml/modeling/kernel_approximation/polynomial_count_sketch.py +79 -43
  101. snowflake/ml/modeling/kernel_approximation/rbf_sampler.py +79 -43
  102. snowflake/ml/modeling/kernel_approximation/skewed_chi2_sampler.py +79 -43
  103. snowflake/ml/modeling/kernel_ridge/kernel_ridge.py +79 -43
  104. snowflake/ml/modeling/lightgbm/lgbm_classifier.py +79 -43
  105. snowflake/ml/modeling/lightgbm/lgbm_regressor.py +79 -43
  106. snowflake/ml/modeling/linear_model/ard_regression.py +79 -43
  107. snowflake/ml/modeling/linear_model/bayesian_ridge.py +79 -43
  108. snowflake/ml/modeling/linear_model/elastic_net.py +79 -43
  109. snowflake/ml/modeling/linear_model/elastic_net_cv.py +79 -43
  110. snowflake/ml/modeling/linear_model/gamma_regressor.py +79 -43
  111. snowflake/ml/modeling/linear_model/huber_regressor.py +79 -43
  112. snowflake/ml/modeling/linear_model/lars.py +79 -43
  113. snowflake/ml/modeling/linear_model/lars_cv.py +79 -43
  114. snowflake/ml/modeling/linear_model/lasso.py +79 -43
  115. snowflake/ml/modeling/linear_model/lasso_cv.py +79 -43
  116. snowflake/ml/modeling/linear_model/lasso_lars.py +79 -43
  117. snowflake/ml/modeling/linear_model/lasso_lars_cv.py +79 -43
  118. snowflake/ml/modeling/linear_model/lasso_lars_ic.py +79 -43
  119. snowflake/ml/modeling/linear_model/linear_regression.py +79 -43
  120. snowflake/ml/modeling/linear_model/logistic_regression.py +79 -43
  121. snowflake/ml/modeling/linear_model/logistic_regression_cv.py +79 -43
  122. snowflake/ml/modeling/linear_model/multi_task_elastic_net.py +79 -43
  123. snowflake/ml/modeling/linear_model/multi_task_elastic_net_cv.py +79 -43
  124. snowflake/ml/modeling/linear_model/multi_task_lasso.py +79 -43
  125. snowflake/ml/modeling/linear_model/multi_task_lasso_cv.py +79 -43
  126. snowflake/ml/modeling/linear_model/orthogonal_matching_pursuit.py +79 -43
  127. snowflake/ml/modeling/linear_model/passive_aggressive_classifier.py +79 -43
  128. snowflake/ml/modeling/linear_model/passive_aggressive_regressor.py +79 -43
  129. snowflake/ml/modeling/linear_model/perceptron.py +79 -43
  130. snowflake/ml/modeling/linear_model/poisson_regressor.py +79 -43
  131. snowflake/ml/modeling/linear_model/ransac_regressor.py +79 -43
  132. snowflake/ml/modeling/linear_model/ridge.py +79 -43
  133. snowflake/ml/modeling/linear_model/ridge_classifier.py +79 -43
  134. snowflake/ml/modeling/linear_model/ridge_classifier_cv.py +79 -43
  135. snowflake/ml/modeling/linear_model/ridge_cv.py +79 -43
  136. snowflake/ml/modeling/linear_model/sgd_classifier.py +79 -43
  137. snowflake/ml/modeling/linear_model/sgd_one_class_svm.py +79 -43
  138. snowflake/ml/modeling/linear_model/sgd_regressor.py +79 -43
  139. snowflake/ml/modeling/linear_model/theil_sen_regressor.py +79 -43
  140. snowflake/ml/modeling/linear_model/tweedie_regressor.py +79 -43
  141. snowflake/ml/modeling/manifold/isomap.py +79 -43
  142. snowflake/ml/modeling/manifold/mds.py +79 -43
  143. snowflake/ml/modeling/manifold/spectral_embedding.py +79 -43
  144. snowflake/ml/modeling/manifold/tsne.py +79 -43
  145. snowflake/ml/modeling/metrics/classification.py +6 -1
  146. snowflake/ml/modeling/metrics/regression.py +517 -9
  147. snowflake/ml/modeling/mixture/bayesian_gaussian_mixture.py +79 -43
  148. snowflake/ml/modeling/mixture/gaussian_mixture.py +79 -43
  149. snowflake/ml/modeling/model_selection/grid_search_cv.py +79 -43
  150. snowflake/ml/modeling/model_selection/randomized_search_cv.py +79 -43
  151. snowflake/ml/modeling/multiclass/one_vs_one_classifier.py +79 -43
  152. snowflake/ml/modeling/multiclass/one_vs_rest_classifier.py +79 -43
  153. snowflake/ml/modeling/multiclass/output_code_classifier.py +79 -43
  154. snowflake/ml/modeling/naive_bayes/bernoulli_nb.py +79 -43
  155. snowflake/ml/modeling/naive_bayes/categorical_nb.py +79 -43
  156. snowflake/ml/modeling/naive_bayes/complement_nb.py +79 -43
  157. snowflake/ml/modeling/naive_bayes/gaussian_nb.py +79 -43
  158. snowflake/ml/modeling/naive_bayes/multinomial_nb.py +79 -43
  159. snowflake/ml/modeling/neighbors/k_neighbors_classifier.py +79 -43
  160. snowflake/ml/modeling/neighbors/k_neighbors_regressor.py +79 -43
  161. snowflake/ml/modeling/neighbors/kernel_density.py +79 -43
  162. snowflake/ml/modeling/neighbors/local_outlier_factor.py +79 -43
  163. snowflake/ml/modeling/neighbors/nearest_centroid.py +79 -43
  164. snowflake/ml/modeling/neighbors/nearest_neighbors.py +79 -43
  165. snowflake/ml/modeling/neighbors/neighborhood_components_analysis.py +79 -43
  166. snowflake/ml/modeling/neighbors/radius_neighbors_classifier.py +79 -43
  167. snowflake/ml/modeling/neighbors/radius_neighbors_regressor.py +79 -43
  168. snowflake/ml/modeling/neural_network/bernoulli_rbm.py +79 -43
  169. snowflake/ml/modeling/neural_network/mlp_classifier.py +79 -43
  170. snowflake/ml/modeling/neural_network/mlp_regressor.py +79 -43
  171. snowflake/ml/modeling/pipeline/pipeline.py +24 -0
  172. snowflake/ml/modeling/preprocessing/one_hot_encoder.py +18 -19
  173. snowflake/ml/modeling/preprocessing/ordinal_encoder.py +2 -0
  174. snowflake/ml/modeling/preprocessing/polynomial_features.py +79 -43
  175. snowflake/ml/modeling/semi_supervised/label_propagation.py +79 -43
  176. snowflake/ml/modeling/semi_supervised/label_spreading.py +79 -43
  177. snowflake/ml/modeling/svm/linear_svc.py +79 -43
  178. snowflake/ml/modeling/svm/linear_svr.py +79 -43
  179. snowflake/ml/modeling/svm/nu_svc.py +79 -43
  180. snowflake/ml/modeling/svm/nu_svr.py +79 -43
  181. snowflake/ml/modeling/svm/svc.py +79 -43
  182. snowflake/ml/modeling/svm/svr.py +79 -43
  183. snowflake/ml/modeling/tree/decision_tree_classifier.py +79 -43
  184. snowflake/ml/modeling/tree/decision_tree_regressor.py +79 -43
  185. snowflake/ml/modeling/tree/extra_tree_classifier.py +79 -43
  186. snowflake/ml/modeling/tree/extra_tree_regressor.py +79 -43
  187. snowflake/ml/modeling/xgboost/xgb_classifier.py +79 -43
  188. snowflake/ml/modeling/xgboost/xgb_regressor.py +79 -43
  189. snowflake/ml/modeling/xgboost/xgbrf_classifier.py +79 -43
  190. snowflake/ml/modeling/xgboost/xgbrf_regressor.py +79 -43
  191. snowflake/ml/registry/model_registry.py +123 -121
  192. snowflake/ml/version.py +1 -1
  193. {snowflake_ml_python-1.0.1.dist-info → snowflake_ml_python-1.0.3.dist-info}/METADATA +50 -8
  194. snowflake_ml_python-1.0.3.dist-info/RECORD +259 -0
  195. snowflake_ml_python-1.0.1.dist-info/RECORD +0 -246
  196. {snowflake_ml_python-1.0.1.dist-info → snowflake_ml_python-1.0.3.dist-info}/WHEEL +0 -0
@@ -0,0 +1,178 @@
1
+ import json
2
+ import logging
3
+ import time
4
+ from typing import Optional
5
+
6
+ from snowflake.ml.model._deploy_client.utils import constants
7
+ from snowflake.snowpark import Session
8
+
9
+
10
+ class SnowServiceClient:
11
+ """
12
+ SnowService client implementation: a Python wrapper for SnowService SQL queries.
13
+ """
14
+
15
+ def __init__(self, session: Session) -> None:
16
+ """Initialization
17
+
18
+ Args:
19
+ session: Snowpark session
20
+ """
21
+ self.session = session
22
+
23
+ def create_or_replace_service(
24
+ self,
25
+ service_name: str,
26
+ compute_pool: str,
27
+ spec_stage_location: str,
28
+ *,
29
+ min_instances: int = 1,
30
+ max_instances: int = 1,
31
+ ) -> None:
32
+ """Create or replace service. Since SnowService doesn't support the CREATE OR REPLACE service syntax, we will
33
+ first attempt to drop the service if it exists, and then create the service. Please note that this approach may
34
+ have side effects due to the lack of transaction support.
35
+
36
+ Args:
37
+ service_name: Name of the service.
38
+ min_instances: Minimum number of service replicas.
39
+ max_instances: Maximum number of service replicas.
40
+ compute_pool: Name of the compute pool.
41
+ spec_stage_location: Stage path for the service spec.
42
+ """
43
+ self._drop_service_if_exists(service_name)
44
+ sql = f"""
45
+ CREATE SERVICE {service_name}
46
+ MIN_INSTANCES={min_instances}
47
+ MAX_INSTANCES={max_instances}
48
+ COMPUTE_POOL={compute_pool}
49
+ SPEC=@{spec_stage_location}
50
+ """
51
+ logging.info(f"Create service with SQL: \n {sql}")
52
+ self.session.sql(sql).collect()
53
+
54
+ def _drop_service_if_exists(self, service_name: str) -> None:
55
+ """Drop service if it already exists.
56
+
57
+ Args:
58
+ service_name: Name of the service.
59
+ """
60
+ self.session.sql(f"DROP SERVICE IF EXISTS {service_name}").collect()
61
+
62
+ def create_or_replace_service_function(
63
+ self,
64
+ service_func_name: str,
65
+ service_name: str,
66
+ *,
67
+ endpoint_name: str = constants.PREDICT,
68
+ path_at_service_endpoint: str = constants.PREDICT,
69
+ ) -> None:
70
+ """Create or replace service function.
71
+
72
+ Args:
73
+ service_func_name: Name of the service function.
74
+ service_name: Name of the service.
75
+ endpoint_name: Name the service endpoint, declared in the service spec, indicating the listening port.
76
+ path_at_service_endpoint: Specify the path/route at the service endpoint. Multiple paths can exist for a
77
+ given endpoint. For example, an inference server listening on port 5000 may have paths like "/predict"
78
+ and "/monitoring
79
+
80
+ """
81
+ sql = f"""
82
+ CREATE OR REPLACE FUNCTION {service_func_name}(input OBJECT)
83
+ RETURNS OBJECT
84
+ SERVICE={service_name}
85
+ ENDPOINT={endpoint_name}
86
+ AS '/{path_at_service_endpoint}'
87
+ """
88
+ logging.info(f"Create service function with SQL: \n {sql}")
89
+ self.session.sql(sql).collect()
90
+
91
+ def block_until_resource_is_ready(
92
+ self,
93
+ resource_name: str,
94
+ resource_type: constants.ResourceType,
95
+ *,
96
+ max_retries: int = 60,
97
+ retry_interval_secs: int = 5,
98
+ ) -> None:
99
+ """Blocks execution until the specified resource is ready.
100
+ Note that this is a best-effort approach because when launching a service, it's possible for it to initially
101
+ fail due to a system error. However, SnowService may automatically retry and recover the service, leading to
102
+ potential false-negative information.
103
+
104
+ Args:
105
+ resource_name: Name of the resource.
106
+ resource_type: Type of the resource.
107
+ max_retries: The maximum number of retries to check the resource readiness (default: 60).
108
+ retry_interval_secs: The number of seconds to wait between each retry (default: 5).
109
+
110
+ Raises:
111
+ RuntimeError: If the resource received the following status [failed, not_found, internal_error, deleting]
112
+ RuntimeError: If the resource does not reach the ready/done state within the specified number of retries.
113
+ """
114
+ for _ in range(max_retries):
115
+ status = self.get_resource_status(resource_name=resource_name, resource_type=resource_type)
116
+ if status in [constants.ResourceStatus.READY, constants.ResourceStatus.DONE]:
117
+ return
118
+ elif status in [
119
+ constants.ResourceStatus.FAILED,
120
+ constants.ResourceStatus.NOT_FOUND,
121
+ constants.ResourceStatus.INTERNAL_ERROR,
122
+ constants.ResourceStatus.DELETING,
123
+ ]:
124
+ error_log = self.get_resource_log(
125
+ resource_name=resource_name,
126
+ resource_type=resource_type,
127
+ container_name=constants.INFERENCE_SERVER_CONTAINER,
128
+ )
129
+ raise RuntimeError(f"{resource_type} {resource_name} failed. \n {error_log if error_log else ''}")
130
+ time.sleep(retry_interval_secs)
131
+
132
+ raise RuntimeError("Resource never reached the ready/done state.")
133
+
134
+ def get_resource_log(
135
+ self, resource_name: str, resource_type: constants.ResourceType, container_name: str
136
+ ) -> Optional[str]:
137
+ if resource_type != constants.ResourceType.SERVICE:
138
+ raise NotImplementedError(f"{resource_type.name} is not yet supported in get_resource_log function")
139
+ try:
140
+ row = self.session.sql(
141
+ f"CALL SYSTEM$GET_SNOWSERVICE_LOGS('{resource_name}', '0', '{container_name}')"
142
+ ).collect()
143
+ return str(row[0]["SYSTEM$GET_SNOWSERVICE_LOGS"])
144
+ except Exception:
145
+ return None
146
+
147
+ def get_resource_status(
148
+ self, resource_name: str, resource_type: constants.ResourceType
149
+ ) -> Optional[constants.ResourceStatus]:
150
+ """Get resource status.
151
+
152
+ Args:
153
+ resource_name: Name of the resource.
154
+ resource_type: Type of the resource.
155
+
156
+ Raises:
157
+ ValueError: If resource type does not have a corresponding system function for querying status.
158
+ RuntimeError: If corresponding status call failed.
159
+
160
+ Returns:
161
+ Optional[constants.ResourceStatus]: The status of the resource, or None if the resource status is empty.
162
+ """
163
+ if resource_type not in constants.RESOURCE_TO_STATUS_FUNCTION_MAPPING:
164
+ raise ValueError(f"Status querying is not supported for resources of type '{resource_type}'.")
165
+ status_func = constants.RESOURCE_TO_STATUS_FUNCTION_MAPPING[resource_type]
166
+ try:
167
+ row = self.session.sql(f"CALL {status_func}('{resource_name}');").collect()
168
+ except Exception as e:
169
+ raise RuntimeError(f"Error while querying the {resource_type} {resource_name} status: {str(e)}")
170
+ resource_metadata = json.loads(row[0][status_func])[0]
171
+ logging.info(f"Resource status metadata: {resource_metadata}")
172
+ if resource_metadata and resource_metadata["status"]:
173
+ try:
174
+ status = resource_metadata["status"]
175
+ return constants.ResourceStatus(status)
176
+ except ValueError:
177
+ logging.warning(f"Unknown status returned: {status}")
178
+ return None
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import posixpath
2
3
  import tempfile
3
4
  import warnings
4
5
  from types import ModuleType
@@ -6,7 +7,8 @@ from typing import IO, List, Optional, Tuple, TypedDict, Union
6
7
 
7
8
  from typing_extensions import Unpack
8
9
 
9
- from snowflake.ml._internal import env as snowml_env, env_utils, file_utils
10
+ from snowflake.ml._internal import env_utils, file_utils
11
+ from snowflake.ml._internal.utils import identifier
10
12
  from snowflake.ml.model import (
11
13
  _env as model_env,
12
14
  _model,
@@ -37,6 +39,7 @@ def _deploy_to_warehouse(
37
39
  **kwargs: Options that control some features in generated udf code.
38
40
 
39
41
  Raises:
42
+ ValueError: Raised when model file name is unable to encoded using ASCII.
40
43
  ValueError: Raised when incompatible model.
41
44
  ValueError: Raised when target method does not exist in model.
42
45
  ValueError: Raised when confronting invalid stage location.
@@ -44,14 +47,20 @@ def _deploy_to_warehouse(
44
47
  Returns:
45
48
  The metadata of the model deployed.
46
49
  """
50
+ # TODO(SNOW-862576): Should remove check on ASCII encoding after SNOW-862576 fixed.
47
51
  if model_dir_path:
48
52
  model_dir_path = os.path.normpath(model_dir_path)
49
53
  model_dir_name = os.path.basename(model_dir_path)
54
+ if not file_utils._able_ascii_encode(model_dir_name):
55
+ raise ValueError(f"Model file name {model_dir_name} cannot be encoded using ASCII. Please rename.")
50
56
  extract_model_code = infer_template._EXTRACT_LOCAL_MODEL_CODE.format(model_dir_name=model_dir_name)
51
57
  meta = _model.load_model(model_dir_path=model_dir_path, meta_only=True)
52
58
  else:
53
59
  assert model_stage_file_path is not None, "Unreachable assertion error."
54
- model_stage_file_name = os.path.basename(model_stage_file_path)
60
+ model_stage_file_name = posixpath.basename(model_stage_file_path)
61
+ if not file_utils._able_ascii_encode(model_stage_file_name):
62
+ raise ValueError(f"Model file name {model_stage_file_name} cannot be encoded using ASCII. Please rename.")
63
+
55
64
  extract_model_code = infer_template._EXTRACT_STAGE_MODEL_CODE.format(
56
65
  model_stage_file_name=model_stage_file_name
57
66
  )
@@ -59,32 +68,26 @@ def _deploy_to_warehouse(
59
68
 
60
69
  relax_version = kwargs.get("relax_version", False)
61
70
 
71
+ disable_local_conda_resolver = kwargs.get("disable_local_conda_resolver", False)
72
+
62
73
  if target_method not in meta.signatures.keys():
63
74
  raise ValueError(f"Target method {target_method} does not exist in model.")
64
75
 
65
- _use_local_snowml = kwargs.get("_use_local_snowml", False)
66
-
67
76
  final_packages = _get_model_final_packages(
68
- meta, session, relax_version=relax_version, _use_local_snowml=_use_local_snowml
77
+ meta, session, relax_version=relax_version, disable_local_conda_resolver=disable_local_conda_resolver
69
78
  )
70
79
 
71
80
  stage_location = kwargs.get("permanent_udf_stage_location", None)
72
81
  if stage_location:
73
- stage_location = stage_location.strip().rstrip("/")
82
+ stage_location = posixpath.normpath(stage_location.strip())
74
83
  if not stage_location.startswith("@"):
75
84
  raise ValueError(f"Invalid stage location {stage_location}.")
76
85
 
77
- _snowml_wheel_path = None
78
- if _use_local_snowml:
79
- _snowml_wheel_path = file_utils.upload_snowml(session, stage_location=stage_location)
80
-
81
- with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f:
86
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False, encoding="utf-8") as f:
82
87
  _write_UDF_py_file(f.file, extract_model_code, target_method, **kwargs)
83
88
  print(f"Generated UDF file is persisted at: {f.name}")
84
- imports = (
85
- ([model_dir_path] if model_dir_path else [])
86
- + ([model_stage_file_path] if model_stage_file_path else [])
87
- + ([_snowml_wheel_path] if _snowml_wheel_path else [])
89
+ imports = ([model_dir_path] if model_dir_path else []) + (
90
+ [model_stage_file_path] if model_stage_file_path else []
88
91
  )
89
92
 
90
93
  class _UDFParams(TypedDict):
@@ -99,7 +102,7 @@ def _deploy_to_warehouse(
99
102
  params = _UDFParams(
100
103
  file_path=f.name,
101
104
  func_name="infer",
102
- name=f"{udf_name}",
105
+ name=identifier.get_inferred_name(udf_name),
103
106
  return_type=st.PandasSeriesType(st.MapType(st.StringType(), st.VariantType())),
104
107
  input_types=[st.PandasDataFrameType([st.MapType()])],
105
108
  imports=list(imports),
@@ -139,6 +142,7 @@ def _write_UDF_py_file(
139
142
  extract_model_code=extract_model_code,
140
143
  keep_order_code=infer_template._KEEP_ORDER_CODE_TEMPLATE if keep_order else "",
141
144
  target_method=target_method,
145
+ code_dir_name=_model_meta.ModelMetadata.MODEL_CODE_DIR,
142
146
  )
143
147
  f.write(udf_code)
144
148
  f.flush()
@@ -148,7 +152,7 @@ def _get_model_final_packages(
148
152
  meta: _model_meta.ModelMetadata,
149
153
  session: snowpark_session.Session,
150
154
  relax_version: Optional[bool] = False,
151
- _use_local_snowml: Optional[bool] = False,
155
+ disable_local_conda_resolver: Optional[bool] = False,
152
156
  ) -> List[str]:
153
157
  """Generate final packages list of dependency of a model to be deployed to warehouse.
154
158
 
@@ -157,7 +161,8 @@ def _get_model_final_packages(
157
161
  session: Snowpark connection session.
158
162
  relax_version: Whether or not relax the version restriction when fail to resolve dependencies.
159
163
  Defaults to False.
160
- _use_local_snowml: Flag to indicate if using local SnowML code as execution library
164
+ disable_local_conda_resolver: Set to disable use local conda resolver to do pre-check on environment and rely on
165
+ the information schema only. Defaults to False.
161
166
 
162
167
  Raises:
163
168
  RuntimeError: Raised when PIP requirements and dependencies from non-Snowflake anaconda channel found.
@@ -174,18 +179,10 @@ def _get_model_final_packages(
174
179
  raise RuntimeError("PIP requirements and dependencies from non-Snowflake anaconda channel is not supported.")
175
180
 
176
181
  deps = meta._conda_dependencies[""]
177
- if _use_local_snowml:
178
- local_snowml_version = snowml_env.VERSION
179
- snowml_dept = next((dep for dep in deps if dep.name == env_utils._SNOWML_PKG_NAME), None)
180
- if snowml_dept:
181
- if not snowml_dept.specifier.contains(local_snowml_version) and not relax_version:
182
- raise RuntimeError(
183
- "Incompatible snowflake-ml-python-version is found. "
184
- + f"Require {snowml_dept.specifier}, got {local_snowml_version}."
185
- )
186
- deps.remove(snowml_dept)
187
182
 
188
183
  try:
184
+ if disable_local_conda_resolver:
185
+ raise ImportError("Raise to disable local conda resolver. Should be captured.")
189
186
  final_packages = env_utils.resolve_conda_environment(
190
187
  deps, [model_env._SNOWFLAKE_CONDA_CHANNEL_URL], python_version=meta.python_version
191
188
  )
@@ -48,18 +48,21 @@ class FileLock:
48
48
  IMPORT_DIRECTORY_NAME = "snowflake_import_directory"
49
49
  import_dir = sys._xoptions[IMPORT_DIRECTORY_NAME]
50
50
 
51
- from snowflake.ml.model import _model
52
-
53
51
  {extract_model_code}
54
52
 
53
+ sys.path.insert(0, os.path.join(extracted_model_dir_path, "{code_dir_name}"))
54
+ from snowflake.ml.model import _model
55
55
  model, meta = _model._load_model_for_deploy(extracted_model_dir_path)
56
56
 
57
+ features = meta.signatures["{target_method}"].inputs
58
+ input_cols = [feature.name for feature in features]
59
+ dtype_map = {{feature.name: feature.as_dtype() for feature in features}}
60
+
57
61
  # TODO(halu): Wire `max_batch_size`.
58
62
  # TODO(halu): Avoid per batch async detection branching.
59
63
  @vectorized(input=pd.DataFrame, max_batch_size=10)
60
64
  def infer(df):
61
- input_cols = [spec.name for spec in meta.signatures["{target_method}"].inputs]
62
- input_df = pd.io.json.json_normalize(df[0])
65
+ input_df = pd.io.json.json_normalize(df[0]).astype(dtype=dtype_map)
63
66
  if inspect.iscoroutinefunction(model.{target_method}):
64
67
  predictions_df = anyio.run(model.{target_method}, input_df[input_cols])
65
68
  else:
@@ -1,9 +1,7 @@
1
- import json
2
1
  import traceback
3
2
  from enum import Enum
4
3
  from typing import Optional, TypedDict, Union, overload
5
4
 
6
- import numpy as np
7
5
  import pandas as pd
8
6
  from typing_extensions import Required
9
7
 
@@ -184,7 +182,6 @@ def predict(
184
182
 
185
183
  Raises:
186
184
  ValueError: Raised when the input is too large to use keep_order option.
187
- NotImplementedError: FeatureGroupSpec is not supported.
188
185
 
189
186
  Returns:
190
187
  The output dataframe.
@@ -199,19 +196,19 @@ def predict(
199
196
  # Validate and prepare input
200
197
  if not isinstance(X, SnowparkDataFrame):
201
198
  df = model_signature._convert_and_validate_local_data(X, sig.inputs)
202
- s_df = session.create_dataframe(df)
199
+ s_df = model_signature._SnowparkDataFrameHandler.convert_from_df(session, df, keep_order=keep_order)
203
200
  else:
204
201
  model_signature._validate_snowpark_data(X, sig.inputs)
205
202
  s_df = X
206
203
 
207
- if keep_order:
208
- # ID is UINT64 type, this we should limit.
209
- if s_df.count() > 2**64:
210
- raise ValueError("Unable to keep order of a DataFrame with more than 2 ** 64 rows.")
211
- s_df = s_df.with_column(
212
- infer_template._KEEP_ORDER_COL_NAME,
213
- F.monotonically_increasing_id(),
214
- )
204
+ if keep_order:
205
+ # ID is UINT64 type, this we should limit.
206
+ if s_df.count() > 2**64:
207
+ raise ValueError("Unable to keep order of a DataFrame with more than 2 ** 64 rows.")
208
+ s_df = s_df.with_column(
209
+ infer_template._KEEP_ORDER_COL_NAME,
210
+ F.monotonically_increasing_id(),
211
+ )
215
212
 
216
213
  # Infer and get intermediate result
217
214
  input_cols = []
@@ -223,7 +220,9 @@ def predict(
223
220
  F.col(col_name),
224
221
  ]
225
222
  )
226
- output_obj = F.call_udf(deployment["name"], F.object_construct(*input_cols)) # type:ignore[arg-type]
223
+ output_obj = F.call_udf(
224
+ identifier.get_inferred_name(deployment["name"]), F.object_construct(*input_cols) # type:ignore[arg-type]
225
+ )
227
226
  if output_with_input_features:
228
227
  df_res = s_df.with_column(INTERMEDIATE_OBJ_NAME, output_obj)
229
228
  else:
@@ -243,24 +242,12 @@ def predict(
243
242
  output_cols.append(F.col(INTERMEDIATE_OBJ_NAME)[output_feature.name].astype(output_feature.as_snowpark_type()))
244
243
 
245
244
  df_res = df_res.with_columns(
246
- [identifier.quote_name_without_upper_casing(output_feature.name) for output_feature in sig.outputs],
245
+ [identifier.get_inferred_name(output_feature.name) for output_feature in sig.outputs],
247
246
  output_cols,
248
247
  ).drop(INTERMEDIATE_OBJ_NAME)
249
248
 
250
249
  # Get final result
251
250
  if not isinstance(X, SnowparkDataFrame):
252
- dtype_map = {}
253
- for feature in sig.outputs:
254
- if isinstance(feature, model_signature.FeatureGroupSpec):
255
- raise NotImplementedError("FeatureGroupSpec is not supported.")
256
- assert isinstance(feature, model_signature.FeatureSpec), "Invalid feature kind."
257
- dtype_map[feature.name] = feature.as_dtype()
258
- df_local = df_res.to_pandas()
259
- # This is because Array and object will generate variant type and requires an additional loads to
260
- # get correct data otherwise it would be string.
261
- for col_name in [col_name for col_name, col_dtype in dtype_map.items() if col_dtype == np.object0]:
262
- df_local[col_name] = df_local[col_name].map(json.loads)
263
- df_local = df_local.astype(dtype=dtype_map)
264
- return pd.DataFrame(df_local)
251
+ return model_signature._SnowparkDataFrameHandler.convert_to_df(df_res, features=sig.outputs)
265
252
  else:
266
253
  return df_res
@@ -36,7 +36,7 @@ def save_conda_env_file(
36
36
  for chan, reqs in deps.items():
37
37
  env["dependencies"].extend([f"{chan}::{str(req)}" if chan else str(req) for req in reqs])
38
38
 
39
- with open(path, "w") as f:
39
+ with open(path, "w", encoding="utf-8") as f:
40
40
  yaml.safe_dump(env, stream=f, default_flow_style=False)
41
41
 
42
42
  return path
@@ -54,7 +54,7 @@ def save_requirements_file(dir_path: str, pip_deps: List[requirements.Requiremen
54
54
  """
55
55
  requirements = "\n".join(map(str, pip_deps))
56
56
  path = os.path.join(dir_path, _REQUIREMENTS_FILE_NAME)
57
- with open(path, "w") as out:
57
+ with open(path, "w", encoding="utf-8") as out:
58
58
  out.write(requirements)
59
59
 
60
60
  return path
@@ -69,7 +69,7 @@ def load_conda_env_file(path: str) -> Tuple[DefaultDict[str, List[requirements.R
69
69
  Returns:
70
70
  A tuple of Dict of conda dependencies after validated and a string 'major.minor.patchlevel' of python version.
71
71
  """
72
- with open(path) as f:
72
+ with open(path, encoding="utf-8") as f:
73
73
  env = yaml.safe_load(stream=f)
74
74
 
75
75
  assert isinstance(env, dict)
@@ -99,7 +99,7 @@ def load_requirements_file(path: str) -> List[requirements.Requirement]:
99
99
  Returns:
100
100
  List of dependencies string after validated.
101
101
  """
102
- with open(path) as f:
102
+ with open(path, encoding="utf-8") as f:
103
103
  reqs = f.readlines()
104
104
 
105
105
  return env_utils.validate_pip_requirement_string_list(reqs)
@@ -1,7 +1,7 @@
1
1
  from abc import ABC, abstractmethod
2
2
  from typing import Generic, Optional
3
3
 
4
- from typing_extensions import TypeGuard
4
+ from typing_extensions import TypeGuard, Unpack
5
5
 
6
6
  from snowflake.ml.model import _model_meta, type_hints as model_types
7
7
 
@@ -43,6 +43,7 @@ class _ModelHandler(ABC, Generic[model_types._ModelType]):
43
43
  model_blobs_dir_path: str,
44
44
  sample_input: Optional[model_types.SupportedDataType] = None,
45
45
  is_sub_model: Optional[bool] = False,
46
+ **kwargs: Unpack[model_types.ModelSaveOption],
46
47
  ) -> None:
47
48
  """Save the model.
48
49
 
@@ -53,6 +54,7 @@ class _ModelHandler(ABC, Generic[model_types._ModelType]):
53
54
  model_blobs_dir_path: Directory path to the model.
54
55
  sample_input: Sample input to infer the signatures from.
55
56
  is_sub_model: Flag to show if it is a sub model, a sub model does not need signature.
57
+ kwargs: Additional saving options.
56
58
  """
57
59
  ...
58
60
 
@@ -1,16 +1,19 @@
1
1
  import inspect
2
2
  import os
3
+ import pathlib
3
4
  import sys
4
5
  from typing import TYPE_CHECKING, Dict, Optional
5
6
 
6
7
  import anyio
7
8
  import cloudpickle
9
+ import pandas as pd
8
10
  from typing_extensions import TypeGuard, Unpack
9
11
 
10
12
  from snowflake.ml._internal import file_utils, type_utils
11
13
  from snowflake.ml.model import (
12
14
  _model_handler,
13
15
  _model_meta as model_meta_api,
16
+ model_signature,
14
17
  type_hints as model_types,
15
18
  )
16
19
  from snowflake.ml.model._handlers import _base
@@ -55,6 +58,10 @@ class _CustomModelHandler(_base._ModelHandler["custom_model.CustomModel"]):
55
58
  target_method = getattr(model, target_method_name, None)
56
59
  assert callable(target_method) and inspect.ismethod(target_method)
57
60
  target_method = target_method.__func__
61
+
62
+ if not isinstance(sample_input, pd.DataFrame):
63
+ sample_input = model_signature._convert_local_data_to_df(sample_input)
64
+
58
65
  if inspect.iscoroutinefunction(target_method):
59
66
  with anyio.start_blocking_portal() as portal:
60
67
  predictions_df = portal.call(target_method, model, sample_input)
@@ -102,7 +109,9 @@ class _CustomModelHandler(_base._ModelHandler["custom_model.CustomModel"]):
102
109
  model_type=_CustomModelHandler.handler_type,
103
110
  path=_CustomModelHandler.MODEL_BLOB_FILE,
104
111
  artifacts={
105
- name: os.path.join(_CustomModelHandler.MODEL_ARTIFACTS_DIR, os.path.basename(os.path.normpath(uri)))
112
+ name: pathlib.Path(
113
+ os.path.join(_CustomModelHandler.MODEL_ARTIFACTS_DIR, os.path.basename(os.path.normpath(path=uri)))
114
+ ).as_posix()
106
115
  for name, uri in model.context.artifacts.items()
107
116
  },
108
117
  )
@@ -129,7 +138,10 @@ class _CustomModelHandler(_base._ModelHandler["custom_model.CustomModel"]):
129
138
  assert issubclass(ModelClass, custom_model.CustomModel)
130
139
 
131
140
  artifacts_meta = model_blob_metadata.artifacts
132
- artifacts = {name: os.path.join(model_blob_path, rel_path) for name, rel_path in artifacts_meta.items()}
141
+ artifacts = {
142
+ name: str(pathlib.PurePath(model_blob_path) / pathlib.PurePosixPath(rel_path))
143
+ for name, rel_path in artifacts_meta.items()
144
+ }
133
145
  models: Dict[str, model_types.SupportedModelType] = dict()
134
146
  for sub_model_name, _ref in m.context.model_refs.items():
135
147
  model_type = model_meta.models[sub_model_name].model_type