Source code for coalib.bearlib.aspects.base
import functools
import re
from coalib.bearlib.languages import Language
import coalib.bearlib.aspects
from .taste import TasteError
[docs]def get_subaspect(parent, subaspect):
"""
Get a subaspect from an aspectclass or aspectclass instance.
>>> import coalib.bearlib.aspects as coala_aspects
>>> metadata = coala_aspects['Metadata']
>>> commit_msg = coala_aspects['CommitMessage']
>>> shortlog = coala_aspects['Shortlog']
We can get direct children.
>>> get_subaspect(metadata, commit_msg)
<aspectclass 'Root.Metadata.CommitMessage'>
Or even a grandchildren.
>>> get_subaspect(metadata, shortlog)
<aspectclass 'Root.Metadata.CommitMessage.Shortlog'>
Or with string of aspect name
>>> get_subaspect(metadata, 'shortlog')
<aspectclass 'Root.Metadata.CommitMessage.Shortlog'>
We can also get child instance of an aspect instance.
>>> get_subaspect(metadata('Python'), commit_msg)
<...CommitMessage object at 0x...>
But, passing subaspect instance as argument is prohibited, because
it doesn't really make sense.
>>> get_subaspect(metadata('Python'), commit_msg('Java'))
Traceback (most recent call last):
...
AttributeError: Cannot search an aspect instance using another ...
:param parent: The parent aspect that should be searched.
:param subaspect: An subaspect that we want to find in an
aspectclass.
:return: An aspectclass. Return None if not found.
"""
# Avoid circular import
from .meta import isaspect, issubaspect
if not isaspect(subaspect):
subaspect = coalib.bearlib.aspects[subaspect]
if not issubaspect(subaspect, parent):
return None
if isinstance(subaspect, aspectbase):
raise AttributeError('Cannot search an aspect instance using '
'another aspect instance as argument.')
parent_qualname = (type(parent).__qualname__ if isinstance(
parent, aspectbase) else parent.__qualname__)
if parent_qualname == subaspect.__qualname__:
return parent
# Trim common parent name
aspect_path = re.sub(r'^%s\.' % parent_qualname, '',
subaspect.__qualname__)
aspect_path = aspect_path.split('.')
child = parent
# Traverse through children until we got our subaspect
for path in aspect_path:
child = child.subaspects[path]
return child
def _get_leaf_aspects(aspect):
"""
Explode an aspect into list of its leaf aspects.
:param aspect: An aspect class or instance.
:return: List of leaf aspects.
"""
# Avoid circular import
from .collections import AspectList
leaf_aspects = AspectList()
def search_leaf(aspects):
for aspect in aspects:
if not aspect.subaspects:
nonlocal leaf_aspects
leaf_aspects.append(aspect)
else:
search_leaf(aspect.subaspects.values())
search_leaf([aspect])
return leaf_aspects
[docs]class SubaspectGetter:
"""
Special "getter" class to implement ``get()`` method in aspectbase that
could be accessed from the aspectclass or aspectclass instance.
"""
def __get__(self, obj, owner):
parent = obj if obj is not None else owner
return functools.partial(get_subaspect, parent)
[docs]class LeafAspectGetter:
"""
Descriptor class for ``get_leaf_aspects()`` method in aspectbase.
This class is required to make the ``get_leaf_aspects()`` accessible from
both aspectclass and aspectclass instance.
"""
def __get__(self, obj, owner):
parent = obj if obj is not None else owner
return functools.partial(_get_leaf_aspects, parent)
[docs]class aspectbase:
"""
Base class for aspectclasses with common features for their instances.
Derived classes must use
:class:`coalib.bearlib.aspects.meta.aspectclass` as metaclass.
This is automatically handled by
:meth:`coalib.bearlib.aspects.meta.aspectclass.subaspect` decorator.
"""
get = SubaspectGetter()
get_leaf_aspects = LeafAspectGetter()
def __init__(self, language, **taste_values):
"""
Instantiate an aspectclass with specific `taste_values`,
including parent tastes.
Given tastes must be available for the given `language`,
which must be a language identifier supported by
:class:`coalib.bearlib.languages.Language`.
All taste values will be casted to the related taste cast types.
Non-given available tastes will get their default values.
"""
# bypass self.__setattr__
self.__dict__['language'] = Language[language]
for name, taste in type(self).tastes.items():
if taste.languages and language not in taste.languages:
if name in taste_values:
raise TasteError('%s.%s is not available for %s.' % (
type(self).__qualname__, name, language))
else:
setattr(self, name, taste_values.get(name, taste.default))
# Recursively instance its subaspects too
instanced_child = {}
for name, child in self.subaspects.items():
instanced_child[name] = child(language, **taste_values)
self.__dict__['subaspects'] = instanced_child
def __eq__(self, other):
return type(self) is type(other) and self.tastes == other.tastes
@property
def tastes(self):
"""
Get a dictionary of all taste names mapped to their specific values,
including parent tastes.
"""
return {name: self.__dict__[name] for name in type(self).tastes
if name in self.__dict__}
def __setattr__(self, name, value):
"""
Don't allow attribute manipulations after instantiation of
aspectclasses.
"""
if name not in type(self).tastes:
raise AttributeError(
"can't set attributes of aspectclass instances")
super().__setattr__(name, value)