Skip to content

Commit

Permalink
Remove unused and obsolete language extensions and utilities (#3801)
Browse files Browse the repository at this point in the history
The `aiida.common.lang` module hosts a variety of utilities that extend
the base python language. Many of these were backports for python 2,
which since now that we support python 3, have become obsolete. Some
also simply were not used:

 * `abstractclassmethod`: now available in `abc.abstractclassmethod`
 * `abstractstaticmethod`: now available in `abc.abstractstaticmethod`
 * `combomethod`: not used at all
 * `EmptyContextManager`: from py3.4 `contextlib.suppress()`
 * `protected_decorator`: only used in two places, but with `check=False`
   so it would never actually raise when the method was called from
   outside the class. What is worse, the `report` method is only useful
   when callable from the outside. All in all, it is best to remove this
   whole concept.
  • Loading branch information
sphuber authored Feb 24, 2020
1 parent 061f6ae commit 9fcb78a
Show file tree
Hide file tree
Showing 7 changed files with 17 additions and 237 deletions.
122 changes: 2 additions & 120 deletions aiida/common/lang.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
###########################################################################
"""Utilities that extend the basic python language."""
import functools
import inspect
import keyword
from inspect import stack, currentframe, getfullargspec


def isidentifier(identifier):
Expand Down Expand Up @@ -40,49 +40,14 @@ def type_check(what, of_type, msg=None, allow_none=False):
raise TypeError(msg)


def protected_decorator(check=False):
"""Decorator to ensure that the decorated method is not called from outside the class hierarchy."""

def wrap(func): # pylint: disable=missing-docstring
if isinstance(func, property):
raise RuntimeError('Protected must go after @property decorator')

args = getfullargspec(func)[0]
if not args:
raise RuntimeError('Can only use the protected decorator on member functions')

# We can only perform checks if the interpreter is capable of giving
# us the stack i.e. currentframe() produces a valid object
if check and currentframe() is not None:

@functools.wraps(func)
def wrapped_fn(self, *args, **kwargs): # pylint: disable=missing-docstring
try:
calling_class = stack()[1][0].f_locals['self']
assert self is calling_class
except (KeyError, AssertionError):
raise RuntimeError(
'Cannot access protected function {} from outside'
' class hierarchy'.format(func.__name__)
)

return func(self, *args, **kwargs)
else:
wrapped_fn = func

return wrapped_fn

return wrap


def override_decorator(check=False):
"""Decorator to signal that a method from a base class is being overridden completely."""

def wrap(func): # pylint: disable=missing-docstring
if isinstance(func, property):
raise RuntimeError('Override must go after @property decorator')

args = getfullargspec(func)[0]
args = inspect.getfullargspec(func)[0]
if not args:
raise RuntimeError('Can only use the override decorator on member functions')

Expand All @@ -104,7 +69,6 @@ def wrapped_fn(self, *args, **kwargs): # pylint: disable=missing-docstring
return wrap


protected = protected_decorator(check=False) # pylint: disable=invalid-name
override = override_decorator(check=False) # pylint: disable=invalid-name


Expand All @@ -122,85 +86,3 @@ def __init__(self, getter):

def __get__(self, instance, owner):
return self.getter(owner)


class abstractclassmethod(classmethod): # pylint: disable=too-few-public-methods, invalid-name
"""
A decorator indicating abstract classmethods.
Backported from python3.
"""
__isabstractmethod__ = True

def __init__(self, callable): # pylint: disable=redefined-builtin
callable.__isabstractmethod__ = True
super().__init__(callable)


class abstractstaticmethod(staticmethod): # pylint: disable=too-few-public-methods, invalid-name
"""
A decorator indicating abstract staticmethods.
Similar to abstractmethod.
Backported from python3.
"""

__isabstractmethod__ = True

def __init__(self, callable): # pylint: disable=redefined-builtin
callable.__isabstractmethod__ = True # pylint: disable=redefined-builtin
super().__init__(callable)


class combomethod: # pylint: disable=invalid-name,too-few-public-methods
"""
A decorator that wraps a function that can be both a classmethod or
instancemethod and behaves accordingly::
class A():
@combomethod
def do(self, **kwargs):
isclass = kwargs.get('isclass')
if isclass:
print("I am a class", self)
else:
print("I am an instance", self)
A.do()
A().do()
>>> I am a class __main__.A
>>> I am an instance <__main__.A instance at 0x7f2efb116e60>
Attention: For ease of handling, pass keyword **isclass**
equal to True if this was called as a classmethod and False if this
was called as an instance.
The argument self is therefore ambiguous!
"""

def __init__(self, method):
self.method = method

def __get__(self, obj=None, objtype=None): # pylint: disable=missing-docstring

@functools.wraps(self.method)
def _wrapper(*args, **kwargs): # pylint: disable=missing-docstring
kwargs.pop('isclass', None)
if obj is not None:
return self.method(obj, *args, isclass=False, **kwargs)
return self.method(objtype, *args, isclass=True, **kwargs)

return _wrapper


class EmptyContextManager: # pylint: disable=too-few-public-methods
"""
A dummy/no-op context manager.
"""

def __enter__(self):
pass

def __exit__(self, exc_type, exc_value, traceback):
pass
4 changes: 1 addition & 3 deletions aiida/engine/processes/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from aiida import orm
from aiida.common import exceptions
from aiida.common.extendeddicts import AttributeDict
from aiida.common.lang import classproperty, override, protected
from aiida.common.lang import classproperty, override
from aiida.common.links import LinkType
from aiida.common.log import LOG_LEVEL_REPORT

Expand Down Expand Up @@ -456,7 +456,6 @@ def runner(self):
"""
return self._runner

@protected
def get_parent_calc(self):
"""
Get the parent process node
Expand Down Expand Up @@ -495,7 +494,6 @@ def build_process_type(cls):

return process_type

@protected
def report(self, msg, *args, **kwargs):
"""Log a message to the logger, which should get saved to the database through the attached DbLogHandler.
Expand Down
6 changes: 4 additions & 2 deletions aiida/orm/implementation/django/comments.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
###########################################################################
"""Django implementations for the Comment entity and collection."""
# pylint: disable=import-error,no-name-in-module
import contextlib

from datetime import datetime
from django.core.exceptions import ObjectDoesNotExist
Expand Down Expand Up @@ -61,13 +62,14 @@ def __init__(self, backend, node, user, content=None, ctime=None, mtime=None):

def store(self):
"""Can only store if both the node and user are stored as well."""
from aiida.common.lang import EmptyContextManager
from aiida.backends.djsite.db.models import suppress_auto_now

if self._dbmodel.dbnode.id is None or self._dbmodel.user.id is None:
raise exceptions.ModificationNotAllowed('The corresponding node and/or user are not stored')

with suppress_auto_now([(models.DbComment, ['mtime'])]) if self.mtime else EmptyContextManager():
# `contextlib.suppress` provides empty context and can be replaced with `contextlib.nullcontext` after we drop
# support for python 3.6
with suppress_auto_now([(models.DbComment, ['mtime'])]) if self.mtime else contextlib.suppress():
super().store()

@property
Expand Down
8 changes: 5 additions & 3 deletions aiida/orm/implementation/django/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -488,14 +488,16 @@ def store(self, links=None, with_transaction=True, clean=True): # pylint: disab
:param with_transaction: if False, do not use a transaction because the caller will already have opened one.
:param clean: boolean, if True, will clean the attributes and extras before attempting to store
"""
from aiida.common.lang import EmptyContextManager
import contextlib
from aiida.backends.djsite.db.models import suppress_auto_now

if clean:
self.clean_values()

with transaction.atomic() if with_transaction else EmptyContextManager():
with suppress_auto_now([(models.DbNode, ['mtime'])]) if self.mtime else EmptyContextManager():
# `contextlib.suppress` provides empty context and can be replaced with `contextlib.nullcontext` after we drop
# support for python 3.6
with transaction.atomic() if with_transaction else contextlib.suppress():
with suppress_auto_now([(models.DbNode, ['mtime'])]) if self.mtime else contextlib.suppress():
# We need to save the node model instance itself first such that it has a pk
# that can be used in the foreign keys that will be needed for setting the
# attributes and links
Expand Down
7 changes: 3 additions & 4 deletions aiida/orm/implementation/querybuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from sqlalchemy.dialects.postgresql import JSONB

from aiida.common import exceptions
from aiida.common.lang import abstractclassmethod, type_check
from aiida.common.lang import type_check
from aiida.common.exceptions import InputValidationError

__all__ = ('BackendQueryBuilder',)
Expand Down Expand Up @@ -126,7 +126,7 @@ def modify_expansions(self, alias, expansions):
This is important for the schema having attributes in a different table.
"""

@abstractclassmethod
@abc.abstractclassmethod
def get_filter_expr_from_attributes(cls, operator, value, attr_key, column=None, column_name=None, alias=None): # pylint: disable=too-many-arguments
"""
Returns an valid SQLAlchemy expression.
Expand Down Expand Up @@ -383,8 +383,7 @@ def iterdict(self, query, batch_size, tag_to_projected_properties_dict, tag_to_a
self.get_session().close()
raise

@staticmethod
@abstractclassmethod
@abc.abstractstaticmethod
def get_table_name(aliased_class):
"""Returns the table name given an Aliased class."""

Expand Down
3 changes: 2 additions & 1 deletion aiida/orm/utils/loaders.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
# For further information please visit http://www.aiida.net #
###########################################################################
"""Module with `OrmEntityLoader` and its sub classes that simplify loading entities through their identifiers."""
from abc import abstractclassmethod
from enum import Enum

from aiida.common.exceptions import MultipleObjectsError, NotExistent
from aiida.common.lang import abstractclassmethod, classproperty
from aiida.common.lang import classproperty
from aiida.orm.querybuilder import QueryBuilder

__all__ = (
Expand Down
104 changes: 0 additions & 104 deletions tests/common/test_lang.py

This file was deleted.

0 comments on commit 9fcb78a

Please sign in to comment.