Skip to content

repred should require an explicit getter for ambiguous __init__ field mappings #28

@pomponchik

Description

@pomponchik

repred reads __init__ source/AST and infers which instance attributes represent constructor parameters. We found a case where this inference appears ambiguous, but no exception is raised.

Example:

@repred(positionals=['slot_function'])
class Slot:
    def __init__(self, slot_function, slot_name):
        self.slot_function = slot_function
        self.slot_name = slot_name if slot_name is not None else slot_function.__name__

Here self.slot_name can be populated from either slot_name or slot_function.__name__. Without an explicit getter, repred silently uses self.slot_name as the representation value for the slot_name parameter. This changes the repr from the intended "only show explicitly provided slot_name" behavior to showing the fallback value as if it had been provided by the caller.

It would be safer if repred raised an exception when more than one constructor parameter can contribute to a represented field, unless the user provides an explicit getter for that field.

Possible minimal regression test:

import pytest

from printo import repred


def test_repred_rejects_ambiguous_field_mapping_without_getter():
    with pytest.raises(Exception):  # ideally a dedicated printo ambiguity error
        @repred(positionals=['function'])
        class Example:
            def __init__(self, function, name):
                self.function = function
                self.name = name if name is not None else function.__name__


def test_repred_accepts_ambiguous_field_mapping_with_explicit_getter():
    @repred(
        positionals=['function'],
        getters={'name': lambda x: x.declared_name},
    )
    class Example:
        def __init__(self, function, name):
            self.function = function
            self.declared_name = name
            self.name = name if name is not None else function.__name__

    def default_name():
        ...

    assert repr(Example(default_name, None)) == 'Example(default_name)'
    assert repr(Example(default_name, 'custom')) == "Example(default_name, name='custom')"

The first test should fail against the current behavior because repred accepts the ambiguous mapping and produces a repr using the normalized fallback value.

Expected behavior:

@repred(
    positionals=['slot_function'],
    getters={'slot_name': lambda x: x.declared_slot_name},
)
class Slot:
    ...

In other words, ambiguous AST mappings should fail closed and require the class author to spell out the intended representation value.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions