aboutsummaryrefslogtreecommitdiff
path: root/diplomacy/utils
diff options
context:
space:
mode:
Diffstat (limited to 'diplomacy/utils')
-rw-r--r--diplomacy/utils/common.py100
-rw-r--r--diplomacy/utils/constants.py2
-rw-r--r--diplomacy/utils/convoy_paths.py4
-rw-r--r--diplomacy/utils/equilateral_triangle.py25
-rw-r--r--diplomacy/utils/errors.py3
-rw-r--r--diplomacy/utils/export.py7
-rw-r--r--diplomacy/utils/jsonable.py44
-rw-r--r--diplomacy/utils/keywords.py1
-rw-r--r--diplomacy/utils/network_data.py3
-rw-r--r--diplomacy/utils/order_results.py19
-rw-r--r--diplomacy/utils/parsing.py34
-rw-r--r--diplomacy/utils/priority_dict.py11
-rw-r--r--diplomacy/utils/scheduler_event.py13
-rw-r--r--diplomacy/utils/sorted_dict.py38
-rw-r--r--diplomacy/utils/sorted_set.py24
-rw-r--r--diplomacy/utils/splitter.py3
-rw-r--r--diplomacy/utils/strings.py6
-rw-r--r--diplomacy/utils/tests/test_jsonable_changes.py7
-rw-r--r--diplomacy/utils/tests/test_parsing.py2
-rw-r--r--diplomacy/utils/time.py3
20 files changed, 241 insertions, 108 deletions
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 <https://www.gnu.org/licenses/>.
# ==============================================================================
""" 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 <https://www.gnu.org/licenses/>.
# ==============================================================================
""" 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 <https://www.gnu.org/licenses/>.
# ==============================================================================
""" 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)