from .achievements import Achievement
from .backends import AchievementBackend
from .signals import goal_achieved, level_increased, highest_level_achieved
from inspect import isclass as _isclass
class AlreadyRegistered(Exception):
pass
class NotRegistered(Exception):
pass
[docs]class AchievementTracker(object):
"""
AchievementTracker tracks achievements and current levels for ``tracked_id`` using a configured
achievement backend.
A default instance of Achievement tracker is created as a singleton when pycheivements is
imported as ``pychievements.tracker``. Most often, this is what you will want to use.
Arguments:
backend:
The backend to use for storing/retrieving achievement data. If ``None``, the default
:py:class:`AchievementBackend` will be used, which stores all data in memory.
.. note::
The backend the tracker is using can be updated at any time using the :py:func:`set_backend`
function.
"""
def __init__(self, backend=None):
self._registry = []
self._backend = AchievementBackend() if backend is None else backend
[docs] def set_backend(self, backend):
"""
Configures a new backend for storing achievement data.
"""
if not isinstance(backend, AchievementBackend):
raise ValueError('Backend must be an instance of an AchievementBackend')
self._backend = backend
[docs] def register(self, achievement_or_iterable, **options):
"""
Registers the given achievement(s) to be tracked.
"""
if _isclass(achievement_or_iterable) and issubclass(achievement_or_iterable, Achievement):
achievement_or_iterable = [achievement_or_iterable]
for achievement in achievement_or_iterable:
if not achievement.category:
raise ValueError('Achievements must specify a category, could not register '
'%s' % achievement.__name__)
if achievement in self._registry:
raise AlreadyRegistered('The achievement %s is already '
'registered' % achievement.__name__)
if achievement is not Achievement:
self._registry.append(achievement)
[docs] def unregister(self, achievement_or_iterable):
"""
Un-registers the given achievement(s).
If an achievement isn't already registered, this will raise NotRegistered.
"""
if _isclass(achievement_or_iterable) and issubclass(achievement_or_iterable, Achievement):
achievement_or_iterable = [achievement_or_iterable]
for achievement in achievement_or_iterable:
if achievement not in self._registry:
raise NotRegistered('The achievement %s is not registered' % achievement.__name__)
self._registry.remove(achievement)
[docs] def is_registered(self, achievement):
"""
Check if an achievement is registered with this `AchievementTracker`
"""
return achievement in self._registry
[docs] def achievements(self, category=None, keywords=[]):
"""
Returns all registered achievements.
Arguments:
category
Filters returned achievements by category. This is a strict string match.
keywords
Filters returned achievements by keywords. Returned achievements will match all
given keywords
"""
achievements = []
for achievement in self._registry:
if category is None or achievement.category == category:
if not keywords or all([_ in achievement.keywords for _ in keywords]):
achievements.append(achievement)
return achievements
[docs] def achievement_for_id(self, tracked_id, achievement):
"""
Returns ``Achievement`` for a given ``tracked_id``. Achievement can be an ``Achievement``
class or a string of the name of an achievement class that has been registered with this
tracker.
Raises NotRegistered if the given achievement is not registered with the tracker.
If ``tracked_id`` has not been tracked yet by this tracker, it will be created.
"""
if isinstance(achievement, Achievement):
achievement = achievement.__class__.__name__
elif _isclass(achievement) and issubclass(achievement, Achievement):
achievement = achievement.__name__
a = [_ for _ in self._registry if _.__name__ == achievement]
if a:
return self._backend.achievement_for_id(tracked_id, a[0])
raise NotRegistered('The achievement %s is not registered with this tracker' % achievement)
[docs] def achievements_for_id(self, tracked_id, category=None, keywords=[]):
""" Returns all of the achievements for tracked_id that match the given category and
keywords """
return self._backend.achievements_for_id(tracked_id, self.achievements(category, keywords))
def _check_signals(self, tracked_id, achievement, old_level, old_achieved):
cur_level = achievement.current[0]
if old_level < cur_level:
level_increased.send_robust(self, tracked_id=tracked_id, achievement=achievement)
if old_achieved != achievement.achieved:
new_goals = [_ for _ in achievement.achieved if _ not in old_achieved]
goal_achieved.send_robust(self, tracked_id=tracked_id, achievement=achievement,
goals=new_goals)
if not achievement.unachieved:
highest_level_achieved.send_robust(self, tracked_id=tracked_id,
achievement=achievement)
return new_goals
return False
[docs] def increment(self, tracked_id, achievement, amount=1, *args, **kwargs):
"""
Increments an achievement for a given ``tracked_id``. Achievement can be an ``Achievement``
class or a string of the name of an achievement class that has been registered with this
tracker.
Raises NotRegistered if the given achievement is not registered with the tracker.
If ``tracked_id`` has not been tracked yet by this tracker, it will be created before
incrementing.
Returns an list of achieved goals if a new goal was reached, or False
"""
achievement = self.achievement_for_id(tracked_id, achievement)
cur_level = achievement.current[0]
achieved = achievement.achieved[:]
achievement.increment(amount, *args, **kwargs)
self._backend.set_level_for_id(tracked_id, achievement.__class__, achievement.current[0])
return self._check_signals(tracked_id, achievement, cur_level, achieved)
[docs] def evaluate(self, tracked_id, achievement, *args, **kwargs):
"""
Evaluates an achievement for a given ``tracked_id``. Achievement can be an ``Achievement``
class or a string of the name of an achievement class that has been registered with
this tracker.
Raises NotRegistered if the given achievement is not registered with the tracker.
If ``tracked_id`` has not been tracked yet by this tracker, it will be created before
evaluating.
Returns list of achieved goals for the given achievement after evaluation
"""
achievement = self.achievement_for_id(tracked_id, achievement)
cur_level = achievement.current[0]
achieved = achievement.achieved[:]
result = achievement.evaluate(*args, **kwargs)
self._backend.set_level_for_id(tracked_id, achievement.__class__, achievement.current[0])
self._check_signals(tracked_id, achievement, cur_level, achieved)
return result
[docs] def current(self, tracked_id, achievement):
"""
Returns ``current`` for a given tracked_id. See :ref:``Achievement``
"""
achievement = self.achievement_for_id(tracked_id, achievement)
return achievement.current
[docs] def achieved(self, tracked_id, achievement):
"""
Returns ``achieved`` for a given tracked_id. See :ref:``Achievement``
"""
achievement = self.achievement_for_id(tracked_id, achievement)
return achievement.achieved
[docs] def unachieved(self, tracked_id, achievement):
"""
Returns ``unachieved`` for a given tracked_id. See :ref:``Achievement``
"""
achievement = self.achievement_for_id(tracked_id, achievement)
return achievement.unachieved
[docs] def set_level(self, tracked_id, achievement, level):
"""
Returns ``set_level`` for a given tracked_id. See :ref:``Achievement``
"""
achievement = self.achievement_for_id(tracked_id, achievement)
cur_level = achievement.current[0]
achieved = achievement.achieved[:]
achievement.set_level(level)
self._backend.set_level_for_id(tracked_id, achievement.__class__, achievement.current[0])
self._check_signals(tracked_id, achievement, cur_level, achieved)
[docs] def get_tracked_ids(self):
""" Returns all tracked ids """
return self._backend.get_tracked_ids()
[docs] def remove_id(self, tracked_id):
""" Remove all tracked information for tracked_id """
self._backend.remove_id(tracked_id)