heaven_base.registry.simple_registry_service

  1import os
  2import json
  3import re
  4from typing import Dict, Any, Optional, List
  5from ..utils.get_env_value import EnvConfigUtil
  6
  7
  8# Registry pointer patterns  
  9REF_KEY_PATTERN = re.compile(r'^registry_key_ref=([^:]+):(.+)$')
 10REF_OBJ_PATTERN = re.compile(r'^registry_object_ref=([^:]+):(.+)$')
 11REF_ALL_PATTERN = re.compile(r'^registry_all_ref=([^:]+)$')
 12MAX_REF_DEPTH = 99
 13
 14
 15class SimpleRegistryService:
 16    """Simplified registry service - just JSON CRUD operations with pointer resolution."""
 17    
 18    def __init__(self, registry_dir: str = None):
 19        """Initialize the SimpleRegistryService.
 20        
 21        Args:
 22            registry_dir: Directory containing registry JSON files (defaults to HEAVEN_DATA_DIR/registry)
 23        """
 24        if registry_dir is None:
 25            registry_dir = os.path.join(EnvConfigUtil.get_heaven_data_dir(), 'registry')
 26        self.registry_dir = registry_dir
 27        os.makedirs(self.registry_dir, exist_ok=True)
 28    
 29    def _get_registry_path(self, registry_name: str) -> str:
 30        """Get the file path for a registry with dual-lookup (user dir first, then library)."""
 31        # Check if name ends with _registry, if not add it
 32        if registry_name.endswith('_registry'):
 33            filename = f"{registry_name}.json"
 34        else:
 35            filename = f"{registry_name}_registry.json"
 36        
 37        # First check user's HEAVEN_DATA_DIR/registry/
 38        user_path = os.path.join(self.registry_dir, filename)
 39        if os.path.exists(user_path):
 40            return user_path
 41            
 42        # Then check library-level heaven_base/registry/
 43        try:
 44            import heaven_base
 45            library_registry_dir = os.path.join(os.path.dirname(heaven_base.__file__), 'registry')
 46            library_path = os.path.join(library_registry_dir, filename)
 47            if os.path.exists(library_path):
 48                return library_path
 49        except ImportError:
 50            pass
 51            
 52        # Default to user path (for creation/writing)
 53        return user_path
 54    
 55    def _load_registry_data(self, registry_name: str) -> Dict[str, Any]:
 56        """Load registry data from JSON file."""
 57        registry_path = self._get_registry_path(registry_name)
 58        if not os.path.exists(registry_path):
 59            return {}
 60        
 61        try:
 62            with open(registry_path, 'r') as f:
 63                return json.load(f)
 64        except (json.JSONDecodeError, IOError):
 65            return {}
 66    
 67    def _save_registry_data(self, registry_name: str, data: Dict[str, Any]) -> None:
 68        """Save registry data to JSON file."""
 69        registry_path = self._get_registry_path(registry_name)
 70        with open(registry_path, 'w') as f:
 71            json.dump(data, f, indent=2)
 72    
 73    def _resolve_if_pointer(self, value: Any, *, _seen: set = None) -> Any:
 74        """Resolve registry-pointer strings.
 75        
 76        • registry_key_ref=<registry>:<key> → returns the locator string "@<registry>/<key>"
 77        • registry_object_ref=<registry>:<key> → opens that registry, fetches the value, then continues resolving
 78        
 79        Cycles are detected; depth is capped at MAX_REF_DEPTH.
 80        """
 81        if not isinstance(value, str):
 82            return value
 83        
 84        _seen = _seen or set()
 85        ref = value.strip()
 86        
 87        if ref in _seen:
 88            raise ValueError(f"Cyclic registry reference detected: {ref}")
 89        if len(_seen) >= MAX_REF_DEPTH:
 90            raise ValueError("Max registry-reference depth exceeded")
 91        
 92        # registry_key_ref → locator string only
 93        m = REF_KEY_PATTERN.match(ref)
 94        if m:
 95            registry_name, key = m.groups()
 96            return f"@{registry_name}/{key}"
 97        
 98        # registry_object_ref → fetch value, then recurse
 99        m = REF_OBJ_PATTERN.match(ref)
100        if m:
101            registry_name, key = m.groups()
102            _seen.add(ref)
103            obj = self.get(registry_name, key)  # may itself be a pointer
104            return self._resolve_if_pointer(obj, _seen=_seen)
105        
106        # registry_all_ref → fetch entire registry contents
107        m = REF_ALL_PATTERN.match(ref)
108        if m:
109            registry_name = m.group(1)
110            _seen.add(ref)
111            all_data = self.get_all(registry_name)  # may contain pointers that get resolved
112            return all_data
113        
114        # not a recognised pointer
115        return value
116    
117    def get(self, registry_name: str, key: str) -> Optional[Any]:
118        """Get an item from a registry with pointer resolution."""
119        data = self._load_registry_data(registry_name)
120        raw_value = data.get(key)
121        return self._resolve_if_pointer(raw_value)
122    
123    def get_all(self, registry_name: str) -> Dict[str, Any]:
124        """Get all items in a registry with pointer resolution."""
125        data = self._load_registry_data(registry_name)
126        return {k: self._resolve_if_pointer(v) for k, v in data.items()}
127    
128    def get_raw(self, registry_name: str, key: str) -> Optional[Any]:
129        """Get raw value without pointer resolution (useful for editing pointers)."""
130        data = self._load_registry_data(registry_name)
131        return data.get(key)
132    
133    def get_all_raw(self, registry_name: str) -> Dict[str, Any]:
134        """Get all raw values without pointer resolution."""
135        return self._load_registry_data(registry_name)
136    
137    def set(self, registry_name: str, key: str, value: Any) -> None:
138        """Set an item in a registry."""
139        data = self._load_registry_data(registry_name)
140        data[key] = value
141        self._save_registry_data(registry_name, data)
142    
143    def delete(self, registry_name: str, key: str) -> bool:
144        """Delete an item from a registry."""
145        data = self._load_registry_data(registry_name)
146        if key not in data:
147            return False
148        
149        del data[key]
150        self._save_registry_data(registry_name, data)
151        return True
152    
153    def update(self, registry_name: str, key: str, value: Any) -> bool:
154        """Update an item with pointer validation."""
155        data = self._load_registry_data(registry_name)
156        current_raw = data.get(key)
157        
158        # Prevent updating pointers (same logic as original)
159        if isinstance(current_raw, str) and current_raw.startswith(
160            ("registry_key_ref=", "registry_object_ref=")
161        ):
162            if current_raw.startswith("registry_key_ref="):
163                _, payload = current_raw.split("=", 1)
164                target_locator = f"@{payload.replace(':', '/')}"
165            else:  # registry_object_ref
166                _, payload = current_raw.split("=", 1)
167                target_registry, target_key = payload.split(":", 1)
168                target_locator = f"@{target_registry}/{target_key}"
169            
170            raise ValueError(
171                f"Cannot update '{registry_name}:{key}'—it is a pointer to "
172                f"'{target_locator}'.\n"
173                "Update the referenced registry/key instead, or replace the "
174                "pointer string explicitly if you intend to repoint it."
175            )
176        
177        if key not in data:
178            return False
179        
180        data[key] = value
181        self._save_registry_data(registry_name, data)
182        return True
183    
184    def add(self, registry_name: str, key: str, value: Any) -> bool:
185        """Add an item to a registry (fails if key already exists)."""
186        data = self._load_registry_data(registry_name)
187        if key in data:
188            return False
189        
190        data[key] = value
191        self._save_registry_data(registry_name, data)
192        return True
193    
194    def list_keys(self, registry_name: str) -> List[str]:
195        """List all keys in a registry."""
196        data = self._load_registry_data(registry_name)
197        return list(data.keys())
198    
199    def list_registries(self) -> List[str]:
200        """List all available registries."""
201        registries = []
202        if not os.path.exists(self.registry_dir):
203            return registries
204        
205        for filename in os.listdir(self.registry_dir):
206            if filename.endswith('_registry.json'):
207                # brain_personas_registry.json -> brain_personas
208                registries.append(filename[:-14])  # Remove _registry.json extension
209            elif filename.endswith('.json'):
210                # Handle files that don't have _registry suffix
211                base_name = filename[:-5]  # Remove .json
212                if base_name.endswith('_registry'):
213                    # This is already a _registry file, extract base name
214                    registries.append(base_name[:-9])  # Remove _registry suffix
215                else:
216                    # This shouldn't happen with our naming convention, but handle it
217                    registries.append(base_name)
218        return sorted(registries)
219    
220    def create_registry(self, registry_name: str) -> bool:
221        """Create a new empty registry."""
222        registry_path = self._get_registry_path(registry_name)
223        if os.path.exists(registry_path):
224            return False
225        
226        self._save_registry_data(registry_name, {})
227        return True
228    
229    def registry_exists(self, registry_name: str) -> bool:
230        """Check if a registry exists."""
231        registry_path = self._get_registry_path(registry_name)
232        return os.path.exists(registry_path)
REF_KEY_PATTERN = re.compile('^registry_key_ref=([^:]+):(.+)$')
REF_OBJ_PATTERN = re.compile('^registry_object_ref=([^:]+):(.+)$')
REF_ALL_PATTERN = re.compile('^registry_all_ref=([^:]+)$')
MAX_REF_DEPTH = 99
class SimpleRegistryService:
 16class SimpleRegistryService:
 17    """Simplified registry service - just JSON CRUD operations with pointer resolution."""
 18    
 19    def __init__(self, registry_dir: str = None):
 20        """Initialize the SimpleRegistryService.
 21        
 22        Args:
 23            registry_dir: Directory containing registry JSON files (defaults to HEAVEN_DATA_DIR/registry)
 24        """
 25        if registry_dir is None:
 26            registry_dir = os.path.join(EnvConfigUtil.get_heaven_data_dir(), 'registry')
 27        self.registry_dir = registry_dir
 28        os.makedirs(self.registry_dir, exist_ok=True)
 29    
 30    def _get_registry_path(self, registry_name: str) -> str:
 31        """Get the file path for a registry with dual-lookup (user dir first, then library)."""
 32        # Check if name ends with _registry, if not add it
 33        if registry_name.endswith('_registry'):
 34            filename = f"{registry_name}.json"
 35        else:
 36            filename = f"{registry_name}_registry.json"
 37        
 38        # First check user's HEAVEN_DATA_DIR/registry/
 39        user_path = os.path.join(self.registry_dir, filename)
 40        if os.path.exists(user_path):
 41            return user_path
 42            
 43        # Then check library-level heaven_base/registry/
 44        try:
 45            import heaven_base
 46            library_registry_dir = os.path.join(os.path.dirname(heaven_base.__file__), 'registry')
 47            library_path = os.path.join(library_registry_dir, filename)
 48            if os.path.exists(library_path):
 49                return library_path
 50        except ImportError:
 51            pass
 52            
 53        # Default to user path (for creation/writing)
 54        return user_path
 55    
 56    def _load_registry_data(self, registry_name: str) -> Dict[str, Any]:
 57        """Load registry data from JSON file."""
 58        registry_path = self._get_registry_path(registry_name)
 59        if not os.path.exists(registry_path):
 60            return {}
 61        
 62        try:
 63            with open(registry_path, 'r') as f:
 64                return json.load(f)
 65        except (json.JSONDecodeError, IOError):
 66            return {}
 67    
 68    def _save_registry_data(self, registry_name: str, data: Dict[str, Any]) -> None:
 69        """Save registry data to JSON file."""
 70        registry_path = self._get_registry_path(registry_name)
 71        with open(registry_path, 'w') as f:
 72            json.dump(data, f, indent=2)
 73    
 74    def _resolve_if_pointer(self, value: Any, *, _seen: set = None) -> Any:
 75        """Resolve registry-pointer strings.
 76        
 77        • registry_key_ref=<registry>:<key> → returns the locator string "@<registry>/<key>"
 78        • registry_object_ref=<registry>:<key> → opens that registry, fetches the value, then continues resolving
 79        
 80        Cycles are detected; depth is capped at MAX_REF_DEPTH.
 81        """
 82        if not isinstance(value, str):
 83            return value
 84        
 85        _seen = _seen or set()
 86        ref = value.strip()
 87        
 88        if ref in _seen:
 89            raise ValueError(f"Cyclic registry reference detected: {ref}")
 90        if len(_seen) >= MAX_REF_DEPTH:
 91            raise ValueError("Max registry-reference depth exceeded")
 92        
 93        # registry_key_ref → locator string only
 94        m = REF_KEY_PATTERN.match(ref)
 95        if m:
 96            registry_name, key = m.groups()
 97            return f"@{registry_name}/{key}"
 98        
 99        # registry_object_ref → fetch value, then recurse
