Skip to content

add_enabled_units leaves a registry context active when alias validation raises #20790

Open

Description

@dprada

Description

add_enabled_units can leave a unit-registry context active when enabling a conflicting alias raises. After an enclosing application's unit context exits, its custom definition unexpectedly remains enabled.

Steps to reproduce

In a fresh process, without PyUnitWizard or any other consumer library:

from astropy import units as u

# u.Unit('kcal') initially raises ValueError in the default registry.
bad = u.def_unit('kcal', 1000 * u.J)
with u.add_enabled_units([bad]):
    try:
        with u.add_enabled_units([u.imperial.kcal]):
            pass
    except ValueError:
        pass

# Expected: the default registry was restored, so this raises ValueError.
# Observed: it succeeds and prints 1000.0.
print(u.Unit('kcal').to(u.J))

Expected and observed behavior

The failed inner activation should preserve the pre-call registry, and the successful outer context should restore the original registry on exit. Instead the custom kcal definition remains active after the outer context exits.

Source inspection suggests that the public context factory constructs/pushes _UnitContext before validating the supplied aliases. If validation raises, the caller never receives the new context manager to unwind; the enclosing application's exit then pops the inner level instead. The current development source appears to retain that construction order; I have not executed this reproducer on a development checkout.

Versions and consumer scope

The Astropy-only reproducer above was executed with Python 3.14.7 / Astropy 8.0.1. The corresponding consumer regression also executes with Python 3.11.18 / Astropy 6.1.7. The finding arose while implementing bounded calorie interoperability in uibcdf/pyunitwizard#137. PyUnitWizard now prevalidates native caloric aliases before entering the context, so it avoids the failing constructor path without mutating private registries. This report concerns the public context's exception-restoration behavior, not whether imperial units should be enabled by default.

No external scientific dataset or credentials are involved.

Activity

  1. pllim commented on Nov 5, 2026

    @pllim
    Member

    Indeed, I see this behavior in astropy v8.1.dev as well.

    with u.add_enabled_units([bad]):
        with u.add_enabled_units([u.imperial.kcal]):   # <--- Error
            print("inside", u.Unit("kcal"))  # <--- Never runs
        print("outside", u.Unit("kcal"))  # <--- Never runs
    print("ooops", u.Unit("kcal"))   # <--- kcal still there

    ValueError: Object with name 'kcal' already exists in namespace. Filter the set of units to avoid name clashes before enabling them.

  2. pllim commented on Nov 5, 2026

    @pllim
    Member

    I am actually not sure if it is ever designed to be used nested like that. It changes the global registry in real time. And even though astropy.units.core.add_enabled_units() says you can use it with with, it is not marked explicitly as a context manager, nor has context enter/exit coded in, nor does it yield. @mhvk , maybe you can clarify?

  3. mhvk commented on Nov 6, 2026

    @mhvk
    Contributor

    Thanks for reporting the bug (and I agree it is a bug). You are right about where the problem is. The simplest solution is that of #20795, but it means basically working around what really is a design flaw. I'd rather update _UnitContext to be more helpful, so that, e.g., code would become something like,

    class _UnitContext(ContextDecorator):
        def __init__(self, init=[], equivalencies=[], command=None, *args, **kwargs):
            new_registry = _UnitRegistry(init=init, equivalencies=equivalencies)
            getattr(new_registry, command)(*args, **kwargs)
            _unit_registries.append(new_registry)
    ...
    
    
    def add_enabled_units(units: object) -> _UnitContext:
        return _UnitContext(get_current_unit_registry(), command="add_enabled_units), units)

    Note that I didn't test it, but it would seem cleaner.

  4. pllim commented on Nov 7, 2026

    @pllim
    Member

    @mhvk does #20803 look about right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions