Skip to content

Commit

Permalink
Add copy of properties module from jaraco.classes 3.2.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaraco committed Sep 1, 2022
1 parent f3b4abf commit e110fb2
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 7 deletions.
7 changes: 7 additions & 0 deletions keyring/_compat.py
@@ -0,0 +1,7 @@
__all__ = ['properties']


try:
from jaraco.compat import properties # pragma: no-cover
except ImportError:
from . import _properties_compat as properties # pragma: no-cover
168 changes: 168 additions & 0 deletions keyring/_properties_compat.py
@@ -0,0 +1,168 @@
# from jaraco.classes 3.2.2

class NonDataProperty:
"""Much like the property builtin, but only implements __get__,
making it a non-data property, and can be subsequently reset.
See http://users.rcn.com/python/download/Descriptor.htm for more
information.
>>> class X(object):
... @NonDataProperty
... def foo(self):
... return 3
>>> x = X()
>>> x.foo
3
>>> x.foo = 4
>>> x.foo
4
"""

def __init__(self, fget):
assert fget is not None, "fget cannot be none"
assert callable(fget), "fget must be callable"
self.fget = fget

def __get__(self, obj, objtype=None):
if obj is None:
return self
return self.fget(obj)


class classproperty:
"""
Like @property but applies at the class level.
>>> class X(metaclass=classproperty.Meta):
... val = None
... @classproperty
... def foo(cls):
... return cls.val
... @foo.setter
... def foo(cls, val):
... cls.val = val
>>> X.foo
>>> X.foo = 3
>>> X.foo
3
>>> x = X()
>>> x.foo
3
>>> X.foo = 4
>>> x.foo
4
Setting the property on an instance affects the class.
>>> x.foo = 5
>>> x.foo
5
>>> X.foo
5
>>> vars(x)
{}
>>> X().foo
5
Attempting to set an attribute where no setter was defined
results in an AttributeError:
>>> class GetOnly(metaclass=classproperty.Meta):
... @classproperty
... def foo(cls):
... return 'bar'
>>> GetOnly.foo = 3
Traceback (most recent call last):
...
AttributeError: can't set attribute
It is also possible to wrap a classmethod or staticmethod in
a classproperty.
>>> class Static(metaclass=classproperty.Meta):
... @classproperty
... @classmethod
... def foo(cls):
... return 'foo'
... @classproperty
... @staticmethod
... def bar():
... return 'bar'
>>> Static.foo
'foo'
>>> Static.bar
'bar'
*Legacy*
For compatibility, if the metaclass isn't specified, the
legacy behavior will be invoked.
>>> class X:
... val = None
... @classproperty
... def foo(cls):
... return cls.val
... @foo.setter
... def foo(cls, val):
... cls.val = val
>>> X.foo
>>> X.foo = 3
>>> X.foo
3
>>> x = X()
>>> x.foo
3
>>> X.foo = 4
>>> x.foo
4
Note, because the metaclass was not specified, setting
a value on an instance does not have the intended effect.
>>> x.foo = 5
>>> x.foo
5
>>> X.foo # should be 5
4
>>> vars(x) # should be empty
{'foo': 5}
>>> X().foo # should be 5
4
"""

class Meta(type):
def __setattr__(self, key, value):
obj = self.__dict__.get(key, None)
if type(obj) is classproperty:
return obj.__set__(self, value)
return super().__setattr__(key, value)

def __init__(self, fget, fset=None):
self.fget = self._ensure_method(fget)
self.fset = fset
fset and self.setter(fset)

def __get__(self, instance, owner=None):
return self.fget.__get__(None, owner)()

def __set__(self, owner, value):
if not self.fset:
raise AttributeError("can't set attribute")
if type(owner) is not classproperty.Meta:
owner = type(owner)
return self.fset.__get__(None, owner)(value)

def setter(self, fset):
self.fset = self._ensure_method(fset)
return self

@classmethod
def _ensure_method(cls, fn):
"""
Ensure fn is a classmethod or staticmethod.
"""
needs_method = not isinstance(fn, (classmethod, staticmethod))
return classmethod(fn) if needs_method else fn
2 changes: 1 addition & 1 deletion keyring/backend.py
Expand Up @@ -12,7 +12,7 @@

from .py310compat import metadata
from . import credentials, errors, util
from jaraco.classes import properties
from ._compat import properties

log = logging.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion keyring/backends/SecretService.py
Expand Up @@ -2,7 +2,7 @@
import logging

from .. import backend
from jaraco.classes import properties
from .._compat import properties
from ..backend import KeyringBackend
from ..credentials import SimpleCredential
from ..errors import (
Expand Down
2 changes: 1 addition & 1 deletion keyring/backends/Windows.py
@@ -1,6 +1,6 @@
import logging

from jaraco.classes import properties
from .._compat import properties
from ..backend import KeyringBackend
from ..credentials import SimpleCredential
from ..errors import PasswordDeleteError, ExceptionRaisedContext
Expand Down
2 changes: 1 addition & 1 deletion keyring/backends/chainer.py
Expand Up @@ -4,7 +4,7 @@
"""

from .. import backend
from jaraco.classes import properties
from .._compat import properties
from . import fail


Expand Down
2 changes: 1 addition & 1 deletion keyring/backends/kwallet.py
Expand Up @@ -6,7 +6,7 @@
from ..credentials import SimpleCredential
from ..errors import PasswordDeleteError
from ..errors import PasswordSetError, InitError, KeyringLocked
from jaraco.classes import properties
from .._compat import properties

try:
import dbus
Expand Down
2 changes: 1 addition & 1 deletion keyring/backends/libsecret.py
@@ -1,7 +1,7 @@
import logging

from .. import backend
from jaraco.classes import properties
from .._compat import properties
from ..backend import KeyringBackend
from ..credentials import SimpleCredential
from ..errors import (
Expand Down
2 changes: 1 addition & 1 deletion keyring/backends/macOS/__init__.py
Expand Up @@ -7,7 +7,7 @@
from ...errors import PasswordDeleteError
from ...errors import KeyringLocked
from ...errors import KeyringError
from jaraco.classes import properties
from ..._compat import properties

try:
from . import api
Expand Down

0 comments on commit e110fb2

Please sign in to comment.