Repository: mesos
Updated Branches:
  refs/heads/master 2a391e803 -> 52b9c0bec


Updated python CLI package to get properly installed via setup.py.

Review: https://reviews.apache.org/r/36819


Project: http://git-wip-us.apache.org/repos/asf/mesos/repo
Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/52b9c0be
Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/52b9c0be
Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/52b9c0be

Branch: refs/heads/master
Commit: 52b9c0becc4cf5c349bce08d5ae3cf7d84b28d5b
Parents: 2a391e8
Author: haosdent huang <[email protected]>
Authored: Fri Aug 14 03:51:58 2015 +0200
Committer: Till Toenshoff <[email protected]>
Committed: Fri Aug 14 03:51:58 2015 +0200

----------------------------------------------------------------------
 Makefile.am                                |   1 +
 bin/mesos.sh.in                            |  11 +-
 configure.ac                               |   1 +
 src/Makefile.am                            |  16 +-
 src/cli/python/mesos/__init__.py           |  17 ---
 src/cli/python/mesos/cli.py                |  57 -------
 src/cli/python/mesos/futures.py            | 188 ------------------------
 src/cli/python/mesos/http.py               |  44 ------
 src/python/cli/setup.py.in                 |  37 +++++
 src/python/cli/src/mesos/__init__.py       |  10 ++
 src/python/cli/src/mesos/cli.py            |  57 +++++++
 src/python/cli/src/mesos/futures.py        | 188 ++++++++++++++++++++++++
 src/python/cli/src/mesos/http.py           |  44 ++++++
 src/python/interface/setup.py.in           |   2 +
 src/python/interface/src/mesos/__init__.py |   4 +
 src/python/native/setup.py.in              |   2 +
 src/python/native/src/mesos/__init__.py    |   4 +
 src/python/protocol/setup.py.in            |   2 +
 src/python/protocol/src/mesos/__init__.py  |   4 +
 src/python/setup.py.in                     |   2 +
 20 files changed, 366 insertions(+), 325 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/Makefile.am
----------------------------------------------------------------------
diff --git a/Makefile.am b/Makefile.am
index f8e958d..cb289b4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -30,6 +30,7 @@ pkgconfig_DATA = mesos.pc
 # Since we generate several files in src/ with config.status, make
 # sure they're regenerated before we recurse into the src directory.
 all-recursive: src/python/setup.py                     \
+       src/python/cli/setup.py                         \
        src/python/interface/setup.py                   \
        src/python/native/setup.py                      \
        src/java/mesos.pom

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/bin/mesos.sh.in
----------------------------------------------------------------------
diff --git a/bin/mesos.sh.in b/bin/mesos.sh.in
index 5cbeac4..499181b 100644
--- a/bin/mesos.sh.in
+++ b/bin/mesos.sh.in
@@ -7,9 +7,9 @@
 # to you under the Apache License, Version 2.0 (the
 # "License"); you may not use this file except in compliance
 # with the License.  You may obtain a copy of the License at
-# 
+#
 #     http://www.apache.org/licenses/LICENSE-2.0
-# 
+#
 # Unless required by applicable law or agreed to in writing, software
 # distributed under the License is distributed on an "AS IS" BASIS,
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -27,11 +27,4 @@ PATH=@abs_top_builddir@/src:${PATH}
 
 export PATH
 
-# Add 'src/cli/python' to PYTHONPATH.
-# TODO(benh): Remove this if/when we install the 'mesos' module via
-# PIP and setuptools.
-PYTHONPATH=@abs_top_srcdir@/src/cli/python:${PYTHONPATH}
-
-export PYTHONPATH
-
 exec @abs_top_builddir@/src/mesos "${@}"

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/configure.ac
----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index b9ecafd..a478ebd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1315,6 +1315,7 @@ There are two possible workarounds for this issue:
   AC_CONFIG_FILES([src/examples/python/test-containerizer],
                   [chmod +x src/examples/python/test-containerizer])
   AC_CONFIG_FILES([src/python/setup.py])
+  AC_CONFIG_FILES([src/python/cli/setup.py])
   AC_CONFIG_FILES([src/python/interface/setup.py])
   AC_CONFIG_FILES([src/python/native/ext_modules.py])
   AC_CONFIG_FILES([src/python/native/setup.py])

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/Makefile.am
----------------------------------------------------------------------
diff --git a/src/Makefile.am b/src/Makefile.am
index ffd2024..e990369 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1030,16 +1030,6 @@ dist_bin_SCRIPTS +=                                      
                \
   cli/mesos-scp                                                                
