1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
|
import json
import re
from marathon.util import to_camel_case, to_snake_case, MarathonJsonEncoder, MarathonMinimalJsonEncoder
class MarathonObject:
"""Base Marathon object."""
def __repr__(self):
return "{clazz}::{obj}".format(clazz=self.__class__.__name__, obj=self.to_json(minimal=False))
def __eq__(self, other):
try:
return self.__dict__ == other.__dict__
except Exception:
return False
def __hash__(self):
# Technically this class shouldn't be hashable because it often
# contains mutable fields, but in practice this class is used more
# like a record or namedtuple.
return hash(self.to_json())
def json_repr(self, minimal=False):
"""Construct a JSON-friendly representation of the object.
:param bool minimal: Construct a minimal representation of the object (ignore nulls and empty collections)
:rtype: dict
"""
if minimal:
return {to_camel_case(k): v for k, v in vars(self).items() if (v or v is False or v == 0)}
else:
return {to_camel_case(k): v for k, v in vars(self).items()}
@classmethod
def from_json(cls, attributes):
"""Construct an object from a parsed response.
:param dict attributes: object attributes from parsed response
"""
return cls(**{to_snake_case(k): v for k, v in attributes.items()})
def to_json(self, minimal=True):
"""Encode an object as a JSON string.
:param bool minimal: Construct a minimal representation of the object (ignore nulls and empty collections)
:rtype: str
"""
if minimal:
return json.dumps(self.json_repr(minimal=True), cls=MarathonMinimalJsonEncoder, sort_keys=True)
else:
return json.dumps(self.json_repr(), cls=MarathonJsonEncoder, sort_keys=True)
class MarathonResource(MarathonObject):
"""Base Marathon resource."""
def __repr__(self):
if 'id' in list(vars(self).keys()):
return f"{self.__class__.__name__}::{self.id}"
else:
return "{clazz}::{obj}".format(clazz=self.__class__.__name__, obj=self.to_json())
def __eq__(self, other):
try:
return self.__dict__ == other.__dict__
except Exception:
return False
def __hash__(self):
# Technically this class shouldn't be hashable because it often
# contains mutable fields, but in practice this class is used more
# like a record or namedtuple.
return hash(self.to_json())
def __str__(self):
return f"{self.__class__.__name__}::" + str(self.__dict__)
# See:
# https://github.com/mesosphere/marathon/blob/2a9d1d20ec2f1cfcc49fbb1c0e7348b26418ef38/src/main/scala/mesosphere/marathon/api/ModelValidation.scala#L224
ID_PATTERN = re.compile(
'^(([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])\\.)*([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])|(\\.|\\.\\.)$')
def assert_valid_path(path):
"""Checks if a path is a correct format that Marathon expects. Raises ValueError if not valid.
:param str path: The app id.
:rtype: str
"""
if path is None:
return
# As seen in:
# https://github.com/mesosphere/marathon/blob/0c11661ca2f259f8a903d114ef79023649a6f04b/src/main/scala/mesosphere/marathon/state/PathId.scala#L71
for id in filter(None, path.strip('/').split('/')):
if not ID_PATTERN.match(id):
raise ValueError(
'invalid path (allowed: lowercase letters, digits, hyphen, "/", ".", ".."): %r' % path)
return path
def assert_valid_id(id):
"""Checks if an id is the correct format that Marathon expects. Raises ValueError if not valid.
:param str id: App or group id.
:rtype: str
"""
if id is None:
return
if not ID_PATTERN.match(id.strip('/')):
raise ValueError(
'invalid id (allowed: lowercase letters, digits, hyphen, ".", ".."): %r' % id)
return id
|