100        m = REF_OBJ_PATTERN.match(ref)
101        if m:
102            registry_name, key = m.groups()
103            _seen.add(ref)
104            obj = self.get(registry_name, key)  # may itself be a pointer
105            return self._resolve_if_pointer(obj, _seen=_seen)
106        
107        # registry_all_ref → fetch entire registry contents
108        m = REF_ALL_PATTERN.match(ref)
109        if m:
110            registry_name = m.group(1)
111            _seen.add(ref)
112            all_data = self.get_all(registry_name)  # may contain pointers that get resolved
113            return all_data
114        
115        # not a recognised pointer
116        return value
117    
118    def get(self, registry_name: str, key: str) -> Optional[Any]:
119        """Get an item from a registry with pointer resolution."""
120        data = self._load_registry_data(registry_name)
121        raw_value = data.get(key)
122        return self._resolve_if_pointer(raw_value)
123    
124    def get_all(self, registry_name: str) -> Dict[str, Any]:
125        """Get all items in a registry with pointer resolution."""
126        data = self._load_registry_data(registry_name)
127        return {k: self._resolve_if_pointer(v) for k, v in data.items()}
128    
129    def get_raw(self, registry_name: str, key: str) -> Optional[Any]:
130        """Get raw value without pointer resolution (useful for editing pointers)."""
131        data = self._load_registry_data(registry_name)
132        return data.get(key)
133    
134    def get_all_raw(self, registry_name: str) -> Dict[str, Any]:
135        """Get all raw values without pointer resolution."""
136        return self._load_registry_data(registry_name)
137    
138    def set(self, registry_name: str, key: str, value: Any) -> None:
139        """Set an item in a registry."""
140        data = self._load_registry_data(registry_name)
141        data[key] = value
142        self._save_registry_data(registry_name, data)
143    
144    def delete(self, registry_name: str, key: str) -> bool:
145        """Delete an item from a registry."""
146        data = self._load_registry_data(registry_name)
147        if key not in data:
148            return False
149        
150        del data[key]
151        self._save_registry_data(registry_name, data)
152        return True
153    
154    def update(self, registry_name: str, key: str, value: Any) -> bool:
155        """Update an item with pointer validation."""
156        data = self._load_registry_data(registry_name)
157        current_raw = data.get(key)
158        
159        # Prevent updating pointers (same logic as original)
160        if isinstance(current_raw, str) and current_raw.startswith(
161            ("registry_key_ref=", "registry_object_ref=")
162        ):
163            if current_raw.startswith("registry_key_ref="):
164                _, payload = current_raw.split("=", 1)
165                target_locator = f"@{payload.replace(':', '/')}"
166            else:  # registry_object_ref
167                _, payload = current_raw.split("=", 1)
168                target_registry, target_key = payload.split(":", 1)
169                target_locator = f"@{target_registry}/{target_key}"
170            
171            raise ValueError(
172                f"Cannot update '{registry_name}:{key}'—it is a pointer to "
173                f"'{target_locator}'.\n"
174                "Update the referenced registry/key instead, or replace the "
175                "pointer string explicitly if you intend to repoint it."
176            )
177        
178        if key not in data:
179            return False
180        
181        data[key] = value
182        self._save_registry_data(registry_name, data)
183        return True
184    
185    def add(self, registry_name: str, key: str, value: Any) -> bool:
186        """Add an item to a registry (fails if key already exists)."""
187        data = self._load_registry_data(registry_name)
188        if key in data:
189            return False
190        
191        data[key] = value
192        self._save_registry_data(registry_name, data)
193        return True
194    
195    def list_keys(self, registry_name: str) -> List[str]:
196        """List all keys in a registry."""
197        data = self._load_registry_data(registry_name)
198        return list(data.keys())
199    
200    def list_registries(self) -> List[str]:
201        """List all available registries."""
202        registries = []
203        if not os.path.exists(self.registry_dir):
204            return registries
205        
206        for filename in os.listdir(self.registry_dir):
207            if filename.endswith('_registry.json'):
208                # brain_personas_registry.json -> brain_personas
209                registries.append(filename[:-14])  # Remove _registry.json extension
210            elif filename.endswith('.json'):
211                # Handle files that don't have _registry suffix
212                base_name = filename[:-5]  # Remove .json
213                if base_name.endswith('_registry'):
214                    # This is already a _registry file, extract base name
215                    registries.append(base_name[:-9])  # Remove _registry suffix
216                else:
217                    # This shouldn't happen with our naming convention, but handle it
218                    registries.append(base_name)
219        return sorted(registries)
220    
221    def create_registry(self, registry_name: str) -> bool:
222        """Create a new empty registry."""
223        registry_path = self._get_registry_path(registry_name)
224        if os.path.exists(registry_path):
225            return False
226        
227        self._save_registry_data(registry_name, {})
228        return True
229    
230    def registry_exists(self, registry_name: str) -> bool:
231        """Check if a registry exists."""
232        registry_path = self._get_registry_path(registry_name)
233        return os.path.exists(registry_path)

