pola-rs/polars · error · ValueError

extension type '{ext_name}' is already registered

Error message

extension type '{ext_name}' is already registered

What it means

polars' extension-type registry is process-global, and register_extension_type refuses duplicate names: registering an ext_name already present in _REGISTRY raises ValueError. A second registration of the same name — even from re-imported or re-executed code — trips it.

Source

Thrown at py-polars/src/polars/datatypes/extension.py:30


@unstable()
def register_extension_type(
    ext_name: str,
    ext_class: type[dt.BaseExtension] | None = None,
    *,
    as_storage: bool = False,
) -> None:
    """
    Register the extension type for the given extension name.

    .. warning::
        This functionality is currently considered **unstable**. It may be
        changed at any point without it being considered a breaking change.
    """
    if "ext_name" in _REGISTRY:
        msg = f"extension type '{ext_name}' is already registered"
        raise ValueError(msg)

    if as_storage:
        assert ext_class is None, "cannot specify ext_class when as_storage is True"
        _REGISTRY[ext_name] = "storage"
        with contextlib.suppress(NameError):  # _plr module may be unavailable
            _register_extension_type(ext_name, None)
    else:
        assert not as_storage, "as_storage must be False when ext_class is provided"
        assert isinstance(ext_class, type)
        assert issubclass(ext_class, dt.BaseExtension)
        _REGISTRY[ext_name] = ext_class
        with contextlib.suppress(NameError):  # _plr module may be unavailable
            _register_extension_type(ext_name, ext_class)


@unstable()
def unregister_extension_type(ext_name: str) -> None:
    """

View on GitHub (pinned to df599052da)

Solutions

  1. Unregister first: polars.datatypes.extension.unregister_extension_type('my_ext'), then re-register
  2. Register once at module import and never re-run that code; in notebooks, guard the cell or track registration state
  3. In tests, use a fixture that unregisters in teardown (try/finally) so each test starts clean

Example fix

# before
register_extension_type('my_ext', MyExtDtype)  # second call: ValueError

# after
from polars.datatypes.extension import register_extension_type, unregister_extension_type
try:
    unregister_extension_type('my_ext')
except Exception:
    pass
register_extension_type('my_ext', MyExtDtype)
Defensive patterns

Strategy: validation

Validate before calling

from polars.datatypes.extension import register_extension_type, unregister_extension_type

def register_once(ext_name: str, ext_class=None, **kw) -> None:
    try:
        unregister_extension_type(ext_name)
    except Exception:
        pass  # not registered yet
    register_extension_type(ext_name, ext_class, **kw)

register_once('my_ext', MyExtDtype)

Try / catch

from polars.datatypes.extension import register_extension_type

try:
    register_extension_type('my_ext', MyExtDtype)
except ValueError as e:
    if 'already registered' in str(e):
        pass  # idempotent: already set up in this process
    else:
        raise

Prevention

When it happens

Trigger: Calling polars.datatypes.extension.register_extension_type('my_ext', ...) twice in one process: re-running a notebook registration cell, importlib.reload of a module that registers at import time, or a library registered from two code paths.

Common situations: Jupyter workflows with re-executed cells or %autoreload; test suites that register per-test without teardown; plugins that register on import and get imported twice via different paths.

Related errors


AI-assisted analysis of pola-rs/polars@df599052da (2026-08-16). Data as JSON: /api/errors/66a40a0c8934e66e. Report an issue: GitHub.