@@ -27,7 +27,6 @@ def __init__(
2727 auth ,
2828 base_url : str ,
2929 config = None ,
30- feature_flags : Optional [Dict [str , bool ]] = None ,
3130 ) -> None :
3231 self .auth = auth
3332 self .base_url = (base_url or "" ).rstrip ("/" )
@@ -51,72 +50,6 @@ def __init__(
5150 # Picklist label cache: (logical_name, attribute_logical) -> {'map': {...}, 'ts': epoch_seconds}
5251 self ._picklist_label_cache = {}
5352 self ._picklist_cache_ttl_seconds = 3600 # 1 hour TTL
54- # Load required feature flags from bundled JSON resource; fail hard if missing or invalid.
55- try :
56- data = ir .files ("dataverse_sdk" ).joinpath ("feature_flags.json" ).read_text (encoding = "utf-8" )
57- except Exception as e :
58- raise RuntimeError (f"Failed to load feature_flags.json resource: { e } " ) from e
59- try :
60- loaded = json .loads (data ) if data else {}
61- except Exception as e :
62- raise RuntimeError (f"feature_flags.json is not valid JSON: { e } " ) from e
63- if not isinstance (loaded , dict ):
64- raise RuntimeError ("feature_flags.json root must be a JSON object mapping feature -> (bool | object)" )
65-
66- self ._features : Dict [str , bool ] = {}
67- self ._feature_metadata : Dict [str , Dict [str , Any ]] = {}
68-
69- for raw_key , raw_val in loaded .items ():
70- # Enforce key type and non-empty constraint
71- if not isinstance (raw_key , str ):
72- raise RuntimeError (f"feature_flags.json key '{ raw_key } ' is not a string" )
73- key = raw_key .strip ().lower ()
74- if not key :
75- raise RuntimeError ("feature_flags.json contains an empty feature name" )
76- # Object form with strict schema: { "default": bool, "description": str }
77- if isinstance (raw_val , dict ):
78- required_keys = {"default" , "description" }
79- unknown = set (raw_val .keys ()) - required_keys
80- if unknown :
81- raise RuntimeError (
82- f"Feature '{ raw_key } ' has unknown metadata keys: { ', ' .join (sorted (unknown ))} . Allowed: default, description"
83- )
84- missing = required_keys - set (raw_val .keys ())
85- if missing :
86- raise RuntimeError (
87- f"Feature '{ raw_key } ' object missing required key(s): { ', ' .join (sorted (missing ))} (requires: default, description)"
88- )
89- if not isinstance (raw_val ["default" ], bool ):
90- raise RuntimeError (f"Feature '{ raw_key } ' field 'default' must be boolean" )
91- desc = raw_val ["description" ]
92- if not isinstance (desc , str ) or not desc .strip ():
93- raise RuntimeError (f"Feature '{ raw_key } ' field 'description' must be a non-empty string" )
94- self ._features [key ] = raw_val ["default" ]
95- self ._feature_metadata [key ] = {"description" : desc .strip ()}
96- continue
97- # Any other type is invalid
98- raise RuntimeError (
99- f"Feature '{ raw_key } ' must be an object with 'default' (bool) and required 'description' (non-empty str)"
100- )
101-
102- # Overlay user overrides (if supplied). Overrides must:
103- # - use existing feature names declared in feature_flags.json
104- # - provide boolean values only (no coercion of truthy/falsy non-bools)
105- # - use non-empty string keys
106- if isinstance (feature_flags , dict ):
107- for k , v in feature_flags .items ():
108- if not isinstance (k , str ) or not k .strip ():
109- raise ValueError ("feature_flags override keys must be non-empty strings" )
110- norm = k .strip ().lower ()
111- if norm not in self ._features :
112- raise ValueError (
113- f"Unknown feature flag override '{ k } ' (not declared in feature_flags.json)"
114- )
115- if not isinstance (v , bool ):
116- raise ValueError (
117- f"Override value for feature '{ k } ' must be boolean (got { type (v ).__name__ } )"
118- )
119- self ._features [norm ] = v
12053
12154 def _headers (self ) -> Dict [str , str ]:
12255 """Build standard OData headers with bearer auth."""
@@ -845,6 +778,10 @@ def _optionset_map(self, entity_set: str, attr_logical: str) -> Optional[Dict[st
845778
846779 Returns empty dict if attribute is not a picklist or has no options. Returns None only
847780 for invalid inputs or unexpected metadata parse failures.
781+
782+ Notes
783+ -----
784+ - This method calls the Web API twice per attribute so it could have perf impact when there are lots of columns on the entity.
848785 """
849786 if not entity_set or not attr_logical :
850787 return None
@@ -944,9 +881,6 @@ def _convert_labels_to_ints(self, entity_set: str, record: Dict[str, Any]) -> Di
944881 Heuristic: For each string value, attempt to resolve against picklist metadata.
945882 If attribute isn't a picklist or label not found, value left unchanged.
946883 """
947- # Fast-path: feature disabled (default). Return original record without copy to avoid overhead.
948- if not self .is_feature_enabled ("option_set_label_conversion" ):
949- return record
950884 out = record .copy ()
951885 for k , v in list (out .items ()):
952886 if not isinstance (v , str ) or not v .strip ():
@@ -1150,21 +1084,4 @@ def _flush_cache(
11501084
11511085 removed = len (self ._picklist_label_cache )
11521086 self ._picklist_label_cache .clear ()
1153- return removed
1154-
1155- # ---------------------- Feature flags / toggles --------------------
1156- def set_feature (self , name : str , enabled : bool ) -> None :
1157- if not isinstance (name , str ) or not name .strip ():
1158- raise ValueError ("Feature name must be a non-empty string" )
1159- self ._features [name .strip ().lower ()] = bool (enabled )
1160-
1161- def enable_feature (self , name : str ) -> None :
1162- self .set_feature (name , True )
1163-
1164- def disable_feature (self , name : str ) -> None :
1165- self .set_feature (name , False )
1166-
1167- def is_feature_enabled (self , name : str ) -> bool :
1168- if not isinstance (name , str ):
1169- return False
1170- return bool (self ._features .get (name .strip ().lower ()))
1087+ return removed
0 commit comments