Module connpy.services.profile_service
Classes
class ProfileService (config=None)-
Expand source code
class ProfileService(BaseService): """Business logic for node profiles management.""" def list_profiles(self, filter_str=None): """List all profile names, optionally filtered.""" profiles = list(self.config.profiles.keys()) case_sensitive = self.config.config.get("case", False) if filter_str: if not case_sensitive: f_str = filter_str.lower() return [p for p in profiles if f_str in p.lower()] else: return [p for p in profiles if filter_str in p] return profiles def get_profile(self, name, resolve=True): """Get the profile dictionary, optionally resolved.""" profile = self.config.profiles.get(name) if not profile: raise ProfileNotFoundError(f"Profile '{name}' not found.") if resolve: return self.resolve_node_data(profile) return profile def add_profile(self, name, data): """Add a new profile.""" if name in self.config.profiles: raise ProfileAlreadyExistsError(f"Profile '{name}' already exists.") # Filter data to match _profiles_add signature and ensure id is passed allowed_keys = {"host", "options", "logs", "password", "port", "protocol", "user", "tags", "jumphost"} filtered_data = {k: v for k, v in data.items() if k in allowed_keys} self.config._profiles_add(id=name, **filtered_data) self.config._saveconfig(self.config.file) def resolve_node_data(self, node_data): """Resolve profile references (@profile) in node data and handle inheritance.""" resolved = node_data.copy() # 1. Identify all referenced profiles to support inheritance referenced_profiles = [] for value in resolved.values(): if isinstance(value, str) and value.startswith("@"): referenced_profiles.append(value[1:]) elif isinstance(value, list): for item in value: if isinstance(item, str) and item.startswith("@"): referenced_profiles.append(item[1:]) # 2. Resolve explicit references for key, value in resolved.items(): if isinstance(value, str) and value.startswith("@"): profile_name = value[1:] try: profile = self.get_profile(profile_name, resolve=True) resolved[key] = profile.get(key, "") except ProfileNotFoundError: resolved[key] = "" elif isinstance(value, list): resolved_list = [] for item in value: if isinstance(item, str) and item.startswith("@"): profile_name = item[1:] try: profile = self.get_profile(profile_name, resolve=True) if "password" in profile: resolved_list.append(profile["password"]) except ProfileNotFoundError: pass else: resolved_list.append(item) resolved[key] = resolved_list # 3. Inheritance: Fill empty keys from the first referenced profile if referenced_profiles: base_profile_name = referenced_profiles[0] try: base_profile = self.get_profile(base_profile_name, resolve=True) for key, value in base_profile.items(): # Fill if key is missing or empty if key not in resolved or resolved[key] == "" or resolved[key] == [] or resolved[key] is None: resolved[key] = value except ProfileNotFoundError: pass # 4. Handle default protocol if resolved.get("protocol") == "" or resolved.get("protocol") is None: try: default_profile = self.get_profile("default", resolve=True) resolved["protocol"] = default_profile.get("protocol", "ssh") except ProfileNotFoundError: resolved["protocol"] = "ssh" return resolved def delete_profile(self, name): """Delete an existing profile, with safety checks.""" if name not in self.config.profiles: raise ProfileNotFoundError(f"Profile '{name}' not found.") if name == "default": raise InvalidConfigurationError("Cannot delete the 'default' profile.") used_by = self.config._profileused(name) if used_by: # We return the list of nodes using it so the UI can inform the user raise InvalidConfigurationError(f"Profile '{name}' is used by nodes: {', '.join(used_by)}") self.config._profiles_del(id=name) self.config._saveconfig(self.config.file) def update_profile(self, name, data): """Update an existing profile.""" if name not in self.config.profiles: raise ProfileNotFoundError(f"Profile '{name}' not found.") # Merge with existing data existing = self.get_profile(name, resolve=False) updated_data = existing.copy() updated_data.update(data) # Filter data to match _profiles_add signature allowed_keys = {"host", "options", "logs", "password", "port", "protocol", "user", "tags", "jumphost"} filtered_data = {k: v for k, v in updated_data.items() if k in allowed_keys} self.config._profiles_add(id=name, **filtered_data) self.config._saveconfig(self.config.file)Business logic for node profiles management.
Initialize the service.
Args
config- An instance of configfile (or None to instantiate a new one/use global context).
Ancestors
Methods
def add_profile(self, name, data)-
Expand source code
def add_profile(self, name, data): """Add a new profile.""" if name in self.config.profiles: raise ProfileAlreadyExistsError(f"Profile '{name}' already exists.") # Filter data to match _profiles_add signature and ensure id is passed allowed_keys = {"host", "options", "logs", "password", "port", "protocol", "user", "tags", "jumphost"} filtered_data = {k: v for k, v in data.items() if k in allowed_keys} self.config._profiles_add(id=name, **filtered_data) self.config._saveconfig(self.config.file)Add a new profile.
def delete_profile(self, name)-
Expand source code
def delete_profile(self, name): """Delete an existing profile, with safety checks.""" if name not in self.config.profiles: raise ProfileNotFoundError(f"Profile '{name}' not found.") if name == "default": raise InvalidConfigurationError("Cannot delete the 'default' profile.") used_by = self.config._profileused(name) if used_by: # We return the list of nodes using it so the UI can inform the user raise InvalidConfigurationError(f"Profile '{name}' is used by nodes: {', '.join(used_by)}") self.config._profiles_del(id=name) self.config._saveconfig(self.config.file)Delete an existing profile, with safety checks.
def get_profile(self, name, resolve=True)-
Expand source code
def get_profile(self, name, resolve=True): """Get the profile dictionary, optionally resolved.""" profile = self.config.profiles.get(name) if not profile: raise ProfileNotFoundError(f"Profile '{name}' not found.") if resolve: return self.resolve_node_data(profile) return profileGet the profile dictionary, optionally resolved.
def list_profiles(self, filter_str=None)-
Expand source code
def list_profiles(self, filter_str=None): """List all profile names, optionally filtered.""" profiles = list(self.config.profiles.keys()) case_sensitive = self.config.config.get("case", False) if filter_str: if not case_sensitive: f_str = filter_str.lower() return [p for p in profiles if f_str in p.lower()] else: return [p for p in profiles if filter_str in p] return profilesList all profile names, optionally filtered.
def resolve_node_data(self, node_data)-
Expand source code
def resolve_node_data(self, node_data): """Resolve profile references (@profile) in node data and handle inheritance.""" resolved = node_data.copy() # 1. Identify all referenced profiles to support inheritance referenced_profiles = [] for value in resolved.values(): if isinstance(value, str) and value.startswith("@"): referenced_profiles.append(value[1:]) elif isinstance(value, list): for item in value: if isinstance(item, str) and item.startswith("@"): referenced_profiles.append(item[1:]) # 2. Resolve explicit references for key, value in resolved.items(): if isinstance(value, str) and value.startswith("@"): profile_name = value[1:] try: profile = self.get_profile(profile_name, resolve=True) resolved[key] = profile.get(key, "") except ProfileNotFoundError: resolved[key] = "" elif isinstance(value, list): resolved_list = [] for item in value: if isinstance(item, str) and item.startswith("@"): profile_name = item[1:] try: profile = self.get_profile(profile_name, resolve=True) if "password" in profile: resolved_list.append(profile["password"]) except ProfileNotFoundError: pass else: resolved_list.append(item) resolved[key] = resolved_list # 3. Inheritance: Fill empty keys from the first referenced profile if referenced_profiles: base_profile_name = referenced_profiles[0] try: base_profile = self.get_profile(base_profile_name, resolve=True) for key, value in base_profile.items(): # Fill if key is missing or empty if key not in resolved or resolved[key] == "" or resolved[key] == [] or resolved[key] is None: resolved[key] = value except ProfileNotFoundError: pass # 4. Handle default protocol if resolved.get("protocol") == "" or resolved.get("protocol") is None: try: default_profile = self.get_profile("default", resolve=True) resolved["protocol"] = default_profile.get("protocol", "ssh") except ProfileNotFoundError: resolved["protocol"] = "ssh" return resolvedResolve profile references (@profile) in node data and handle inheritance.
def update_profile(self, name, data)-
Expand source code
def update_profile(self, name, data): """Update an existing profile.""" if name not in self.config.profiles: raise ProfileNotFoundError(f"Profile '{name}' not found.") # Merge with existing data existing = self.get_profile(name, resolve=False) updated_data = existing.copy() updated_data.update(data) # Filter data to match _profiles_add signature allowed_keys = {"host", "options", "logs", "password", "port", "protocol", "user", "tags", "jumphost"} filtered_data = {k: v for k, v in updated_data.items() if k in allowed_keys} self.config._profiles_add(id=name, **filtered_data) self.config._saveconfig(self.config.file)Update an existing profile.
Inherited members