PK|¼Fx‚Aé  marathon/exceptions.pyclass MarathonError(Exception): pass class MarathonHttpError(MarathonError): def __init__(self, response): """ :param :class:`requests.Response` response: HTTP response """ content = response.json() self.status_code = response.status_code self.error_message = content['message'] super(MarathonHttpError, self).__init__(self.__str__() ) def __repr__(self): return 'MarathonHttpError: HTTP %s returned with message, "%s"' % \ (self.status_code, self.error_message) def __str__(self): return self.__repr__() class NotFoundError(MarathonHttpError): pass class InternalServerError(MarathonHttpError): pass class InvalidChoiceError(MarathonError): def __init__(self, param, value, options): super(InvalidChoiceError, self).__init__( 'Invalid choice "{value}" for param "{param}". Must be one of {options}'.format( param=param, value=value, options=options ) )PKðxøF­¥m“««marathon/util.pyimport collections import datetime import types try: import json except ImportError: import simplejson as json import re def is_stringy(obj): return isinstance(obj, str) or isinstance(obj, unicode) class MarathonJsonEncoder(json.JSONEncoder): """Custom JSON encoder for Marathon object serialization.""" def default(self, obj): if hasattr(obj, 'json_repr'): return self.default(obj.json_repr()) if isinstance(obj, datetime.datetime): return obj.isoformat() if isinstance(obj, collections.Iterable) and not is_stringy(obj): try: return {k: self.default(v) for k,v in obj.items()} except AttributeError: return [self.default(e) for e in obj] return obj class MarathonMinimalJsonEncoder(json.JSONEncoder): """Custom JSON encoder for Marathon object serialization.""" def default(self, obj): if hasattr(obj, 'json_repr'): return self.default(obj.json_repr(minimal=True)) if isinstance(obj, datetime.datetime): return obj.isoformat() if isinstance(obj, collections.Iterable) and not is_stringy(obj): try: return {k: self.default(v) for k,v in obj.items() if (v or v == False)} except AttributeError: return [self.default(e) for e in obj if (e or e == False)] return obj def to_camel_case(snake_str): words = snake_str.split('_') return words[0] + ''.join(w.title() for w in words[1:]) def to_snake_case(camel_str): s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel_str) return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() PK6}tGªó¿çmarathon/__init__.pyimport logging from .client import MarathonClient from .models import MarathonResource, MarathonApp, MarathonTask, MarathonConstraint from .exceptions import MarathonError, MarathonHttpError, NotFoundError, InvalidChoiceError log = logging.getLogger(__name__) PK6}tGßÀAü¯S¯Smarathon/client.pyimport itertools import time import sys try: import json except ImportError: import simplejson as json # Support Python 2 & 3 if sys.version_info[0] == 3: import urllib.parse as urlparse from urllib.error import HTTPError else: import urlparse from urllib2 import HTTPError import requests import requests.exceptions import marathon from .models import MarathonApp, MarathonDeployment, MarathonGroup, MarathonInfo, MarathonTask, MarathonEndpoint from .exceptions import InternalServerError, NotFoundError, MarathonHttpError, MarathonError class MarathonClient(object): """Client interface for the Marathon REST API.""" def __init__(self, servers, username=None, password=None, timeout=10): """Create a MarathonClient instance. If multiple servers are specified, each will be tried in succession until a non-"Connection Error"-type response is received. Servers are expected to have the same username and password. :param servers: One or a priority-ordered list of Marathon URLs (e.g., 'http://host:8080' or ['http://host1:8080','http://host2:8080']) :type servers: str or list[str] :param str username: Basic auth username :param str password: Basic auth password :param int timeout: Timeout (in seconds) for requests to Marathon """ self.servers = servers if isinstance(servers, list) else [servers] self.auth = (username, password) if username and password else None self.timeout = timeout def __repr__(self): return 'Connection:%s' % self.servers @staticmethod def _parse_response(response, clazz, is_list=False, resource_name=None): """Parse a Marathon response into an object or list of objects.""" target = response.json()[resource_name] if resource_name else response.json() if is_list: return [clazz.from_json(resource) for resource in target] else: return clazz.from_json(target) def _do_request(self, method, path, params=None, data=None): """Query Marathon server.""" headers = {'Content-Type': 'application/json', 'Accept': 'application/json'} response = None servers = list(self.servers) while servers and response is None: server = servers.pop(0) url = ''.join([server.rstrip('/'), path]) try: response = requests.request(method, url, params=params, data=data, headers=headers, auth=self.auth, timeout=self.timeout) marathon.log.info('Got response from %s', server) except requests.exceptions.RequestException as e: marathon.log.error('Error while calling %s: %s', url, e.message) if response is None: raise MarathonError('No remaining Marathon servers to try') if response.status_code >= 500: marathon.log.error('Got HTTP {code}: {body}'.format(code=response.status_code, body=response.text)) raise InternalServerError(response) elif response.status_code >= 400: marathon.log.error('Got HTTP {code}: {body}'.format(code=response.status_code, body=response.text)) if response.status_code == 404: raise NotFoundError(response) else: raise MarathonHttpError(response) elif response.status_code >= 300: marathon.log.warn('Got HTTP {code}: {body}'.format(code=response.status_code, body=response.text)) else: marathon.log.debug('Got HTTP {code}: {body}'.format(code=response.status_code, body=response.text)) return response def list_endpoints(self): """List the current endpoints for all applications :returns: list of endpoints :rtype: list[`MarathonEndpoint`] """ return MarathonEndpoint.from_tasks(self.list_tasks()) def create_app(self, app_id, app): """Create and start an app. :param str app_id: application ID :param :class:`marathon.models.app.MarathonApp` app: the application to create :returns: the created app (on success) :rtype: :class:`marathon.models.app.MarathonApp` or False """ app.id = app_id data = app.to_json() response = self._do_request('POST', '/v2/apps', data=data) if response.status_code == 201: return self._parse_response(response, MarathonApp) else: return False def list_apps(self, cmd=None, embed_tasks=False, embed_failures=False, **kwargs): """List all apps. :param str app_id: application ID :param str cmd: if passed, only show apps with a matching `cmd` :param bool embed_tasks: embed tasks in result :param bool embed_failures: embed tasks and last task failure in result :param kwargs: arbitrary search filters :returns: list of applications :rtype: list[:class:`marathon.models.app.MarathonApp`] """ params = {} if cmd: params['cmd'] = cmd if embed_failures: params['embed'] = 'apps.failures' elif embed_tasks: params['embed'] = 'apps.tasks' response = self._do_request('GET', '/v2/apps', params=params) apps = self._parse_response(response, MarathonApp, is_list=True, resource_name='apps') for k, v in kwargs.items(): apps = [o for o in apps if getattr(o, k) == v] return apps def get_app(self, app_id, embed_tasks=False): """Get a single app. :param str app_id: application ID :param bool embed_tasks: embed tasks in result :returns: application :rtype: :class:`marathon.models.app.MarathonApp` """ params = {'embed': 'apps.tasks'} if embed_tasks else {} response = self._do_request('GET', '/v2/apps/{app_id}'.format(app_id=app_id), params=params) return self._parse_response(response, MarathonApp, resource_name='app') def update_app(self, app_id, app, force=False, minimal=True): """Update an app. Applies writable settings in `app` to `app_id` Note: this method can not be used to rename apps. :param str app_id: target application ID :param app: application settings :type app: :class:`marathon.models.app.MarathonApp` :param bool force: apply even if a deployment is in progress :param bool minimal: ignore nulls and empty collections :returns: a dict containing the deployment id and version :rtype: dict """ # Changes won't take if version is set - blank it for convenience app.version = None params = {'force': force} data = app.to_json(minimal=minimal) response = self._do_request('PUT', '/v2/apps/{app_id}'.format(app_id=app_id), params=params, data=data) return response.json() def rollback_app(self, app_id, version, force=False): """Roll an app back to a previous version. :param str app_id: application ID :param str version: application version :param bool force: apply even if a deployment is in progress :returns: a dict containing the deployment id and version :rtype: dict """ params = {'force': force} data = json.dumps({'version': version}) response = self._do_request('PUT', '/v2/apps/{app_id}'.format(app_id=app_id), params=params, data=data) return response.json() def delete_app(self, app_id, force=False): """Stop and destroy an app. :param str app_id: application ID :param bool force: apply even if a deployment is in progress :returns: a dict containing the deployment id and version :rtype: dict """ params = {'force': force} response = self._do_request('DELETE', '/v2/apps/{app_id}'.format(app_id=app_id), params=params) return response.json() def scale_app(self, app_id, instances=None, delta=None, force=False): """Scale an app. Scale an app to a target number of instances (with `instances`), or scale the number of instances up or down by some delta (`delta`). If the resulting number of instances would be negative, desired instances will be set to zero. If both `instances` and `delta` are passed, use `instances`. :param str app_id: application ID :param int instances: [optional] the number of instances to scale to :param int delta: [optional] the number of instances to scale up or down by :param bool force: apply even if a deployment is in progress :returns: a dict containing the deployment id and version :rtype: dict """ if instances is None and delta is None: marathon.log.error('instances or delta must be passed') return try: app = self.get_app(app_id) except NotFoundError: marathon.log.error('App "{app}" not found'.format(app=app_id)) return desired = instances if instances is not None else (app.instances + delta) return self.update_app(app.id, MarathonApp(instances=desired), force=force) def create_group(self, group): """Create and start a group. :param :class:`marathon.models.group.MarathonGroup` group: the group to create :returns: success :rtype: dict containing the version ID """ data = group.to_json() response = self._do_request('POST', '/v2/groups', data=data) return response.json() def list_groups(self, **kwargs): """List all groups. :param kwargs: arbitrary search filters :returns: list of groups :rtype: list[:class:`marathon.models.group.MarathonGroup`] """ response = self._do_request('GET', '/v2/groups') groups = self._parse_response(response, MarathonGroup, is_list=True, resource_name='groups') for k, v in kwargs.items(): groups = [o for o in groups if getattr(o, k) == v] return groups def get_group(self, group_id): """Get a single group. :param str group_id: group ID :returns: group :rtype: :class:`marathon.models.group.MarathonGroup` """ response = self._do_request('GET', '/v2/groups/{group_id}'.format(group_id=group_id)) return self._parse_response(response, MarathonGroup, resource_name='group') def update_group(self, group_id, group, force=False, minimal=True): """Update a group. Applies writable settings in `group` to `group_id` Note: this method can not be used to rename groups. :param str group_id: target group ID :param group: group settings :type group: :class:`marathon.models.group.MarathonGroup` :param bool force: apply even if a deployment is in progress :param bool minimal: ignore nulls and empty collections :returns: a dict containing the deployment id and version :rtype: dict """ # Changes won't take if version is set - blank it for convenience group.version = None params = {'force': force} data = group.to_json(minimal=minimal) response = self._do_request('PUT', '/v2/groups/{group_id}'.format(group_id=group_id), data=data, params=params) return response.json() def rollback_group(self, group_id, version, force=False): """Roll a group back to a previous version. :param str group_id: group ID :param str version: group version :param bool force: apply even if a deployment is in progress :returns: a dict containing the deployment id and version :rtype: dict """ params = {'force': force} response = self._do_request('PUT', '/v2/groups/{group_id}/versions/{version}'.format(group_id=group_id, version=version), params=params) return response.json() def delete_group(self, group_id, force=False): """Stop and destroy a group. :param str group_id: group ID :param bool force: apply even if a deployment is in progress :returns: a dict containing the deleted version :rtype: dict """ params = {'force': force} response = self._do_request('DELETE', '/v2/groups/{group_id}'.format(group_id=group_id), params=params) return response.json() def scale_group(self, group_id, scale_by): """Scale a group by a factor. :param str group_id: group ID :param int scale_by: factor to scale by :returns: a dict containing the deployment id and version :rtype: dict """ params = {'scaleBy': scale_by} response = self._do_request('PUT', '/v2/groups/{group_id}'.format(group_id=group_id), params=params) return response.json() def list_tasks(self, app_id=None, **kwargs): """List running tasks, optionally filtered by app_id. :param str app_id: if passed, only show tasks for this application :param kwargs: arbitrary search filters :returns: list of tasks :rtype: list[:class:`marathon.models.task.MarathonTask`] """ response = self._do_request('GET', '/v2/tasks') tasks = self._parse_response(response, MarathonTask, is_list=True, resource_name='tasks') if app_id: tasks = [task for task in tasks if task.app_id == app_id] [setattr(t, 'app_id', app_id) for t in tasks if app_id and t.app_id is None] for k, v in kwargs.items(): tasks = [o for o in tasks if getattr(o, k) == v] return tasks def kill_tasks(self, app_id, scale=False, host=None, batch_size=0, batch_delay=0): """Kill all tasks belonging to app. :param str app_id: application ID :param bool scale: if true, scale down the app by the number of tasks killed :param str host: if provided, only terminate tasks on this Mesos slave :param int batch_size: if non-zero, terminate tasks in groups of this size :param int batch_delay: time (in seconds) to wait in between batched kills. If zero, automatically determine :returns: list of killed tasks :rtype: list[:class:`marathon.models.task.MarathonTask`] """ def batch(iterable, size): sourceiter = iter(iterable) while True: batchiter = itertools.islice(sourceiter, size) yield itertools.chain([next(batchiter)], batchiter) if batch_size == 0: # Terminate all at once params = {'scale': scale} if host: params['host'] = host response = self._do_request('DELETE', '/v2/apps/{app_id}/tasks'.format(app_id=app_id), params) return self._parse_response(response, MarathonTask, is_list=True, resource_name='tasks') else: # Terminate in batches tasks = self.list_tasks(app_id, host=host) if host else self.list_tasks(app_id) for tbatch in batch(tasks, batch_size): killed_tasks = [self.kill_task(app_id, t.id, scale=scale) for t in tbatch] # Pause until the tasks have been killed to avoid race conditions killed_task_ids = set(t.id for t in killed_tasks) running_task_ids = killed_task_ids while killed_task_ids.intersection(running_task_ids): time.sleep(1) running_task_ids = set(t.id for t in self.get_app(app_id).tasks) if batch_delay == 0: # Pause until the replacement tasks are healthy desired_instances = self.get_app(app_id).instances running_instances = 0 while running_instances < desired_instances: time.sleep(1) running_instances = sum(t.started_at is None for t in self.get_app(app_id).tasks) else: time.sleep(batch_delay) return tasks def kill_task(self, app_id, task_id, scale=False): """Kill a task. :param str app_id: application ID :param str task_id: the task to kill :param bool scale: if true, scale down the app by one if the task exists :returns: the killed task :rtype: :class:`marathon.models.task.MarathonTask` """ params = {'scale': scale} response = self._do_request('DELETE', '/v2/apps/{app_id}/tasks/{task_id}' .format(app_id=app_id, task_id=task_id), params) return self._parse_response(response, MarathonTask, resource_name='task') def list_versions(self, app_id): """List the versions of an app. :param str app_id: application ID :returns: list of versions :rtype: list[str] """ response = self._do_request('GET', '/v2/apps/{app_id}/versions'.format(app_id=app_id)) return [version for version in response.json()['versions']] def get_version(self, app_id, version): """Get the configuration of an app at a specific version. :param str app_id: application ID :param str version: application version :return: application configuration :rtype: :class:`marathon.models.app.MarathonApp` """ response = self._do_request('GET', '/v2/apps/{app_id}/versions/{version}' .format(app_id=app_id, version=version)) return MarathonApp(response.json()) def list_event_subscriptions(self): """List the event subscriber callback URLs. :returns: list of callback URLs :rtype: list[str] """ response = self._do_request('GET', '/v2/eventSubscriptions') return [url for url in response.json()['callbackUrls']] def create_event_subscription(self, url): """Register a callback URL as an event subscriber. :param str url: callback URL :returns: the created event subscription :rtype: dict """ params = {'callbackUrl': url} response = self._do_request('POST', '/v2/eventSubscriptions', params) return response.json() def delete_event_subscription(self, url): """Deregister a callback URL as an event subscriber. :param str url: callback URL :returns: the deleted event subscription :rtype: dict """ params = {'callbackUrl': url} response = self._do_request('DELETE', '/v2/eventSubscriptions', params) return response.json() def list_deployments(self): """List all running deployments. :returns: list of deployments :rtype: list[:class:`marathon.models.deployment.MarathonDeployment`] """ response = self._do_request('GET', '/v2/deployments') return self._parse_response(response, MarathonDeployment, is_list=True) def delete_deployment(self, deployment_id, force=False): """Cancel a deployment. :param str deployment_id: deployment id :param bool force: if true, don't create a rollback deployment to restore the previous configuration :returns: a dict containing the deployment id and version (empty dict if force=True) :rtype: dict """ if force: params = {'force': True} self._do_request('DELETE', '/v2/deployments/{deployment}'.format(deployment=deployment_id), params=params) # Successful DELETE with ?force=true returns empty text (and status code 202). Client code should poll until deployment is removed. return {} else: response = self._do_request('DELETE', '/v2/deployments/{deployment}'.format(deployment=deployment_id)) return response.json() def get_info(self): """Get server configuration information. :returns: server config info :rtype: :class:`marathon.models.info.MarathonInfo` """ response = self._do_request('GET', '/v2/info') return self._parse_response(response, MarathonInfo) def get_leader(self): """Get the current marathon leader. :returns: leader endpoint :rtype: dict """ response = self._do_request('GET', '/v2/leader') return response.json() def delete_leader(self): """Causes the current leader to abdicate, triggers a new election. :returns: message saying leader abdicated :rtype: dict """ response = self._do_request('DELETE', '/v2/leader') return response.json() def ping(self): """Ping the Marathon server. :returns: the text response :rtype: str """ response = self._do_request('GET', '/ping') return response.text def get_metrics(self): """Get server metrics :returns: metrics dict :rtype: dict """ response = self._do_request('GET', '/metrics') return response.json() PK|¼F>…ºóómarathon/models/constraint.pyfrom ..exceptions import InvalidChoiceError from .base import MarathonObject class MarathonConstraint(MarathonObject): """Marathon placement constraint. See https://mesosphere.github.io/marathon/docs/constraints.html :param str field: constraint operator target :param str operator: must be one of [UNIQUE, CLUSTER, GROUP_BY, LIKE, UNLIKE] :param value: [optional] if `operator` is CLUSTER, constrain tasks to servers where `field` == `value`. If `operator` is GROUP_BY, place at most `value` tasks per group. If `operator` is `LIKE` or `UNLIKE`, filter servers using regexp. :type value: str, int, or None """ OPERATORS = ['UNIQUE', 'CLUSTER', 'GROUP_BY', 'LIKE', 'UNLIKE'] """Valid operators""" def __init__(self, field, operator, value=None): if not operator in self.OPERATORS: raise InvalidChoiceError('operator', operator, self.OPERATORS) self.field = field self.operator = operator self.value = value def __repr__(self): if self.value: template = "MarathonConstraint::{field}:{operator}:{value}" else: template = "MarathonConstraint::{field}:{operator}" return template.format(**self.__dict__) def json_repr(self, minimal=False): """Construct a JSON-friendly representation of the object. :param bool minimal: [ignored] :rtype: list """ if self.value: return [self.field, self.operator, self.value] else: return [self.field, self.operator] @classmethod def from_json(cls, obj): """Construct a MarathonConstraint from a parsed response. :param dict attributes: object attributes from parsed response :rtype: :class:`MarathonConstraint` """ if len(obj) == 2: (field, operator) = obj return cls(field, operator) if len(obj) > 2: (field, operator, value) = obj return cls(field, operator, value) PKÛxøF7‹ý marathon/models/container.pyfrom ..exceptions import InvalidChoiceError from .base import MarathonObject class MarathonContainer(MarathonObject): """Marathon health check. See https://mesosphere.github.io/marathon/docs/native-docker.html :param docker: docker field (e.g., {"image": "mygroup/myimage"})' :type docker: :class:`marathon.models.container.MarathonDockerContainer` or dict :param str type: :param volumes: :type volumes: list[:class:`marathon.models.container.MarathonContainerVolume`] or list[dict] """ TYPES = ['DOCKER'] """Valid container types""" def __init__(self, docker=None, type='DOCKER', volumes=None): if not type in self.TYPES: raise InvalidChoiceError('type', type, self.TYPES) self.type = type self.docker = docker if isinstance(docker, MarathonDockerContainer) \ else MarathonDockerContainer().from_json(docker) self.volumes = [ v if isinstance(v, MarathonContainerVolume) else MarathonContainerVolume().from_json(v) for v in (volumes or []) ] class MarathonDockerContainer(MarathonObject): """Docker options. See https://mesosphere.github.io/marathon/docs/native-docker.html :param str image: docker image :param str network: :param port_mappings: :type port_mappings: list[:class:`marathon.models.container.MarathonContainerPortMapping`] or list[dict] :param dict parameters: :param bool privileged: run container in privileged mode :param bool force_pull_image: Force a docker pull before launching """ NETWORK_MODES=['BRIDGE', 'HOST'] """Valid network modes""" def __init__(self, image=None, network='HOST', port_mappings=None, parameters=None, privileged=None, force_pull_image=None, **kwargs): self.image = image if network: if not network in self.NETWORK_MODES: raise InvalidChoiceError('network', network, self.NETWORK_MODES) self.network = network self.port_mappings = [ pm if isinstance(pm, MarathonContainerPortMapping) else MarathonContainerPortMapping().from_json(pm) for pm in (port_mappings or []) ] self.parameters = parameters or {} self.privileged = privileged or False self.force_pull_image = force_pull_image or False class MarathonContainerPortMapping(MarathonObject): """Container port mapping. See https://mesosphere.github.io/marathon/docs/native-docker.html :param int container_port: :param int host_port: :param str protocol: """ PROTOCOLS=['tcp', 'udp'] """Valid protocols""" def __init__(self, container_port=None, host_port=0, service_port=None, protocol='tcp'): self.container_port = container_port self.host_port = host_port self.service_port = service_port if not protocol in self.PROTOCOLS: raise InvalidChoiceError('protocol', protocol, self.PROTOCOLS) self.protocol = protocol class MarathonContainerVolume(MarathonObject): """Volume options. See https://mesosphere.github.io/marathon/docs/native-docker.html :param str container_path: container path :param str host_path: host path :param str mode: one of ['RO', 'RW'] """ MODES=['RO', 'RW'] def __init__(self, container_path=None, host_path=None, mode='RW'): self.container_path = container_path self.host_path = host_path if not mode in self.MODES: raise InvalidChoiceError('mode', mode, self.MODES) self.mode = mode PKðxøFͳ `òòmarathon/models/group.pyfrom .base import MarathonResource, assert_valid_id from .app import MarathonApp class MarathonGroup(MarathonResource): """Marathon group resource. See: https://mesosphere.github.io/marathon/docs/rest-api.html#groups :param apps: :type apps: list[:class:`marathon.models.app.MarathonApp`] or list[dict] :param list[str] dependencies: :param groups: :type groups: list[:class:`marathon.models.group.MarathonGroup`] or list[dict] :param str id: :param str version: """ def __init__(self, apps=None, dependencies=None, groups=None, id=None, version=None): self.apps = [ a if isinstance(a, MarathonApp) else MarathonApp().from_json(a) for a in (apps or []) ] self.dependencies = dependencies or [] self.groups = [ g if isinstance(g, MarathonGroup) else MarathonGroup().from_json(g) for g in (groups or []) ] self.id = assert_valid_id(id) self.version = version PKðxøF=2.0.0)"]}], "summary": "Marathon Client Library", "version": "0.7.4"}PKYtGLuÛE &marathon-0.7.4.dist-info/top_level.txtmarathon PKYtGìndªnnmarathon-0.7.4.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any PKYtGD0¶p‡‡!marathon-0.7.4.dist-info/METADATAMetadata-Version: 2.0 Name: marathon Version: 0.7.4 Summary: Marathon Client Library Home-page: https://github.com/thefactory/marathon-python Author: Mike Babineau Author-email: michael.babineau@gmail.com License: MIT Platform: Posix; MacOS X; Windows Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python Classifier: Topic :: Software Development :: Libraries :: Python Modules Requires-Dist: requests (>=2.0.0) Python interface to the Mesos Marathon REST API. PKYtGNtÝ  marathon-0.7.4.dist-info/RECORDmarathon/__init__.py,sha256=gj-lXynEr3jAnPj9eLC_Ih59eAtSWKzbIis6_NlC7Yo,262 marathon/client.py,sha256=MLHzwKLSTMHzVYpuhK07K0M12wuutB8uAkQaTHGK-cM,21423 marathon/exceptions.py,sha256=r6nPwOl3EKutwTfEEjxcwvRx5lnSz4_pgQSORFmvsoE,1037 marathon/util.py,sha256=yj0iIJV8Mp2pHLSj8bsOvrU1CHiPerigp0qDoD4islw,1707 marathon/models/__init__.py,sha256=MLNIejUmwoLEWqtRYgEyi8xTO10Q9K6Z_1zLzOmaaoI,425 marathon/models/app.py,sha256=ELX0Ag_XVhcJdc3sy-PdsK25B061wlg8Q1Qy1t0MJrM,11143 marathon/models/base.py,sha256=x48M0p_9TY7kdd8vf-U1O3Mc2vHClJafL8U8zZP230M,3375 marathon/models/constraint.py,sha256=IY6xPBeyVOR6h-hnXZODEuMdzXQmhI0lx0Jj1WZK8Cg,2035 marathon/models/container.py,sha256=-bdpTGFVmk7rRwqCmhtfZTZSHS3n5IZMAADnAaX-BwU,3614 marathon/models/deployment.py,sha256=C6TQlA86RU2H0ilb-6LUZhZVs8Q7XuBKNtRbN_pEuqY,3119 marathon/models/endpoint.py,sha256=uq0yU-lICh-ba3gB16BSfLpLK-6t92v2mqF1dlp2a6E,1689 marathon/models/events.py,sha256=hv2a15buCLSko-T9SgPBXcEfnWgbpEBpT6_U-HHQSmQ,4696 marathon/models/group.py,sha256=SjnN81IvugALv1V6Zd90478FYli8TIp3K-MMoi0Drnw,1010 marathon/models/info.py,sha256=aAT1bR5iuXIy13bw9dq97LJXY5nQGr0LzAQ1YxnyATc,5526 marathon/models/queue.py,sha256=npqTc3r8knWF4h002Lm9Se23e3B1fxZsKhTx5uXToxU,493 marathon/models/task.py,sha256=RjYX252rM3Zsm5O77qgNijq7tYOimta7BNhY7n76tyg,3463 marathon-0.7.4.dist-info/DESCRIPTION.rst,sha256=_6eTm2JXvr2-G5NyJpHQLmf1_dRUradFufn2DVCb_MI,51 marathon-0.7.4.dist-info/METADATA,sha256=s9KyTa_L6jN6TRMuM7uUXnHFhKPTj_7iSZUIfIADyCw,903 marathon-0.7.4.dist-info/RECORD,, marathon-0.7.4.dist-info/WHEEL,sha256=GrqQvamwgBV4nLoJe0vhYRSWzWsx7xjlt74FT0SWYfE,110 marathon-0.7.4.dist-info/metadata.json,sha256=eQzFbqan9Plkum0up_C9lygjuC_WSqQRQZbcGAA9OQc,1015 marathon-0.7.4.dist-info/top_level.txt,sha256=0NONVDdiSTkPOrfjARqseY2RGlv_QdX1hXJkXXIR7MA,9 PK|¼Fx‚Aé  marathon/exceptions.pyPKðxøF­¥m“««Amarathon/util.pyPK6}tGªó¿ç marathon/__init__.pyPK6}tGßÀAü¯S¯SR marathon/client.pyPK|¼F>…ºóó1`marathon/models/constraint.pyPKÛxøF7‹ý _hmarathon/models/container.pyPKðxøFͳ `òò·vmarathon/models/group.pyPKðxøF