Skip to content

Commit bd2450a

Browse files
committed
fix(feature-store): Fix FeatureGroupManager code issues and improve test coverage
- Use isinstance() for Unassigned checks instead of == Unassigned() - Add class-level type annotation for _lf_client_cache - Replace fragile docstring inheritance with proper docstring - Fix create() to return FeatureGroupManager instead of FeatureGroup by calling cls.get() after super().create() - Update create() return type annotation to Optional[FeatureGroupManager] - Add feature_group_arn validation before S3 policy generation - Fix integ test logger name (feature_group -> feature_group_manager) - Rename test_lakeformation.py to test_feature_group_manager.py - Add unit tests for: return type verification, Iceberg table format S3 path handling, missing ARN validation, happy-path return values, session/region pass-through, and region inference from session --- X-AI-Prompt: Review FeatureGroupManager class, fix identified issues, increase test coverage X-AI-Tool: kiro-cli
1 parent 77516d6 commit bd2450a

File tree

3 files changed

+384
-16
lines changed

3 files changed

+384
-16
lines changed

sagemaker-mlops/src/sagemaker/mlops/feature_store/feature_group_manager.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,9 @@ class LakeFormationConfig(Base):
5656

5757

5858
class FeatureGroupManager(FeatureGroup):
59+
"""FeatureGroup with extended management capabilities."""
5960

60-
# Inherit parent docstring and append our additions
61-
if FeatureGroup.__doc__ and __doc__:
62-
__doc__ = FeatureGroup.__doc__
61+
_lf_client_cache: dict
6362

6463
@staticmethod
6564
def _s3_uri_to_arn(s3_uri: str, region: Optional[str] = None) -> str:
@@ -221,8 +220,8 @@ def _get_lake_formation_client(
221220
A boto3 Lake Formation client.
222221
"""
223222
cache_key = (id(session), region)
224-
if not hasattr(self, "_lf_client_cache"):
225-
self._lf_client_cache: dict = {}
223+
if not hasattr(self, "_lf_client_cache") or self._lf_client_cache is None:
224+
self._lf_client_cache = {}
226225

227226
if cache_key not in self._lf_client_cache:
228227
boto_session = session or Session()
@@ -470,14 +469,14 @@ def enable_lake_formation(
470469
)
471470

472471
# Validate offline store exists
473-
if self.offline_store_config is None or self.offline_store_config == Unassigned():
472+
if self.offline_store_config is None or isinstance(self.offline_store_config, Unassigned):
474473
raise ValueError(
475474
f"Feature Group '{self.feature_group_name}' does not have an offline store configured. "
476475
"Lake Formation can only be enabled for Feature Groups with offline stores."
477476
)
478477

479478
# Get role ARN from Feature Group config
480-
if self.role_arn is None or self.role_arn == Unassigned():
479+
if self.role_arn is None or isinstance(self.role_arn, Unassigned):
481480
raise ValueError(
482481
f"Feature Group '{self.feature_group_name}' does not have a role_arn configured. "
483482
"Lake Formation requires a role ARN to grant permissions."
@@ -493,7 +492,7 @@ def enable_lake_formation(
493492
raise ValueError("Offline store S3 configuration is missing")
494493

495494
resolved_s3_uri = s3_config.resolved_output_s3_uri
496-
if resolved_s3_uri is None or resolved_s3_uri == Unassigned():
495+
if resolved_s3_uri is None or isinstance(resolved_s3_uri, Unassigned):
497496
raise ValueError(
498497
"Resolved S3 URI not available. Ensure the Feature Group is in 'Created' status."
499498
)
@@ -599,6 +598,13 @@ def enable_lake_formation(
599598

600599
# Generate and optionally print S3 deny policy
601600
if show_s3_policy:
601+
# Validate feature_group_arn is available for account ID extraction
602+
if self.feature_group_arn is None or isinstance(self.feature_group_arn, Unassigned):
603+
raise ValueError(
604+
"Feature Group ARN is required to generate S3 policy. "
605+
"Ensure the Feature Group is in 'Created' status."
606+
)
607+
602608
# Extract bucket name and prefix from resolved S3 URI using core utility
603609
bucket_name, s3_prefix = parse_s3_url(resolved_s3_uri_str)
604610

@@ -659,9 +665,9 @@ def create(
659665
lake_formation_config: Optional[LakeFormationConfig] = None,
660666
session: Optional[Session] = None,
661667
region: Optional[StrPipeVar] = None,
662-
) -> Optional["FeatureGroup"]:
668+
) -> Optional["FeatureGroupManager"]:
663669
"""
664-
Create a FeatureGroup resource with optional Lake Formation governance.
670+
Create a FeatureGroupManager resource with optional Lake Formation governance.
665671
666672
Parameters:
667673
feature_group_name: The name of the FeatureGroup.
@@ -682,7 +688,7 @@ def create(
682688
region: Region name.
683689
684690
Returns:
685-
The FeatureGroup resource.
691+
The FeatureGroupManager resource.
686692
687693
Raises:
688694
botocore.exceptions.ClientError: This exception is raised for AWS service related errors.
@@ -744,7 +750,10 @@ def create(
744750
if use_pre_prod_offline_store_replicator_lambda is not None:
745751
create_kwargs["use_pre_prod_offline_store_replicator_lambda"] = use_pre_prod_offline_store_replicator_lambda
746752

747-
feature_group = super().create(**create_kwargs)
753+
super().create(**create_kwargs)
754+
755+
# Get as FeatureGroupManager instance (super().create() returns FeatureGroup)
756+
feature_group = cls.get(feature_group_name=feature_group_name, session=session, region=region)
748757

749758
# Enable Lake Formation if requested
750759
if lake_formation_config is not None and lake_formation_config.enabled:

sagemaker-mlops/tests/integ/test_featureStore_lakeformation.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,7 @@ def test_enable_lake_formation_full_flow_with_policy_output(s3_uri, role, region
498498
assert fg.feature_group_status == "Created"
499499

500500
# Enable Lake Formation governance with policy output
501-
with caplog.at_level(logging.INFO, logger="sagemaker.mlops.feature_store.feature_group"):
501+
with caplog.at_level(logging.INFO, logger="sagemaker.mlops.feature_store.feature_group_manager"):
502502
result = fg.enable_lake_formation(show_s3_policy=True)
503503

504504
# Verify all phases completed successfully
@@ -573,7 +573,7 @@ def test_enable_lake_formation_no_policy_output_by_default(s3_uri, role, region,
573573
assert fg.feature_group_status == "Created"
574574

575575
# Enable Lake Formation governance WITHOUT policy output (default)
576-
with caplog.at_level(logging.INFO, logger="sagemaker.mlops.feature_store.feature_group"):
576+
with caplog.at_level(logging.INFO, logger="sagemaker.mlops.feature_store.feature_group_manager"):
577577
result = fg.enable_lake_formation()
578578

579579
# Verify all phases completed successfully
@@ -624,7 +624,7 @@ def test_enable_lake_formation_with_custom_role_policy_output(s3_uri, role, regi
624624

625625
# Enable Lake Formation with custom registration role and policy output
626626
# Using the same role for both execution and registration for test simplicity
627-
with caplog.at_level(logging.INFO, logger="sagemaker.mlops.feature_store.feature_group"):
627+
with caplog.at_level(logging.INFO, logger="sagemaker.mlops.feature_store.feature_group_manager"):
628628
result = fg.enable_lake_formation(
629629
use_service_linked_role=False,
630630
registration_role_arn=role,

0 commit comments

Comments
 (0)