Throw more informative error classes from perf.
[pdm.git] / pdm / cli.py
index 9924d40..df04883 100644 (file)
@@ -1,7 +1,9 @@
-"""Management for daemon processes
+"""Python Daemon Management -- Client functions
 
 
-This module provides some client support for the daemon management
-provided in the pdm.srv module.
+This module implements the client part of the PDM protocols. The
+primary objects of interest are the replclient and perfclient classes,
+which implement support for their respective protocols. See their
+documentation for details.
 """
 
 import socket, pickle, struct, select, threading
 """
 
 import socket, pickle, struct, select, threading
@@ -17,16 +19,21 @@ def resolve(spec):
         return spec
     sk = None
     try:
         return spec
     sk = None
     try:
-        if "/" in spec:
+        if ":" in spec:
+            p = spec.rindex(":")
+            first, second = spec[:p], spec[p + 1:]
+            if "/" in second:
+                from . import sshsock
+                sk = sshsock.sshsocket(first, second)
+            else:
+                sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+                sk.connect((first, second))
+        elif "/" in spec:
             sk = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
             sk.connect(spec)
         elif spec.isdigit():
             sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             sk.connect(("localhost", int(spec)))
             sk = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
             sk.connect(spec)
         elif spec.isdigit():
             sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             sk.connect(("localhost", int(spec)))
-        elif ":" in spec:
-            sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-            p = spec.rindex(":")
-            sk.connect((spec[:p], int(spec[p + 1:])))
         else:
             raise Exception("Unknown target specification %r" % spec)
         rv = sk
         else:
             raise Exception("Unknown target specification %r" % spec)
         rv = sk
@@ -49,12 +56,12 @@ class client(object):
         """Create a client object connected to the specified
         server. `sk' can either be a socket object, which is used as
         it is, or a string specification very similar to the
         """Create a client object connected to the specified
         server. `sk' can either be a socket object, which is used as
         it is, or a string specification very similar to the
-        specification for pdm.srv.listen, so see its documentation for
-        details. The differences are only that this function does not
-        take arguments specific to socket creation, like the mode and
-        group arguments for Unix sockets. If `proto' is given, that
-        subprotocol will negotiated with the server (by calling the
-        select() method).
+        specification for L{pdm.srv.listen}, so see its documentation
+        for details. The differences are only that this function does
+        not take arguments specific to socket creation, like the mode
+        and group arguments for Unix sockets. If `proto' is given,
+        that subprotocol will negotiated with the server (by calling
+        the select() method).
         """
         self.sk = resolve(sk)
         self.buf = b""
         """
         self.sk = resolve(sk)
         self.buf = b""
@@ -64,13 +71,19 @@ class client(object):
         if proto is not None:
             self.select(proto)
 
         if proto is not None:
             self.select(proto)
 
+    @property
+    def closed(self):
+        return self.sk is None
+
     def close(self):
         """Close this connection"""
     def close(self):
         """Close this connection"""
-        self.sk.close()
+        if self.sk is not None:
+            self.sk.close()
+            self.sk = None
 
     def fileno(self):
         """Return the file descriptor of the underlying socket."""
 
     def fileno(self):
         """Return the file descriptor of the underlying socket."""
-        return self.sk.fileno()
+        return self.sk.fileno() if self.sk else None
 
     def readline(self):
         """Read a single NL-terminated line and return it."""
 
     def readline(self):
         """Read a single NL-terminated line and return it."""
@@ -106,8 +119,8 @@ class client(object):
 class replclient(client):
     """REPL protocol client
     
 class replclient(client):
     """REPL protocol client
     
-    Implements the client side of the REPL protocol; see pdm.srv.repl
-    for details on the protocol and its functionality.
+    Implements the client side of the REPL protocol; see
+    L{pdm.srv.repl} for details on the protocol and its functionality.
     """
     def __init__(self, sk):
         """Create a connected client as documented in the `client' class."""
     """
     def __init__(self, sk):
         """Create a connected client as documented in the `client' class."""
@@ -115,8 +128,8 @@ class replclient(client):
 
     def run(self, code):
         """Run a single block of Python code on the server. Returns
 
     def run(self, code):
         """Run a single block of Python code on the server. Returns
-        the output of the command (as documented in pdm.srv.repl) as a
-        string.
+        the output of the command (as documented in L{pdm.srv.repl})
+        as a string.
         """
         while True:
             ncode = code.replace("\n\n", "\n")
         """
         while True:
             ncode = code.replace("\n\n", "\n")
@@ -190,7 +203,8 @@ class perfproxy(object):
 
     def close(self):
         if self.id is not None:
 
     def close(self):
         if self.id is not None:
-            self.cl.run("unbind", self.id)
+            if not self.cl.closed:
+                self.cl.run("unbind", self.id)
             del self.cl.proxies[self.id]
             self.id = None
 
             del self.cl.proxies[self.id]
             self.id = None
 
@@ -207,8 +221,8 @@ class perfproxy(object):
 class perfclient(client):
     """PERF protocol client
     
 class perfclient(client):
     """PERF protocol client
     
-    Implements the client side of the PERF protocol; see pdm.srv.perf
-    for details on the protocol and its functionality.
+    Implements the client side of the PERF protocol; see
+    L{pdm.srv.perf} for details on the protocol and its functionality.
 
     This client class implements functions for finding PERF objects on
     the server, and returns, for each server-side object looked up, a
 
     This client class implements functions for finding PERF objects on
     the server, and returns, for each server-side object looked up, a
@@ -218,8 +232,8 @@ class perfclient(client):
     they implement a close() method for that purpose, and can also be
     used in `with' statements.
 
     they implement a close() method for that purpose, and can also be
     used in `with' statements.
 
-    See pdm.srv.perf for details on the various PERF interfaces that
-    the proxy objects might implement.
+    See L{pdm.srv.perf} for details on the various PERF interfaces
+    that the proxy objects might implement.
     """
     def __init__(self, sk):
         """Create a connected client as documented in the `client' class."""
     """
     def __init__(self, sk):
         """Create a connected client as documented in the `client' class."""
@@ -315,6 +329,11 @@ class perfclient(client):
         of the slash. For instance, find("pdm.perf.sysres/cputime")
         will return the built-in attribute for reading the CPU time
         used by the server process.
         of the slash. For instance, find("pdm.perf.sysres/cputime")
         will return the built-in attribute for reading the CPU time
         used by the server process.
+
+        The proxy objects returned by this function are cached and the
+        same object is returned the next time the same name is
+        requested, which means that they are kept live until the
+        client connection is closed.
         """
         ret = self.names.get(name)
         if ret is None:
         """
         ret = self.names.get(name)
         if ret is None: