Skip to content

Callback execution silences ValueError #98

@edelooff

Description

@edelooff

I'm working on an application that uses venusian to help with resource registration, and it's excellent for that.

What I'm running into is a problem where ValueError raised by the registration machinery gets silenced by the Scanner during its scan execution. An minimal example that can be run to see the problem:

import sys
import venusian


def register(resource_name):
    def decorator(wrapped):
        def callback(scanner, name, ob):
            scanner.registry.add(resource_name, wrapped)
            return

        venusian.attach(wrapped, callback)
        return wrapped

    return decorator


@register(resource_name="unique-thing")
def f(): ...


@register(resource_name="unique-thing")
def g(): ...


class Registry:
    def __init__(self):
        self.resources = {}

    def add(self, name, callable):
        if name in self.resources:
            print(f"Already have a resource by {name=}")
            raise ValueError(f"Already have a resource by {name=}")
        self.resources[name] = callable


scanner = venusian.Scanner(registry=Registry())
scanner.scan(sys.modules["__main__"])  # prints, but does not raise a ValueError

This all works perfectly fine if I raise something like ConfigurationError rather than ValueError (exactly what Pyramid does), but this is unexpected at least. And I'd like to keep the introduction of new classes to a minimum.

Where the silencing comes from

The indiscriminate catching of ValueError in the scan is documented to be due to metaclass annoyances:

try:
    # Metaclasses might trick us by reaching this far and then
    # fail with too little values to unpack.
    for callback, cb_mod_name, liftid, scope in callbacks:
        if cb_mod_name != mod_name:
            # avoid processing objects that were imported into
            # this module but were not actually defined there
            continue
        callback(self, name, ob)
except ValueError:  # pragma: nocover
    continue

As far as I understand, this intends to catch and recover from an error in the destructuring assignment on the for-loop but unfortunately also squashes the same in the callback execution.

I haven't tested this other than on this application, but the following narrows the except block to just the tuple expansion and resolves my immediate problem:

# Metaclasses might trick us by reaching this far and then
# fail with too little values to unpack.
try:
    callback, cb_mod_name, liftid, scope = cb_tuple
except ValueError: # pragma: nocover
    continue
if cb_mod_name != mod_name:
    # avoid processing objects that were imported into
    # this module but were not actually defined there
    continue
callback(self, name, ob)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions