Does adding a yaml file overwriting names defined in the script?

I just started to play with adding bundle.yaml files to all my pushbuttons to use the highlight: new feature but something I am noticing is that the names of the tools are changing to the the folder names.

Is this suppose to happen?

I have this in the the script
image

but have seen the name change to the folder name once i add a yaml.
image

the only code i have in the yaml file is this:
image

I think that if a bundle.yaml exist, the fallback is the folder name.
And if no bundle.yaml exist, the script __title__ is checked and folder name is the fallback again.

Is swapping fully to the bundle.yaml too much work?
Here is a quick and dirty AI-generated script that will automate the process for you, it will also create missing bundle.yaml files with the title: FolderName for you:

import os
import re

def find_script_title(script_path):
    """Find __title__ in a Python script."""
    try:
        with open(script_path, 'r', encoding='utf-8') as f:
            content = f.read()
            match = re.search(r'__title__\s*=\s*[\'"](.+?)[\'"]', content)
            if match:
                return match.group(1)
    except Exception as e:
        print(f"Error reading {script_path}: {e}")
    return None

def check_bundle_yaml(bundle_path):
    """Check bundle.yaml for missing title and find __title__ in script."""
    try:
        # Read bundle.yaml
        with open(bundle_path, 'r', encoding='utf-8') as f:
            content = f.read()
            # Simple check if title exists
            if 'title:' not in content:
                # Find corresponding script
                script_path = None
                for file in os.listdir(os.path.dirname(bundle_path)):
                    if file.endswith('.py') and not file.endswith('_script.py'):
                        script_path = os.path.join(os.path.dirname(bundle_path), file)
                        break

                if script_path:
                    script_title = find_script_title(script_path)
                    if script_title:
                        return {
                            'bundle_path': bundle_path,
                            'script_title': script_title,
                            'script_path': script_path
                        }
    except Exception as e:
        print(f"Error processing {bundle_path}: {e}")
    return None

def update_bundle_yaml(bundle_path, title):
    """Add title to bundle.yaml."""
    try:
        with open(bundle_path, 'r', encoding='utf-8') as f:
            content = f.read()

        # Add title at the top
        new_content = f"title: {title}\n"
        if content.strip():  # If there's existing content
            new_content += content

        with open(bundle_path, 'w', encoding='utf-8') as f:
            f.write(new_content)
        return True
    except Exception as e:
        print(f"Error updating {bundle_path}: {e}")
        return False

def create_bundle_yaml(bundle_path, title):
    """Create new bundle.yaml with title."""
    try:
        with open(bundle_path, 'w', encoding='utf-8') as f:
            f.write(f"title: {title}\n")
        return True
    except Exception as e:
        print(f"Error creating {bundle_path}: {e}")
        return False

def main():
    # Find all bundle.yaml files and Python scripts
    bundle_files = []
    python_scripts = []
    total_files_checked = 0
    print("\nScanning directories...")
    
    for root, dirs, files in os.walk('.'):
        for file in files:
            total_files_checked += 1
            if file == 'bundle.yaml':
                bundle_files.append(os.path.join(root, file))
                print(f"Found bundle.yaml: {os.path.join(root, file)}")
            elif file.endswith('.py') and not file.endswith('_script.py'):
                python_scripts.append(os.path.join(root, file))

    print(f"\nTotal files checked: {total_files_checked}")
    print(f"Total bundle.yaml files found: {len(bundle_files)}")
    print(f"Total Python scripts found: {len(python_scripts)}")

    # Check for missing bundle.yaml files
    missing_bundles = []
    for script_path in python_scripts:
        script_dir = os.path.dirname(script_path)
        bundle_path = os.path.join(script_dir, 'bundle.yaml')
        if not os.path.exists(bundle_path):
            script_title = find_script_title(script_path)
            if script_title:
                missing_bundles.append({
                    'bundle_path': bundle_path,
                    'script_title': script_title,
                    'script_path': script_path
                })

    # Check existing bundle.yaml files for missing titles
    missing_titles = []
    for bundle_path in bundle_files:
        result = check_bundle_yaml(bundle_path)
        if result:
            missing_titles.append(result)

    # Handle missing bundle.yaml files
    if missing_bundles:
        print("\nFound Python scripts without bundle.yaml:")
        for i, item in enumerate(missing_bundles, 1):
            print(f"\n{i}. Script: {item['script_path']}")
            print(f"   Would create: {item['bundle_path']}")
            print(f"   With title: {item['script_title']}")

        response = input("\nDo you want to create these bundle.yaml files? (y/n): ").lower()
        if response == 'y':
            print("\nCreating bundle.yaml files...")
            for item in missing_bundles:
                if create_bundle_yaml(item['bundle_path'], item['script_title']):
                    print(f"Created: {item['bundle_path']}")
                else:
                    print(f"Failed to create: {item['bundle_path']}")

    # Handle missing titles in existing bundle.yaml files
    if missing_titles:
        print("\nFound bundle.yaml files missing titles:")
        for i, item in enumerate(missing_titles, 1):
            print(f"\n{i}. Bundle: {item['bundle_path']}")
            print(f"   Script: {item['script_path']}")
            print(f"   Found __title__: {item['script_title']}")

        response = input("\nDo you want to update these files? (y/n): ").lower()
        if response == 'y':
            print("\nUpdating files...")
            for item in missing_titles:
                if update_bundle_yaml(item['bundle_path'], item['script_title']):
                    print(f"Updated: {item['bundle_path']}")
                else:
                    print(f"Failed to update: {item['bundle_path']}")
    elif not missing_bundles:
        print("\nNo issues found with bundle.yaml files.")

if __name__ == '__main__':
    main()

Note, this is a py3 script, so it needs to be run in the root of your extension, outside pyRevit/Revit context.

If you want to add more to the generated bundle.yaml just add to the create_bundle_yaml

def create_bundle_yaml(bundle_path, title):
    """Create new bundle.yaml with title."""
    try:
        content = f"""title: {title}
# highlight: new
"""
        with open(bundle_path, 'w', encoding='utf-8') as f:
            f.write(content)
        return True
    except Exception as e:
        print(f"Error creating {bundle_path}: {e}")
        return False
1 Like

This is the part I needed to know. I have never worked with yaml files before so did not know.

This is just the logic in the current pyrevit code, it could probably be changed to first look for bundle.yaml and a title value in it, and if missing; then look for a __title__ in the script, and then fallback to the folder name. Might add a slight delay to building the panel though, i don’t know.

1 Like

At least now I know how it currently works. Hopefully others also learning pyRevit will come across this post.

When a bundle.yaml file is present, any keys it defines (title, tooltip, help_url, highlight, min_revit, …) overrides both the folder name and the in-script variables for those same fields. The docstring or globals are still loaded, but the YAML values are what the UI shows. Fields you leave out in the YAML fall back to the script variables, and if neither is present pyRevit falls back to the folder name.

2 Likes

This is not what I see when testing this?

If the bundle.yaml exist, but does not contain a “title: ButtonName”-value, it will fallback to the folder name. Even if there is a __title__ = ButtonScriptName defined in the script, it is not used if the bundle.yaml exist.

Disclaimer: I might have done something with the setup, but it seems to me the logic is as I described above

This is exactly what I witnessed. I originally had title = Tool name in the script itself and in the panel, the button was Tool Name.

Then added a .yaml with the following line
highlight = new
and button updated/defaulted to Folder name.

I therefore had to add title into the .yaml and move any other descriptive details into it.

There’s nothing wrong with this, I simply didn’t know how the workflow was.