\
   cli/mesos-tail
 
-# Also install the supporting scripts for the Python based CLI tools.
-# TODO(benh): Use PIP and python/setup.py to do this correctly.
-mesospythonpkglibexecdir = $(pkglibexecdir)/python/mesos
-
-dist_mesospythonpkglibexec_SCRIPTS =                                   \
-  cli/python/mesos/__init__.py                                         \
-  cli/python/mesos/cli.py                                              \
-  cli/python/mesos/futures.py                                          \
-  cli/python/mesos/http.py
-
 # Need to distribute/install webui javascript. We use 'pkgdatadir'
 # instead of 'datadir' as the install directory so we get the the
 # package name (i.e., 'mesos') as part of the path (i.e.,
@@ -1295,6 +1285,10 @@ PHONY_TARGETS += clean-java
 # distribution unconditionally.
 PYTHON_SOURCE =                                                                
\
        python/src/mesos/__init__.py                                    \
+       python/cli/src/mesos/__init__.py                                \
+       python/cli/src/mesos/cli.py                                     \
+       python/cli/src/mesos/futures.py                                 \
+       python/cli/src/mesos/http.py                                    \
        python/interface/src/mesos/__init__.py                          \
        python/interface/src/mesos/interface/__init__.py                \
        python/native/src/mesos/__init__.py                             \
@@ -1354,11 +1348,13 @@ $(PYTHON_SOURCE):
 # can be easily uninstalled. They end up being what gets installed/uninstalled.
 MESOS_EGGS =                                                                   
        \
        python/dist/mesos-$(PACKAGE_VERSION)$(PYTHON_EGG_PUREPY_POSTFIX).egg    
        \
+       
python/dist/mesos.cli-$(PACKAGE_VERSION)$(PYTHON_EGG_PUREPY_POSTFIX).egg        
\
        
python/dist/mesos.interface-$(PACKAGE_VERSION)$(PYTHON_EGG_PUREPY_POSTFIX).egg  
\
        python/dist/mesos.native-$(PACKAGE_VERSION)$(PYTHON_EGG_POSTFIX).egg
 
 MESOS_WHLS =                                                                   
        \
        python/dist/mesos-$(PACKAGE_VERSION)$(PYTHON_WHL_PUREPY_POSTFIX).whl    
        \
+       
python/dist/mesos.cli-$(PACKAGE_VERSION)$(PYTHON_WHL_PUREPY_POSTFIX).whl        
\
        
