# Test case for property
# more tests are in test_descr

import sys
import unittest
from test import support

class PropertyBase(Exception):
    pass

class PropertyGet(PropertyBase):
    pass

class PropertySet(PropertyBase):
    pass

class PropertyDel(PropertyBase):
    pass

class BaseClass(object):
    def __init__(self):
        self._spam = 5

    @property
    def spam(self):
        """BaseClass.getter"""
        return self._spam

    @spam.setter
    def spam(self, value):
        self._spam = value

    @spam.deleter
    def spam(self):
        del self._spam

class SubClass(BaseClass):

    @BaseClass.spam.getter
    def spam(self):
        """SubClass.getter"""
        raise PropertyGet(self._spam)

    @spam.setter
    def spam(self, value):
        raise PropertySet(self._spam)

    @spam.deleter
    def spam(self):
        raise PropertyDel(self._spam)

class PropertyDocBase(object):
    _spam = 1
    def _get_spam(self):
        return self._spam
    spam = property(_get_spam, doc="spam spam spam")

class PropertyDocSub(PropertyDocBase):
    @PropertyDocBase.spam.getter
    def spam(self):
        """The decorator does not use this doc string"""
        return self._spam

class PropertySubNewGetter(BaseClass):
    @BaseClass.spam.getter
    def spam(self):
        """new docstring"""
        return 5

class PropertyNewGetter(object):
    @property
    def spam(self):
        """original docstring"""
        return 1
    @spam.getter
    def spam(self):
        """new docstring"""
        return 8

class PropertyTests(unittest.TestCase):
    def test_property_decorator_baseclass(self):
        # see #1620
        base = BaseClass()
        self.assertEqual(base.spam, 5)
        self.assertEqual(base._spam, 5)
        base.spam = 10
        self.assertEqual(base.spam, 10)
        self.assertEqual(base._spam, 10)
        delattr(base, "spam")
        self.assertTrue(not hasattr(base, "spam"))
        self.assertTrue(not hasattr(base, "_spam"))
        base.spam = 20
        self.assertEqual(base.spam, 20)
        self.assertEqual(base._spam, 20)

    def test_property_decorator_subclass(self):
        # see #1620
        sub = SubClass()
        self.assertRaises(PropertyGet, getattr, sub, "spam")
        self.assertRaises(PropertySet, setattr, sub, "spam", None)
        self.assertRaises(PropertyDel, delattr, sub, "spam")

    @unittest.skipIf(sys.flags.optimize >= 2,
                     "Docstrings are omitted with -O2 and above")
    def test_property_decorator_subclass_doc(self):
        sub = SubClass()
        self.assertEqual(sub.__class__.spam.__doc__, "SubClass.getter")

    @unittest.skipIf(sys.flags.optimize >= 2,
                     "Docstrings are omitted with -O2 and above")
    def test_property_decorator_baseclass_doc(self):
        base = BaseClass()
        self.assertEqual(base.__class__.spam.__doc__, "BaseClass.getter")

    def test_property_decorator_doc(self):
        base = PropertyDocBase()
        sub = PropertyDocSub()
        self.assertEqual(base.__class__.spam.__doc__, "spam spam spam")
        self.assertEqual(sub.__class__.spam.__doc__, "spam spam spam")

    @unittest.skipIf(sys.flags.optimize >= 2,
                     "Docstrings are omitted with -O2 and above")
    def test_property_getter_doc_override(self):
        newgettersub = PropertySubNewGetter()
        self.assertEqual(newgettersub.spam, 5)
        self.assertEqual(newgettersub.__class__.spam.__doc__, "new docstring")
        newgetter = PropertyNewGetter()
        self.assertEqual(newgetter.spam, 8)
        self.assertEqual(newgetter.__class__.spam.__doc__, "new docstring")

    def test_property___isabstractmethod__descriptor(self):
        for val in (True, False, [], [1], '', '1'):
            class C(object):
                def foo(self):
                    pass
                foo.__isabstractmethod__ = val
                foo = property(foo)
            self.assertIs(C.foo.__isabstractmethod__, bool(val))

        # check that the property's __isabstractmethod__ descriptor does the
        # right thing when presented with a value that fails truth testing:
        class NotBool(object):
            def __bool__(self):
                raise ValueError()
            __len__ = __bool__
        with self.assertRaises(ValueError):
            class C(object):
                def foo(self):
                    pass
                foo.__isabstractmethod__ = NotBool()
                foo = property(foo)
            C.foo.__isabstractmethod__

    @unittest.skipIf(sys.flags.optimize >= 2,
                     "Docstrings are omitted with -O2 and above")
    def test_property_builtin_doc_writable(self):
        p = property(doc='basic')
        self.assertEqual(p.__doc__, 'basic')
        p.__doc__ = 'extended'
        self.assertEqual(p.__doc__, 'extended')

    @unittest.skipIf(sys.flags.optimize >= 2,
                     "Docstrings are omitted with -O2 and above")
    def test_property_decorator_doc_writable(self):
        class PropertyWritableDoc(object):

            @property
            def spam(self):
                """Eggs"""
                return "eggs"

        sub = PropertyWritableDoc()
        self.assertEqual(sub.__class__.spam.__doc__, 'Eggs')
        sub.__class__.spam.__doc__ = 'Spam'
        self.assertEqual(sub.__class__.spam.__doc__, 'Spam')

    @support.refcount_test
    def test_refleaks_in___init__(self):
        gettotalrefcount = support.get_attribute(sys, 'gettotalrefcount')
        fake_prop = property('fget', 'fset', 'fdel', 'doc')
        refs_before = gettotalrefcount()
        for i in range(100):
            fake_prop.__init__('fget', 'fset', 'fdel', 'doc')
        self.assertAlmostEqual(gettotalrefcount() - refs_before, 0, delta=10)

    @unittest.skipIf(sys.flags.optimize >= 2,
                     "Docstrings are omitted with -O2 and above")
    def test_class_property(self):
        class A:
            @classmethod
            @property
            def __doc__(cls):
                return 'A doc for %r' % cls.__name__
        self.assertEqual(A.__doc__, "A doc for 'A'")

    @unittest.skipIf(sys.flags.optimize >= 2,
                     "Docstrings are omitted with -O2 and above")
    def test_class_property_override(self):
        class A:
            """First"""
            @classmethod
            @property
            def __doc__(cls):
                return 'Second'
        self.assertEqual(A.__doc__, 'Second')

    @unittest.expectedFailure # pypy has a different error message
    def test_property_set_name_incorrect_args(self):
        p = property()

        for i in (0, 1, 3):
            with self.assertRaisesRegex(
                TypeError,
                fr'^__set_name__\(\) takes 2 positional arguments but {i} were given$'
            ):
                p.__set_name__(*([0] * i))

    def test_property_setname_on_property_subclass(self):
        # https://github.com/python/cpython/issues/100942
        # Copy was setting the name field without first
        # verifying that the copy was an actual property
        # instance.  As a result, the code below was
        # causing a segfault.

        class pro(property):
            def __new__(typ, *args, **kwargs):
                return "abcdef"

        class A:
            pass

        p = property.__new__(pro)
        p.__set_name__(A, 1)
        np = p.getter(lambda self: 1)

