File: base.py

package info (click to toggle)
python-marathon 0.13.0-7
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 460 kB
  • sloc: python: 1,969; makefile: 185; sh: 58
file content (120 lines) | stat: -rw-r--r-- 4,025 bytes parent folder | download | duplicates (3)
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