python/dist/mesos.interface-$(PACKAGE_VERSION)$(PYTHON_WHL_PUREPY_POSTFIX).whl  
\
        python/dist/mesos.native-$(PACKAGE_VERSION)$(PYTHON_WHL_POSTFIX).whl
 

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/cli/python/mesos/__init__.py
----------------------------------------------------------------------
diff --git a/src/cli/python/mesos/__init__.py b/src/cli/python/mesos/__init__.py
deleted file mode 100644
index 028b0d2..0000000
--- a/src/cli/python/mesos/__init__.py
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/env python
-
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/cli/python/mesos/cli.py
----------------------------------------------------------------------
diff --git a/src/cli/python/mesos/cli.py b/src/cli/python/mesos/cli.py
deleted file mode 100644
index 857059e..0000000
--- a/src/cli/python/mesos/cli.py
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/usr/bin/env python
-
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Helper for printing out a message and then the "usage" then exiting.
-def usage(message, parser):
-    import sys
-    sys.stderr.write(message + '\n')
-    parser.print_help()
-    sys.exit(-1)
-
-
-# Helper for printing out a message and then exiting.
-def fatal(message):
-    import sys
-    sys.stderr.write(message + '\n')
-    sys.exit(-1)
-
-
-# Helper that uses 'mesos-resolve' to resolve a master IP:port from
-# one of:
-#     zk://host1:port1,host2:port2,.../path
-#     zk://username:password@host1:port1,host2:port2,.../path
-#     file://path/to/file (where file contains one of the above)
-def resolve(master):
-    import subprocess
-
-    process = subprocess.Popen(
-        ['mesos-resolve', master],
-        stdin=None,
-        stdout=subprocess.PIPE,
-        stderr=subprocess.PIPE,
-        shell=False)
-
-    status = process.wait()
-    if status != 0:
-        raise Exception('Failed to execute \'mesos-resolve %s\':\n%s'
-                        % (master, process.stderr.read()))
-
-    result = process.stdout.read()
-    process.stdout.close()
-    process.stderr.close()
-    return result

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/cli/python/mesos/futures.py
----------------------------------------------------------------------
diff --git a/src/cli/python/mesos/futures.py b/src/cli/python/mesos/futures.py
deleted file mode 100644
index da2f4ce..0000000
--- a/src/cli/python/mesos/futures.py
+++ /dev/null
@@ -1,188 +0,0 @@
-#!/usr/bin/env python
-
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-try:
-    from concurrent.futures import *
-except ImportError:
-    import threading
-    import time
-
-    from Queue import Queue
-    from Queue import Empty
-
-    class TimeoutError(Exception):
-        """The operation timed out"""
-
-    class Future(object):
-        def __init__(self):
-            self._lock = threading.RLock()
-            self._condition = threading.Condition(self._lock)
-            self._done = False
-            self._result = None
-            self._exception = None
-            self._exc_info = None
-            self._callbacks = []
-
-        def cancel(self):
-            # These futures are possibly backed by threads which can
-            # not (easily or portably) be interrupted so instead we
-            # simply don't let people cancel.
-
-            # TODO(benh): If more use cases come about that want to
-            # differentiate a started versus a
-            raise False
-
-        def cancelled(self):
-            return False
-
-        def running(self):
-            return not self.done()
-
-        def done(self):
-            with self._lock:
-                return self._done
-
-        def result(self, timeout=None):
-            with self._lock:
-                self._await(timeout)
-                if self._exception:
-                    raise self._exception
-                return self._result
-
-        def exception(self, timeout=None):
-            with self._lock:
-                self._await(timeout)
-                if self._exception:
-                    return self._exception
-                return None
-
-        def add_done_callback(self, fn):
-            run = False
-            with self._lock:
-                if self._done:
-                    run = True
-                else:
-                    self._callbacks.append(fn)
-            if run:
-                try:
-                    fn(self)
-                except:
-                    # TODO(benh): Log if Exception, but semantics tell
-                    # us to ignore regardless.
-                    pass
-
-        def set_result(self, result):
-            with self._lock:
-                self._result = result
-                self._finish()
-
-        def set_exception(self, exception):
-            with self._lock:
-                self._exception = exception
-                self._finish()
-
-        def _await(self, timeout):
-            with self._lock:
-                if not self._done:
-                    self._condition.wait(timeout)
-                    if not self._done:
-                        raise TimeoutError()
-
-        def _finish(self):
-            callbacks = None
-            with self._lock:
-                self._done = True
-                callbacks = self._callbacks
-                self._callbacks = None
-            for cb in callbacks:
-                try:
-                    cb(self)
-                except:
-                    # TODO(benh): Log if Exception, but semantics tell
-                    # us to ignore regardless.
-                    pass
-
-
-    class Executor(object):
-        def __enter__(self):
-            return self
-
-        def __exit__(self, type, value, traceback):
-            self.shutdown()
-
-
-    def as_completed(futures, timeout=None):
-        # Record the start time in order to determine the remaining
-        # timeout as futures are completed.
-        start = time.time()
-
-        # Use a queue to collect the done futures.
-        queue = Queue()
-
-        # Define a helper for the future "done callback".
-        def done(future):
-            queue.put(future)
-
-        # Add a "done callback" for each future.
-        for future in futures:
-            future.add_done_callback(done)
-
-        # Helper to determine the remaining timeout.
-        def remaining():
-            if timeout is None:
-                return None
-            end = start + timeout
-            remaining = end - time.time()
-            return remaining if remaining >= 0 else 0
-
-        # Now wait until all the futures have completed or we timeout.
-        finished = 0
-        while finished < len(futures):
-            try:
-                yield queue.get(timeout=remaining())
-            except Empty:
-                raise TimeoutError()
-            else:
-                finished += 1
-
-
-class ThreadingExecutor(Executor):
-    def __init__(self):
-        self._threads = []
-
-    def submit(self, fn, *args, **kwargs):
-        future = Future()
-        def run():
-            try:
-                future.set_result(fn(*args, **kwargs))
-            except Exception as e:
-                future.set_exception(e)
-        thread = threading.Thread(target=run)
-        thread.start()
-        self._threads.append(thread)
-        return future
-
-    def map(self, func, iterables, timeout=None):
-        # TODO(benh): Implement!
-        raise NotImplementedError()
-
-    def shutdown(self, wait=True):
-        if wait:
-            for thread in self._threads:
-                thread.join()
-        self._threads = []

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/cli/python/mesos/http.py
----------------------------------------------------------------------
diff --git a/src/cli/python/mesos/http.py b/src/cli/python/mesos/http.py
deleted file mode 100644
index 0e19aa8..0000000
--- a/src/cli/python/mesos/http.py
+++ /dev/null
@@ -1,44 +0,0 @@
-#!/usr/bin/env python
-
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Helper for doing an HTTP GET given a PID, a path, and a query dict.
-# For example:
-#
-#     get('[email protected]:123',
-#         '/endpoint',
-#         {'first': 'ben',
-#          'last': 'hindman'})
-#
-# Would yield: 1.2.3.4:123/endpoint?first='ben'&last='hindman'
-#
-# Note that you can also pass an IP:port (or hostname:port) for 'pid'
-# (i.e., you can omit the ID component of the PID, e.g., 'foo@').
-def get(pid, path, query=None):
-    import urllib2
-
-    from contextlib import closing
-
-    url = 'http://' + pid[(pid.find('@') + 1):] + path
-
-    if query is not None and len(query) > 0:
-        url += '?' + '&'.join(
-            ['%s=%s' % (urllib2.quote(str(key)), urllib2.quote(str(value)))
-             for (key, value) in query.items()])
-
-    with closing(urllib2.urlopen(url)) as file:
-        return file.read()

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/python/cli/setup.py.in
----------------------------------------------------------------------
diff --git a/src/python/cli/setup.py.in b/src/python/cli/setup.py.in
new file mode 100644
index 0000000..0259bc6
--- /dev/null
+++ b/src/python/cli/setup.py.in
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+config = {
+    'name': 'mesos.cli',
+    'version': '@PACKAGE_VERSION@',
+    'description': 'Mesos command line utilities',
+    'author': 'Apache Mesos',
+    'author_email': '[email protected]',
+    'url': 'http://pypi.python.org/pypi/mesos.cli',
+    'namespace_packages': [ 'mesos' ],
+    'packages': [ 'mesos' ],
+    'package_dir': { '': 'src' },
+    'install_requires': [ ],
+    'license': 'Apache 2.0',
+    'keywords': 'mesos',
+    'classifiers': [ ]
+}
+
+from setuptools import setup
+
+setup(**config)

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/python/cli/src/mesos/__init__.py
----------------------------------------------------------------------
diff --git a/src/python/cli/src/mesos/__init__.py 
b/src/python/cli/src/mesos/__init__.py
new file mode 100644
index 0000000..3fcba01
--- /dev/null
+++ b/src/python/cli/src/mesos/__init__.py
@@ -0,0 +1,10 @@
+# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+# Because python does not normally allow the contents of a package to be
+# retrieved from more than one location, this code snippet ensures that the
+# namespace package machinery is operating and that the current package is
+# registered as a namespace package.
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+    from pkgutil import extend_path
+    __path__ = extend_path(__path__, __name__)

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/python/cli/src/mesos/cli.py
----------------------------------------------------------------------
diff --git a/src/python/cli/src/mesos/cli.py b/src/python/cli/src/mesos/cli.py
new file mode 100644
index 0000000..857059e
--- /dev/null
+++ b/src/python/cli/src/mesos/cli.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Helper for printing out a message and then the "usage" then exiting.
+def usage(message, parser):
+    import sys
+    sys.stderr.write(message + '\n')
+    parser.print_help()
+    sys.exit(-1)
+
+
+# Helper for printing out a message and then exiting.
+def fatal(message):
+    import sys
+    sys.stderr.write(message + '\n')
+    sys.exit(-1)
+
+
+# Helper that uses 'mesos-resolve' to resolve a master IP:port from
+# one of:
+#     zk://host1:port1,host2:port2,.../path
+#     zk://username:password@host1:port1,host2:port2,.../path
+#     file://path/to/file (where file contains one of the above)
+def resolve(master):
+    import subprocess
+
+    process = subprocess.Popen(
+        ['mesos-resolve', master],
+        stdin=None,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        shell=False)
+
+    status = process.wait()
+    if status != 0:
+        raise Exception('Failed to execute \'mesos-resolve %s\':\n%s'
+                        % (master, process.stderr.read()))
+
+    result = process.stdout.read()
+    process.stdout.close()
+    process.stderr.close()
+    return result

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/python/cli/src/mesos/futures.py
----------------------------------------------------------------------
diff --git a/src/python/cli/src/mesos/futures.py 
b/src/python/cli/src/mesos/futures.py
new file mode 100644
index 0000000..da2f4ce
--- /dev/null
+++ b/src/python/cli/src/mesos/futures.py
@@ -0,0 +1,188 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+try:
+    from concurrent.futures import *
+except ImportError:
+    import threading
+    import time
+
+    from Queue import Queue
+    from Queue import Empty
+
+    class TimeoutError(Exception):
+        """The operation timed out"""
+
+    class Future(object):
+        def __init__(self):
+            self._lock = threading.RLock()
+            self._condition = threading.Condition(self._lock)
+            self._done = False
+            self._result = None
+            self._exception = None
+            self._exc_info = None
+            self._callbacks = []
+
+        def cancel(self):
+            # These futures are possibly backed by threads which can
+            # not (easily or portably) be interrupted so instead we
+            # simply don't let people cancel.
+
+            # TODO(benh): If more use cases come about that want to
+            # differentiate a started versus a
+            raise False
+
+        def cancelled(self):
+            return False
+
+        def running(self):
+            return not self.done()
+
+        def done(self):
+            with self._lock:
+                return self._done
+
+        def result(self, timeout=None):
+            with self._lock:
+                self._await(timeout)
+                if self._exception:
+                    raise self._exception
+                return self._result
+
+        def exception(self, timeout=None):
+            with self._lock:
+                self._await(timeout)
+                if self._exception:
+                    return self._exception
+                return None
+
+        def add_done_callback(self, fn):
+            run = False
+            with self._lock:
+                if self._done:
+                    run = True
+                else:
+                    self._callbacks.append(fn)
+            if run:
+                try:
+                    fn(self)
+                except:
+                    # TODO(benh): Log if Exception, but semantics tell
+                    # us to ignore regardless.
+                    pass
+
+        def set_result(self, result):
+            with self._lock:
+                self._result = result
+                self._finish()
+
+        def set_exception(self, exception):
+            with self._lock:
+                self._exception = exception
+                self._finish()
+
+        def _await(self, timeout):
+            with self._lock:
+                if not self._done:
+                    self._condition.wait(timeout)
+                    if not self._done:
+                        raise TimeoutError()
+
+        def _finish(self):
+            callbacks = None
+            with self._lock:
+                self._done = True
+                callbacks = self._callbacks
+                self._callbacks = None
+            for cb in callbacks:
+                try:
+                    cb(self)
+                except:
+                    # TODO(benh): Log if Exception, but semantics tell
+                    # us to ignore regardless.
+                    pass
+
+
+    class Executor(object):
+        def __enter__(self):
+            return self
+
+        def __exit__(self, type, value, traceback):
+            self.shutdown()
+
+
+    def as_completed(futures, timeout=None):
+        # Record the start time in order to determine the remaining
+        # timeout as futures are completed.
+        start = time.time()
+
+        # Use a queue to collect the done futures.
+        queue = Queue()
+
+        # Define a helper for the future "done callback".
+        def done(future):
+            queue.put(future)
+
+        # Add a "done callback" for each future.
+        for future in futures:
+            future.add_done_callback(done)
+
+        # Helper to determine the remaining timeout.
+        def remaining():
+            if timeout is None:
+                return None
+            end = start + timeout
+            remaining = end - time.time()
+            return remaining if remaining >= 0 else 0
+
+        # Now wait until all the futures have completed or we timeout.
+        finished = 0
+        while finished < len(futures):
+            try:
+                yield queue.get(timeout=remaining())
+            except Empty:
+                raise TimeoutError()
+            else:
+                finished += 1
+
+
+class ThreadingExecutor(Executor):
+    def __init__(self):
+        self._threads = []
+
+    def submit(self, fn, *args, **kwargs):
+        future = Future()
+        def run():
+            try:
+                future.set_result(fn(*args, **kwargs))
+            except Exception as e:
+                future.set_exception(e)
+        thread = threading.Thread(target=run)
+        thread.start()
+        self._threads.append(thread)
+        return future
+
+    def map(self, func, iterables, timeout=None):
+        # TODO(benh): Implement!
+        raise NotImplementedError()
+
+    def shutdown(self, wait=True):
+        if wait:
+            for thread in self._threads:
+                thread.join()
+        self._threads = []

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/python/cli/src/mesos/http.py
----------------------------------------------------------------------
diff --git a/src/python/cli/src/mesos/http.py b/src/python/cli/src/mesos/http.py
new file mode 100644
index 0000000..0e19aa8
--- /dev/null
+++ b/src/python/cli/src/mesos/http.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Helper for doing an HTTP GET given a PID, a path, and a query dict.
+# For example:
+#
+#     get('[email protected]:123',
+#         '/endpoint',
+#         {'first': 'ben',
+#          'last': 'hindman'})
+#
+# Would yield: 1.2.3.4:123/endpoint?first='ben'&last='hindman'
+#
+# Note that you can also pass an IP:port (or hostname:port) for 'pid'
+# (i.e., you can omit the ID component of the PID, e.g., 'foo@').
+def get(pid, path, query=None):
+    import urllib2
+
+    from contextlib import closing
+
+    url = 'http://' + pid[(pid.find('@') + 1):] + path
+
+    if query is not None and len(query) > 0:
+        url += '?' + '&'.join(
+            ['%s=%s' % (urllib2.quote(str(key)), urllib2.quote(str(value)))
+             for (key, value) in query.items()])
+
+    with closing(urllib2.urlopen(url)) as file:
+        return file.read()

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/python/interface/setup.py.in
----------------------------------------------------------------------
diff --git a/src/python/interface/setup.py.in b/src/python/interface/setup.py.in
index 880e2a6..d739967 100644
--- a/src/python/interface/setup.py.in
+++ b/src/python/interface/setup.py.in
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/python/interface/src/mesos/__init__.py
----------------------------------------------------------------------
diff --git a/src/python/interface/src/mesos/__init__.py 
b/src/python/interface/src/mesos/__init__.py
index f48ad10..3fcba01 100644
--- a/src/python/interface/src/mesos/__init__.py
+++ b/src/python/interface/src/mesos/__init__.py
@@ -1,4 +1,8 @@
 # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+# Because python does not normally allow the contents of a package to be
+# retrieved from more than one location, this code snippet ensures that the
+# namespace package machinery is operating and that the current package is
+# registered as a namespace package.
 try:
     __import__('pkg_resources').declare_namespace(__name__)
 except ImportError:

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/python/native/setup.py.in
----------------------------------------------------------------------
diff --git a/src/python/native/setup.py.in b/src/python/native/setup.py.in
index 9fc9ad2..49ed612 100644
--- a/src/python/native/setup.py.in
+++ b/src/python/native/setup.py.in
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/python/native/src/mesos/__init__.py
----------------------------------------------------------------------
diff --git a/src/python/native/src/mesos/__init__.py 
b/src/python/native/src/mesos/__init__.py
index f48ad10..3fcba01 100644
--- a/src/python/native/src/mesos/__init__.py
+++ b/src/python/native/src/mesos/__init__.py
@@ -1,4 +1,8 @@
 # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+# Because python does not normally allow the contents of a package to be
+# retrieved from more than one location, this code snippet ensures that the
+# namespace package machinery is operating and that the current package is
+# registered as a namespace package.
 try:
     __import__('pkg_resources').declare_namespace(__name__)
 except ImportError:

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/python/protocol/setup.py.in
----------------------------------------------------------------------
diff --git a/src/python/protocol/setup.py.in b/src/python/protocol/setup.py.in
index 72cb770..4c50fbb 100644
--- a/src/python/protocol/setup.py.in
+++ b/src/python/protocol/setup.py.in
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/python/protocol/src/mesos/__init__.py
----------------------------------------------------------------------
diff --git a/src/python/protocol/src/mesos/__init__.py 
b/src/python/protocol/src/mesos/__init__.py
index f48ad10..3fcba01 100644
--- a/src/python/protocol/src/mesos/__init__.py
+++ b/src/python/protocol/src/mesos/__init__.py
@@ -1,4 +1,8 @@
 # See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
+# Because python does not normally allow the contents of a package to be
+# retrieved from more than one location, this code snippet ensures that the
+# namespace package machinery is operating and that the current package is
+# registered as a namespace package.
 try:
     __import__('pkg_resources').declare_namespace(__name__)
 except ImportError:

http://git-wip-us.apache.org/repos/asf/mesos/blob/52b9c0be/src/python/setup.py.in
----------------------------------------------------------------------
diff --git a/src/python/setup.py.in b/src/python/setup.py.in
index 8ffde68..7370669 100644
--- a/src/python/setup.py.in
+++ b/src/python/setup.py.in
@@ -1,3 +1,5 @@
+#!/usr/bin/env python
+
 # Licensed to the Apache Software Foundation (ASF) under one
 # or more contributor license agreements.  See the NOTICE file
 # distributed with this work for additional information

Reply via email to