#!/usr/bin/env python3
# pylint: disable=unused-import
from nonstdlib import MagicFormatter, fmt
from nonstdlib import log, debug, info, warning, error, critical
from pprint import pprint
## Anatomy of an error message
# ============================
# 1. One line that succinctly states what the problem is. Be mindful that
# errors can be raised in situations you might not have thought of, so try
# to phrase this line in a way that doesn't make assumptions and that won't
# mislead the user if it's triggered in an unexpected way.
#
# 2. In a separate paragraph, a few sentences that elaborate on the problem.
# In particular, try to cover the following points:
#
# - Explain the most likely cause of the problem. This is in slight contrast
# to the first line, which should strive to be context-neutral.
#
# - Explain why this is a problem, or in other words, why the engine had to
# raise an exception when it encountered this scenario.
#
# - Suggest a way to solve the problem.
## Anatomy of an assertion message
# ================================
# 1. One sentence explaining why this line should never have been reached.
#
# "SomeClass.some_method() should've refused to ..."
## ApiUsageError vs assertions
# ============================
# Use ApiUsageError for errors that the user trigger by misusing the game
# engine's public API.
#
# Use assertions for errors the user should not be able to trigger without
# changing private attributes or calling private methods.
#
# For example, there are several places where the game engine could notice that
# the same token is being added to the world twice. It's good for all these
# places to make the check to be sure they're being used correctly and that a
# token can't be added to the world twice, but only the first one should raise
# an ApiUsageError. The rest should be assertions, and should mention what was
# supposed to have happened.
msg = format_assertion_message
[docs]class ApiUsageError(Exception):
"""
Tell the user when they're misusing the game engine and suggest how they
should be using it instead.
"""
message_width = 79
def __init__(self, message, *args, **kwargs):
prefix = '{cls.__module__}.{cls.__name__}: '.format(cls=self.__class__)
message = format_error_message(prefix, 3, message, *args, **kwargs)
super().__init__(message)
[docs]def debug_only(function):
if __debug__:
return function
else:
return lambda *args, **kwargs: None
[docs]@debug_only
def require_instance(prototype, object):
prototype_cls = prototype.__class__.__name__
object_cls = object.__class__.__name__
if not isinstance(object, type(prototype)):
raise ApiUsageError("""\
expected {prototype_cls}, but got {object_cls} instead.""")
for member_name in prototype.__dict__:
if not hasattr(object, member_name):
raise ApiUsageError("""\
forgot to call the {prototype_cls} constructor in
{object_cls}.__init__().
The game engine was passed an object that inherits from
{prototype_cls} but is missing the '{member_name}' attribute.
This usually means that you forgot to call the {prototype_cls}
constructor in your subclass.""")