From abb42dcd4886705d6ba8af27f68ef605218ac67c Mon Sep 17 00:00:00 2001 From: Philip Paquette Date: Wed, 11 Sep 2019 12:58:45 -0400 Subject: Added ReadtheDocs documentation for the public API - Reformatted the docstring to be compatible - Added tests to make sure the documentation compiles properly - Added sphinx as a pip requirement Co-authored-by: Philip Paquette Co-authored-by: notoraptor --- diplomacy/utils/common.py | 100 +++++++++++++++++-------- diplomacy/utils/constants.py | 2 + diplomacy/utils/convoy_paths.py | 4 + diplomacy/utils/equilateral_triangle.py | 25 ++++--- diplomacy/utils/errors.py | 3 + diplomacy/utils/export.py | 7 +- diplomacy/utils/jsonable.py | 44 ++++++----- diplomacy/utils/keywords.py | 1 + diplomacy/utils/network_data.py | 3 +- diplomacy/utils/order_results.py | 19 +++++ diplomacy/utils/parsing.py | 34 +++++++-- diplomacy/utils/priority_dict.py | 11 ++- diplomacy/utils/scheduler_event.py | 13 ++-- diplomacy/utils/sorted_dict.py | 38 +++++----- diplomacy/utils/sorted_set.py | 24 +++--- diplomacy/utils/splitter.py | 3 + diplomacy/utils/strings.py | 6 +- diplomacy/utils/tests/test_jsonable_changes.py | 7 +- diplomacy/utils/tests/test_parsing.py | 2 +- diplomacy/utils/time.py | 3 + 20 files changed, 241 insertions(+), 108 deletions(-) (limited to 'diplomacy/utils') diff --git a/diplomacy/utils/common.py b/diplomacy/utils/common.py index e8d49d6..dbe5f28 100644 --- a/diplomacy/utils/common.py +++ b/diplomacy/utils/common.py @@ -38,9 +38,12 @@ REGEX_UNDERSCORE_THEN_LETTER = re.compile('_([a-z])') REGEX_START_BY_LOWERCASE = re.compile('^[a-z]') def _sub_hash_password(password): - """ Hash long password to allow bcrypt to handle password longer than 72 characters. Module private method. + """ Hash long password to allow bcrypt to handle password longer than 72 characters. + Module private method. + :param password: password to hash. - :return: (String) The hashed password. + :return: The hashed password. + :rtype: str """ # Bcrypt only handles passwords up to 72 characters. We use this hashing method as a work around. # Suggested in bcrypt PyPI page (2018/02/08 12:36 EST): https://pypi.python.org/pypi/bcrypt/3.1.0 @@ -48,16 +51,20 @@ def _sub_hash_password(password): def is_valid_password(password, hashed): """ Check if password matches hashed. + :param password: password to check. :param hashed: a password hashed with method hash_password(). - :return: (Boolean). Indicates if the password matches the hash. + :return: Indicates if the password matches the hash. + :rtype: bool """ return bcrypt.checkpw(_sub_hash_password(password), hashed.encode('utf-8')) def hash_password(password): """ Hash password. Accepts password longer than 72 characters. Public method. + :param password: The password to hash - :return: (String). The hashed password. + :return: The hashed password. + :rtype: str """ return bcrypt.hashpw(_sub_hash_password(password), bcrypt.gensalt(14)).decode('utf-8') @@ -67,8 +74,10 @@ def generate_token(n_bytes=128): def is_dictionary(dict_to_check): """ Check if given variable is a dictionary-like object. + :param dict_to_check: Dictionary to check. - :return: (Boolean). Indicates if the object is a dictionary. + :return: Indicates if the object is a dictionary. + :rtype: bool """ return isinstance(dict_to_check, dict) or all( hasattr(dict_to_check, expected_attribute) @@ -87,8 +96,10 @@ def is_dictionary(dict_to_check): def is_sequence(seq_to_check): """ Check if given variable is a sequence-like object. Note that strings and dictionary-like objects will not be considered as sequences. + :param seq_to_check: Sequence-like object to check. - :return: (Boolean). Indicates if the object is sequence-like. + :return: Indicates if the object is sequence-like. + :rtype: bool """ # Strings and dicts are not valid sequences. if isinstance(seq_to_check, str) or is_dictionary(seq_to_check): @@ -97,8 +108,10 @@ def is_sequence(seq_to_check): def camel_case_to_snake_case(name): """ Convert a string (expected to be in camel case) to snake case. + :param name: string to convert. - :return: string: snake case version of given name. + :return: snake case version of given name. + :rtype: str """ if name == '': return name @@ -106,10 +119,12 @@ def camel_case_to_snake_case(name): return REGEX_LOWER_THEN_UPPER_CASES.sub(r'\1_\2', separated_consecutive_uppers).lower() def snake_case_to_upper_camel_case(name): - """ Convert a string (expected to be in snake case) to camel case and convert first letter to upper case - if it's in lowercase. + """ Convert a string (expected to be in snake case) to camel case and convert first letter + to upper case if it's in lowercase. + :param name: string to convert. :return: camel case version of given name. + :rtype: str """ if name == '': return name @@ -118,6 +133,7 @@ def snake_case_to_upper_camel_case(name): def assert_no_common_keys(dict1, dict2): """ Check that dictionaries does not share keys. + :param dict1: dict :param dict2: dict """ @@ -131,7 +147,8 @@ def assert_no_common_keys(dict1, dict2): def timestamp_microseconds(): """ Return current timestamp with microsecond resolution. - :return: int + + :rtype: int """ delta = datetime.now() - EPOCH return (delta.days * 24 * 60 * 60 + delta.seconds) * 1000000 + delta.microseconds @@ -140,27 +157,29 @@ def str_cmp_class(compare_function): """ Return a new class to be used as string comparator. Example: - ``` - def my_cmp_func(a, b): - # a and b are two strings to compare with a specific code. - # Return -1 if a < b, 0 if a == b, 1 otherwise. - - my_class = str_cmp_class(my_cmp_func) - wrapped_str_1 = my_class(str_to_compare_1) - wrapped_str_2 = my_class(str_to_compare_2) - my_list = [wrapped_str_1, wrapped_str_2] - - # my_list will be sorted according to my_cmp_func. - my_list.sort() - ``` - - :param compare_function: a callable that takes 2 strings a and b, and compares it according to custom rules. - This function should return: - -1 (or a negative value) if a < b - 0 if a == b - 1 (or a positive value) if a > b + .. code-block:: python + + def my_cmp_func(a, b): + # a and b are two strings to compare with a specific code. + # Return -1 if a < b, 0 if a == b, 1 otherwise. + + my_class = str_cmp_class(my_cmp_func) + wrapped_str_1 = my_class(str_to_compare_1) + wrapped_str_2 = my_class(str_to_compare_2) + my_list = [wrapped_str_1, wrapped_str_2] + + # my_list will be sorted according to my_cmp_func. + my_list.sort() + + :param compare_function: a callable that takes 2 strings a and b, and compares + it according to custom rules. This function should return: + + * -1 (or a negative value) if a < b + * 0 if a == b + * 1 (or a positive value) if a > b :return: a comparator class, instanciable with a string. + :type compare_function: callable """ class StringComparator: @@ -188,12 +207,28 @@ def str_cmp_class(compare_function): StringComparator.__name__ = 'StringComparator%s' % (id(compare_function)) return StringComparator -class StringableCode(): +def to_string(element): + """ Convert element to a string and make sure string is wrapped in either simple quotes + (if contains double quotes) or double quotes (if contains simple quotes). + + :param element: element to convert + :return: string version of element + :rtype: str + """ + element = str(element) + if '"' in element: + return "'%s'" % element + if "'" in element: + return '"%s"' % element + return element + +class StringableCode: """ Represents a stringable version of a code (with an optional message) """ def __init__(self, code, message=None): """ Build a StringableCode + :param code: int - code - :param message: Optional. human readable string message associated to the cide + :param message: Optional. human readable string message associated to the code """ if isinstance(code, str) or message is None: message = code @@ -246,13 +281,14 @@ class StringableCode(): """ Format the message of the result """ return StringableCode(self._code, self._message.format(*values)) -class Tornado(): +class Tornado: """ Utilities for Tornado. """ @staticmethod def stop_loop_on_callback_error(io_loop): """ Modify exception handler method of given IO loop so that IO loop stops and raises as soon as an exception is thrown from a callback. + :param io_loop: IO loop :type io_loop: tornado.ioloop.IOLoop """ diff --git a/diplomacy/utils/constants.py b/diplomacy/utils/constants.py index d929e33..47b1419 100644 --- a/diplomacy/utils/constants.py +++ b/diplomacy/utils/constants.py @@ -48,6 +48,8 @@ PRIVATE_BOT_PASSWORD = '#bot:password:28131821--mx1fh5g7hg5gg5g´[],s222222223dj # Time to wait to let a bot set orders for a dummy power. PRIVATE_BOT_TIMEOUT_SECONDS = 60 +# Default rules used to construct a Game object when no rules are provided. +DEFAULT_GAME_RULES = ('SOLITAIRE', 'NO_PRESS', 'IGNORE_ERRORS', 'POWER_CHOICE') class OrderSettings: """ Constants to define flags for attribute Power.order_is_set. """ diff --git a/diplomacy/utils/convoy_paths.py b/diplomacy/utils/convoy_paths.py index 27b6836..7c21863 100644 --- a/diplomacy/utils/convoy_paths.py +++ b/diplomacy/utils/convoy_paths.py @@ -47,6 +47,7 @@ DISK_CACHE_PATH = os.path.join(HOME_DIRECTORY, '.cache', 'diplomacy', CACHE_FILE def display_progress_bar(queue, max_loop_iters): """ Displays a progress bar + :param queue: Multiprocessing queue to display the progress bar :param max_loop_iters: The expected maximum number of iterations """ @@ -122,6 +123,7 @@ def get_convoy_paths(map_object, start_location, max_convoy_length, queue): def build_convoy_paths_cache(map_object, max_convoy_length): """ Builds the convoy paths cache for a map + :param map_object: The instantiated map object :param max_convoy_length: The maximum convoy length permitted :return: A dictionary where the key is the number of fleets in the path and @@ -158,6 +160,7 @@ def build_convoy_paths_cache(map_object, max_convoy_length): def get_file_md5(file_path): """ Calculates a file MD5 hash + :param file_path: The file path :return: The computed md5 hash """ @@ -169,6 +172,7 @@ def get_file_md5(file_path): def add_to_cache(map_name): """ Lazy generates convoys paths for a map and adds it to the disk cache + :param map_name: The name of the map :return: The convoy_paths for that map """ diff --git a/diplomacy/utils/equilateral_triangle.py b/diplomacy/utils/equilateral_triangle.py index d31fe79..6535665 100644 --- a/diplomacy/utils/equilateral_triangle.py +++ b/diplomacy/utils/equilateral_triangle.py @@ -16,15 +16,17 @@ # ============================================================================== # pylint: disable=anomalous-backslash-in-string """ Helper class to compute intersection of a line (OM) with a side of an equilateral triangle, - with O the barycenter of the equilateral triangle and M a point outside the triangle. - A - / | M - / O | - C /______| B - - A = top, B = right, C = left - O = center of triangle - M = point outside of triangle + with O the barycenter of the equilateral triangle and M a point outside the triangle.:: + + A + / | M + / O | + C /______| B + + A = top, B = right, C = left + O = center of triangle + M = point outside of triangle + """ class EquilateralTriangle: """ Helper class that represent an equilateral triangle. @@ -53,6 +55,7 @@ class EquilateralTriangle: def __line_om(self, x_m, y_m): """ Returns the slope and the intersect of the line between O and M + :return: a, b - respectively the slope and the intercept of the line OM """ # pylint:disable=invalid-name @@ -62,6 +65,7 @@ class EquilateralTriangle: def _intersection_with_ab(self, x_m, y_m): """ Return coordinates of intersection of line (OM) with line (AB). + :param x_m: x coordinate of M :param y_m: y coordinate of M :return: coordinates (x, y) of intersection, or (None, None) if either @@ -85,6 +89,7 @@ class EquilateralTriangle: def _intersection_with_ac(self, x_m, y_m): """ Return coordinates of intersection of line (OM) with line (AC). + :param x_m: x coordinate of M :param y_m: y coordinate of M :return: coordinates (x, y) of intersection, or (None, None) if either @@ -107,6 +112,7 @@ class EquilateralTriangle: def _intersection_with_bc(self, x_m, y_m): """ Return coordinates of intersection of line (OM) with line (BC). NB: (BC) is an horizontal line. + :param x_m: x coordinate of M :param y_m: y coordinate of M :return: coordinates (x, y) of intersection, or (None, None) if either @@ -129,6 +135,7 @@ class EquilateralTriangle: """ Return coordinates of the intersection of (OM) with equilateral triangle, with M the point with coordinates (x_m, y_m). Only the intersection with the side of triangle near M is considered. + :param x_m: x coordinate of M :param y_m: y coordinate of M :return: a couple (x, y) of floating values. diff --git a/diplomacy/utils/errors.py b/diplomacy/utils/errors.py index 64e760e..1a63bb1 100644 --- a/diplomacy/utils/errors.py +++ b/diplomacy/utils/errors.py @@ -31,6 +31,7 @@ class MapError(Error): """ Represents a map error """ def __init__(self, code, message): """ Build a MapError + :param code: int code of the error :param message: human readable string message associated to the error """ @@ -40,6 +41,7 @@ class GameError(Error): """ Represents a game error """ def __init__(self, code, message): """ Build a GameError + :param code: int code of the error :param message: human readable string message associated to the error """ @@ -49,6 +51,7 @@ class StdError(Error): """ Represents a standard error """ def __init__(self, code, message): """ Build a StdError + :param code: int code of the error :param message: human readable string message associated to the error """ diff --git a/diplomacy/utils/export.py b/diplomacy/utils/export.py index 6c54689..7ff68d2 100644 --- a/diplomacy/utils/export.py +++ b/diplomacy/utils/export.py @@ -26,9 +26,11 @@ RULES_TO_SKIP = ['SOLITAIRE', 'NO_DEADLINE', 'CD_DUMMIES', 'ALWAYS_WAIT', 'IGNOR def to_saved_game_format(game): """ Converts a game to a standardized JSON format + :param game: game to convert. - :return: A game in the standard JSON format used to saved game (returned object is a dictionary) - :type game: Game + :return: A game in the standard format used to saved game, that can be converted to JSON for serialization + :type game: diplomacy.engine.game.Game + :rtype: Dict """ # Get phase history. @@ -53,6 +55,7 @@ def to_saved_game_format(game): def is_valid_saved_game(saved_game): """ Checks if the saved game is valid. This is an expensive operation because it replays the game. + :param saved_game: The saved game (from to_saved_game_format) :return: A boolean that indicates if the game is valid """ diff --git a/diplomacy/utils/jsonable.py b/diplomacy/utils/jsonable.py index 0ce1a21..a103cbb 100644 --- a/diplomacy/utils/jsonable.py +++ b/diplomacy/utils/jsonable.py @@ -16,25 +16,30 @@ # ============================================================================== """ Abstract Jsonable class with automatic attributes checking and conversion to/from JSON dict. To write a Jsonable sub-class: + - Define a model with expected attribute names and types. Use module `parsing` to describe expected types. - - Override initializer __init__(**kwargs): - - **first**: initialize each attribute defined in model with value None. - - **then** : call parent __init__() method. Attributes will be checked and filled by - Jsonable's __init__() method. - - If needed, add further initialization code after call to parent __init__() method. At this point, - attributes were correctly set based on defined model, and you can now work with them. + - Override initializer ``__init__(**kwargs)``: + + - **first**: initialize each attribute defined in model with value None. + - **then** : call parent __init__() method. Attributes will be checked and filled by + Jsonable's __init__() method. + - If needed, add further initialization code after call to parent __init__() method. At this point, + attributes were correctly set based on defined model, and you can now work with them. Example: - ``` - class MyClass(Jsonable): - model = { - 'my_attribute': parsing.Sequence(int), - } - def __init__(**kwargs): - self.my_attribute = None - super(MyClass, self).__init__(**kwargs) - # my_attribute is now initialized based on model. You can then do any further initialization if needed. - ``` + + .. code-block:: python + + class MyClass(Jsonable): + model = { + 'my_attribute': parsing.Sequence(int), + } + def __init__(**kwargs): + self.my_attribute = None + super(MyClass, self).__init__(**kwargs) + # my_attribute is now initialized based on model. + # You can then do any further initialization if needed. + """ import logging import ujson as json @@ -43,7 +48,7 @@ from diplomacy.utils import exceptions, parsing LOGGER = logging.getLogger(__name__) -class Jsonable(): +class Jsonable: """ Abstract class to ease conversion from/to JSON dict. """ __slots__ = [] __cached__models__ = {} @@ -78,12 +83,14 @@ class Jsonable(): def json(self): """ Convert this object to a JSON string ready to be sent/saved. + :return: string """ return json.dumps(self.to_dict()) def to_dict(self): """ Convert this object to a python dictionary ready for any JSON work. + :return: dict """ model = self.get_model() @@ -95,6 +102,7 @@ class Jsonable(): JSON dictionary is passed by class method from_dict() (see below), and is guaranteed to contain at least all expected model keys. Some keys may be associated to None if initial JSON dictionary did not provide values for them. + :param json_dict: a JSON dictionary to be parsed. :type json_dict: dict """ @@ -102,6 +110,7 @@ class Jsonable(): @classmethod def from_dict(cls, json_dict): """ Convert a JSON dictionary to an instance of this class. + :param json_dict: a JSON dictionary to parse. Dictionary with basic types (int, bool, dict, str, None, etc.) :return: an instance from this class or from a derived one from which it's called. :rtype: cls @@ -133,6 +142,7 @@ class Jsonable(): def get_model(cls): """ Return model associated to current class, and cache it for future uses, to avoid multiple rendering of model for each class derived from Jsonable. Private method. + :return: dict: model associated to current class. """ if cls not in cls.__cached__models__: diff --git a/diplomacy/utils/keywords.py b/diplomacy/utils/keywords.py index cdd9362..ae55fcd 100644 --- a/diplomacy/utils/keywords.py +++ b/diplomacy/utils/keywords.py @@ -15,6 +15,7 @@ # with this program. If not, see . # ============================================================================== """ Aliases and keywords + - Contains aliases and keywords - Keywords are always single words - Aliases are only converted in a second pass, so if they contain a keyword, you should replace diff --git a/diplomacy/utils/network_data.py b/diplomacy/utils/network_data.py index 18e3869..d5fb5a3 100644 --- a/diplomacy/utils/network_data.py +++ b/diplomacy/utils/network_data.py @@ -15,14 +15,13 @@ # with this program. If not, see . # ============================================================================== """ Abstract Jsonable class to create data intended to be exchanged on network. + Used for requests, responses and notifications. To write a sub-class, you must first write a base class for data category (e.g. notifications): - Define header model for network data. - - Define ID field for data category (e.g. "notification_id"). This will be used to create unique identifier for every data exchanged on network. - - Then every sub-class from base class must define parameters (params) model. Params and header must not share any field. """ diff --git a/diplomacy/utils/order_results.py b/diplomacy/utils/order_results.py index fea2773..9720cfe 100644 --- a/diplomacy/utils/order_results.py +++ b/diplomacy/utils/order_results.py @@ -15,6 +15,7 @@ # with this program. If not, see . # ============================================================================== """ Results + - Contains the results labels and code used by the engine """ from diplomacy.utils.common import StringableCode @@ -26,17 +27,35 @@ class OrderResult(StringableCode): """ Represents an order result """ def __init__(self, code, message): """ Build a Order Result + :param code: int code of the order result :param message: human readable string message associated to the order result """ super(OrderResult, self).__init__(code, message) OK = OrderResult(0, '') +"""Order result OK, printed as ``''``""" + NO_CONVOY = OrderResult(ORDER_RESULT_OFFSET + 1, 'no convoy') +"""Order result NO_CONVOY, printed as ``'no convoy'``""" + BOUNCE = OrderResult(ORDER_RESULT_OFFSET + 2, 'bounce') +"""Order result BOUNCE, printed as ``'bounce'``""" + VOID = OrderResult(ORDER_RESULT_OFFSET + 3, 'void') +"""Order result VOID, printed as ``'void'``""" + CUT = OrderResult(ORDER_RESULT_OFFSET + 4, 'cut') +"""Order result CUT, printed as ``'cut'``""" + DISLODGED = OrderResult(ORDER_RESULT_OFFSET + 5, 'dislodged') +"""Order result DISLODGED, printed as ``'dislodged'``""" + DISRUPTED = OrderResult(ORDER_RESULT_OFFSET + 6, 'disrupted') +"""Order result DISRUPTED, printed as ``'disrupted'``""" + DISBAND = OrderResult(ORDER_RESULT_OFFSET + 7, 'disband') +"""Order result DISBAND, printed as ``'disband'``""" + MAYBE = OrderResult(ORDER_RESULT_OFFSET + 8, 'maybe') +"""Order result MAYBE, printed as ``'maybe'``""" diff --git a/diplomacy/utils/parsing.py b/diplomacy/utils/parsing.py index 5175a80..fb29f51 100644 --- a/diplomacy/utils/parsing.py +++ b/diplomacy/utils/parsing.py @@ -44,6 +44,7 @@ LOGGER = logging.getLogger(__name__) def update_model(model, additional_keys, allow_duplicate_keys=True): """ Return a copy of model updated with additional keys. + :param model: (Dictionary). Model to extend :param additional_keys: (Dictionary). Definition of the additional keys to use to update the model. :param allow_duplicate_keys: Boolean. If True, the model key will be updated if present in additional keys. @@ -60,6 +61,7 @@ def update_model(model, additional_keys, allow_duplicate_keys=True): def extend_model(model, additional_keys): """ Return a copy of model updated with additional model keys. Model and additional keys must no share any key. + :param model: (Dictionary). Model to update :param additional_keys: (Dictionary). Definition of the additional keys to add to model. :return: The updated model with the additional keys. @@ -68,6 +70,7 @@ def extend_model(model, additional_keys): def get_type(desired_type): """ Return a ParserType sub-class that matches given type. + :param desired_type: basic type or ParserType sub-class. :return: ParserType sub-class instance. """ @@ -88,8 +91,8 @@ def get_type(desired_type): return PrimitiveType(desired_type) def to_type(json_value, parser_type): - """ Convert a JSON value (python built-in type) to the type - described by parser_type. + """ Convert a JSON value (python built-in type) to the type described by parser_type. + :param json_value: JSON value to convert. :param parser_type: either an instance of a ParserType, or a type convertible to a ParserType (see function get_type() above). @@ -99,6 +102,7 @@ def to_type(json_value, parser_type): def to_json(raw_value, parser_type): """ Convert a value from the type described by parser_type to a JSON value. + :param raw_value: The raw value to convert to JSON. :param parser_type: Either an instance of a ParserType, or a type convertible to a ParserType. :return: The value converted to an equivalent JSON value. @@ -107,6 +111,7 @@ def to_json(raw_value, parser_type): def validate_data(data, model): """ Validates that the data complies with the model + :param data: (Dictionary). A dict of values to validate against the model. :param model: (Dictionary). The model to use for validation. """ @@ -125,6 +130,7 @@ def validate_data(data, model): def update_data(data, model): """ Modifies the data object to add default values if needed + :param data: (Dictionary). A dict of values to update. :param model: (Dictionary). The model to use. """ @@ -142,12 +148,14 @@ def update_data(data, model): class ParserType(metaclass=ABCMeta): """ Abstract base class to check a specific type. """ __slots__ = [] - # We include dict into primitive types to allow parser to accept raw untyped dict (e.g. engine game state). + # We include dict into primitive types to allow parser to accept raw untyped dict + # (e.g. engine game state). primitives = (bytes, int, float, bool, str, dict) @abstractmethod def validate(self, element): """ Makes sure the element is a valid element for this parser type + :param element: The element to validate. :return: None, but raises Error if needed. """ @@ -155,6 +163,7 @@ class ParserType(metaclass=ABCMeta): def update(self, element): """ Returns the correct value to use in the data object. + :param element: The element the model wants to store in the data object of this parser type. :return: The updated element to store in the data object. The updated element might be a different value (e.g. if a default value is present) @@ -164,6 +173,7 @@ class ParserType(metaclass=ABCMeta): def to_type(self, json_value): """ Converts a json_value to this parser type. + :param json_value: The JSON value to convert. :return: The converted JSON value. """ @@ -172,6 +182,7 @@ class ParserType(metaclass=ABCMeta): def to_json(self, raw_value): """ Converts a raw value (of this type) to JSON. + :param raw_value: The raw value (of this type) to convert. :return: The resulting JSON value. """ @@ -185,6 +196,7 @@ class ConverterType(ParserType): """ def __init__(self, element_type, converter_function, json_converter_function=None): """ Initialize a converter type. + :param element_type: expected type :param converter_function: function to be used to check and convert values to expected type. converter_function(value) -> value_compatible_with_expected_type @@ -217,6 +229,7 @@ class DefaultValueType(ParserType): def __init__(self, element_type, default_json_value): """ Initialize a default type checker with expected element type and a default value (if None is present). + :param element_type: The expected type for elements (except if None is provided). :param default_json_value: The default value to set if element=None. Must be a JSON value convertible to element_type, so that new default value is generated from this JSON value @@ -256,7 +269,8 @@ class OptionalValueType(DefaultValueType): def __init__(self, element_type): """ Initialized a optional type checker with expected element type. - :param element_type: The expected type for elements. + + :param element_type: The expected type for elements. """ super(OptionalValueType, self).__init__(element_type, None) @@ -266,6 +280,7 @@ class SequenceType(ParserType): def __init__(self, element_type, sequence_builder=None): """ Initialize a sequence type checker with value type and optional sequence builder. + :param element_type: Expected type for sequence elements. :param sequence_builder: (Optional). A callable used to build the sequence type. Expected args: Iterable @@ -301,6 +316,7 @@ class JsonableClassType(ParserType): def __init__(self, jsonable_element_type): """ Initialize a sub-class of Jsonable. + :param jsonable_element_type: Expected type (should be a subclass of Jsonable). """ # We import Jsonable here to prevent recursive import with module jsonable. @@ -331,12 +347,13 @@ class StringableType(ParserType): So, object may have any type as long as: str(obj) == str( object loaded from str(obj) ) - Expected type: a class with compatible str(cls(string_repr)) or str(cls.from_string(string_repr)). + Expected type: a class with compatible str(cls(str_repr)) or str(cls.from_string(str_repr)). """ __slots__ = ['element_type', 'use_from_string'] def __init__(self, element_type): """ Initialize a parser type with a type convertible from/to string. + :param element_type: Expected type. Needs to be convertible to/from String. """ if hasattr(element_type, 'from_string'): @@ -376,6 +393,7 @@ class DictType(ParserType): def __init__(self, key_type, val_type, dict_builder=None): """ Initialize a dict parser type with expected key type, val type, and optional dict builder. + :param key_type: The expected key type. Must be string or a stringable class. :param val_type: The expected value type. :param dict_builder: Callable to build attribute values. @@ -412,7 +430,8 @@ class IndexedSequenceType(ParserType): __slots__ = ['dict_type', 'sequence_type', 'key_name'] def __init__(self, dict_type, key_name): - """ Initializer: + """ Initializer. + :param dict_type: dictionary parser type to be used to manage object in memory. :param key_name: name of attribute to take in sequence elements to convert sequence to a dictionary. dct = {getattr(element, key_name): element for element in sequence} @@ -447,6 +466,7 @@ class EnumerationType(ParserType): def __init__(self, enum_values): """ Initialize sequence of values type with a sequence of allowed (primitive) values. + :param enum_values: Sequence of allowed values. """ enum_values = set(enum_values) @@ -472,6 +492,7 @@ class SequenceOfPrimitivesType(ParserType): def __init__(self, seq_of_primitives): """ Initialize sequence of primitives type with a sequence of allowed primitives. + :param seq_of_primitives: Sequence of primitives. """ assert seq_of_primitives and all(primitive in self.primitives for primitive in seq_of_primitives) @@ -491,6 +512,7 @@ class PrimitiveType(ParserType): def __init__(self, element_type): """ Initialize a primitive type. + :param element_type: Primitive type. """ assert element_type in self.primitives, 'Expected a primitive type, got %s.' % element_type diff --git a/diplomacy/utils/priority_dict.py b/diplomacy/utils/priority_dict.py index acef8f9..66662e7 100644 --- a/diplomacy/utils/priority_dict.py +++ b/diplomacy/utils/priority_dict.py @@ -18,13 +18,15 @@ import heapq # ------------------------------------------------ -# Adapted from (2018/03/14s): https://docs.python.org/3.6/library/heapq.html#priority-queue-implementation-notes +# Adapted from (2018/03/14s): +# https://docs.python.org/3.6/library/heapq.html#priority-queue-implementation-notes # Unlicensed class PriorityDict(dict): """ Priority Dictionary Implementation """ def __init__(self, **kwargs): """ Initialize the priority queue. + :param kwargs: (optional) initial values for priority queue. """ self.__heap = [] # Heap for entries. An entry is a triple (priority value, key, valid entry flag (boolean)). @@ -36,6 +38,7 @@ class PriorityDict(dict): def __setitem__(self, key, val): """ Sets a key with his associated priority + :param key: The key to set in the dictionary :param val: The priority to associate with the key :return: None @@ -48,7 +51,9 @@ class PriorityDict(dict): heapq.heappush(self.__heap, entry) def __delitem__(self, key): - """ Removes key from dict and marks associated heap entry as invalid (False). Raises KeyError if not found. """ + """ Removes key from dict and marks associated heap entry as invalid (False). + Raises KeyError if not found. + """ entry = self.pop(key) entry[-1] = False @@ -71,6 +76,7 @@ class PriorityDict(dict): def smallest(self): """ Finds the smallest item in the priority dict + :return: A tuple of (priority, key) for the item with the smallest priority, or None if dict is empty. """ while self.__heap and not self.__heap[0][-1]: @@ -85,6 +91,7 @@ class PriorityDict(dict): def copy(self): """ Return a copy of this priority dict. + :rtype: PriorityDict """ return PriorityDict(**{key: entry[0] for key, entry in dict.items(self)}) diff --git a/diplomacy/utils/scheduler_event.py b/diplomacy/utils/scheduler_event.py index 1d097d8..8eaf0ff 100644 --- a/diplomacy/utils/scheduler_event.py +++ b/diplomacy/utils/scheduler_event.py @@ -19,12 +19,15 @@ from diplomacy.utils.jsonable import Jsonable class SchedulerEvent(Jsonable): - """ Scheduler event class. Properties: - - time_unit: unit time (in seconds) used by scheduler (time between 2 tasks checkings). + """ Scheduler event class. + + Properties: + + - **time_unit**: unit time (in seconds) used by scheduler (time between 2 tasks checkings). Currently 1 second in server scheduler. - - time_added: scheduler time (nb. time units) when data was added to scheduler. - - delay: scheduler time (nb. time units) to wait before processing time. - - current_time: current scheduler time (nb. time units). + - **time_added**: scheduler time (nb. time units) when data was added to scheduler. + - **delay**: scheduler time (nb. time units) to wait before processing time. + - **current_time**: current scheduler time (nb. time units). """ __slots__ = ['time_unit', 'time_added', 'delay', 'current_time'] model = { diff --git a/diplomacy/utils/sorted_dict.py b/diplomacy/utils/sorted_dict.py index c1e5cd7..f5ac6d7 100644 --- a/diplomacy/utils/sorted_dict.py +++ b/diplomacy/utils/sorted_dict.py @@ -18,12 +18,13 @@ from diplomacy.utils.common import is_dictionary from diplomacy.utils.sorted_set import SortedSet -class SortedDict(): +class SortedDict: """ Dict with sorted keys. """ __slots__ = ['__val_type', '__keys', '__couples'] def __init__(self, key_type, val_type, kwargs=None): """ Initialize a typed SortedDict. + :param key_type: expected type for keys. :param val_type: expected type for values. :param kwargs: (optional) dictionary-like object: initial values for sorted dict. @@ -40,6 +41,9 @@ class SortedDict(): def builder(key_type, val_type): """ Return a function to build sorted dicts from a dictionary-like object. Returned function expects a dictionary parameter (an object with method items()). + + .. code-block:: python + builder_fn = SortedDict.builder(str, int) my_sorted_dict = builder_fn({'a': 1, 'b': 2}) @@ -157,17 +161,18 @@ class SortedDict(): return self.__keys[position_from:(position_to + 1)] def sub(self, key_from=None, key_to=None): - """ Return a list of values associated to keys between key_from and key_to (both bounds included). + """ Return a list of values associated to keys between key_from and key_to + (both bounds included). - If key_from is None, lowest key in dict is used. - If key_to is None, greatest key in dict is used. - If key_from is not in dict, lowest key in dict greater than key_from is used. - If key_to is not in dict, greatest key in dict less than key_to is used. + - If key_from is None, lowest key in dict is used. + - If key_to is None, greatest key in dict is used. + - If key_from is not in dict, lowest key in dict greater than key_from is used. + - If key_to is not in dict, greatest key in dict less than key_to is used. - If dict is empty, return empty list. - With keys (None, None) return a copy of all values. - With keys (None, key_to), return values from first to the one associated to key_to. - With keys (key_from, None), return values from the one associated to key_from to the last value. + - If dict is empty, return empty list. + - With keys (None, None) return a copy of all values. + - With keys (None, key_to), return values from first to the one associated to key_to. + - With keys (key_from, None), return values from the one associated to key_from to the last value. :param key_from: start key :param key_to: end key @@ -184,7 +189,7 @@ class SortedDict(): :param key_from: start key :param key_to: end key :return: nothing - """ + """ position_from, position_to = self._get_keys_interval(key_from, key_to) keys_to_remove = self.__keys[position_from:(position_to + 1)] for key in keys_to_remove: @@ -207,13 +212,12 @@ class SortedDict(): to easily retrieve values in closed interval [index of key_from; index of key_to] corresponding to Python slice [index of key_from : (index of key_to + 1)] - If dict is empty, return (0, -1), so that python slice [0 : -1 + 1] corresponds to empty interval. - If key_from is None, lowest key in dict is used. - If key_to is None, greatest key in dict is used. - If key_from is not in dict, lowest key in dict greater than key_from is used. - If key_to is not in dict, greatest key in dict less than key_to is used. + - If dict is empty, return (0, -1), so that python slice [0 : -1 + 1] corresponds to empty interval. + - If key_from is None, lowest key in dict is used. + - If key_to is None, greatest key in dict is used. + - If key_from is not in dict, lowest key in dict greater than key_from is used. + - If key_to is not in dict, greatest key in dict less than key_to is used. - Thus: - With keys (None, None), we get interval of all values. - With keys (key_from, None), we get interval for values from key_from to the last key. - With keys (None, key_to), we get interval for values from the first key to key_to. diff --git a/diplomacy/utils/sorted_set.py b/diplomacy/utils/sorted_set.py index 01cfb34..0bd327c 100644 --- a/diplomacy/utils/sorted_set.py +++ b/diplomacy/utils/sorted_set.py @@ -21,15 +21,16 @@ from copy import copy from diplomacy.utils import exceptions from diplomacy.utils.common import is_sequence -class SortedSet(): +class SortedSet: """ Sorted set (sorted values, each value appears once). """ __slots__ = ('__type', '__list') def __init__(self, element_type, content=()): """ Initialize a typed sorted set. + :param element_type: Expected type for values. :param content: (optional) Sequence of values to initialize sorted set with. - """ + """ if not is_sequence(content): raise exceptions.TypeException('sequence', type(content)) self.__type = element_type @@ -40,12 +41,15 @@ class SortedSet(): @staticmethod def builder(element_type): """ Return a function to build sorted sets from content (sequence of values). - :param element_type: expected type for sorted set values. - :return: callable - Returned function expects a content parameter like SortedSet initializer. + + .. code-block:: python + builder_fn = SortedSet.builder(str) my_sorted_set = builder_fn(['c', '3', 'p', '0']) + + :param element_type: expected type for sorted set values. + :return: callable """ return lambda iterable: SortedSet(element_type, iterable) @@ -104,9 +108,9 @@ class SortedSet(): return best_position def get_next_value(self, element): - """ Get lowest value in sorted set greater than given element, or None if such values does not exists - in the sorted set. Given element may not exists in the sorted set. - """ + """ Get lowest value in sorted set greater than given element, or None if such values + does not exists in the sorted set. Given element may not exists in the sorted set. + """ assert isinstance(element, self.__type) if self.__list: best_position = bisect.bisect_right(self.__list, element) @@ -118,8 +122,8 @@ class SortedSet(): return None def get_previous_value(self, element): - """ Get greatest value in sorted set less the given element, or None if such value does not exists - in the sorted set. Given element may not exists in the sorted set. + """ Get greatest value in sorted set less the given element, or None if such value + does not exists in the sorted set. Given element may not exists in the sorted set. """ assert isinstance(element, self.__type) if self.__list: diff --git a/diplomacy/utils/splitter.py b/diplomacy/utils/splitter.py index 5de502e..e1e4169 100644 --- a/diplomacy/utils/splitter.py +++ b/diplomacy/utils/splitter.py @@ -24,6 +24,7 @@ class AbstractStringSplitter(metaclass=ABCMeta): """ Breaks a string into its components - Generic class """ def __init__(self, string, length): """ Constructor + :param string: the string to split :param length: the maximum length of the split """ @@ -60,6 +61,7 @@ class OrderSplitter(AbstractStringSplitter): """ Splits an order into its components """ def __init__(self, string): """ Constructor + :param string: the string to split """ self._unit_index = None @@ -196,6 +198,7 @@ class PhaseSplitter(AbstractStringSplitter): """ Splits a phase into its components """ def __init__(self, string): """ Constructor + :param string: the string to split """ self._season_index = None diff --git a/diplomacy/utils/strings.py b/diplomacy/utils/strings.py index 65efb64..80b83a3 100644 --- a/diplomacy/utils/strings.py +++ b/diplomacy/utils/strings.py @@ -63,6 +63,7 @@ DUMMY = 'dummy' DUMMY_PLAYER = 'dummy_player' DUMMY_POWERS = 'dummy_powers' ERROR = 'error' +ERROR_TYPE = 'error_type' FOR_OMNISCIENCE = 'for_omniscience' FORCED = 'forced' FORCED_ORDERS = 'forced_orders' @@ -235,8 +236,9 @@ def role_is_special(role): def switch_special_role(role): """ Return opposite special role of given special role: - - observer role if omniscient role is given - - omniscient role if observer role is given + + - observer role if omniscient role is given + - omniscient role if observer role is given """ if role == OBSERVER_TYPE: return OMNISCIENT_TYPE diff --git a/diplomacy/utils/tests/test_jsonable_changes.py b/diplomacy/utils/tests/test_jsonable_changes.py index 992a8fb..2f37db3 100644 --- a/diplomacy/utils/tests/test_jsonable_changes.py +++ b/diplomacy/utils/tests/test_jsonable_changes.py @@ -103,9 +103,10 @@ class Version22(Jsonable): class Version3(Jsonable): """ Version 1 with a modified, b removed, e added. To parse a dict between Version3 and Version1: - - a must be convertible in both versions. - - b must be optional in Version1. - - e must be optional in Version3. + + - a must be convertible in both versions. + - b must be optional in Version1. + - e must be optional in Version3. """ model = { 'a': parsing.ConverterType(str, converter_function=str), diff --git a/diplomacy/utils/tests/test_parsing.py b/diplomacy/utils/tests/test_parsing.py index f64ad26..f96a981 100644 --- a/diplomacy/utils/tests/test_parsing.py +++ b/diplomacy/utils/tests/test_parsing.py @@ -21,7 +21,7 @@ from diplomacy.utils.sorted_set import SortedSet from diplomacy.utils.tests.test_common import assert_raises from diplomacy.utils.tests.test_jsonable import MyJsonable -class MyStringable(): +class MyStringable: """ Example of Stringable class. As instances of such class may be used as dict keys, class should define a proper __hash__(). """ diff --git a/diplomacy/utils/time.py b/diplomacy/utils/time.py index 6f250dd..9ac7edd 100644 --- a/diplomacy/utils/time.py +++ b/diplomacy/utils/time.py @@ -24,6 +24,7 @@ import pytz def str_to_seconds(offset_str): """ Converts a time in format 00W00D00H00M00S in number of seconds + :param offset_str: The string to convert (e.g. '20D') :return: Its equivalent in seconds = 1728000 """ @@ -47,6 +48,7 @@ def str_to_seconds(offset_str): def trunc_time(timestamp, trunc_interval, time_zone='GMT'): """ Truncates time at a specific interval (e.g. 20M) (i.e. Rounds to the next :20, :40, :60) + :param timestamp: The unix epoch to truncate (e.g. 1498746120) :param trunc_interval: The truncation interval (e.g. 60*60 or '1H') :param time_zone: The time to use for conversion (defaults to GMT otherwise) @@ -65,6 +67,7 @@ def trunc_time(timestamp, trunc_interval, time_zone='GMT'): def next_time_at(timestamp, time_at, time_zone='GMT'): """ Returns the next timestamp at a specific 'hh:mm' + :param timestamp: The unix timestamp to convert :param time_at: The next 'hh:mm' to have the time rounded to, or 0 to skip :param time_zone: The time to use for conversion (defaults to GMT otherwise) -- cgit v1.2.3