Skip to content

Addon fails to register after update: check_module() ignores version pin, stale SpoutGL install crashes register() #64

Description

@gyomh

Disclosure

This issue was diagnosed and drafted with the help of an AI coding assistant (Claude) — it
reproduced the crash live in a running Blender session via MCP, traced it to the SpoutGL
version mismatch below, and drafted this write-up and patch. I reviewed and validated the
fix before posting.

For full transparency (yes, this is getting recursive): I then asked the AI to add this
disclosure paragraph after it had already written the issue, and then asked it to make the
disclosure paragraph funnier after that. So if this reads like a Wikipedia "citation needed"
chain, that's why. The bug and the fix are real either way — feel free to hold the patch to
the same standard you'd hold any other PR.

This part is the real me writing: I asked Claude to be a bit funny about it. It's not,
at all... but hey, maybe the world isn't so f***ed up right now.

Bug

After updating the addon (or on a machine that already had an older SpoutGL installed
from a previous version of this addon), register() throws:

Error: 'SpoutGL._spoutgl.SpoutReceiver' object has no attribute 'getSenderList'

Root cause

pip_importer.check_module() only checks whether the module is importable — it never
compares the installed version against the version pin declared via
add_package(Package(name, version="==0.1.1", ...)):

def check_module(package):
    ...
    try:
        module = sys.modules[package.module]
        if hasattr(module, '__path__'):
            package._location = module.__path__[0]
            package._registered = True   # <-- marks "installed" regardless of version
    except KeyError:
        ...

So if a stale SpoutGL==0.0.6 (which predates getSenderList(), added in 0.1.x) is already
on sys.path, auto_install_packages() never upgrades it, and register() crashes as soon
as it tries to enumerate senders.

Secondary issue (makes this harder to debug)

__init__.py::register() only wraps the operators/ui/keys import+register in
try/except ModuleNotFoundError. Any other exception (like the one above) propagates up
uncaught, but pip_importer.register() has already run and registered its own operator/
preferences classes. Since Blender's addon_enable then reports the addon as not enabled,
any subsequent retry immediately fails on:

Error: register_class(...): already registered as a subclass 'Pip_Update_package'

...which completely hides the real error (the SpoutGL version mismatch) behind a confusing,
unrelated one. A user hitting this has no way to see the actual crash without manually
unregistering the orphaned classes first.

Suggested fix

  1. In check_module, compare the installed version to package.version (e.g. via
    importlib.metadata.version(package.module)) and only set _registered = True if it
    satisfies the pin — otherwise let auto_install_packages() reinstall/upgrade it.
  2. In __init__.py::register(), catch broader exceptions around the whole registration
    block, call unregister() to roll back any partially-registered classes, and re-raise
    (or report) the original exception so the real cause is visible on the next attempt.

Happy to open a PR with both fixes if useful.

Suggested patch (pip_importer.py)
+from importlib import metadata as importlib_metadata
+
 def check_module(package):
     import sys, site

     p = site.USER_SITE
     if p not in sys.path:
         sys.path.append(p)
     try:
         module = sys.modules[package.module]
         if hasattr(module, '__path__'):
             package._location = module.__path__[0]
-            package._registered = True
+            package._registered = _version_satisfies(package)

     except KeyError:
         package._registered = False
         try:
             __import__(package.module)
             get_package_show(package)
+            package._registered = _version_satisfies(package)
         except ModuleNotFoundError:
             pass

     return package._registered

+def _version_satisfies(package):
+    if not package.version:
+        return True
+    try:
+        installed = importlib_metadata.version(package.name)
+    except importlib_metadata.PackageNotFoundError:
+        return True  # can't verify, don't block on it
+    required = package.version.lstrip("=<>~! ")
+    return installed == required

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions