You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写Python脚本实现Pycharm配置Black自动保存格式化及外部工具添加

Python Script to Automate Black Setup for PyCharm on Linux

Step 1: The Complete Script

import os
import subprocess
import xml.etree.ElementTree as ET
from xml.dom import minidom

def install_black():
    """Install or upgrade Black using pip"""
    try:
        subprocess.run(["pip3", "install", "--upgrade", "black"], check=True)
        print("✅ Black installed/upgraded successfully")
    except subprocess.CalledProcessError:
        print("❌ Failed to install Black. Ensure pip3 is installed.")
        exit(1)

def find_pycharm_config_dirs():
    """Locate all PyCharm config directories in ~/.config/JetBrains/"""
    jetbrains_dir = os.path.expanduser("~/.config/JetBrains")
    if not os.path.exists(jetbrains_dir):
        print("❌ No PyCharm config directories found. Run PyCharm at least once first.")
        exit(1)
    
    pycharm_dirs = [d for d in os.listdir(jetbrains_dir) if d.startswith("PyCharm")]
    if not pycharm_dirs:
        print("❌ No PyCharm config directories found. Run PyCharm at least once first.")
        exit(1)
    
    return [os.path.join(jetbrains_dir, d) for d in pycharm_dirs]

def prettify_xml(element):
    """Prettify XML output with proper indentation"""
    rough_string = ET.tostring(element, 'utf-8')
    reparsed = minidom.parseString(rough_string)
    return reparsed.toprettyxml(indent="  ")

def add_external_tool(config_dir):
    """Add Black as an external tool in PyCharm"""
    tools_dir = os.path.join(config_dir, "tools")
    os.makedirs(tools_dir, exist_ok=True)
    tools_file = os.path.join(tools_dir, "ExternalTools.xml")
    
    # Define the Black tool XML structure
    if os.path.exists(tools_file):
        tree = ET.parse(tools_file)
        root = tree.getroot()
    else:
        root = ET.Element("application")
        component = ET.SubElement(root, "component", name="ExternalTools")
        root.append(component)
    
    # Check if Black tool already exists
    component = root.find(".//component[@name='ExternalTools']")
    existing_tool = component.find(".//tool[@id='Black']")
    if existing_tool is not None:
        print(f"ℹ️ Black external tool already exists in {config_dir}")
        return
    
    # Create the tool element
    tool = ET.SubElement(component, "tool", {
        "id": "Black",
        "name": "Black",
        "description": "Black code formatter",
        "showInMainMenu": "false",
        "showInEditor": "true",
        "showInProject": "true",
        "showInSearchPopup": "true",
        "enabled": "true",
        "useConsole": "false",
        "showConsoleOnStdOut": "false",
        "showConsoleOnStdErr": "false",
        "synchronizeAfterRun": "true"
    })
    
    exec_elem = ET.SubElement(tool, "exec")
    ET.SubElement(exec_elem, "option", {"name": "COMMAND", "value": "black"})
    ET.SubElement(exec_elem, "option", {"name": "PARAMETERS", "value": "$FilePath$"})
    ET.SubElement(exec_elem, "option", {"name": "WORKING_DIRECTORY", "value": "$ProjectFileDir$"})
    
    # Write the prettified XML
    with open(tools_file, "w") as f:
        f.write(prettify_xml(root))
    
    print(f"✅ Added Black as external tool in {config_dir}")

