Skip to content

medcat.plugins.installer

Plugin installation functionality.

Classes:

Attributes:

logger module-attribute

logger = getLogger(__name__)

PipInstaller

Plugin installer using pip.

Methods:

get_name

get_name() -> str
Source code in medcat-v2/medcat/plugins/installer.py
60
61
def get_name(self) -> str:
    return "pip"

install

install(spec: PluginInstallSpec, dry_run: bool = False) -> bool

Install a plugin using pip.

Source code in medcat-v2/medcat/plugins/installer.py
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def install(
    self, 
    spec: PluginInstallSpec, 
    dry_run: bool = False
) -> bool:
    """Install a plugin using pip."""
    cmd = [
        sys.executable, "-m", "pip", "install",
        spec.to_pip_spec()
    ]

    if dry_run:
        cmd.insert(3, "--dry-run")

    logger.info(f"Installing {spec.name}: {' '.join(cmd)}")

    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            check=True
        )
        logger.debug(f"Install output: {result.stdout}")
        return True

    except subprocess.CalledProcessError as e:
        logger.error(f"Installation failed: {e.stderr}")
        return False

is_available

is_available() -> bool

Check if pip is available.

Source code in medcat-v2/medcat/plugins/installer.py
48
49
50
51
52
53
54
55
56
57
58
def is_available(self) -> bool:
    """Check if pip is available."""
    try:
        subprocess.run(
            [sys.executable, "-m", "pip", "--version"],
            capture_output=True,
            check=True
        )
        return True
    except (subprocess.CalledProcessError, FileNotFoundError):
        return False

PluginInstallationManager

PluginInstallationManager(installer: Optional[PluginInstaller] = None)

Manages plugin installation.

Initialize the installation manager.

Parameters:

Methods:

Attributes:

Source code in medcat-v2/medcat/plugins/installer.py
67
68
69
70
71
72
73
74
75
def __init__(self, installer: Optional[PluginInstaller] = None):
    """
    Initialize the installation manager.

    Args:
        installer: Plugin installer to use (defaults to PipInstaller)
    """
    self.installer = installer or PipInstaller()
    self.catalog = get_catalog()

catalog instance-attribute

catalog = get_catalog()

installer instance-attribute

installer = installer or PipInstaller()

install_multiple

install_multiple(plugin_names: list[str], dry_run: bool = False) -> dict

Install multiple plugins.

Returns:

  • dict

    Dictionary mapping plugin names to success status

Source code in medcat-v2/medcat/plugins/installer.py
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def install_multiple(
    self, 
    plugin_names: list[str],
    dry_run: bool = False
) -> dict:
    """
    Install multiple plugins.

    Returns:
        Dictionary mapping plugin names to success status
    """
    results = {}
    for name in plugin_names:
        try:
            results[name] = self.install_plugin(name, dry_run=dry_run)
        except Exception as e:
            logger.error(f"Failed to install {name}: {e}")
            results[name] = False

    return results

install_plugin

install_plugin(plugin_name: str, dry_run: bool = False, force_version: Optional[str] = None) -> bool

Install a curated plugin.

Parameters:

  • plugin_name

    (str) –

    Name of the plugin to install

  • dry_run

    (bool, default: False ) –

    If True, only check what would be installed

  • force_version

    (Optional[str], default: None ) –

    Specific version/ref to install (overrides compatibility)

Returns:

  • bool

    True if installation succeeded

Raises:

Source code in medcat-v2/medcat/plugins/installer.py
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def install_plugin(
    self, 
    plugin_name: str,
    dry_run: bool = False,
    force_version: Optional[str] = None
) -> bool:
    """
    Install a curated plugin.

    Args:
        plugin_name: Name of the plugin to install
        dry_run: If True, only check what would be installed
        force_version: Specific version/ref to install (overrides compatibility)

    Returns:
        True if installation succeeded

    Raises:
        ValueError: If plugin is not in curated catalog
        RuntimeError: If no compatible version found
    """
    plugin_info = self.catalog.get_plugin(plugin_name)

    if not plugin_info:
        plugins = ', '.join(p.name for p in self.catalog.list_plugins())
        raise ValueError(
            f"Plugin '{plugin_name}' is not in the curated catalog.\n"
            f"Available plugins: {plugins}"
        )

    # Warn about authentication if needed
    if plugin_info.requires_auth:
        logger.warning(
            f"Plugin '{plugin_name}' requires authentication.\n"
            "Ensure you have configured Git credentials for "
            f"{plugin_info.source_spec.source}"
        )

    # Determine version/ref to install
    if force_version:
        version_spec = force_version
    else:
        version_spec = self.catalog.get_compatible_version(
            plugin_name,
            medcat.__version__
        )

        if not version_spec:
            raise RuntimeError(
                f"No compatible version of '{plugin_name}' found for "
                f"MedCAT {medcat.__version__}.\n"
                f"Visit {plugin_info.homepage} for more information."
            )

    spec = PluginInstallSpec(
        name=plugin_name,
        version_spec=version_spec,
        source_spec=plugin_info.source_spec,
    )

    logger.info(
        f"Installing {plugin_info.display_name} "
        f"({plugin_name}{version_spec})"
    )

    if plugin_info.source_spec.subdirectory:
        logger.info(f"  From subdirectory: {plugin_info.source_spec.subdirectory}")

    try:
        return self.installer.install(spec, dry_run=dry_run)
    except subprocess.CalledProcessError as e:
        # Provide helpful error messages
        if "subdirectory" in spec.to_pip_spec():
            logger.error(
                "Installation failed. This plugin is in a subdirectory.\n"
                "Common issues:\n"
                "  - The subdirectory path might be incorrect\n"
                f"  - The git ref '{version_spec}' might not exist\n"
                "  - setup.py/pyproject.toml might be missing in the subdirectory"
            )

        if plugin_info.requires_auth:
            logger.error(
                "Authentication might be required.\n"
                "Configure git credentials with:\n"
                "  git config --global credential.helper store"
            )

        raise