Simplified registry service - just JSON CRUD operations with pointer resolution.

SimpleRegistryService(registry_dir: str = None)
19    def __init__(self, registry_dir: str = None):
20        """Initialize the SimpleRegistryService.
21        
22        Args:
23            registry_dir: Directory containing registry JSON files (defaults to HEAVEN_DATA_DIR/registry)
24        """
25        if registry_dir is None:
26            registry_dir = os.path.join(EnvConfigUtil.get_heaven_data_dir(), 'registry')
27        self.registry_dir = registry_dir
28        os.makedirs(self.registry_dir, exist_ok=True)

Initialize the SimpleRegistryService.

Args: registry_dir: Directory containing registry JSON files (defaults to HEAVEN_DATA_DIR/registry)

registry_dir
def get(self, registry_name: str, key: str) -> Optional[Any]:
118    def get(self, registry_name: str, key: str) -> Optional[Any]:
119        """Get an item from a registry with pointer resolution."""
120        data = self._load_registry_data(registry_name)
121        raw_value = data.get(key)
122        return self._resolve_if_pointer(raw_value)

Get an item from a registry with pointer resolution.

def get_all(self, registry_name: str) -> Dict[str, Any]:
124    def get_all(self, registry_name: str) -> Dict[str, Any]:
125        """Get all items in a registry with pointer resolution."""
126        data = self._load_registry_data(registry_name)
127        return {k: self._resolve_if_pointer(v) for k, v in data.items()}

Get all items in a registry with pointer resolution.

def get_raw(self, registry_name: str, key: str) -> Optional[Any]:
129    def get_raw(self, registry_name: str, key: str) -> Optional[Any]:
130        """Get raw value without pointer resolution (useful for editing pointers)."""
131        data = self._load_registry_data(registry_name)
132        return data.get(key)

Get raw value without pointer resolution (useful for editing pointers).

def get_all_raw(self, registry_name: str) -> Dict[str, Any]:
134    def get_all_raw(self, registry_name: str) -> Dict[str, Any]:
135        """Get all raw values without pointer resolution."""
136        return self._load_registry_data(registry_name)

Get all raw values without pointer resolution.

def set(self, registry_name: str, key: str, value: Any) -> None:
138    def set(self, registry_name: str, key: str, value: Any) -> None:
139        """Set an item in a registry."""
140        data = self._load_registry_data(registry_name)
141        data[key] = value
142        self._save_registry_data(registry_name, data)

Set an item in a registry.

def delete(self, registry_name: str, key: str) -> bool:
144    def delete(self, registry_name: str, key: str) -> bool:
145        """Delete an item from a registry."""
146        data = self._load_registry_data(registry_name)
147        if key not in data:
148            return False
149        
150        del data[key]
151        self._save_registry_data(registry_name, data)
152        return True

Delete an item from a registry.

def update(self, registry_name: str, key: str, value: Any) -> bool:
154    def update(self, registry_name: str, key: str, value: Any) -> bool:
155        """Update an item with pointer validation."""
156        data = self._load_registry_data(registry_name)
157        current_raw = data.get(key)
158        
159        # Prevent updating pointers (same logic as original)
160        if isinstance(current_raw, str) and current_raw.startswith(
161            ("registry_key_ref=", "registry_object_ref=")
162        ):
163            if current_raw.startswith("registry_key_ref="):
164                _, payload = current_raw.split("=", 1)
165                target_locator = f"@{payload.replace(':', '/')}"
166            else:  # registry_object_ref
167                _, payload = current_raw.split("=", 1)
168                target_registry, target_key = payload.split(":", 1)
169                target_locator = f"@{target_registry}/{target_key}"
170            
171            raise ValueError(
172                f"Cannot update '{registry_name}:{key}'—it is a pointer to "
173                f"'{target_locator}'.\n"
174                "Update the referenced registry/key instead, or replace the "
175                "pointer string explicitly if you intend to repoint it."
176            )
177        
178        if key not in data:
179            return False
180        
181        data[key] = value
182        self._save_registry_data(registry_name, data)
183        return True

Update an item with pointer validation.

def add(self, registry_name: str, key: str, value: Any) -> bool:
185    def add(self, registry_name: str, key: str, value: Any) -> bool:
186        """Add an item to a registry (fails if key already exists)."""
187        data = self._load_registry_data(registry_name)
188        if key in data:
189            return False
190        
191        data[key] = value
192        self._save_registry_data(registry_name, data)
193        return True

Add an item to a registry (fails if key already exists).

def list_keys(self, registry_name: str) -> List[str]:
195    def list_keys(self, registry_name: str) -> List[str]:
196        """List all keys in a registry."""
197        data = self._load_registry_data(registry_name)
198        return list(data.keys())

List all keys in a registry.

def list_registries(self) -> List[str]:
200    def list_registries(self) -> List[str]:
201        """List all available registries."""
202        registries = []
203        if not os.path.exists(self.registry_dir):
204            return registries
205        
206        for filename in os.listdir(self.registry_dir):
207            if filename.endswith('_registry.json'):
208                # brain_personas_registry.json -> brain_personas
209                registries.append(filename[:-14])  # Remove _registry.json extension
210            elif filename.endswith('.json'):
211                # Handle files that don't have _registry suffix
212                base_name = filename[:-5]  # Remove .json
213                if base_name.endswith('_registry'):
214                    # This is already a _registry file, extract base name
215                    registries.append(base_name[:-9])  # Remove _registry suffix
216                else:
217                    # This shouldn't happen with our naming convention, but handle it
218                    registries.append(base_name)
219        return sorted(registries)

List all available registries.

def create_registry(self, registry_name: str) -> bool:
221    def create_registry(self, registry_name: str) -> bool:
222        """Create a new empty registry."""
223        registry_path = self._get_registry_path(registry_name)
224        if os.path.exists(registry_path):
225            return False
226        
227        self._save_registry_data(registry_name, {})
228        return True

Create a new empty registry.

def registry_exists(self, registry_name: str) -> bool:
230    def registry_exists(self, registry_name: str) -> bool:
231        """Check if a registry exists."""
232        registry_path = self._get_registry_path(registry_name)
233        return os.path.exists(registry_path)

Check if a registry exists.