diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b0986667..1b836a47 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,8 @@ Changelog Unreleased ++++++++++ +- Added dataclass-equivalent for attrs.define + 0.18.1 (2021-11-17) +++++++++++++++++++ diff --git a/jedi/plugins/stdlib.py b/jedi/plugins/stdlib.py index 17f1df3b..81803090 100644 --- a/jedi/plugins/stdlib.py +++ b/jedi/plugins/stdlib.py @@ -803,6 +803,15 @@ _implemented = { # For now this works at least better than Jedi trying to understand it. 'dataclass': _dataclass }, + # attrs exposes declaration interface roughly compatible with dataclasses + # via attrs.define, attrs.frozen and attrs.mutable + # https://www.attrs.org/en/stable/names.html + 'attr': { + 'define': _dataclass, + }, + 'attrs': { + 'define': _dataclass, + }, 'os.path': { 'dirname': _create_string_input_function(os.path.dirname), 'abspath': _create_string_input_function(os.path.abspath), diff --git a/setup.py b/setup.py index 0615d25a..675ca7ae 100755 --- a/setup.py +++ b/setup.py @@ -41,6 +41,7 @@ setup(name='jedi', # coloroma for colored debug output 'colorama', 'Django<3.1', # For now pin this. + 'attrs', ], 'qa': [ 'flake8==3.8.3', diff --git a/test/test_inference/test_signature.py b/test/test_inference/test_signature.py index 9ee8a55c..f3087783 100644 --- a/test/test_inference/test_signature.py +++ b/test/test_inference/test_signature.py @@ -355,6 +355,48 @@ def test_dataclass_signature(Script, skip_pre_python37, start, start_params): price, = sig.params[-2].infer() assert price.name == 'float' +@pytest.mark.parametrize( + 'start, start_params', [ + ['@define\nclass X:', []], + ['@frozen\nclass X:', []], + ['@define(eq=True)\nclass X:', []], + [dedent(''' + class Y(): + y: int + @define + class X(Y):'''), []], + [dedent(''' + @define + class Y(): + y: int + z = 5 + @define + class X(Y):'''), ['y']], + ] +) +def test_attrs_signature(Script, skip_pre_python37, start, start_params): + has_attrs = bool(Script('import attrs').infer()) + if not has_attrs: + raise pytest.skip("attrs needed in target environment to run this test") + + code = dedent(''' + name: str + foo = 3 + price: float + quantity: int = 0.0 + + X(''') + + # attrs exposes two namespaces + code = 'from attrs import define, frozen\n' + start + code + + sig, = Script(code).get_signatures() + assert [p.name for p in sig.params] == start_params + ['name', 'price', 'quantity'] + quantity, = sig.params[-1].infer() + assert quantity.name == 'int' + price, = sig.params[-2].infer() + assert price.name == 'float' + @pytest.mark.parametrize( 'stmt, expected', [