123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857 |
- # Copyright (c) 2016-2020 Claudiu Popa <pcmanticore@gmail.com>
- # Copyright (c) 2016 Derek Gustafson <degustaf@gmail.com>
- # Copyright (c) 2017-2018 Bryce Guinta <bryce.paul.guinta@gmail.com>
- # Copyright (c) 2017 Ceridwen <ceridwenv@gmail.com>
- # Copyright (c) 2017 Calen Pennington <cale@edx.org>
- # Copyright (c) 2018 Ville Skyttä <ville.skytta@iki.fi>
- # Copyright (c) 2018 Nick Drozd <nicholasdrozd@gmail.com>
- # Copyright (c) 2020-2021 hippo91 <guillaume.peillex@gmail.com>
- # Copyright (c) 2021 Pierre Sassoulas <pierre.sassoulas@gmail.com>
- # Copyright (c) 2021 Daniël van Noord <13665637+DanielNoord@users.noreply.github.com>
- # Copyright (c) 2021 Marc Mueller <30130371+cdce8p@users.noreply.github.com>
- # Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
- # For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
- """
- Data object model, as per https://docs.python.org/3/reference/datamodel.html.
- This module describes, at least partially, a data object model for some
- of astroid's nodes. The model contains special attributes that nodes such
- as functions, classes, modules etc have, such as __doc__, __class__,
- __module__ etc, being used when doing attribute lookups over nodes.
- For instance, inferring `obj.__class__` will first trigger an inference
- of the `obj` variable. If it was successfully inferred, then an attribute
- `__class__ will be looked for in the inferred object. This is the part
- where the data model occurs. The model is attached to those nodes
- and the lookup mechanism will try to see if attributes such as
- `__class__` are defined by the model or not. If they are defined,
- the model will be requested to return the corresponding value of that
- attribute. Thus the model can be viewed as a special part of the lookup
- mechanism.
- """
- import itertools
- import os
- import pprint
- import types
- from functools import lru_cache
- from typing import TYPE_CHECKING, Optional
- import astroid
- from astroid import util
- from astroid.context import InferenceContext, copy_context
- from astroid.exceptions import AttributeInferenceError, InferenceError, NoDefault
- from astroid.manager import AstroidManager
- from astroid.nodes import node_classes
- objects = util.lazy_import("objects")
- if TYPE_CHECKING:
- from astroid.objects import Property
- IMPL_PREFIX = "attr_"
- def _dunder_dict(instance, attributes):
- obj = node_classes.Dict(parent=instance)
- # Convert the keys to node strings
- keys = [
- node_classes.Const(value=value, parent=obj) for value in list(attributes.keys())
- ]
- # The original attribute has a list of elements for each key,
- # but that is not useful for retrieving the special attribute's value.
- # In this case, we're picking the last value from each list.
- values = [elem[-1] for elem in attributes.values()]
- obj.postinit(list(zip(keys, values)))
- return obj
- class ObjectModel:
- def __init__(self):
- self._instance = None
- def __repr__(self):
- result = []
- cname = type(self).__name__
- string = "%(cname)s(%(fields)s)"
- alignment = len(cname) + 1
- for field in sorted(self.attributes()):
- width = 80 - len(field) - alignment
- lines = pprint.pformat(field, indent=2, width=width).splitlines(True)
- inner = [lines[0]]
- for line in lines[1:]:
- inner.append(" " * alignment + line)
- result.append(field)
- return string % {
- "cname": cname,
- "fields": (",\n" + " " * alignment).join(result),
- }
- def __call__(self, instance):
- self._instance = instance
- return self
- def __get__(self, instance, cls=None):
- # ObjectModel needs to be a descriptor so that just doing
- # `special_attributes = SomeObjectModel` should be enough in the body of a node.
- # But at the same time, node.special_attributes should return an object
- # which can be used for manipulating the special attributes. That's the reason
- # we pass the instance through which it got accessed to ObjectModel.__call__,
- # returning itself afterwards, so we can still have access to the
- # underlying data model and to the instance for which it got accessed.
- return self(instance)
- def __contains__(self, name):
- return name in self.attributes()
- @lru_cache(maxsize=None)
- def attributes(self):
- """Get the attributes which are exported by this object model."""
- return [
- obj[len(IMPL_PREFIX) :] for obj in dir(self) if obj.startswith(IMPL_PREFIX)
- ]
- def lookup(self, name):
- """Look up the given *name* in the current model
- It should return an AST or an interpreter object,
- but if the name is not found, then an AttributeInferenceError will be raised.
- """
- if name in self.attributes():
- return getattr(self, IMPL_PREFIX + name)
- raise AttributeInferenceError(target=self._instance, attribute=name)
- class ModuleModel(ObjectModel):
- def _builtins(self):
- builtins_ast_module = AstroidManager().builtins_module
- return builtins_ast_module.special_attributes.lookup("__dict__")
- @property
- def attr_builtins(self):
- return self._builtins()
- @property
- def attr___path__(self):
- if not self._instance.package:
- raise AttributeInferenceError(target=self._instance, attribute="__path__")
- path_objs = [
- node_classes.Const(
- value=path
- if not path.endswith("__init__.py")
- else os.path.dirname(path),
- parent=self._instance,
- )
- for path in self._instance.path
- ]
- container = node_classes.List(parent=self._instance)
- container.postinit(path_objs)
- return container
- @property
- def attr___name__(self):
- return node_classes.Const(value=self._instance.name, parent=self._instance)
- @property
- def attr___doc__(self):
- return node_classes.Const(value=self._instance.doc, parent=self._instance)
- @property
- def attr___file__(self):
- return node_classes.Const(value=self._instance.file, parent=self._instance)
- @property
- def attr___dict__(self):
- return _dunder_dict(self._instance, self._instance.globals)
- @property
- def attr___package__(self):
- if not self._instance.package:
- value = ""
- else:
- value = self._instance.name
- return node_classes.Const(value=value, parent=self._instance)
- # These are related to the Python 3 implementation of the
- # import system,
- # https://docs.python.org/3/reference/import.html#import-related-module-attributes
- @property
- def attr___spec__(self):
- # No handling for now.
- return node_classes.Unknown()
- @property
- def attr___loader__(self):
- # No handling for now.
- return node_classes.Unknown()
- @property
- def attr___cached__(self):
- # No handling for now.
- return node_classes.Unknown()
- class FunctionModel(ObjectModel):
- @property
- def attr___name__(self):
- return node_classes.Const(value=self._instance.name, parent=self._instance)
- @property
- def attr___doc__(self):
- return node_classes.Const(value=self._instance.doc, parent=self._instance)
- @property
- def attr___qualname__(self):
- return node_classes.Const(value=self._instance.qname(), parent=self._instance)
- @property
- def attr___defaults__(self):
- func = self._instance
- if not func.args.defaults:
- return node_classes.Const(value=None, parent=func)
- defaults_obj = node_classes.Tuple(parent=func)
- defaults_obj.postinit(func.args.defaults)
- return defaults_obj
- @property
- def attr___annotations__(self):
- obj = node_classes.Dict(parent=self._instance)
- if not self._instance.returns:
- returns = None
- else:
- returns = self._instance.returns
- args = self._instance.args
- pair_annotations = itertools.chain(
- zip(args.args or [], args.annotations),
- zip(args.kwonlyargs, args.kwonlyargs_annotations),
- zip(args.posonlyargs or [], args.posonlyargs_annotations),
- )
- annotations = {
- arg.name: annotation for (arg, annotation) in pair_annotations if annotation
- }
- if args.varargannotation:
- annotations[args.vararg] = args.varargannotation
- if args.kwargannotation:
- annotations[args.kwarg] = args.kwargannotation
- if returns:
- annotations["return"] = returns
- items = [
- (node_classes.Const(key, parent=obj), value)
- for (key, value) in annotations.items()
- ]
- obj.postinit(items)
- return obj
- @property
- def attr___dict__(self):
- return node_classes.Dict(parent=self._instance)
- attr___globals__ = attr___dict__
- @property
- def attr___kwdefaults__(self):
- def _default_args(args, parent):
- for arg in args.kwonlyargs:
- try:
- default = args.default_value(arg.name)
- except NoDefault:
- continue
- name = node_classes.Const(arg.name, parent=parent)
- yield name, default
- args = self._instance.args
- obj = node_classes.Dict(parent=self._instance)
- defaults = dict(_default_args(args, obj))
- obj.postinit(list(defaults.items()))
- return obj
- @property
- def attr___module__(self):
- return node_classes.Const(self._instance.root().qname())
- @property
- def attr___get__(self):
- # pylint: disable=import-outside-toplevel; circular import
- from astroid import bases
- func = self._instance
- class DescriptorBoundMethod(bases.BoundMethod):
- """Bound method which knows how to understand calling descriptor binding."""
- def implicit_parameters(self):
- # Different than BoundMethod since the signature
- # is different.
- return 0
- def infer_call_result(self, caller, context=None):
- if len(caller.args) > 2 or len(caller.args) < 1:
- raise InferenceError(
- "Invalid arguments for descriptor binding",
- target=self,
- context=context,
- )
- context = copy_context(context)
- try:
- cls = next(caller.args[0].infer(context=context))
- except StopIteration as e:
- raise InferenceError(context=context, node=caller.args[0]) from e
- if cls is astroid.Uninferable:
- raise InferenceError(
- "Invalid class inferred", target=self, context=context
- )
- # For some reason func is a Node that the below
- # code is not expecting
- if isinstance(func, bases.BoundMethod):
- yield func
- return
- # Rebuild the original value, but with the parent set as the
- # class where it will be bound.
- new_func = func.__class__(
- name=func.name,
- doc=func.doc,
- lineno=func.lineno,
- col_offset=func.col_offset,
- parent=func.parent,
- )
- # pylint: disable=no-member
- new_func.postinit(func.args, func.body, func.decorators, func.returns)
- # Build a proper bound method that points to our newly built function.
- proxy = bases.UnboundMethod(new_func)
- yield bases.BoundMethod(proxy=proxy, bound=cls)
- @property
- def args(self):
- """Overwrite the underlying args to match those of the underlying func
- Usually the underlying *func* is a function/method, as in:
- def test(self):
- pass
- This has only the *self* parameter but when we access test.__get__
- we get a new object which has two parameters, *self* and *type*.
- """
- nonlocal func
- positional_or_keyword_params = func.args.args.copy()
- positional_or_keyword_params.append(astroid.AssignName(name="type"))
- positional_only_params = func.args.posonlyargs.copy()
- arguments = astroid.Arguments(parent=func.args.parent)
- arguments.postinit(
- args=positional_or_keyword_params,
- posonlyargs=positional_only_params,
- defaults=[],
- kwonlyargs=[],
- kw_defaults=[],
- annotations=[],
- )
- return arguments
- return DescriptorBoundMethod(proxy=self._instance, bound=self._instance)
- # These are here just for completion.
- @property
- def attr___ne__(self):
- return node_classes.Unknown()
- attr___subclasshook__ = attr___ne__
- attr___str__ = attr___ne__
- attr___sizeof__ = attr___ne__
- attr___setattr___ = attr___ne__
- attr___repr__ = attr___ne__
- attr___reduce__ = attr___ne__
- attr___reduce_ex__ = attr___ne__
- attr___new__ = attr___ne__
- attr___lt__ = attr___ne__
- attr___eq__ = attr___ne__
- attr___gt__ = attr___ne__
- attr___format__ = attr___ne__
- attr___delattr___ = attr___ne__
- attr___getattribute__ = attr___ne__
- attr___hash__ = attr___ne__
- attr___init__ = attr___ne__
- attr___dir__ = attr___ne__
- attr___call__ = attr___ne__
- attr___class__ = attr___ne__
- attr___closure__ = attr___ne__
- attr___code__ = attr___ne__
- class ClassModel(ObjectModel):
- def __init__(self):
- # Add a context so that inferences called from an instance don't recurse endlessly
- self.context = InferenceContext()
- super().__init__()
- @property
- def attr___module__(self):
- return node_classes.Const(self._instance.root().qname())
- @property
- def attr___name__(self):
- return node_classes.Const(self._instance.name)
- @property
- def attr___qualname__(self):
- return node_classes.Const(self._instance.qname())
- @property
- def attr___doc__(self):
- return node_classes.Const(self._instance.doc)
- @property
- def attr___mro__(self):
- if not self._instance.newstyle:
- raise AttributeInferenceError(target=self._instance, attribute="__mro__")
- mro = self._instance.mro()
- obj = node_classes.Tuple(parent=self._instance)
- obj.postinit(mro)
- return obj
- @property
- def attr_mro(self):
- if not self._instance.newstyle:
- raise AttributeInferenceError(target=self._instance, attribute="mro")
- # pylint: disable=import-outside-toplevel; circular import
- from astroid import bases
- other_self = self
- # Cls.mro is a method and we need to return one in order to have a proper inference.
- # The method we're returning is capable of inferring the underlying MRO though.
- class MroBoundMethod(bases.BoundMethod):
- def infer_call_result(self, caller, context=None):
- yield other_self.attr___mro__
- implicit_metaclass = self._instance.implicit_metaclass()
- mro_method = implicit_metaclass.locals["mro"][0]
- return MroBoundMethod(proxy=mro_method, bound=implicit_metaclass)
- @property
- def attr___bases__(self):
- obj = node_classes.Tuple()
- context = InferenceContext()
- elts = list(self._instance._inferred_bases(context))
- obj.postinit(elts=elts)
- return obj
- @property
- def attr___class__(self):
- # pylint: disable=import-outside-toplevel; circular import
- from astroid import helpers
- return helpers.object_type(self._instance)
- @property
- def attr___subclasses__(self):
- """Get the subclasses of the underlying class
- This looks only in the current module for retrieving the subclasses,
- thus it might miss a couple of them.
- """
- # pylint: disable=import-outside-toplevel; circular import
- from astroid import bases
- from astroid.nodes import scoped_nodes
- if not self._instance.newstyle:
- raise AttributeInferenceError(
- target=self._instance, attribute="__subclasses__"
- )
- qname = self._instance.qname()
- root = self._instance.root()
- classes = [
- cls
- for cls in root.nodes_of_class(scoped_nodes.ClassDef)
- if cls != self._instance and cls.is_subtype_of(qname, context=self.context)
- ]
- obj = node_classes.List(parent=self._instance)
- obj.postinit(classes)
- class SubclassesBoundMethod(bases.BoundMethod):
- def infer_call_result(self, caller, context=None):
- yield obj
- implicit_metaclass = self._instance.implicit_metaclass()
- subclasses_method = implicit_metaclass.locals["__subclasses__"][0]
- return SubclassesBoundMethod(proxy=subclasses_method, bound=implicit_metaclass)
- @property
- def attr___dict__(self):
- return node_classes.Dict(parent=self._instance)
- class SuperModel(ObjectModel):
- @property
- def attr___thisclass__(self):
- return self._instance.mro_pointer
- @property
- def attr___self_class__(self):
- return self._instance._self_class
- @property
- def attr___self__(self):
- return self._instance.type
- @property
- def attr___class__(self):
- return self._instance._proxied
- class UnboundMethodModel(ObjectModel):
- @property
- def attr___class__(self):
- # pylint: disable=import-outside-toplevel; circular import
- from astroid import helpers
- return helpers.object_type(self._instance)
- @property
- def attr___func__(self):
- return self._instance._proxied
- @property
- def attr___self__(self):
- return node_classes.Const(value=None, parent=self._instance)
- attr_im_func = attr___func__
- attr_im_class = attr___class__
- attr_im_self = attr___self__
- class BoundMethodModel(FunctionModel):
- @property
- def attr___func__(self):
- return self._instance._proxied._proxied
- @property
- def attr___self__(self):
- return self._instance.bound
- class GeneratorModel(FunctionModel):
- def __new__(cls, *args, **kwargs):
- # Append the values from the GeneratorType unto this object.
- ret = super().__new__(cls, *args, **kwargs)
- generator = AstroidManager().builtins_module["generator"]
- for name, values in generator.locals.items():
- method = values[0]
- def patched(cls, meth=method):
- return meth
- setattr(type(ret), IMPL_PREFIX + name, property(patched))
- return ret
- @property
- def attr___name__(self):
- return node_classes.Const(
- value=self._instance.parent.name, parent=self._instance
- )
- @property
- def attr___doc__(self):
- return node_classes.Const(
- value=self._instance.parent.doc, parent=self._instance
- )
- class AsyncGeneratorModel(GeneratorModel):
- def __new__(cls, *args, **kwargs):
- # Append the values from the AGeneratorType unto this object.
- ret = super().__new__(cls, *args, **kwargs)
- astroid_builtins = AstroidManager().builtins_module
- generator = astroid_builtins.get("async_generator")
- if generator is None:
- # Make it backward compatible.
- generator = astroid_builtins.get("generator")
- for name, values in generator.locals.items():
- method = values[0]
- def patched(cls, meth=method):
- return meth
- setattr(type(ret), IMPL_PREFIX + name, property(patched))
- return ret
- class InstanceModel(ObjectModel):
- @property
- def attr___class__(self):
- return self._instance._proxied
- @property
- def attr___module__(self):
- return node_classes.Const(self._instance.root().qname())
- @property
- def attr___doc__(self):
- return node_classes.Const(self._instance.doc)
- @property
- def attr___dict__(self):
- return _dunder_dict(self._instance, self._instance.instance_attrs)
- # Exception instances
- class ExceptionInstanceModel(InstanceModel):
- @property
- def attr_args(self):
- message = node_classes.Const("")
- args = node_classes.Tuple(parent=self._instance)
- args.postinit((message,))
- return args
- @property
- def attr___traceback__(self):
- builtins_ast_module = AstroidManager().builtins_module
- traceback_type = builtins_ast_module[types.TracebackType.__name__]
- return traceback_type.instantiate_class()
- class SyntaxErrorInstanceModel(ExceptionInstanceModel):
- @property
- def attr_text(self):
- return node_classes.Const("")
- class OSErrorInstanceModel(ExceptionInstanceModel):
- @property
- def attr_filename(self):
- return node_classes.Const("")
- @property
- def attr_errno(self):
- return node_classes.Const(0)
- @property
- def attr_strerror(self):
- return node_classes.Const("")
- attr_filename2 = attr_filename
- class ImportErrorInstanceModel(ExceptionInstanceModel):
- @property
- def attr_name(self):
- return node_classes.Const("")
- @property
- def attr_path(self):
- return node_classes.Const("")
- class UnicodeDecodeErrorInstanceModel(ExceptionInstanceModel):
- @property
- def attr_object(self):
- return node_classes.Const("")
- BUILTIN_EXCEPTIONS = {
- "builtins.SyntaxError": SyntaxErrorInstanceModel,
- "builtins.ImportError": ImportErrorInstanceModel,
- "builtins.UnicodeDecodeError": UnicodeDecodeErrorInstanceModel,
- # These are all similar to OSError in terms of attributes
- "builtins.OSError": OSErrorInstanceModel,
- "builtins.BlockingIOError": OSErrorInstanceModel,
- "builtins.BrokenPipeError": OSErrorInstanceModel,
- "builtins.ChildProcessError": OSErrorInstanceModel,
- "builtins.ConnectionAbortedError": OSErrorInstanceModel,
- "builtins.ConnectionError": OSErrorInstanceModel,
- "builtins.ConnectionRefusedError": OSErrorInstanceModel,
- "builtins.ConnectionResetError": OSErrorInstanceModel,
- "builtins.FileExistsError": OSErrorInstanceModel,
- "builtins.FileNotFoundError": OSErrorInstanceModel,
- "builtins.InterruptedError": OSErrorInstanceModel,
- "builtins.IsADirectoryError": OSErrorInstanceModel,
- "builtins.NotADirectoryError": OSErrorInstanceModel,
- "builtins.PermissionError": OSErrorInstanceModel,
- "builtins.ProcessLookupError": OSErrorInstanceModel,
- "builtins.TimeoutError": OSErrorInstanceModel,
- }
- class DictModel(ObjectModel):
- @property
- def attr___class__(self):
- return self._instance._proxied
- def _generic_dict_attribute(self, obj, name):
- """Generate a bound method that can infer the given *obj*."""
- class DictMethodBoundMethod(astroid.BoundMethod):
- def infer_call_result(self, caller, context=None):
- yield obj
- meth = next(self._instance._proxied.igetattr(name), None)
- return DictMethodBoundMethod(proxy=meth, bound=self._instance)
- @property
- def attr_items(self):
- elems = []
- obj = node_classes.List(parent=self._instance)
- for key, value in self._instance.items:
- elem = node_classes.Tuple(parent=obj)
- elem.postinit((key, value))
- elems.append(elem)
- obj.postinit(elts=elems)
- obj = objects.DictItems(obj)
- return self._generic_dict_attribute(obj, "items")
- @property
- def attr_keys(self):
- keys = [key for (key, _) in self._instance.items]
- obj = node_classes.List(parent=self._instance)
- obj.postinit(elts=keys)
- obj = objects.DictKeys(obj)
- return self._generic_dict_attribute(obj, "keys")
- @property
- def attr_values(self):
- values = [value for (_, value) in self._instance.items]
- obj = node_classes.List(parent=self._instance)
- obj.postinit(values)
- obj = objects.DictValues(obj)
- return self._generic_dict_attribute(obj, "values")
- class PropertyModel(ObjectModel):
- """Model for a builtin property"""
- # pylint: disable=import-outside-toplevel
- def _init_function(self, name):
- from astroid.nodes.node_classes import Arguments
- from astroid.nodes.scoped_nodes import FunctionDef
- args = Arguments()
- args.postinit(
- args=[],
- defaults=[],
- kwonlyargs=[],
- kw_defaults=[],
- annotations=[],
- posonlyargs=[],
- posonlyargs_annotations=[],
- kwonlyargs_annotations=[],
- )
- function = FunctionDef(name=name, parent=self._instance)
- function.postinit(args=args, body=[])
- return function
- @property
- def attr_fget(self):
- from astroid.nodes.scoped_nodes import FunctionDef
- func = self._instance
- class PropertyFuncAccessor(FunctionDef):
- def infer_call_result(self, caller=None, context=None):
- nonlocal func
- if caller and len(caller.args) != 1:
- raise InferenceError(
- "fget() needs a single argument", target=self, context=context
- )
- yield from func.function.infer_call_result(
- caller=caller, context=context
- )
- property_accessor = PropertyFuncAccessor(name="fget", parent=self._instance)
- property_accessor.postinit(args=func.args, body=func.body)
- return property_accessor
- @property
- def attr_fset(self):
- from astroid.nodes.scoped_nodes import FunctionDef
- func = self._instance
- def find_setter(func: "Property") -> Optional[astroid.FunctionDef]:
- """
- Given a property, find the corresponding setter function and returns it.
- :param func: property for which the setter has to be found
- :return: the setter function or None
- """
- for target in [
- t for t in func.parent.get_children() if t.name == func.function.name
- ]:
- for dec_name in target.decoratornames():
- if dec_name.endswith(func.function.name + ".setter"):
- return target
- return None
- func_setter = find_setter(func)
- if not func_setter:
- raise InferenceError(
- f"Unable to find the setter of property {func.function.name}"
- )
- class PropertyFuncAccessor(FunctionDef):
- def infer_call_result(self, caller=None, context=None):
- nonlocal func_setter
- if caller and len(caller.args) != 2:
- raise InferenceError(
- "fset() needs two arguments", target=self, context=context
- )
- yield from func_setter.infer_call_result(caller=caller, context=context)
- property_accessor = PropertyFuncAccessor(name="fset", parent=self._instance)
- property_accessor.postinit(args=func_setter.args, body=func_setter.body)
- return property_accessor
- @property
- def attr_setter(self):
- return self._init_function("setter")
- @property
- def attr_deleter(self):
- return self._init_function("deleter")
- @property
- def attr_getter(self):
- return self._init_function("getter")
- # pylint: enable=import-outside-toplevel
|