From 40e1e3f560fdb9d7afd85d9337cf767158f57c0b Mon Sep 17 00:00:00 2001 From: Alex Ford Date: Mon, 10 Oct 2022 11:15:27 -0700 Subject: [PATCH 1/5] Extend dataclass constructor hinting to attrs next-gen apis. Trivially extends dataclass constructor hinting to attrs next-gen APIs. This will stumble in cases where attrs extends beyond the standard dataclasses API, such as complex use of defaults, converters, et al. However, it likely covers the vast majority of cases which fall solidly in the intersection of the two APIs. Extension beyond these cases could use [PEP0681 dataclass_transforms], however this is definitely a problem for another day. [PEP0681 dataclass_transforms]: https://peps.python.org/pep-0681/ https://github.com/davidhalter/jedi/issues/1835 --- jedi/plugins/stdlib.py | 9 +++++++ test/test_inference/test_signature.py | 39 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) 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/test/test_inference/test_signature.py b/test/test_inference/test_signature.py index 9ee8a55c..b471a19d 100644 --- a/test/test_inference/test_signature.py +++ b/test/test_inference/test_signature.py @@ -355,6 +355,45 @@ 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:', []], + ['@mutable\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): + 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', [ From 239d9e0b223172b90e08252daf157457ba031830 Mon Sep 17 00:00:00 2001 From: Alex Ford Date: Mon, 10 Oct 2022 11:27:46 -0700 Subject: [PATCH 2/5] Add note to changelog --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) 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) +++++++++++++++++++ From eaa66b3dbb3c652cfaea6a813bfb96d6d498e844 Mon Sep 17 00:00:00 2001 From: Alex Ford Date: Tue, 11 Oct 2022 11:12:34 -0700 Subject: [PATCH 3/5] Update setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) 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', From e15f51ecc1a50b810b803c4970c460e213db74f3 Mon Sep 17 00:00:00 2001 From: Alex Ford Date: Tue, 11 Oct 2022 17:55:57 -0700 Subject: [PATCH 4/5] Remove mutable from attrs signature tests --- test/test_inference/test_signature.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_inference/test_signature.py b/test/test_inference/test_signature.py index b471a19d..0c1550d8 100644 --- a/test/test_inference/test_signature.py +++ b/test/test_inference/test_signature.py @@ -359,7 +359,6 @@ def test_dataclass_signature(Script, skip_pre_python37, start, start_params): 'start, start_params', [ ['@define\nclass X:', []], ['@frozen\nclass X:', []], - ['@mutable\nclass X:', []], ['@define(eq=True)\nclass X:', []], [dedent(''' class Y(): From 4d1e00c3abf175a567d0c6f221cfd510465979f9 Mon Sep 17 00:00:00 2001 From: Alex Ford Date: Wed, 12 Oct 2022 14:14:00 -0700 Subject: [PATCH 5/5] Skip if attrs not in target environment. Add check for attrs in test environment and skip if not installed. This is patterned off the existing django tests. --- test/test_inference/test_signature.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/test_inference/test_signature.py b/test/test_inference/test_signature.py index 0c1550d8..f3087783 100644 --- a/test/test_inference/test_signature.py +++ b/test/test_inference/test_signature.py @@ -375,6 +375,10 @@ def test_dataclass_signature(Script, skip_pre_python37, start, start_params): ] ) 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