def add_file_watcher(config_dir):
    """Add a File Watcher to run Black on Python file save"""
    watchers_dir = os.path.join(config_dir, "fileWatches")
    os.makedirs(watchers_dir, exist_ok=True)
    watchers_file = os.path.join(watchers_dir, "fileWatches.xml")
    
    if os.path.exists(watchers_file):
        tree = ET.parse(watchers_file)
        root = tree.getroot()
    else:
        root = ET.Element("application")
        component = ET.SubElement(root, "component", name="FileWatcherManager")
        watchers = ET.SubElement(component, "watchers")
        root.append(component)
    
    # Check if Black watcher already exists
    component = root.find(".//component[@name='FileWatcherManager']")
    watchers = component.find("watchers") if component is not None else None
    if not watchers:
        watchers = ET.SubElement(component, "watchers")
    
    existing_watcher = watchers.find(".//watcher[@id='Black']")
    if existing_watcher is not None:
        print(f"ℹ️ Black File Watcher already exists in {config_dir}")
        return
    
    # Create the watcher element
    watcher = ET.SubElement(watchers, "watcher", {
        "id": "Black",
        "name": "Black",
        "fileType": "Python",
        "scope": "Project Files",
        "program": "black",
        "parameters": "$FilePath$",
        "workingDir": "$ProjectFileDir$",
        "trackOnlyRoots": "false",
        "showConsole": "ALWAYS",
        "isEnabled": "true",
        "hideIgnoredFiles": "false"
    })
    
    filters = ET.SubElement(watcher, "filters")
    filter_elem = ET.SubElement(filters, "filter")
    ET.SubElement(filter_elem, "option", {"name": "FILE_EXTENSIONS", "value": "py"})
    ET.SubElement(filter_elem, "option", {"name": "FILE_NAMES"})
    
    triggering = ET.SubElement(watcher, "triggering")
    ET.SubElement(triggering, "option", {"name": "AFTER_SAVE", "value": "true"})
    
    advanced = ET.SubElement(watcher, "advancedOptions")
    ET.SubElement(advanced, "option", {"name": "autoSave", "value": "true"})
    ET.SubElement(advanced, "option", {"name": "autoSync", "value": "true"})
    
    # Write the prettified XML
    with open(watchers_file, "w") as f:
        f.write(prettify_xml(root))
    
    print(f"✅ Added Black File Watcher (runs on save) in {config_dir}")

def main():
    print("=== Starting Black setup for PyCharm ===")
    install_black()
    pycharm_dirs = find_pycharm_config_dirs()
    
    for dir_path in pycharm_dirs:
        print(f"\nProcessing config directory: {dir_path}")
        add_external_tool(dir_path)
        add_file_watcher(dir_path)
    
    print("\n=== Setup Complete ===")
    print("⚠️ Restart PyCharm for changes to take effect.")
    print("🔧 Black will now automatically format Python files when you save them.")
    print("🖥️ You can also run Black manually via Tools > External Tools > Black.")

if __name__ == "__main__":
    main()

Step 2: How to Use the Script

  • Save the script as setup_black_pycharm.py
  • Run it with Python 3: python3 setup_black_pycharm.py
  • Use user-level pip install (preferred) or run with sudo only if necessary for system-wide installation.

Step 3: Key Details Explained

Installing Black

The script uses pip3 install --upgrade black to ensure the latest version is installed. It exits with an error if pip3 isn't available—ensure Python 3 and pip3 are installed first.

Locating PyCharm Configs

PyCharm stores user settings in ~/.config/JetBrains/. The script detects all folders starting with PyCharm, covering both Community/Professional editions and different versions.

Adding External Tool

The script modifies tools/ExternalTools.xml to add Black as an external tool accessible via PyCharm's Tools menu. It uses built-in variables like $FilePath$ to target the current open file.

Setting Up Auto-Save Formatting

A File Watcher is added in fileWatches/fileWatches.xml that triggers Black whenever a .py file is saved. The watcher is enabled to run automatically after save, with auto-sync enabled to reload the formatted file in PyCharm.

Notes

  • Restart Required: PyCharm must be restarted to apply the new configuration changes.
  • Multiple PyCharm Versions: The script configures all detected PyCharm installations automatically.
  • Existing Configs: If Black is already set up, the script skips duplicate setup steps to avoid conflicts.

内容的提问来源于stack exchange,提问作者TatevAram

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 00:28:14