blob: b82dfa427fb8d491524ac4c029d55a2c9fd8d80a [file]
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
# For details: https://github.com/pylint-dev/astroid/blob/main/LICENSE
# Copyright (c) https://github.com/pylint-dev/astroid/blob/main/CONTRIBUTORS.txt
from __future__ import annotations
import unittest
from astroid import builder, nodes, util
from astroid.exceptions import AttributeInferenceError
class NamedTupleTest(unittest.TestCase):
def test_namedtuple_base(self) -> None:
klass = builder.extract_node("""
from collections import namedtuple
class X(namedtuple("X", ["a", "b", "c"])):
pass
""")
assert isinstance(klass, nodes.ClassDef)
self.assertEqual(
[anc.name for anc in klass.ancestors()], ["X", "tuple", "object"]
)
# See: https://github.com/pylint-dev/pylint/issues/5982
self.assertNotIn("X", klass.locals)
for anc in klass.ancestors():
self.assertFalse(anc.parent is None)
def test_namedtuple_inference(self) -> None:
klass = builder.extract_node("""
from collections import namedtuple
name = "X"
fields = ["a", "b", "c"]
class X(namedtuple(name, fields)):
pass
""")
assert isinstance(klass, nodes.ClassDef)
base = next(base for base in klass.ancestors() if base.name == "X")
self.assertSetEqual({"a", "b", "c"}, set(base.instance_attrs))
def test_namedtuple_inference_failure(self) -> None:
klass = builder.extract_node("""
from collections import namedtuple
def foo(fields):
return __(namedtuple("foo", fields))
""")
self.assertIs(util.Uninferable, next(klass.infer()))
def test_namedtuple_advanced_inference(self) -> None:
# urlparse return an object of class ParseResult, which has a
# namedtuple call and a mixin as base classes
result = builder.extract_node("""
from urllib.parse import urlparse
result = __(urlparse('gopher://'))
""")
instance = next(result.infer())
self.assertGreaterEqual(len(instance.getattr("scheme")), 1)
self.assertGreaterEqual(len(instance.getattr("port")), 1)
with self.assertRaises(AttributeInferenceError):
instance.getattr("foo")
self.assertGreaterEqual(len(instance.getattr("geturl")), 1)
self.assertEqual(instance.name, "ParseResult")
def test_namedtuple_instance_attrs(self) -> None:
result = builder.extract_node("""
from collections import namedtuple
namedtuple('a', 'a b c')(1, 2, 3) #@
""")
inferred = next(result.infer())
for name, attr in inferred.instance_attrs.items():
self.assertEqual(attr[0].attrname, name)
def test_namedtuple_uninferable_fields(self) -> None:
node = builder.extract_node("""
x = [A] * 2
from collections import namedtuple
l = namedtuple('a', x)
l(1)
""")
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred)
def test_namedtuple_access_class_fields(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "field other")
Tuple #@
""")
inferred = next(node.infer())
self.assertIn("field", inferred.locals)
self.assertIn("other", inferred.locals)
def test_namedtuple_rename_keywords(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "abc def", rename=True)
Tuple #@
""")
inferred = next(node.infer())
self.assertIn("abc", inferred.locals)
self.assertIn("_1", inferred.locals)
def test_namedtuple_rename_duplicates(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "abc abc abc", rename=True)
Tuple #@
""")
inferred = next(node.infer())
self.assertIn("abc", inferred.locals)
self.assertIn("_1", inferred.locals)
self.assertIn("_2", inferred.locals)
def test_namedtuple_rename_uninferable(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "a b c", rename=UNINFERABLE)
Tuple #@
""")
inferred = next(node.infer())
self.assertIn("a", inferred.locals)
self.assertIn("b", inferred.locals)
self.assertIn("c", inferred.locals)
def test_namedtuple_func_form(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple(typename="Tuple", field_names="a b c", rename=UNINFERABLE)
Tuple #@
""")
inferred = next(node.infer())
self.assertEqual(inferred.name, "Tuple")
self.assertIn("a", inferred.locals)
self.assertIn("b", inferred.locals)
self.assertIn("c", inferred.locals)
def test_namedtuple_func_form_args_and_kwargs(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", field_names="a b c", rename=UNINFERABLE)
Tuple #@
""")
inferred = next(node.infer())
self.assertEqual(inferred.name, "Tuple")
self.assertIn("a", inferred.locals)
self.assertIn("b", inferred.locals)
self.assertIn("c", inferred.locals)
def test_namedtuple_bases_are_actually_names_not_nodes(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", field_names="a b c", rename=UNINFERABLE)
Tuple #@
""")
inferred = next(node.infer())
self.assertIsInstance(inferred, nodes.ClassDef)
self.assertIsInstance(inferred.bases[0], nodes.Name)
self.assertEqual(inferred.bases[0].name, "tuple")
def test_invalid_label_does_not_crash_inference(self) -> None:
code = """
import collections
a = collections.namedtuple( 'a', ['b c'] )
a
"""
node = builder.extract_node(code)
inferred = next(node.infer())
assert isinstance(inferred, nodes.ClassDef)
assert "b" not in inferred.locals
assert "c" not in inferred.locals
def test_no_rename_duplicates_does_not_crash_inference(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "abc abc")
Tuple #@
""")
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred) # would raise ValueError
def test_no_rename_keywords_does_not_crash_inference(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "abc def")
Tuple #@
""")
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred) # would raise ValueError
def test_no_rename_nonident_does_not_crash_inference(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "123 456")
Tuple #@
""")
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred) # would raise ValueError
def test_no_rename_underscore_does_not_crash_inference(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", "_1")
Tuple #@
""")
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred) # would raise ValueError
def test_invalid_typename_does_not_crash_inference(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("123", "abc")
Tuple #@
""")
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred) # would raise ValueError
def test_keyword_typename_does_not_crash_inference(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("while", "abc")
Tuple #@
""")
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred) # would raise ValueError
def test_typeerror_does_not_crash_inference(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
Tuple = namedtuple("Tuple", [123, 456])
Tuple #@
""")
inferred = next(node.infer())
# namedtuple converts all arguments to strings so these should be too
# and catch on the isidentifier() check
self.assertIs(util.Uninferable, inferred)
def test_pathological_str_does_not_crash_inference(self) -> None:
node = builder.extract_node("""
from collections import namedtuple
class Invalid:
def __str__(self):
return 123 # will raise TypeError
Tuple = namedtuple("Tuple", [Invalid()])
Tuple #@
""")
inferred = next(node.infer())
self.assertIs(util.Uninferable, inferred)
def test_name_as_typename(self) -> None:
"""Reported in https://github.com/pylint-dev/pylint/issues/7429 as a crash."""
good_node, good_node_two, bad_node = builder.extract_node("""
import collections
collections.namedtuple(typename="MyTuple", field_names=["birth_date", "city"]) #@
collections.namedtuple("MyTuple", field_names=["birth_date", "city"]) #@
collections.namedtuple(["birth_date", "city"], typename="MyTuple") #@
""")
good_inferred = next(good_node.infer())
assert isinstance(good_inferred, nodes.ClassDef)
good_node_two_inferred = next(good_node_two.infer())
assert isinstance(good_node_two_inferred, nodes.ClassDef)
bad_node_inferred = next(bad_node.infer())
assert bad_node_inferred == util.Uninferable