sagemaker-mlp-sdk 0.1.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.
@@ -0,0 +1,308 @@
1
+ """
2
+ Feature Store wrapper for mlp_sdk
3
+ Provides simplified feature group creation with configuration-driven defaults
4
+ """
5
+
6
+ from typing import Optional, Dict, Any, List
7
+ from ..exceptions import MLPSDKError, ValidationError, AWSServiceError, MLPLogger
8
+ from ..config import ConfigurationManager
9
+
10
+
11
+ class FeatureStoreWrapper:
12
+ """
13
+ Wrapper for SageMaker Feature Store operations.
14
+ Applies default configurations from ConfigurationManager.
15
+ """
16
+
17
+ def __init__(self, config_manager: ConfigurationManager, logger: Optional[MLPLogger] = None):
18
+ """
19
+ Initialize Feature Store wrapper.
20
+
21
+ Args:
22
+ config_manager: Configuration manager instance
23
+ logger: Optional logger instance
24
+ """
25
+ self.config_manager = config_manager
26
+ self.logger = logger or MLPLogger("mlp_sdk.feature_store")
27
+
28
+ def create_feature_group(self,
29
+ sagemaker_session,
30
+ feature_group_name: str,
31
+ record_identifier_name: str,
32
+ event_time_feature_name: str,
33
+ feature_definitions: List[Dict[str, str]],
34
+ **kwargs) -> Any:
35
+ """
36
+ Create a feature group with default configurations.
37
+
38
+ Applies defaults from configuration for:
39
+ - Offline store S3 URI
40
+ - Online store enablement
41
+ - Security group IDs
42
+ - Subnets
43
+ - KMS key ID
44
+ - IAM role
45
+
46
+ Runtime parameters override configuration defaults.
47
+
48
+ Args:
49
+ sagemaker_session: SageMaker session object
50
+ feature_group_name: Name of the feature group
51
+ record_identifier_name: Name of the record identifier feature
52
+ event_time_feature_name: Name of the event time feature
53
+ feature_definitions: List of feature definitions
54
+ **kwargs: Additional parameters that override defaults
55
+
56
+ Returns:
57
+ FeatureGroup object
58
+
59
+ Raises:
60
+ ValidationError: If required parameters are missing or invalid
61
+ AWSServiceError: If feature group creation fails
62
+ """
63
+ self.logger.info("Creating feature group", name=feature_group_name)
64
+
65
+ # Validate required parameters
66
+ if not feature_group_name:
67
+ raise ValidationError("feature_group_name is required")
68
+ if not record_identifier_name:
69
+ raise ValidationError("record_identifier_name is required")
70
+ if not event_time_feature_name:
71
+ raise ValidationError("event_time_feature_name is required")
72
+ if not feature_definitions:
73
+ raise ValidationError("feature_definitions is required and cannot be empty")
74
+
75
+ # Validate runtime parameter overrides
76
+ self.validate_parameter_override(kwargs)
77
+
78
+ try:
79
+ from sagemaker.feature_store.feature_group import FeatureGroup
80
+ except ImportError as e:
81
+ raise MLPSDKError(
82
+ "SageMaker SDK not installed. Install with: pip install sagemaker>=3.0.0"
83
+ ) from e
84
+
85
+ # Build configuration with defaults
86
+ config = self._build_feature_group_config(kwargs)
87
+
88
+ # Create FeatureGroup object
89
+ feature_group = FeatureGroup(
90
+ name=feature_group_name,
91
+ sagemaker_session=sagemaker_session
92
+ )
93
+
94
+ # Load feature definitions
95
+ feature_group.load_feature_definitions(feature_definitions)
96
+
97
+ try:
98
+ # Create the feature group with merged configuration
99
+ self.logger.debug("Creating feature group with config",
100
+ name=feature_group_name,
101
+ has_online_store=config.get('enable_online_store', False),
102
+ has_offline_store=bool(config.get('offline_store_config')))
103
+
104
+ # Build create parameters
105
+ create_params = {
106
+ 'record_identifier_name': record_identifier_name,
107
+ 'event_time_feature_name': event_time_feature_name,
108
+ }
109
+
110
+ # Add role ARN if available
111
+ if config.get('role_arn'):
112
+ create_params['role_arn'] = config['role_arn']
113
+
114
+ # Add online store config if enabled
115
+ if config.get('enable_online_store'):
116
+ online_store_config = {'EnableOnlineStore': True}
117
+
118
+ # Add security config if available
119
+ if config.get('online_store_security_config'):
120
+ online_store_config['SecurityConfig'] = config['online_store_security_config']
121
+
122
+ create_params['online_store_config'] = online_store_config
123
+
124
+ # Add offline store config if available
125
+ if config.get('offline_store_config'):
126
+ create_params['offline_store_config'] = config['offline_store_config']
127
+
128
+ # Add description if provided
129
+ if config.get('description'):
130
+ create_params['description'] = config['description']
131
+
132
+ # Add tags if provided
133
+ if config.get('tags'):
134
+ create_params['tags'] = config['tags']
135
+
136
+ # Create the feature group
137
+ feature_group.create(**create_params)
138
+
139
+ self.logger.info("Feature group created successfully", name=feature_group_name)
140
+ return feature_group
141
+
142
+ except Exception as e:
143
+ self.logger.error("Failed to create feature group",
144
+ name=feature_group_name,
145
+ error=e)
146
+ raise AWSServiceError(
147
+ f"Failed to create feature group '{feature_group_name}': {e}",
148
+ aws_error=e
149
+ ) from e
150
+
151
+ def _build_feature_group_config(self, runtime_params: Dict[str, Any]) -> Dict[str, Any]:
152
+ """
153
+ Build feature group configuration by merging defaults with runtime parameters.
154
+
155
+ Parameter precedence: runtime > config > SageMaker defaults
156
+
157
+ This implements the parameter override behavior specified in Requirements 3.5 and 8.2:
158
+ - Runtime parameters always take precedence over configuration defaults
159
+ - Configuration defaults take precedence over SageMaker SDK defaults
160
+ - SageMaker SDK defaults are used when neither runtime nor config provide values
161
+
162
+ Args:
163
+ runtime_params: Runtime parameters provided by user
164
+
165
+ Returns:
166
+ Merged configuration dictionary
167
+ """
168
+ config = {}
169
+
170
+ # Get configuration objects
171
+ feature_store_config = self.config_manager.get_feature_store_config()
172
+ networking_config = self.config_manager.get_networking_config()
173
+ iam_config = self.config_manager.get_iam_config()
174
+ kms_config = self.config_manager.get_kms_config()
175
+
176
+ # Apply offline store defaults (runtime > config)
177
+ if feature_store_config:
178
+ # Offline store configuration
179
+ if 'offline_store_config' in runtime_params:
180
+ # Runtime parameter takes precedence
181
+ config['offline_store_config'] = runtime_params['offline_store_config']
182
+ self.logger.debug("Using runtime offline_store_config")
183
+ else:
184
+ # Use config default
185
+ offline_store_config = {
186
+ 'S3StorageConfig': {
187
+ 'S3Uri': feature_store_config.offline_store_s3_uri
188
+ }
189
+ }
190
+
191
+ # Add KMS key if available
192
+ if kms_config and kms_config.key_id:
193
+ offline_store_config['S3StorageConfig']['KmsKeyId'] = kms_config.key_id
194
+
195
+ config['offline_store_config'] = offline_store_config
196
+ self.logger.debug("Using config offline_store_config",
197
+ s3_uri=feature_store_config.offline_store_s3_uri)
198
+
199
+ # Online store enablement (runtime > config)
200
+ if 'enable_online_store' in runtime_params:
201
+ config['enable_online_store'] = runtime_params['enable_online_store']
202
+ self.logger.debug("Using runtime enable_online_store",
203
+ value=runtime_params['enable_online_store'])
204
+ else:
205
+ config['enable_online_store'] = feature_store_config.enable_online_store
206
+ self.logger.debug("Using config enable_online_store",
207
+ value=feature_store_config.enable_online_store)
208
+
209
+ # Apply networking defaults for online store (runtime > config)
210
+ if config.get('enable_online_store'):
211
+ if 'online_store_security_config' in runtime_params:
212
+ config['online_store_security_config'] = runtime_params['online_store_security_config']
213
+ self.logger.debug("Using runtime online_store_security_config")
214
+ elif kms_config and kms_config.key_id:
215
+ config['online_store_security_config'] = {
216
+ 'KmsKeyId': kms_config.key_id
217
+ }
218
+ self.logger.debug("Using config online_store_security_config")
219
+
220
+ # Apply IAM role default (runtime > config)
221
+ if 'role_arn' in runtime_params:
222
+ config['role_arn'] = runtime_params['role_arn']
223
+ self.logger.debug("Using runtime role_arn")
224
+ elif iam_config:
225
+ config['role_arn'] = iam_config.execution_role
226
+ self.logger.debug("Using config role_arn", role=iam_config.execution_role)
227
+
228
+ # Apply any remaining runtime parameters (these override everything)
229
+ for key, value in runtime_params.items():
230
+ if key not in ['offline_store_config', 'enable_online_store',
231
+ 'online_store_security_config', 'role_arn']:
232
+ config[key] = value
233
+ self.logger.debug(f"Using runtime parameter: {key}")
234
+
235
+ return config
236
+
237
+ def _validate_feature_definitions(self, feature_definitions: List[Dict[str, str]]) -> None:
238
+ """
239
+ Validate feature definitions format.
240
+
241
+ Args:
242
+ feature_definitions: List of feature definitions
243
+
244
+ Raises:
245
+ ValidationError: If feature definitions are invalid
246
+ """
247
+ if not isinstance(feature_definitions, list):
248
+ raise ValidationError("feature_definitions must be a list")
249
+
250
+ if not feature_definitions:
251
+ raise ValidationError("feature_definitions cannot be empty")
252
+
253
+ for idx, feature_def in enumerate(feature_definitions):
254
+ if not isinstance(feature_def, dict):
255
+ raise ValidationError(f"Feature definition at index {idx} must be a dictionary")
256
+
257
+ if 'FeatureName' not in feature_def:
258
+ raise ValidationError(f"Feature definition at index {idx} missing 'FeatureName'")
259
+
260
+ if 'FeatureType' not in feature_def:
261
+ raise ValidationError(f"Feature definition at index {idx} missing 'FeatureType'")
262
+
263
+ def validate_parameter_override(self, runtime_params: Dict[str, Any]) -> None:
264
+ """
265
+ Validate runtime parameter overrides.
266
+
267
+ This ensures that runtime parameters are valid and compatible with the configuration.
268
+ Implements validation requirements from Requirements 3.5 and 8.2.
269
+
270
+ Args:
271
+ runtime_params: Runtime parameters to validate
272
+
273
+ Raises:
274
+ ValidationError: If runtime parameters are invalid
275
+ """
276
+ # Validate offline_store_config structure if provided
277
+ if 'offline_store_config' in runtime_params:
278
+ offline_config = runtime_params['offline_store_config']
279
+ if not isinstance(offline_config, dict):
280
+ raise ValidationError("offline_store_config must be a dictionary")
281
+
282
+ if 'S3StorageConfig' in offline_config:
283
+ s3_config = offline_config['S3StorageConfig']
284
+ if not isinstance(s3_config, dict):
285
+ raise ValidationError("S3StorageConfig must be a dictionary")
286
+
287
+ if 'S3Uri' not in s3_config:
288
+ raise ValidationError("S3StorageConfig must contain 'S3Uri'")
289
+
290
+ # Validate enable_online_store type if provided
291
+ if 'enable_online_store' in runtime_params:
292
+ if not isinstance(runtime_params['enable_online_store'], bool):
293
+ raise ValidationError("enable_online_store must be a boolean")
294
+
295
+ # Validate role_arn format if provided
296
+ if 'role_arn' in runtime_params:
297
+ role_arn = runtime_params['role_arn']
298
+ if not isinstance(role_arn, str):
299
+ raise ValidationError("role_arn must be a string")
300
+
301
+ if not role_arn.startswith('arn:aws:iam::'):
302
+ raise ValidationError(f"Invalid IAM role ARN format: {role_arn}")
303
+
304
+ # Validate online_store_security_config if provided
305
+ if 'online_store_security_config' in runtime_params:
306
+ security_config = runtime_params['online_store_security_config']
307
+ if not isinstance(security_config, dict):
308
+ raise ValidationError("online_store_security_config must be a dictionary")