# Issue 5890: subclasses of property do not preserve method __doc__ strings
class PropertySub(property):
    """This is a subclass of property"""

class PropertySubSlots(property):
    """This is a subclass of property that defines __slots__"""
    __slots__ = ()

class PropertySubclassTests(unittest.TestCase):

    def test_slots_docstring_copy_exception(self):
        try:
            class Foo(object):
                @PropertySubSlots
                def spam(self):
                    """Trying to copy this docstring will raise an exception"""
                    return 1
        except AttributeError:
            pass
        else:
            raise Exception("AttributeError not raised")

    @unittest.skipIf(sys.flags.optimize >= 2,
                     "Docstrings are omitted with -O2 and above")
    def test_docstring_copy(self):
        class Foo(object):
            @PropertySub
            def spam(self):
                """spam wrapped in property subclass"""
                return 1
        self.assertEqual(
            Foo.spam.__doc__,
            "spam wrapped in property subclass")

    @unittest.skipIf(sys.flags.optimize >= 2,
                     "Docstrings are omitted with -O2 and above")
    def test_property_setter_copies_getter_docstring(self):
        class Foo(object):
            def __init__(self): self._spam = 1
            @PropertySub
            def spam(self):
                """spam wrapped in property subclass"""
                return self._spam
            @spam.setter
            def spam(self, value):
                """this docstring is ignored"""
                self._spam = value
        foo = Foo()
        self.assertEqual(foo.spam, 1)
        foo.spam = 2
        self.assertEqual(foo.spam, 2)
        self.assertEqual(
            Foo.spam.__doc__,
            "spam wrapped in property subclass")
        class FooSub(Foo):
            @Foo.spam.setter
            def spam(self, value):
                """another ignored docstring"""
                self._spam = 'eggs'
        foosub = FooSub()
        self.assertEqual(foosub.spam, 1)
        foosub.spam = 7
        self.assertEqual(foosub.spam, 'eggs')
        self.assertEqual(
            FooSub.spam.__doc__,
            "spam wrapped in property subclass")

    @unittest.skipIf(sys.flags.optimize >= 2,
                     "Docstrings are omitted with -O2 and above")
    def test_property_new_getter_new_docstring(self):

        class Foo(object):
            @PropertySub
            def spam(self):
                """a docstring"""
                return 1
            @spam.getter
            def spam(self):
                """a new docstring"""
                return 2
        self.assertEqual(Foo.spam.__doc__, "a new docstring")
        class FooBase(object):
            @PropertySub
            def spam(self):
                """a docstring"""
                return 1
        class Foo2(FooBase):
            @FooBase.spam.getter
            def spam(self):
                """a new docstring"""
                return 2
        self.assertEqual(Foo.spam.__doc__, "a new docstring")


class _PropertyUnreachableAttribute:
    msg_format = None
    obj = None
    cls = None

    def _format_exc_msg(self, msg):
        return self.msg_format.format(msg)

    @classmethod
    def setUpClass(cls):
        cls.obj = cls.cls()

    def test_get_property(self):
        with self.assertRaisesRegex(AttributeError, self._format_exc_msg("unreadable attribute")):
            self.obj.foo

    def test_set_property(self):
        with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't set attribute")):
            self.obj.foo = None

    def test_del_property(self):
        # PYPY: make message consistent with other 'delete' messages
        with self.assertRaisesRegex(AttributeError, self._format_exc_msg("can't delete attribute" )):
            del self.obj.foo


class PropertyUnreachableAttributeWithName(_PropertyUnreachableAttribute, unittest.TestCase):
    msg_format = "^{} 'foo'$"

    class cls:
        foo = property()


class PropertyUnreachableAttributeNoName(_PropertyUnreachableAttribute, unittest.TestCase):
    msg_format = "^{}$"

    class cls:
        pass

    cls.foo = property()


if __name__ == '__main__':
    unittest.main()
