Skip to content

Decorator @sentry_sdk.trace is not compatible with Click (edge case) #3939

@alexanderilyin

Description

@alexanderilyin

Environment

SaaS (https://sentry.io/)

Steps to Reproduce

Given custom command loader:

class Loader(RichGroup):

    COMMANDS_FOLDER = os.path.join(os.path.dirname(__file__), "commands")

    def list_commands(self, ctx) -> list[str]:
        rv = []
        try:
            for filename in os.listdir(self.COMMANDS_FOLDER):
                if filename.endswith(".py") and filename != "__init__.py":
                    rv.append(filename[:-3])
            rv.sort()
            return rv
        except OSError as e:
            logging.error("Failed to list commands: %s", e)
            return []

    def get_command(self, ctx, name: str) -> click.Command:
        if not name.isalnum():
            raise click.ClickException("Invalid command name")

        ns = {}
        fn = os.path.join(self.COMMANDS_FOLDER, name + ".py")
        logging.debug("Loading %s", fn)

        if not os.path.exists(fn) or not os.path.isfile(fn):
            raise click.ClickException(f"Command '{name}' not found")

        try:
            with open(fn) as f:
                exec(f.read(), ns, ns)
            if "cli" not in ns:
                raise click.ClickException(f"Command '{name}' is invalid: missing 'cli' attribute")
            return ns["cli"]
        except OSError as e:
            logging.exception(e)
            raise click.ClickException(f"Failed to load command '{name}'") from e
        except SyntaxError as e:
            logging.exception(e)
            raise click.ClickException(f"Command '{name}' contains invalid Python code") from e

When using decorator on a command loaded by loader:

@click.command(help="...")
@sentry_sdk.trace
def cli() -> None:
    pass

Workaround

.../lib/python3.10/site-packages/sentry_sdk/utils.py

 # Python 3: methods, functions, classes
 if func_qualname is not None:
-    if hasattr(func, "__module__"):
+    if hasattr(func, "__module__") and func.__module__ is not None:
         func_qualname = func.__module__ + "." + func_qualname
     func_qualname = prefix + func_qualname + suffix

Expected Result

No exception is raised.

Actual Result

Exception is raised:

⬢ [Docker] ❯ pc status
Traceback (most recent call last):
  File "/home/vscode/.cache/pypoetry/virtualenvs/partcad-dev-_yoEiCkE-py3.10/bin/pc", line 6, in <module>
    sys.exit(cli())
  File "/home/vscode/.cache/pypoetry/virtualenvs/partcad-dev-_yoEiCkE-py3.10/lib/python3.10/site-packages/rich_click/rich_command.py", line 367, in __call__
    return super().__call__(*args, **kwargs)
  File "/home/vscode/.cache/pypoetry/virtualenvs/partcad-dev-_yoEiCkE-py3.10/lib/python3.10/site-packages/click/core.py", line 1161, in __call__
    return self.main(*args, **kwargs)
  File "/home/vscode/.cache/pypoetry/virtualenvs/partcad-dev-_yoEiCkE-py3.10/lib/python3.10/site-packages/rich_click/rich_command.py", line 152, in main
    rv = self.invoke(ctx)
  File "/home/vscode/.cache/pypoetry/virtualenvs/partcad-dev-_yoEiCkE-py3.10/lib/python3.10/site-packages/click/core.py", line 1697, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/home/vscode/.cache/pypoetry/virtualenvs/partcad-dev-_yoEiCkE-py3.10/lib/python3.10/site-packages/click/core.py", line 1443, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/home/vscode/.cache/pypoetry/virtualenvs/partcad-dev-_yoEiCkE-py3.10/lib/python3.10/site-packages/click/core.py", line 788, in invoke
    return __callback(*args, **kwargs)
  File "/home/vscode/.cache/pypoetry/virtualenvs/partcad-dev-_yoEiCkE-py3.10/lib/python3.10/site-packages/sentry_sdk/tracing_utils.py", line 705, in func_with_tracing
    qualname_from_function(func),
  File "/home/vscode/.cache/pypoetry/virtualenvs/partcad-dev-_yoEiCkE-py3.10/lib/python3.10/site-packages/sentry_sdk/utils.py", line 1505, in qualname_from_function
    func_qualname = func.__module__ + "." + func_qualname
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'
Sentry is attempting to send 3 pending events
Waiting up to 1 seconds
Press Ctrl-C to quit

Product Area

APIs

Link

No response

DSN

No response

Version

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No fields configured for issues without a type.

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions