🔍 Code Extractor

class MetadataCatalog

Maturity: 28

Helper class to manage FileCloud metadata sets and attributes. This class provides methods to work with FileCloud metadata by providing a more user-friendly interface on top of the raw API.

File:
/tf/active/vicechatdev/metadata_catalog.py
Lines:
9 - 1398
Complexity:
moderate

Purpose

Helper class to manage FileCloud metadata sets and attributes. This class provides methods to work with FileCloud metadata by providing a more user-friendly interface on top of the raw API.

Source Code

class MetadataCatalog:
    """
    Helper class to manage FileCloud metadata sets and attributes.
    
    This class provides methods to work with FileCloud metadata by
    providing a more user-friendly interface on top of the raw API.
    """
    
    def __init__(self, api_client: FileCloudAPI, debug: bool = False):
        """
        Initialize the metadata catalog.
        
        Args:
            api_client: Authenticated FileCloudAPI client
            debug: Enable detailed debug output
        """
        self.api = api_client
        self.sets = {}
        self.initialized = False
        self.debug = debug
    
    def _debug_print(self, message: str, data: Any = None):
        """
        Print debug information if debug mode is enabled.
        
        Args:
            message: Debug message
            data: Optional data to print
        """
        if self.debug:
            print(f"[MetadataCatalog Debug] {message}")
            if data is not None:
                if isinstance(data, dict) or isinstance(data, list):
                    try:
                        print(json.dumps(data, indent=2))
                    except:
                        print(data)
                else:
                    print(data)
    
    def _ensure_initialized(self):
        """
        Ensure the catalog is initialized.
        """
        if not self.initialized:
            self.initialize()
    
    def initialize(self):
        """
        Initialize the metadata catalog by loading all metadata sets and attributes.
        This version uses a single API call to get all metadata sets with their attributes.
        """
        self.metadata_sets = {}
        self.metadata_attributes = {}
        
        # Get all metadata sets in one API call
        result = self.api.get_metadata_sets()
        
        if not result.get('success'):
            logger.error(f"Failed to load metadata sets: {result.get('message', 'Unknown error')}")
            return False
        
        try:
            # Extract metadata sets from the response
            metadata_sets_data = result.get('data', {}).get('metadatasets', {}).get('metadataset', [])
            
            if not metadata_sets_data:
                logger.warning("No metadata sets found")
                return False
            
            # Process each metadata set
            for metadata_set in metadata_sets_data:
                set_id = metadata_set.get('id')
                set_name = metadata_set.get('name')
                
                if not set_id:
                    continue
                
                # Store metadata set information
                self.metadata_sets[set_id] = {
                    'id': set_id,
                    'name': set_name,
                    'description': metadata_set.get('description', ''),
                    'type': metadata_set.get('type', ''),
                    'attributes': {}  # Will store attributes for this set
                }
                
                # Process attributes for this set
                attributes_total = int(metadata_set.get('attributes_total', 0))
                
                for i in range(attributes_total):
                    # Extract attribute properties using the pattern in the response
                    attribute_id = metadata_set.get(f'attribute{i}_attributeid')
                    
                    if not attribute_id:
                        continue
                    
                    attribute_name = metadata_set.get(f'attribute{i}_name', '')
                    attribute_type = metadata_set.get(f'attribute{i}_type', '1')
                    attribute_description = metadata_set.get(f'attribute{i}_description', '')
                    attribute_default = metadata_set.get(f'attribute{i}_defaultvalue', '')
                    attribute_required = metadata_set.get(f'attribute{i}_required', '0') == '1'
                    attribute_disabled = metadata_set.get(f'attribute{i}_disabled', '0') == '1'
                    
                    # Process predefined values if any
                    predefined_values = []
                    predefined_values_total = int(metadata_set.get(f'attribute{i}_predefinedvalues_total', 0))
                    
                    for j in range(predefined_values_total):
                        predefined_value = metadata_set.get(f'attribute{i}_predefinedvalue{j}', '')
                        if predefined_value:
                            predefined_values.append(predefined_value)
                    
                    # Create attribute object
                    attribute = {
                        'id': attribute_id,
                        'name': attribute_name,
                        'description': attribute_description,
                        'type': self._get_attribute_type_name(attribute_type),
                        'type_id': attribute_type,
                        'default': attribute_default,
                        'required': attribute_required,
                        'disabled': attribute_disabled,
                        'predefined_values': predefined_values,
                        'set_id': set_id,
                        'set_name': set_name
                    }
                    
                    # Store in both collections
                    self.metadata_attributes[attribute_id] = attribute
                    self.metadata_sets[set_id]['attributes'][attribute_id] = attribute
                
            logger.info(f"Successfully loaded {len(self.metadata_sets)} metadata sets with {len(self.metadata_attributes)} attributes")
            self.initialized=True
            return True
            
        except Exception as e:
            logger.error(f"Error initializing metadata catalog: {e}")
            import traceback
            logger.error(traceback.format_exc())
            return False
    
    def _get_attribute_type_name(self, type_id):
        """Map attribute type ID to a readable name."""
        type_map = {
            '1': 'Text',
            '2': 'Integer',
            '3': 'Decimal',
            '4': 'Boolean',
            '5': 'Date',
            '6': 'Select',
            '7': 'Tags',
            '8': 'User',
            '9': 'Color'
        }
        return type_map.get(str(type_id), 'Unknown')
    

    def list_metadata_sets(self) -> List[Dict]:
        """
        List all available metadata sets.
        
        Returns:
            List[Dict]: List of metadata sets with their information
        """
        self._ensure_initialized()
        
        return [{"id": set_id, **set_info} for set_id, set_info in self.metadata_sets.items()]
    
    def list_attributes(self, set_id: str) -> List[Dict]:
        """
        List all attributes in a specific metadata set.
        
        Args:
            set_id: ID of the metadata set
            
        Returns:
            List[Dict]: List of attributes with their information
        """
        self._ensure_initialized()
        
        if set_id not in self.metadata_sets:
            return []
        
        attributes = self.metadata_sets[set_id].get('attributes', {})
        return [{"id": attr_id, **attr_info} for attr_id, attr_info in attributes.items()]
    
    def get_metadata_set_by_name(self, set_name: str) -> Optional[Dict]:
        """
        Get a metadata set by its name.
        
        Args:
            set_name: Name of the metadata set to find
            
        Returns:
            Optional[Dict]: Metadata set information or None if not found
        """
        self._ensure_initialized()
        
        for set_id, set_info in self.metadata_sets.items():
            if set_info.get('name') == set_name:
                return {
                    "id": set_id,
                    **set_info
                }
        
        return None
    
    def get_metadata_set_by_id(self, set_id: str) -> Optional[Dict]:
        """
        Get a metadata set by its ID.
        
        Args:
            set_id: ID of the metadata set to find
            
        Returns:
            Optional[Dict]: Metadata set information or None if not found
        """
        self._ensure_initialized()
        
        if set_id in self.metadata_sets:
            return {
                "id": set_id,
                **self.metadata_sets[set_id]
            }
        
        return None
    
    def get_attribute_by_name(self, set_id: str, attribute_name: str) -> Optional[Dict]:
        """
        Get a metadata attribute by its name within a specific set.
        
        Args:
            set_id: ID of the metadata set
            attribute_name: Name of the attribute to find
            
        Returns:
            Optional[Dict]: Attribute information or None if not found
        """
        self._ensure_initialized()
        
        if set_id not in self.metadata_sets:
            return None
        
        attributes = self.metadata_sets[set_id].get('attributes', {})
        for attr_id, attr_info in attributes.items():
            if attr_info.get('name') == attribute_name:
                return {
                    "id": attr_id,
                    **attr_info
                }
        
        return None
    
    def get_attribute_by_id(self, set_id: str, attribute_id: str) -> Optional[Dict]:
        """
        Get a metadata attribute by its ID within a specific set.
        
        Args:
            set_id: ID of the metadata set
            attribute_id: ID of the attribute to find
            
        Returns:
            Optional[Dict]: Attribute information or None if not found
        """
        self._ensure_initialized()
        
        if set_id not in self.metadata_sets:
            return None
        
        attributes = self.metadata_sets[set_id].get('attributes', {})
        if attribute_id in attributes:
            return {
                "id": attribute_id,
                **attributes[attribute_id]
            }
        
        return None
    
    def get_metadata_values(self, file_path: str) -> Dict:
        """
        Get metadata values for a specific file in a structured format.
        Supports multiple metadata sets per file.
        
        Args:
            file_path: Path to the file
            
        Returns:
            Dict: Structured metadata values with the following format:
                {
                    'success': bool,
                    'file_path': str,
                    'sets': [
                        {
                            'id': str,
                            'name': str,
                            'description': str,
                            'attributes': [
                                {
                                    'id': str,
                                    'name': str,
                                    'description': str,
                                    'type': str,
                                    'value': str,
                                    'required': bool,
                                    'disabled': bool
                                },
                                ...
                            ]
                        },
                        ...
                    ],
                    'raw_response': dict  # Original API response
                }
        """
        # Get raw metadata values from API
        raw_response = self.api.get_metadata_values(file_path)
        
        # Initialize result structure
        result = {
            'success': raw_response.get('success', False),
            'file_path': file_path,
            'sets': [],
            'raw_response': raw_response
        }
        
        # Return early if API call was not successful
        if not result['success']:
            return result
        
        try:
            # Extract metadata sets info and attributes
            metadata_values = raw_response.get('data', {}).get('metadatavalues', {})
            
            # Check if we have multiple sets or a single set
            metadata_sets = metadata_values.get('metadatasetvalue', [])
            if not isinstance(metadata_sets, list):
                metadata_sets = [metadata_sets]  # Convert to list for consistent processing
            
            # Process each metadata set
            for metadata_set in metadata_sets:
                if not metadata_set:
                    continue
                    
                # Extract metadata set information
                set_info = {
                    'id': metadata_set.get('id', ''),
                    'name': metadata_set.get('name', ''),
                    'description': metadata_set.get('description', ''),
                    'attributes': []
                }
                
                # Extract attributes
                attributes_total = int(metadata_set.get('attributes_total', 0))
                
                for i in range(attributes_total):
                    attribute = {
                        'id': metadata_set.get(f'attribute{i}_attributeid', ''),
                        'name': metadata_set.get(f'attribute{i}_name', ''),
                        'description': metadata_set.get(f'attribute{i}_description', ''),
                        'type': self._get_attribute_type_name(metadata_set.get(f'attribute{i}_datatype', '1')),
                        'type_id': metadata_set.get(f'attribute{i}_datatype', '1'),
                        'value': metadata_set.get(f'attribute{i}_value', ''),
                        'required': metadata_set.get(f'attribute{i}_required', '0') == '1',
                        'disabled': metadata_set.get(f'attribute{i}_disabled', '0') == '1'
                    }
                    set_info['attributes'].append(attribute)
                
                result['sets'].append(set_info)
                
            # For backward compatibility, add the first set as 'set' and its attributes as 'attributes'
            if result['sets']:
                result['set'] = result['sets'][0].copy()
                del result['set']['attributes']  # Remove attributes from the set copy
                result['attributes'] = result['sets'][0]['attributes']
            
        except Exception as e:
            logger.error(f"Error processing metadata response: {str(e)}")
            logger.error(traceback.format_exc())
            result['error'] = str(e)
        
        return result
    
    def get_attribute_by_name_from_meta(self, metadata: Dict, name: str) -> Optional[Dict]:
        """
        Get an attribute from metadata results by its name.
        
        Args:
            metadata: Metadata dictionary returned by get_metadata_values()
            name: Name of the attribute to find
            
        Returns:
            Dict or None: The attribute dictionary or None if not found
        """
        if not metadata or not isinstance(metadata, dict):
            return None
            
        attributes = metadata.get('attributes', [])
        
        for attr in attributes:
            if attr.get('name') == name:
                return attr
                
        return None
    
    def get_attributes_as_dict(self, metadata: Dict) -> Dict[str, Dict]:
        """
        Convert the attributes list to a dictionary keyed by attribute names.
        
        Args:
            metadata: Metadata dictionary returned by get_metadata_values()
            
        Returns:
            Dict: Dictionary of attributes with attribute names as keys
        """
        if not metadata or not isinstance(metadata, dict):
            return {}
            
        attributes = metadata.get('attributes', [])
        return {attr.get('name'): attr for attr in attributes if 'name' in attr}
    
    def get_attribute_values(self, metadata: Dict) -> Dict[str, str]:
        """
        Extract just the attribute names and values as a simple dictionary.
        Combines attributes from all metadata sets.
        
        Args:
            metadata: Metadata dictionary returned by get_metadata_values()
            
        Returns:
            Dict: Dictionary with attribute names as keys and their values as values
        """
        if not metadata or not isinstance(metadata, dict):
            return {}
        
        attributes = {}
        
        # Check if we're using the new multi-set format
        if 'sets' in metadata and isinstance(metadata['sets'], list):
            for set_info in metadata['sets']:
                for attr in set_info.get('attributes', []):
                    if 'name' in attr:
                        attributes[attr.get('name')] = attr.get('value', '')
        
        # Fallback to the old single-set format for backward compatibility
        elif 'attributes' in metadata and isinstance(metadata['attributes'], list):
            for attr in metadata['attributes']:
                if 'name' in attr:
                    attributes[attr.get('name')] = attr.get('value', '')
        
        return attributes
    
    def save_attribute_values(self, file_path: str, metadata: Dict) -> Dict:
        """
        Save metadata values for a specific file using structured format.
        
        Args:
            file_path: Path to the file
            metadata: Structured metadata values in the format returned by get_metadata_values.
                Must contain at least 'set' with 'id' and 'attributes' list with 'id' and 'value'.
                {
                    'set': {'id': 'set_id'},
                    'attributes': [
                        {'id': 'attr1_id', 'value': 'new_value1'},
                        {'id': 'attr2_id', 'value': 'new_value2'},
                        ...
                    ]
                }
            
        Returns:
            Dict: Response from the API with additional structured information
        """
        # Validate input
        if not isinstance(metadata, dict):
            return {'success': False, 'message': 'Invalid metadata format'}
        
        set_info = metadata.get('set', {})
        set_id = set_info.get('id')
        
        if not set_id:
            return {'success': False, 'message': 'Missing metadata set ID'}
        
        attributes = metadata.get('attributes', [])
        
        if not attributes:
            return {'success': False, 'message': 'No attributes provided'}
        
        # Convert the structured attributes to the format expected by FileCloud API
        attribute_values = {}
        for attr in attributes:
            attr_id = attr.get('id')
            attr_value = attr.get('value')
            
            if attr_id and attr_value is not None:  # Allow empty string values
                attribute_values[attr_id] = str(attr_value)
        
        # If no valid attribute values, return error
        if not attribute_values:
            return {'success': False, 'message': 'No valid attribute values provided'}
        
        # Call the API to save the attribute values
        api_response = self.api.save_attribute_values(file_path, set_id, attribute_values)
        
        # Return structured response
        return {
            'success': api_response.get('success', False),
            'file_path': file_path,
            'set_id': set_id,
            'updated_attributes': len(attribute_values),
            'message': api_response.get('message', ''),
            'raw_response': api_response
        }
    
    def save_attribute_values_by_name(self, file_path: str, set_name: str, attributes: Dict[str, str]) -> Dict:
        """
        Save metadata values using set name and attribute names instead of IDs.
        
        This is a convenience method that translates human-readable names to IDs and then
        calls save_attribute_values with the properly formatted input.
        
        Args:
            file_path: Path to the file
            set_name: Name of the metadata set
            attributes: Dictionary with attribute names as keys and their values as values
                {
                    'attr_name1': 'value1',
                    'attr_name2': 'value2',
                    ...
                }
            
        Returns:
            Dict: Response from the API with additional structured information
        """
        self._ensure_initialized()
        
        # Initialize result structure
        result = {
            'success': False,
            'file_path': file_path,
            'set_name': set_name,
            'attributes': attributes,
            'message': '',
            'raw_response': None
        }
        
        # Find the set ID from the name
        set_info = self.get_metadata_set_by_name(set_name)
        if not set_info:
            result['message'] = f"Metadata set with name '{set_name}' not found"
            self._debug_print(f"Error: {result['message']}")
            return result
        
        set_id = set_info['id']
        
        # Convert attribute names to IDs
        attribute_values = {}
        for attr_name, value in attributes.items():
            attr_info = self.get_attribute_by_name(set_id, attr_name)
            if attr_info:
                attribute_values[attr_info['id']] = str(value) if value is not None else ""
                self._debug_print(f"Mapped attribute {attr_name} to ID {attr_info['id']} with value {value}")
            else:
                self._debug_print(f"Warning: Attribute {attr_name} not found in set {set_name}")
                # We could skip this attribute, but let's keep it with the name as ID
                # for better error handling in the API
                attribute_values[attr_name] = str(value) if value is not None else ""
        
        if not attribute_values:
            result['message'] = f"No valid attributes found for metadata set '{set_name}'"
            self._debug_print(f"Error: {result['message']}")
            return result
        
        # Call API to save the attribute values
        self._debug_print(f"Saving attributes for file {file_path}, set ID {set_id}")
        self._debug_print("Attribute values:", attribute_values)
        
        raw_response = self.api.save_attribute_values(file_path, set_id, attribute_values)
        result['raw_response'] = raw_response
        
        # Update result based on API response
        result['success'] = raw_response.get('success', False)
        if not result['success']:
            result['message'] = raw_response.get('message', 'Unknown error')
            self._debug_print(f"API error: {result['message']}")
        else:
            result['message'] = 'Attributes saved successfully'
            self._debug_print("Attributes saved successfully")
        
        return result

    def create_search_attributes(self, search_criteria: List[Dict]) -> List[Dict]:
        """
        Create a list of search attributes formatted for the API, translating
        friendly names to IDs.
        
        Args:
            search_criteria: A list of dictionaries with keys 'set_name', 
                            'attribute_name', and 'value'
        
        Returns:
            List[Dict]: Formatted attributes for search_metadata API call
        """
        self._ensure_initialized()
        
        formatted_attributes = []
        
        for criteria in search_criteria:
            set_name = criteria.get('set_name')
            attribute_name = criteria.get('attribute_name')
            value = criteria.get('value')
            
            # Skip if any required field is missing
            if not set_name or not attribute_name or value is None:
                continue
            
            # Find the set by name
            metadata_set = self.get_metadata_set_by_name(set_name)
            if not metadata_set:
                print(f"Warning: Metadata set '{set_name}' not found")
                continue
            
            set_id = metadata_set.get('id')
            
            # Find the attribute by name
            attribute = self.get_attribute_by_name(set_id, attribute_name)
            if not attribute:
                print(f"Warning: Attribute '{attribute_name}' not found in set '{set_name}'")
                continue
            
            attribute_id = attribute.get('id')
            type_id = attribute.get('type_id')
            
            # Create the formatted attribute
            formatted_attr = {
                'setid': set_id,
                'type_id': type_id,
                'attributeid': attribute_id,
                'value': value
            }
            
            # Add operator if specified (default to 'equals')
            if 'operator' in criteria:
                formatted_attr['operator'] = criteria['operator']
            else:
                formatted_attr['operator'] = 'equals'
            
            formatted_attributes.append(formatted_attr)
        
        return formatted_attributes
    
    def search_files_by_metadata(self, search_criteria: List[Dict], 
                                search_string: str = "**", 
                                search_location: Optional[str] = None) -> List[str]:
        """
        Search for files that match specific metadata criteria.
        
        Args:
            search_criteria: A list of dictionaries with keys 'set_name', 
                            'attribute_name', 'value', and optionally 'operator'
            search_string: Search string for filename pattern (use "**" for all files)
            search_location: Optional path to limit search to
            
        Returns:
            List[str]: List of file paths that match the criteria
        """
        # Create formatted search attributes
        search_attributes = self.create_search_attributes(search_criteria)
        
        if not search_attributes:
            print("Warning: No valid search attributes could be created")
            return []
        
        # Perform the search
        result = self.api.search_metadata(
            search_string=search_string,
            search_scope='3',  # All files/folders
            attributes=search_attributes,
            #search_location=search_location
        )
        
        # Return paths or empty list if not successful
        return result.get('paths', [])

    def create_metadata_editor(self, file_path: str = None, metadata: Dict = None, 
                             views: List[str] = None):
        """
        Create an interactive panel for editing file metadata.
        
        This method creates a Holoviz Panel interface for viewing and editing
        metadata values. Once created, the panel can be displayed in a notebook
        or web application.
        
        Args:
            file_path: Optional path to the file whose metadata to edit.
                       If provided and metadata is None, metadata will be loaded
                       from this file.
            metadata: Optional pre-loaded metadata to edit. If provided,
                      file_path is only used when saving changes.
            views: Optional list of views to display, choose from ['form', 'table', 'json']
                  Default is ['form'] if not specified
                      
        Returns:
            panel.Column: Panel layout that can be displayed in notebooks or web apps
        """
        try:
            import panel as pn
            import pandas as pd
            from panel.widgets import Button, Select, TextInput, DataFrame, TextAreaInput, Switch
            from panel.layout import Column, Row, Tabs, HSpacer
            
            # Load panel extensions
            pn.extension()
        except ImportError:
            raise ImportError(
                "This feature requires Panel and pandas. Install with: "
                "pip install panel==1.6.1 pandas"
            )
            
        # Default to just the form view if not specified
        if views is None:
            views = ['form']
            
        # Define common styles for consistent look and feel
        STYLES = {
            "container": {"background": "#f8f9fa", "padding": "10px", "border-radius": "5px"},
            "header": {"background": "#4a86e8", "color": "white", "padding": "10px", 
                      "font-weight": "bold", "border-radius": "5px 5px 0 0"},
            "label": {"font-weight": "bold", "margin-bottom": "5px", "color": "#333333"},
            "input": {"background": "#ffffff", "border": "1px solid #ddd", 
                     "padding": "8px", "border-radius": "4px", "width": "100%",
                     "margin-bottom": "10px", "color": "#333333"},
            "button_primary": {"background": "#4a86e8", "color": "white"},
            "button_success": {"background": "#28a745", "color": "white"},
            "message": {"padding": "10px", "border-radius": "4px", 
                       "font-weight": "bold", "margin-top": "10px"},
            "description": {"font-style": "italic", "color": "#666", "margin-left": "10px"}
        }
            
        # Create container for dynamic content with improved styling
        message_area = pn.pane.Markdown("", styles=STYLES["message"])
        
        # Function to load metadata
        def load_metadata(path):
            nonlocal metadata
            if not path:
                message_area.object = "âš ī¸ Please enter a valid file path"
                message_area.styles = {**STYLES["message"], "background": "#fff3cd", "color": "#856404"}
                return None
                
            try:
                md = self.get_metadata_values(path)
                if not md.get('success', False):
                    error_msg = md.get('message', 'Unknown error')
                    message_area.object = f"❌ Error loading metadata: {error_msg}"
                    message_area.styles = {**STYLES["message"], "background": "#f8d7da", "color": "#721c24"}
                    return None
                    
                message_area.object = f"✅ Metadata loaded for {path}"
                message_area.styles = {**STYLES["message"], "background": "#d4edda", "color": "#155724"}
                return md
            except Exception as e:
                message_area.object = f"❌ Error: {str(e)}"
                message_area.styles = {**STYLES["message"], "background": "#f8d7da", "color": "#721c24"}
                return None
                
        # Create file path input with better styling
        file_path_input = TextInput(
            name="File Path", 
            value=file_path or "", 
            placeholder="/path/to/file", 
            width=600,
            styles={"background": "#ffffff", "color": "#333333", "border": "1px solid #ddd"}
        )
        
        # Load button with better styling
        load_button = Button(
            name="Load Metadata", 
            button_type="primary", 
            width=150,
            styles=STYLES["button_primary"]
        )
        
        # Tabs for different views
        tabs = pn.Tabs()
        
        # Define editors dictionary at a higher scope so it's available to all functions
        editors = {}
        
        # Function to update display when metadata changes
        def update_display(md):
            if not md:
                return
                
            # Clear existing tabs
            tabs.clear()
            
            # Clear existing editors
            nonlocal editors
            editors = {}
            
            # Create DataFrame for table view
            attrs_list = []
            if 'attributes' in md:
                for attr in md['attributes']:
                    attrs_list.append({
                        'Name': attr.get('name', ''),
                        'Value': attr.get('value', ''),
                        'Description': attr.get('description', ''),
                        'Type': attr.get('type', ''),
                        'Required': '✓' if attr.get('required', False) else '',
                        'ID': attr.get('id', '')
                    })
            
            df = pd.DataFrame(attrs_list)
            
            # Create editors based on attribute types
            form_layout = []
            
            # Add set information with better styling
            set_info = md.get('set', {})
            set_name = set_info.get('name', 'Unknown Set')
            set_desc = set_info.get('description', '')
            
            # Add set header with improved styling
            header = pn.pane.Markdown(f"# {set_name}", styles=STYLES["header"])
            form_layout.append(header)
            
            if set_desc:
                form_layout.append(pn.pane.Markdown(f"*{set_desc}*", styles={"padding": "5px 10px", "color": "#333333"}))
            
            # Add container for form fields with consistent styling
            form_container = pn.Column(styles=STYLES["container"])
            
            if 'attributes' in md:
                # Sort attributes by name
                sorted_attrs = sorted(md['attributes'], key=lambda x: x.get('name', ''))
                
                # Create form fields for each attribute
                for attr in sorted_attrs:
                    attr_name = attr.get('name', 'Unnamed')
                    attr_id = attr.get('id', '')
                    attr_value = attr.get('value', '')
                    attr_type = attr.get('type', '')
                    attr_desc = attr.get('description', '')
                    attr_required = attr.get('required', False)
                    attr_disabled = attr.get('disabled', False)
                    
                    # Create field label with required indicator if needed
                    field_label = f"{attr_name}" + (" *" if attr_required else "")
                    label_widget = pn.pane.Markdown(
                        f"**{field_label}**", 
                        styles={"font-weight": "bold", "color": "#333333", "margin-bottom": "5px"},
                        width=200
                    )
                    
                    # Create appropriate editor based on type with consistent styling
                    if attr_type == 'Boolean':
                        # Boolean switch with better styling
                        editor = Switch(
                            name='',  # Remove duplicate name since we have a separate label
                            value=attr_value.lower() in ('true', 'yes', '1'),
                            disabled=attr_disabled,
                            styles={"margin-top": "8px"}
                        )
                    elif attr_type == 'Text' and len(attr_value) > 50:
                        # Text area for longer text with better styling
                        editor = TextAreaInput(
                            name='',  # Remove duplicate name
                            value=attr_value,
                            placeholder=f"Enter {attr_name}...",
                            disabled=attr_disabled,
                            height=100,
                            width=400,
                            styles={"background": "#ffffff", "color": "#333333", "border": "1px solid #ddd"}
                        )
                    else:
                        # Default to text input with better styling
                        editor = TextInput(
                            name='',  # Remove duplicate name
                            value=attr_value,
                            placeholder=f"Enter {attr_name}...",
                            disabled=attr_disabled,
                            width=400,
                            styles={"background": "#ffffff", "color": "#333333", "border": "1px solid #ddd"}
                        )
                    
                    # Store editor with attribute ID for later access
                    editors[attr_id] = editor
                    
                    # Create a row with label, editor, and description if available
                    row_items = [label_widget, editor]
                    
                    if attr_desc:
                        desc_widget = pn.pane.Markdown(
                            f"*{attr_desc}*", 
                            styles={"font-style": "italic", "color": "#666666", "margin-left": "10px"},
                            width=200
                        )
                        row_items.append(desc_widget)
                    
                    # Create a consistent row with better alignment
                    field_row = pn.Row(
                        *row_items,
                        align='center',
                        styles={"margin-bottom": "5px", "padding": "5px 0"}
                    )
                    
                    form_container.append(field_row)
            
            # Add form container to layout
            form_layout.append(form_container)
            
            # Save function
            def save_changes(event):
                try:
                    save_path = file_path_input.value
                    
                    if not save_path:
                        message_area.object = "âš ī¸ Please enter a file path to save metadata"
                        message_area.styles = {**STYLES["message"], "background": "#fff3cd", "color": "#856404"}
                        return
                    
                    # Check if we need to sync data from table view first
                    active_tab = tabs.active
                    if active_tab == 1 and 'table' in views:  # If we're in table view
                        try:
                            # Get DataFrame from table view - Fix the access path to the DataFrame
                            # Access the DataFrame directly as it's the 4th item (index 3) in the table container
                            table_container = tabs[1]
                            df_widget = None
                            
                            # Loop through the objects to find the DataFrame widget
                            for obj in table_container:
                                if isinstance(obj, pn.widgets.DataFrame):
                                    df_widget = obj
                                    break
                            
                            if df_widget is not None:
                                table_df = df_widget.value
                                
                                # Update form editors with values from table
                                for _, row in table_df.iterrows():
                                    attr_id = row['ID']
                                    new_value = row['Value']
                                    
                                    # Only update if the editor for this attribute exists
                                    if attr_id in editors:
                                        # Find attribute in metadata to get its type
                                        attr_type = next((attr['type'] for attr in md['attributes'] 
                                                        if attr['id'] == attr_id), None)
                                        
                                        if attr_type == 'Boolean':
                                            editors[attr_id].value = new_value.lower() in ('true', 'yes', '1')
                                        else:
                                            editors[attr_id].value = new_value
                        except Exception as e:
                            print(f"Error syncing table data during save: {e}")
                    
                    # Update metadata values from editors
                    updated_md = md.copy()
                    for attr in updated_md.get('attributes', []):
                        attr_id = attr.get('id')
                        if attr_id in editors:
                            editor = editors[attr_id]
                            new_value = str(editor.value)
                            
                            # Convert boolean values to string format expected by API
                            if attr.get('type') == 'Boolean' and isinstance(editor.value, bool):
                                new_value = 'true' if editor.value else 'false'
                                
                            attr['value'] = new_value
                    
                    # Save changes
                    result = self.save_attribute_values(save_path, updated_md)
                    
                    if result.get('success'):
                        message_area.object = f"✅ Saved metadata to {save_path}"
                        message_area.styles = {**STYLES["message"], "background": "#d4edda", "color": "#155724"}
                        
                        # If saving from table view, also update the table view
                        if active_tab == 1 and 'table' in views:
                            # Refresh the form data in metadata
                            metadata.update(updated_md)
                            
                            # Update table data - use the same logic as above to find the DataFrame
                            table_container = tabs[1]
                            df_widget = None
                            
                            for obj in table_container:
                                if isinstance(obj, pn.widgets.DataFrame):
                                    df_widget = obj
                                    break
                                    
                            if df_widget is not None:
                                table_df = df_widget.value
                                for i, row in table_df.iterrows():
                                    attr_id = row['ID']
                                    for attr in updated_md.get('attributes', []):
                                        if attr.get('id') == attr_id:
                                            table_df.at[i, 'Value'] = attr.get('value', '')
                                            break
                                
                                # Set updated dataframe back to widget
                                df_widget.value = table_df
                    else:
                        message_area.object = f"❌ Error saving metadata: {result.get('message', 'Unknown error')}"
                        message_area.styles = {**STYLES["message"], "background": "#f8d7da", "color": "#721c24"}
                except Exception as e:
                    message_area.object = f"❌ Error: {str(e)}"
                    message_area.styles = {**STYLES["message"], "background": "#f8d7da", "color": "#721c24"}
            
            # Create save button with better styling
            save_button = Button(
                name="Save Changes", 
                button_type="success",
                width=150,
                styles=STYLES["button_success"]
            )
            save_button.on_click(save_changes)
            
            # Add save button at the bottom of the form with proper spacing
            form_layout.append(pn.layout.Spacer(height=20))
            form_layout.append(pn.Row(save_button, align='center'))
            
            # Create tabs based on requested views
            if 'form' in views:
                form_panel = Column(*form_layout, scroll=True, width=850)
                tabs.append(("Form View", form_panel))
            
            # Only add table view if requested and there are attributes
            if 'table' in views and len(attrs_list) > 0:
                # 1. Create value-only editable columns dictionary
                value_columns = {'Value': {'editable': True}}
                other_columns = {col: {'editable': False} for col in df.columns if col != 'Value'}
                editable_columns = {**other_columns, **value_columns}
                
                # 2. Create styled DataFrame widget for table view
                df_widget = DataFrame(
                    df, 
                    width=830,
                    height=400,
                    show_index=False,
                    editors=editable_columns,  # Apply column-specific editability
                    # Use CSS for styling the table properly
                    styles={
                        'color': '#333333',
                        'background-color': 'white'
                    }
                )
                
                # 3. Improved table styling with custom CSS
                table_css = """
                <style>
                .metadata-table .bk-data-table {
                    color: #333333;
                    background-color: white;
                }
                .metadata-table .bk-data-table th {
                    background-color: #f2f2f2;
                    color: #333333;
                    font-weight: bold;
                    border-bottom: 1px solid #ddd;
                    padding: 8px;
                }
                .metadata-table .bk-data-table td {
                    color: #333333;
                    background-color: white;
                    border-bottom: 1px solid #f0f0f0;
                    padding: 6px 8px;
                }
                .metadata-table .slick-row:nth-child(odd) {
                    background-color: #f9f9f9;
                }
                .metadata-table .slick-cell.active {
                    border: 2px solid #4a86e8;
                }
                </style>
                """
                table_style_pane = pn.pane.HTML(table_css, width=0, height=0, margin=0)
                
                # Create table container with styling
                table_container = Column(
                    pn.pane.Markdown("## Metadata Table View", styles=STYLES["header"]),
                    table_style_pane,  # Include the CSS styling
                    pn.pane.Markdown(
                        "âœī¸ *Only the Value column can be edited. Click Save Changes below to save your changes.*",
                        styles={"color": "#666666", "font-style": "italic", "margin": "5px 0 10px 0"}
                    ),
                    df_widget,
                    pn.layout.Spacer(height=20),  # Add space before the button
                    pn.Row(
                        Button(
                            name="Save Changes", 
                            button_type="success",
                            width=150,
                            styles=STYLES["button_success"],
                            on_click=save_changes  # Use the same save function
                        ),
                        align='center'
                    ),
                    css_classes=['metadata-table'],  # Apply custom CSS class
                    styles=STYLES["container"]
                )
                tabs.append(("Table View", table_container))
            
            # Add an improved JSON view for developers if requested
            if 'json' in views:
                # Create a formatted, collapsible JSON tree view
                formatted_json = {
                    "set": md.get('set', {}),
                    "attributes": {attr.get('name', f'attr_{i}'): {
                        "id": attr.get('id', ''),
                        "value": attr.get('value', ''),
                        "type": attr.get('type', ''),
                        "description": attr.get('description', ''),
                        "required": attr.get('required', False),
                        "disabled": attr.get('disabled', False)
                    } for i, attr in enumerate(md.get('attributes', []))}
                }
                
                json_tree_widget = pn.widgets.JSONEditor(
                    value=formatted_json,
                    width=830,
                    height=500,
                    styles={"color": "#333333", "background": "white", "font-family": "monospace"}
                )
                
                # Add instructions for using the JSON view
                json_help = pn.pane.Markdown(
                    """
                    â„šī¸ **JSON View Help**
                    
                    - Click the â–ļ arrows to expand/collapse sections
                    - This view provides a better organized representation of the metadata
                    - The attributes are organized by name for easier reference
                    - Changes in this view won't be saved to FileCloud
                    """,
                    styles={"background": "#e9f5ff", "color": "#333", "padding": "10px", "border-radius": "4px", "margin-bottom": "10px"}
                )
                
                json_container = Column(
                    pn.pane.Markdown("## JSON View", styles=STYLES["header"]),
                    json_help,
                    json_tree_widget,
                    styles=STYLES["container"]
                )
                tabs.append(("JSON", json_container))
                
        # Wire up the load button
        def load_click(event):
            md = load_metadata(file_path_input.value)
            if md:
                nonlocal metadata
                metadata = md
                update_display(md)
                
        load_button.on_click(load_click)
        
        # Function to sync table data back to form editors when switching tabs
        def sync_table_to_form(event):
            # Only sync if we're coming from the table view tab to the form view
            if event.new == 0 and event.old == 1 and 'table' in views:
                try:
                    # Get DataFrame from table widget
                    table_df = tabs[1][0].objects[3].value
                    
                    # Update form editors with values from table
                    for _, row in table_df.iterrows():
                        attr_name = row['Name']
                        attr_id = row['ID']
                        new_value = row['Value']
                        
                        # Only update if the editor for this attribute exists
                        if attr_id in editors:
                            # Handle boolean values specially
                            attr_type = next((attr['type'] for attr in metadata['attributes'] 
                                            if attr['id'] == attr_id), None)
                            if attr_type == 'Boolean':
                                editors[attr_id].value = new_value.lower() in ('true', 'yes', '1')
                            else:
                                editors[attr_id].value = new_value
                except Exception as e:
                    print(f"Error syncing table data: {e}")
        
        # Add tab change callback to sync data
        tabs.param.watch(sync_table_to_form, 'active')
        
        # Initialize display if metadata was provided
        if metadata is None and file_path:
            metadata = load_metadata(file_path)
            
        if metadata:
            update_display(metadata)
        
        # Create file selector with better alignment
        file_selector = Row(
            file_path_input,
            load_button,
            align='center',
            styles={"margin": "10px 0"}
        )
        
        # Create header with styled title
        header = pn.pane.Markdown(
            "# FileCloud Metadata Editor", 
            styles={"color": "#2c3e50", "background": "#ecf0f1", "padding": "10px", 
                   "text-align": "center", "border-radius": "5px"}
        )
        
        # Apply better styling to tabs for better readability
        tabs_css = """
        <style>
        /* Improve tab visibility with stronger colors and borders */
        .bk-header.bk-tab {
            background-color: #e6e6e6;
            color: #333333;
            font-weight: bold;
            border: 1px solid #ccc;
            border-bottom: none;
            border-radius: 5px 5px 0 0;
            padding: 8px 15px;
            margin-right: 2px;
        }
        .bk-header.bk-tab.bk-active {
            background-color: #4a86e8;
            color: white;
            border-color: #3a76d8;
        }
        .bk-tab-content {
            border-top: 2px solid #4a86e8;
            padding-top: 10px;
        }
        </style>
        """
        
        # Build final layout with consistent styling and improved tabs
        layout = Column(
            header,
            file_selector,
            message_area,
            pn.layout.Divider(),
            pn.pane.HTML(tabs_css, width=0, height=0, margin=0),  # Add tab styling
            tabs,
            width=850,
            styles={"background": "#f8f9fa", "padding": "15px", "border-radius": "8px"}
        )
        
        return layout

    def display_metadata_editor(self, file_path: str = None, metadata: Dict = None, 
                             views: List[str] = None):
        """
        Create and display an interactive metadata editor in a Jupyter notebook.
        
        Args:
            file_path: Optional path to the file whose metadata to edit
            metadata: Optional pre-loaded metadata to edit
            views: Optional list of views to display, choose from ['form', 'table', 'json']
                  Default is ['form'] if not specified
            
        Returns:
            panel.Column: The displayed panel object
        """
        try:
            import panel as pn
            pn.extension()
        except ImportError:
            raise ImportError("This feature requires Panel. Install with: pip install panel==1.6.1")
        
        # Default to just the form view if not specified
        if views is None:
            views = ['form']
            
        editor = self.create_metadata_editor(file_path, metadata, views)
        return editor

    def debug_attribute_lookup(self, set_id: str, attribute_name: str) -> Dict:
        """
        Debug function to help diagnose attribute lookup issues.
        
        Args:
            set_id: ID of the metadata set
            attribute_name: Name of the attribute to find
            
        Returns:
            Dict: Debug information about the lookup
        """
        self._ensure_initialized()
        
        result = {
            'set_id': set_id,
            'attribute_name': attribute_name,
            'found': False,
            'set_exists': set_id in self.metadata_sets,
            'attributes_count': 0,
            'available_attributes': [],
            'attribute_names_in_set': []
        }
        
        if not result['set_exists']:
            return result
            
        attributes = self.metadata_sets[set_id].get('attributes', {})
        result['attributes_count'] = len(attributes)
        
        # Get all attribute names for debugging
        for attr_id, attr_info in attributes.items():
            attr_name = attr_info.get('name', '')
            result['attribute_names_in_set'].append(attr_name)
            result['available_attributes'].append({
                'id': attr_id,
                'name': attr_name,
                'type': attr_info.get('type', '')
            })
        
        # Check if we can find the attribute by name
        for attr_id, attr_info in attributes.items():
            if attr_info.get('name') == attribute_name:
                result['found'] = True
                result['attribute_id'] = attr_id
                result['attribute_info'] = attr_info
                break
        
        return result

    def add_set_to_file_object(self, file_path: str, set_id: str = None, set_name: str = None) -> Dict:
        """
        Add a metadata set to a file or folder.
        
        Args:
            file_path: Path to the file or folder
            set_id: ID of the metadata set to add (either set_id or set_name must be provided)
            set_name: Name of the metadata set to add (used if set_id is not provided)
            
        Returns:
            Dict: Response with the following format:
                {
                    'success': bool,
                    'file_path': str,
                    'set_id': str,
                    'set_name': str,
                    'message': str,
                    'raw_response': dict  # Original API response
                }
        """
        self._ensure_initialized()
        
        # Initialize result structure
        result = {
            'success': False,
            'file_path': file_path,
            'set_id': set_id,
            'set_name': set_name,
            'message': '',
            'raw_response': None
        }
        
        # If set_id is not provided, try to find it by name
        if not set_id and set_name:
            set_info = self.get_metadata_set_by_name(set_name)
            if set_info:
                set_id = set_info.get('id')
                result['set_id'] = set_id
            else:
                result['message'] = f"Metadata set with name '{set_name}' not found"
                return result
        
        # If we still don't have a set_id, we can't proceed
        if not set_id:
            result['message'] = "Either set_id or set_name must be provided"
            return result
        
        # Call the API to add the set to the file object
        self._debug_print(f"Adding metadata set '{set_id}' to file '{file_path}'")
        raw_response = self.api.add_set_to_file_object(file_path, set_id)
        result['raw_response'] = raw_response
        
        # Update result based on API response
        result['success'] = raw_response.get('success', False)
        if not result['success']:
            result['message'] = raw_response.get('message', 'Unknown error')
        else:
            # If we only had the set_id but not the name, look up the name
            if not set_name and set_id in self.metadata_sets:
                result['set_name'] = self.metadata_sets[set_id].get('name', '')
            
            result['message'] = 'Metadata set successfully added to file object'
        
        self._debug_print(f"Result: {result['message']}", result if self.debug else None)
        return result

Parameters

Name Type Default Kind
bases - -

Parameter Details

bases: Parameter of type

Return Value

Returns unspecified type

Class Interface

Methods

__init__(self, api_client, debug)

Purpose: Initialize the metadata catalog. Args: api_client: Authenticated FileCloudAPI client debug: Enable detailed debug output

Parameters:

  • api_client: Type: FileCloudAPI
  • debug: Type: bool

Returns: None

_debug_print(self, message, data)

Purpose: Print debug information if debug mode is enabled. Args: message: Debug message data: Optional data to print

Parameters:

  • message: Type: str
  • data: Type: Any

Returns: None

_ensure_initialized(self)

Purpose: Ensure the catalog is initialized.

Returns: None

initialize(self)

Purpose: Initialize the metadata catalog by loading all metadata sets and attributes. This version uses a single API call to get all metadata sets with their attributes.

Returns: None

_get_attribute_type_name(self, type_id)

Purpose: Map attribute type ID to a readable name.

Parameters:

  • type_id: Parameter

Returns: None

list_metadata_sets(self) -> List[Dict]

Purpose: List all available metadata sets. Returns: List[Dict]: List of metadata sets with their information

Returns: Returns List[Dict]

list_attributes(self, set_id) -> List[Dict]

Purpose: List all attributes in a specific metadata set. Args: set_id: ID of the metadata set Returns: List[Dict]: List of attributes with their information

Parameters:

  • set_id: Type: str

Returns: Returns List[Dict]

get_metadata_set_by_name(self, set_name) -> Optional[Dict]

Purpose: Get a metadata set by its name. Args: set_name: Name of the metadata set to find Returns: Optional[Dict]: Metadata set information or None if not found

Parameters:

  • set_name: Type: str

Returns: Returns Optional[Dict]

get_metadata_set_by_id(self, set_id) -> Optional[Dict]

Purpose: Get a metadata set by its ID. Args: set_id: ID of the metadata set to find Returns: Optional[Dict]: Metadata set information or None if not found

Parameters:

  • set_id: Type: str

Returns: Returns Optional[Dict]

get_attribute_by_name(self, set_id, attribute_name) -> Optional[Dict]

Purpose: Get a metadata attribute by its name within a specific set. Args: set_id: ID of the metadata set attribute_name: Name of the attribute to find Returns: Optional[Dict]: Attribute information or None if not found

Parameters:

  • set_id: Type: str
  • attribute_name: Type: str

Returns: Returns Optional[Dict]

get_attribute_by_id(self, set_id, attribute_id) -> Optional[Dict]

Purpose: Get a metadata attribute by its ID within a specific set. Args: set_id: ID of the metadata set attribute_id: ID of the attribute to find Returns: Optional[Dict]: Attribute information or None if not found

Parameters:

  • set_id: Type: str
  • attribute_id: Type: str

Returns: Returns Optional[Dict]

get_metadata_values(self, file_path) -> Dict

Purpose: Get metadata values for a specific file in a structured format. Supports multiple metadata sets per file. Args: file_path: Path to the file Returns: Dict: Structured metadata values with the following format: { 'success': bool, 'file_path': str, 'sets': [ { 'id': str, 'name': str, 'description': str, 'attributes': [ { 'id': str, 'name': str, 'description': str, 'type': str, 'value': str, 'required': bool, 'disabled': bool }, ... ] }, ... ], 'raw_response': dict # Original API response }

Parameters:

  • file_path: Type: str

Returns: Returns Dict

get_attribute_by_name_from_meta(self, metadata, name) -> Optional[Dict]

Purpose: Get an attribute from metadata results by its name. Args: metadata: Metadata dictionary returned by get_metadata_values() name: Name of the attribute to find Returns: Dict or None: The attribute dictionary or None if not found

Parameters:

  • metadata: Type: Dict
  • name: Type: str

Returns: Returns Optional[Dict]

get_attributes_as_dict(self, metadata) -> Dict[str, Dict]

Purpose: Convert the attributes list to a dictionary keyed by attribute names. Args: metadata: Metadata dictionary returned by get_metadata_values() Returns: Dict: Dictionary of attributes with attribute names as keys

Parameters:

  • metadata: Type: Dict

Returns: Returns Dict[str, Dict]

get_attribute_values(self, metadata) -> Dict[str, str]

Purpose: Extract just the attribute names and values as a simple dictionary. Combines attributes from all metadata sets. Args: metadata: Metadata dictionary returned by get_metadata_values() Returns: Dict: Dictionary with attribute names as keys and their values as values

Parameters:

  • metadata: Type: Dict

Returns: Returns Dict[str, str]

save_attribute_values(self, file_path, metadata) -> Dict

Purpose: Save metadata values for a specific file using structured format. Args: file_path: Path to the file metadata: Structured metadata values in the format returned by get_metadata_values. Must contain at least 'set' with 'id' and 'attributes' list with 'id' and 'value'. { 'set': {'id': 'set_id'}, 'attributes': [ {'id': 'attr1_id', 'value': 'new_value1'}, {'id': 'attr2_id', 'value': 'new_value2'}, ... ] } Returns: Dict: Response from the API with additional structured information

Parameters:

  • file_path: Type: str
  • metadata: Type: Dict

Returns: Returns Dict

save_attribute_values_by_name(self, file_path, set_name, attributes) -> Dict

Purpose: Save metadata values using set name and attribute names instead of IDs. This is a convenience method that translates human-readable names to IDs and then calls save_attribute_values with the properly formatted input. Args: file_path: Path to the file set_name: Name of the metadata set attributes: Dictionary with attribute names as keys and their values as values { 'attr_name1': 'value1', 'attr_name2': 'value2', ... } Returns: Dict: Response from the API with additional structured information

Parameters:

  • file_path: Type: str
  • set_name: Type: str
  • attributes: Type: Dict[str, str]

Returns: Returns Dict

create_search_attributes(self, search_criteria) -> List[Dict]

Purpose: Create a list of search attributes formatted for the API, translating friendly names to IDs. Args: search_criteria: A list of dictionaries with keys 'set_name', 'attribute_name', and 'value' Returns: List[Dict]: Formatted attributes for search_metadata API call

Parameters:

  • search_criteria: Type: List[Dict]

Returns: Returns List[Dict]

search_files_by_metadata(self, search_criteria, search_string, search_location) -> List[str]

Purpose: Search for files that match specific metadata criteria. Args: search_criteria: A list of dictionaries with keys 'set_name', 'attribute_name', 'value', and optionally 'operator' search_string: Search string for filename pattern (use "**" for all files) search_location: Optional path to limit search to Returns: List[str]: List of file paths that match the criteria

Parameters:

  • search_criteria: Type: List[Dict]
  • search_string: Type: str
  • search_location: Type: Optional[str]

Returns: Returns List[str]

create_metadata_editor(self, file_path, metadata, views)

Purpose: Create an interactive panel for editing file metadata. This method creates a Holoviz Panel interface for viewing and editing metadata values. Once created, the panel can be displayed in a notebook or web application. Args: file_path: Optional path to the file whose metadata to edit. If provided and metadata is None, metadata will be loaded from this file. metadata: Optional pre-loaded metadata to edit. If provided, file_path is only used when saving changes. views: Optional list of views to display, choose from ['form', 'table', 'json'] Default is ['form'] if not specified Returns: panel.Column: Panel layout that can be displayed in notebooks or web apps

Parameters:

  • file_path: Type: str
  • metadata: Type: Dict
  • views: Type: List[str]

Returns: See docstring for return details

display_metadata_editor(self, file_path, metadata, views)

Purpose: Create and display an interactive metadata editor in a Jupyter notebook. Args: file_path: Optional path to the file whose metadata to edit metadata: Optional pre-loaded metadata to edit views: Optional list of views to display, choose from ['form', 'table', 'json'] Default is ['form'] if not specified Returns: panel.Column: The displayed panel object

Parameters:

  • file_path: Type: str
  • metadata: Type: Dict
  • views: Type: List[str]

Returns: See docstring for return details

debug_attribute_lookup(self, set_id, attribute_name) -> Dict

Purpose: Debug function to help diagnose attribute lookup issues. Args: set_id: ID of the metadata set attribute_name: Name of the attribute to find Returns: Dict: Debug information about the lookup

Parameters:

  • set_id: Type: str
  • attribute_name: Type: str

Returns: Returns Dict

add_set_to_file_object(self, file_path, set_id, set_name) -> Dict

Purpose: Add a metadata set to a file or folder. Args: file_path: Path to the file or folder set_id: ID of the metadata set to add (either set_id or set_name must be provided) set_name: Name of the metadata set to add (used if set_id is not provided) Returns: Dict: Response with the following format: { 'success': bool, 'file_path': str, 'set_id': str, 'set_name': str, 'message': str, 'raw_response': dict # Original API response }

Parameters:

  • file_path: Type: str
  • set_id: Type: str
  • set_name: Type: str

Returns: Returns Dict

Required Imports

from typing import Dict
from typing import List
from typing import Optional
from typing import Any
from CDocs.utils.FC_api import FileCloudAPI

Usage Example

# Example usage:
# result = MetadataCatalog(bases)

Similar Components

AI-powered semantic similarity - components with related functionality:

  • class CorporateCatalogAppMetadata 59.2% similar

    App metadata for apps stored in the corporate catalog.

    From: /tf/active/vicechatdev/SPFCsync/venv/lib64/python3.11/site-packages/office365/sharepoint/marketplace/app_metadata.py
  • class CorporateCatalogAppMetadataCollection 56.5% similar

    A collection class for managing and retrieving corporate catalog app metadata in SharePoint, extending EntityCollection to provide specialized operations for app metadata entities.

    From: /tf/active/vicechatdev/SPFCsync/venv/lib64/python3.11/site-packages/office365/sharepoint/marketplace/app_metadata_collection.py
  • class FileCloudClient 55.0% similar

    A client class for interacting with FileCloud server API, providing authentication, file management, folder creation, and file upload capabilities.

    From: /tf/active/vicechatdev/SPFCsync/filecloud_client.py
  • class FileCloudAPI 53.3% similar

    Python wrapper for the FileCloud REST API. This class provides methods to interact with FileCloud server APIs, handling authentication, session management, and various file operations.

    From: /tf/active/vicechatdev/FC_api copy.py
  • class FileCloudAPI_v1 51.9% similar

    Python wrapper for the FileCloud REST API. This class provides methods to interact with FileCloud server APIs, handling authentication, session management, and various file operations.

    From: /tf/active/vicechatdev/FC_api.py
← Back to Browse