Skip to content

Logger injection fails for functions with positional-only parameters #166

Description

@Bartman0

Summary

annotate_logs injects annotated_logger as a keyword argument unless the decorated function has a *args parameter. A function with positional-only parameters and no *args therefore raises TypeError on every call.

The code that makes this decision is _determine_signature_adjustments in annotated_logger/__init__.py:

by_index = False  # pragma: no mutate
# Check for a var positional or positional only
# If present that means we'll have values in args when invoking
# but, if not everything will be in kwargs
for v in written_signature.parameters.values():
    if v.kind == inspect.Parameter.VAR_POSITIONAL:
        by_index = True

The comment says "var positional or positional only", but the loop only tests for VAR_POSITIONAL. POSITIONAL_ONLY is never checked, so by_index stays False and _inject_by_kwarg is used.

Reproduction

from annotated_logger import AnnotatedAdapter, AnnotatedLogger

annotated_logger = AnnotatedLogger()
annotate_logs = annotated_logger.annotate_logs


@annotate_logs(_typing_self=False, _typing_requested=True)
def positional_only(annotated_logger: AnnotatedAdapter, a: int, /, b: int) -> int:
    """Positional-only parameters, no *args."""
    return a * b


@annotate_logs(_typing_self=False, _typing_requested=True)
def positional_only_with_var_args(
    annotated_logger: AnnotatedAdapter, a: int, /, *rest: int
) -> int:
    """Same, but the *args flips injection to by-index."""
    return a * sum(rest)


print("with *args ->", positional_only_with_var_args(2, 3, 4))  # works
print("without *args ->", positional_only(3, 4))                # raises

Actual result:

TypeError: positional_only() got some positional-only arguments passed as keyword arguments: 'annotated_logger'

raised from wrap_function at annotated_logger/__init__.py:762. The start log is emitted first, then the call fails and is logged as Uncaught Exception in logged function, so the failure surfaces as a runtime error in the decorated function rather than at decoration time.

Expected: the call succeeds and logs start/success, as it does for the *args variant.

Suggested fix

Include POSITIONAL_ONLY in the check, matching what the comment already describes:

if v.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.POSITIONAL_ONLY):
    by_index = True

I haven't checked how _inject_by_index behaves for every combination of self/cls, provided=True, and positional-only parameters, so the fix may need more than the one-line change, plus test cases in test/test_decorator.py alongside the existing positional_only example (which currently uses keyword-only parameters after *, not positional-only ones before /).

Environment

  • annotated-logger 1.3.5 (VERSION in annotated_logger/__init__.py)
  • Python 3.13.12, makefun 1.16.0, macOS

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions