def infer_parents_subscript( subscript_node: nodes.Subscript, ctx: context.InferenceContext | None = None ) -> Iterator[bases.Instance]: if isinstance(subscript_node.slice, nodes.Const): path_cls = next(_extract_single_node(PATH_TEMPLATE).infer()) return iter([path_cls.instantiate_class()]) raise UseInferenceDefault
def test_infer_property(self): class_with_property = _extract_single_node(""" class Something: def getter(): return 5 asd = property(getter) #@ """) inferred_property = list(class_with_property.value.infer())[0] self.assertTrue(isinstance(inferred_property, objects.Property)) self.assertTrue(hasattr(inferred_property, "args"))
def test_recursion_during_inference(mocked) -> None: """Check that we don't crash if we hit the recursion limit during inference.""" node: nodes.Call = _extract_single_node( """ from module import something something() """ ) with pytest.raises(InferenceError) as error: next(node.infer()) assert error.value.message.startswith("RecursionError raised")
def test_sys_modules(self) -> None: """Test that we can gather the items of a living dict object.""" node = _extract_single_node( """ import sys sys.modules """ ) inferred = list(node.infer()) assert len(inferred) == 1 assert isinstance(inferred[0], nodes.Dict) assert inferred[0].items
def test_sys_builtin_module_names(self) -> None: """Test that we can gather the elements of a living tuple object.""" node = _extract_single_node( """ import sys sys.builtin_module_names """ ) inferred = list(node.infer()) assert len(inferred) == 1 assert isinstance(inferred[0], nodes.Tuple) assert inferred[0].elts
def infer_typing_alias( node: Call, ctx: context.InferenceContext | None = None ) -> Iterator[ClassDef]: """ Infers the call to _alias function Insert ClassDef, with same name as aliased class, in mro to simulate _GenericAlias. :param node: call node :param context: inference context """ if ( not isinstance(node.parent, Assign) or not len(node.parent.targets) == 1 or not isinstance(node.parent.targets[0], AssignName) ): raise UseInferenceDefault try: res = next(node.args[0].infer(context=ctx)) except StopIteration as e: raise InferenceError(node=node.args[0], context=context) from e assign_name = node.parent.targets[0] class_def = ClassDef( name=assign_name.name, lineno=assign_name.lineno, col_offset=assign_name.col_offset, parent=node.parent, ) if res != Uninferable and isinstance(res, ClassDef): # Only add `res` as base if it's a `ClassDef` # This isn't the case for `typing.Pattern` and `typing.Match` class_def.postinit(bases=[res], body=[], decorators=None) maybe_type_var = node.args[1] if ( not PY39_PLUS and not (isinstance(maybe_type_var, Tuple) and not maybe_type_var.elts) or PY39_PLUS and isinstance(maybe_type_var, Const) and maybe_type_var.value > 0 ): # If typing alias is subscriptable, add `__class_getitem__` to ClassDef func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE) class_def.locals["__class_getitem__"] = [func_to_add] else: # If not, make sure that `__class_getitem__` access is forbidden. # This is an issue in cases where the aliased class implements it, # but the typing alias isn't subscriptable. E.g., `typing.ByteString` for PY39+ _forbid_class_getitem_access(class_def) return iter([class_def])
def infer_pattern_match(node: nodes.Call, ctx: context.InferenceContext | None = None): """Infer re.Pattern and re.Match as classes. For PY39+ add `__class_getitem__`.""" class_def = nodes.ClassDef( name=node.parent.targets[0].name, lineno=node.lineno, col_offset=node.col_offset, parent=node.parent, ) if PY39_PLUS: func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE) class_def.locals["__class_getitem__"] = [func_to_add] return iter([class_def])
def infer_typedDict( # pylint: disable=invalid-name node: FunctionDef, ctx: context.InferenceContext | None = None ) -> Iterator[ClassDef]: """Replace TypedDict FunctionDef with ClassDef.""" class_def = ClassDef( name="TypedDict", lineno=node.lineno, col_offset=node.col_offset, parent=node.parent, ) class_def.postinit(bases=[extract_node("dict")], body=[], decorators=None) func_to_add = _extract_single_node("dict") class_def.locals["__call__"] = [func_to_add] return iter([class_def])
def infer_enum( node: nodes.Call, context: InferenceContext | None = None) -> Iterator[bases.Instance]: """Specific inference function for enum Call node.""" enum_meta = _extract_single_node(""" class EnumMeta(object): 'docstring' def __call__(self, node): class EnumAttribute(object): name = '' value = 0 return EnumAttribute() def __iter__(self): class EnumAttribute(object): name = '' value = 0 return [EnumAttribute()] def __reversed__(self): class EnumAttribute(object): name = '' value = 0 return (EnumAttribute, ) def __next__(self): return next(iter(self)) def __getitem__(self, attr): class Value(object): @property def name(self): return '' @property def value(self): return attr return Value() __members__ = [''] """) class_node = infer_func_form(node, [enum_meta], context=context, enum=True)[0] return iter([class_node.instantiate_class()])
def infer_typing_attr( node: Subscript, ctx: context.InferenceContext | None = None ) -> Iterator[ClassDef]: """Infer a typing.X[...] subscript""" try: value = next(node.value.infer()) # type: ignore[union-attr] # value shouldn't be None for Subscript. except (InferenceError, StopIteration) as exc: raise UseInferenceDefault from exc if not value.qname().startswith("typing.") or value.qname() in TYPING_ALIAS: # If typing subscript belongs to an alias handle it separately. raise UseInferenceDefault if isinstance(value, ClassDef) and value.qname() in { "typing.Generic", "typing.Annotated", "typing_extensions.Annotated", }: # typing.Generic and typing.Annotated (PY39) are subscriptable # through __class_getitem__. Since astroid can't easily # infer the native methods, replace them for an easy inference tip func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE) value.locals["__class_getitem__"] = [func_to_add] if ( isinstance(node.parent, ClassDef) and node in node.parent.bases and getattr(node.parent, "__cache", None) ): # node.parent.slots is evaluated and cached before the inference tip # is first applied. Remove the last result to allow a recalculation of slots cache = node.parent.__cache # type: ignore[attr-defined] # Unrecognized getattr if cache.get(node.parent.slots) is not None: del cache[node.parent.slots] return iter([value]) node = extract_node(TYPING_TYPE_TEMPLATE.format(value.qname().split(".")[-1])) return node.infer(context=ctx)
def infer_special_alias( node: Call, ctx: context.InferenceContext | None = None ) -> Iterator[ClassDef]: """Infer call to tuple alias as new subscriptable class typing.Tuple.""" if not ( isinstance(node.parent, Assign) and len(node.parent.targets) == 1 and isinstance(node.parent.targets[0], AssignName) ): raise UseInferenceDefault try: res = next(node.args[0].infer(context=ctx)) except StopIteration as e: raise InferenceError(node=node.args[0], context=context) from e assign_name = node.parent.targets[0] class_def = ClassDef( name=assign_name.name, parent=node.parent, ) class_def.postinit(bases=[res], body=[], decorators=None) func_to_add = _extract_single_node(CLASS_GETITEM_TEMPLATE) class_def.locals["__class_getitem__"] = [func_to_add] return iter([class_def])
def test_string_format(self, format_string: str) -> None: node: nodes.Call = _extract_single_node(format_string) inferred = next(node.infer()) assert isinstance(inferred, nodes.Const) assert inferred.value == "My name is Daniel, I'm 12"
def test_string_format_with_specs(self) -> None: node: nodes.Call = _extract_single_node( """"My name is {}, I'm {:.2f}".format("Daniel", 12)""") inferred = next(node.infer()) assert isinstance(inferred, nodes.Const) assert inferred.value == "My name is Daniel, I'm 12.00"
def test_string_format_uninferable(self, format_string: str) -> None: node: nodes.Call = _extract_single_node(format_string) inferred = next(node.infer()) assert inferred is util.Uninferable
def infer_old_typedDict( # pylint: disable=invalid-name node: ClassDef, ctx: context.InferenceContext | None = None ) -> Iterator[ClassDef]: func_to_add = _extract_single_node("dict") node.locals["__call__"] = [func_to_add] return iter([node])