You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by gs...@apache.org on 2014/10/10 14:54:43 UTC

svn commit: r1630834 - in /qpid/proton/branches/examples: proton-c/bindings/python/cproton.i proton-c/bindings/python/proton.py tests/python/proton_tests/__init__.py tests/python/proton_tests/url.py

Author: gsim
Date: Fri Oct 10 12:54:43 2014
New Revision: 1630834

URL: http://svn.apache.org/r1630834
Log:
PROTON-693: Python Url class to wrap C function pni_parse_url

Also added unit tests.

Added:
    qpid/proton/branches/examples/tests/python/proton_tests/url.py
Modified:
    qpid/proton/branches/examples/proton-c/bindings/python/cproton.i
    qpid/proton/branches/examples/proton-c/bindings/python/proton.py
    qpid/proton/branches/examples/tests/python/proton_tests/__init__.py

Modified: qpid/proton/branches/examples/proton-c/bindings/python/cproton.i
URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/proton-c/bindings/python/cproton.i?rev=1630834&r1=1630833&r2=1630834&view=diff
==============================================================================
--- qpid/proton/branches/examples/proton-c/bindings/python/cproton.i (original)
+++ qpid/proton/branches/examples/proton-c/bindings/python/cproton.i Fri Oct 10 12:54:43 2014
@@ -280,4 +280,41 @@ int pn_ssl_get_peer_hostname(pn_ssl_t *s
   }
 %}
 
+
+/**
+   pni_parse_url(char* url, char **scheme, char **user, char **pass, char **host, char **port, char **path)
+   The following type maps convert this into a python function that taks a URL string argument
+   and returns a list of strings [scheme, user, pass, host, port, path]
+   This probably could be done more neatly.
+*/
+
+// Typemap to copy the url string as it will be modified by parse_url
+%typemap(in,noblock=1,fragment="SWIG_AsCharPtrAndSize") char *url (int res, char *t = 0, size_t n = 0, int alloc = 0) {
+  res = SWIG_AsCharPtrAndSize($input, &t, &n, &alloc);
+  if (!SWIG_IsOK(res)) {
+    %argument_fail(res, "char *url", $symname, $argnum);
+  }
+  $1 = %new_array(n, $*1_ltype);
+  memcpy($1,t,sizeof(char)*n);
+  if (alloc == SWIG_NEWOBJ) %delete_array(t);
+  $1[n-1] = 0;
+}
+%typemap(freearg,match="in") char *url "free($1);";
+%typemap(argout) char *url "";
+
+// Typemap for char** return strings. Don't free them.
+%typemap(in,numinputs=0) char **OUTSTR($*1_ltype temp = 0) "$1 = &temp;";
+%typemap(freearg,match="in") char **OUTSTR "";
+%typemap(argout,noblock=1,fragment="SWIG_FromCharPtr") char **OUTSTR {
+    %append_output(SWIG_FromCharPtr(*$1));
+}
+
+// Typemap to initialize result as empty list
+%typemap(out) void "$result = PyList_New(0);";
+
+
+%apply char** OUTSTR {char **scheme, char **user, char **pass, char **host, char **port, char **path};
+void pni_parse_url(char* url, char **scheme, char **user, char **pass, char **host, char **port, char **path);
+%ignore pni_parse_url;
+
 %include "proton/cproton.i"

Modified: qpid/proton/branches/examples/proton-c/bindings/python/proton.py
URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/proton-c/bindings/python/proton.py?rev=1630834&r1=1630833&r2=1630834&view=diff
==============================================================================
--- qpid/proton/branches/examples/proton-c/bindings/python/proton.py (original)
+++ qpid/proton/branches/examples/proton-c/bindings/python/proton.py Fri Oct 10 12:54:43 2014
@@ -3654,3 +3654,117 @@ __all__ = [
            "timestamp",
            "ulong"
            ]
+
+
+class Url(object):
+    """
+    Simple URL parser/constructor, handles URLs of the form:
+
+      <scheme>://<user>:<password>@<host>:<port>/<path>
+
+    All components can be None if not specifeid in the URL string.
+
+    The port can be specified as a service name, e.g. 'amqp' in the
+    URL string but Url.port always gives the integer value.
+
+    @ivar scheme: Url scheme e.g. 'amqp' or 'amqps'
+    @ivar user: Username
+    @ivar password: Password
+    @ivar host: Host name, ipv6 literal or ipv4 dotted quad.
+    @ivar port: Integer port.
+    @ivar host_port: Returns host:port
+    """
+
+    AMQPS = "amqps"
+    AMQP = "amqp"
+
+    class Port(int):
+      """An integer port number that can also have an associated service name string"""
+
+      def __new__(cls, value):
+        port = super(Url.Port, cls).__new__(cls, cls.port_int(value))
+        setattr(port, 'name', str(value))
+        return port
+
+      def __eq__(self, x): return str(self) == x or int(self) == x
+      def __ne__(self, x): return not self == x
+      def __str__(self): return str(self.name)
+
+      @staticmethod
+      def port_int(value):
+        """Convert service, an integer or a service name, into an integer port number."""
+        try:
+          return int(value)
+        except ValueError:
+          try:
+            return socket.getservbyname(value)
+          except socket.error:
+            raise ValueError("Not a valid port number or service name: '%s'" % value)
+
+    def __init__(self, url=None, **kwargs):
+        """
+        @param url: String or Url instance to parse or copy.
+        @param kwargs: URL fields: scheme, user, password, host, port, path.
+            If specified, replaces corresponding component in url.
+        """
+
+        fields = ['scheme', 'user', 'password', 'host', 'port', 'path']
+
+        for f in fields: setattr(self, f, None)
+        for k in kwargs: getattr(self, k) # Check for invalid kwargs
+
+        if isinstance(url, Url): # Copy from another Url instance.
+            self.__dict__.update(url.__dict__)
+        elif url is not None:   # Parse from url
+            parts = pni_parse_url(str(url))
+            if not filter(None, parts): raise ValueError("Invalid AMQP URL: '%s'" % url)
+            self.scheme, self.user, self.password, self.host, port, self.path = parts
+            if not self.host: self.host = None
+            self.port = port and self.Port(port)
+
+        # Let kwargs override values previously set from url
+        for field in fields:
+            setattr(self, field, kwargs.get(field, getattr(self, field)))
+
+    def __repr__(self):
+        return "Url(%r)" % str(self)
+
+    def __str__(self):
+        s = ""
+        if self.scheme:
+            s += "%s://" % self.scheme
+        if self.user:
+            s += self.user
+        if self.password:
+            s += ":%s" % self.password
+        if self.user or self.password:
+            s += '@'
+        if self.host and ':' in self.host:
+            s += "[%s]" % self.host
+        elif self.host:
+            s += self.host
+        if self.port:
+            s += ":%s" % self.port
+        if self.path:
+            s += "/%s" % self.path
+        return s
+
+    def __eq__(self, url):
+        return \
+            self.scheme == url.scheme and \
+            self.user == url.user and self.password == url.password and \
+            self.host == url.host and self.port == url.port and \
+            self.path == url.path
+
+    def __ne__(self, url):
+        return not self.__eq__(url)
+
+    def defaults(self):
+        """
+        Fill in missing values with defaults
+        @return: self
+        """
+        self.scheme = self.scheme or self.AMQP
+        self.host = self.host or '0.0.0.0'
+        self.port = self.port or self.Port(self.scheme)
+        return self

Modified: qpid/proton/branches/examples/tests/python/proton_tests/__init__.py
URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/tests/python/proton_tests/__init__.py?rev=1630834&r1=1630833&r2=1630834&view=diff
==============================================================================
--- qpid/proton/branches/examples/tests/python/proton_tests/__init__.py (original)
+++ qpid/proton/branches/examples/tests/python/proton_tests/__init__.py Fri Oct 10 12:54:43 2014
@@ -26,4 +26,4 @@ import proton_tests.transport
 import proton_tests.ssl
 import proton_tests.interop
 import proton_tests.soak
-
+import proton_tests.url

Added: qpid/proton/branches/examples/tests/python/proton_tests/url.py
URL: http://svn.apache.org/viewvc/qpid/proton/branches/examples/tests/python/proton_tests/url.py?rev=1630834&view=auto
==============================================================================
--- qpid/proton/branches/examples/tests/python/proton_tests/url.py (added)
+++ qpid/proton/branches/examples/tests/python/proton_tests/url.py Fri Oct 10 12:54:43 2014
@@ -0,0 +1,117 @@
+#
+# 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.
+#
+
+
+import common
+from proton import Url
+
+class UrlTest(common.Test):
+    def assertEqual(self, a, b):
+        assert a == b, "%s != %s" % (a, b)
+
+    def assertNotEqual(self, a, b):
+        assert a != b, "%s == %s" % (a, b)
+
+    def assertUrl(self, u, scheme, user, password, host, port, path):
+        self.assertEqual((u.scheme, u.user, u.password, u.host, u.port, u.path),
+                         (scheme, user, password, host, port, path))
+
+    def testUrl(self):
+        url = Url('amqp://me:secret@myhost:1234/foobar')
+        self.assertEqual(str(url), "amqp://me:secret@myhost:1234/foobar")
+        self.assertUrl(url, 'amqp', 'me', 'secret', 'myhost', 1234, 'foobar')
+        self.assertEqual(str(url), "amqp://me:secret@myhost:1234/foobar")
+
+    def testDefaults(self):
+        # Check that we allow None for scheme, port
+        url = Url(user='me', password='secret', host='myhost', path='foobar')
+        self.assertEqual(str(url), "me:secret@myhost/foobar")
+        self.assertUrl(url, None, 'me', 'secret', 'myhost', None, 'foobar')
+
+        # Scheme defaults
+        self.assertEqual(str(Url("me:secret@myhost/foobar").defaults()),
+                         "amqp://me:secret@myhost:amqp/foobar")
+        # Correct port for amqps vs. amqps
+        self.assertEqual(str(Url("amqps://me:secret@myhost/foobar").defaults()),
+                         "amqps://me:secret@myhost:amqps/foobar")
+        self.assertEqual(str(Url("amqp://me:secret@myhost/foobar").defaults()),
+                         "amqp://me:secret@myhost:amqp/foobar")
+
+        # Empty string vs. None for path
+        self.assertEqual(Url("myhost/").path, "")
+        assert Url("myhost").path is None
+
+    def assertPort(self, port, portint, portstr):
+        self.assertEqual((port, port), (portint, portstr))
+        self.assertEqual((int(port), str(port)), (portint, portstr))
+
+    def testPort(self):
+        self.assertPort(Url.Port('amqp'), 5672, 'amqp')
+        self.assertPort(Url.Port(5672), 5672, '5672')
+        self.assertPort(Url.Port('amqps'), 5671, 'amqps')
+        self.assertPort(Url.Port(5671), 5671, '5671')
+        self.assertEqual(Url.Port(5671)+1, 5672) # Treat as int
+        self.assertEqual(str(Url.Port(5672)), '5672')
+
+        self.assertPort(Url.Port(Url.Port('amqp')), 5672, 'amqp')
+        self.assertPort(Url.Port(Url.Port(5672)), 5672, '5672')
+
+        try:
+            Url.Port('xxx')
+            assert False, "Expected ValueError"
+        except ValueError: pass
+
+        self.assertEqual(str(Url("host:amqp")), "host:amqp")
+        self.assertEqual(Url("host:amqp").port, 5672)
+        self.assertEqual(str(Url("host:amqps")), "host:amqps")
+        self.assertEqual(Url("host:amqps").port, 5671)
+
+    def testArgs(self):
+        u = Url("amqp://u:p@host:amqp/path", scheme='foo', host='bar', port=1234, path='garden')
+        self.assertUrl(u, 'foo', 'u', 'p', 'bar', 1234, 'garden')
+        u = Url()
+        self.assertUrl(u, None, None, None, None, None, None)
+
+    def assertRaises(self, exception, function, *args, **kwargs):
+        try:
+            function(*args, **kwargs)
+            assert False, "Expected exception %s" % exception.__name__
+        except exception: pass
+
+    def testMissing(self):
+        self.assertUrl(Url(), None, None, None, None, None, None)
+        self.assertUrl(Url('amqp://'), 'amqp', None, None, None, None, None)
+        self.assertUrl(Url('user@'), None, 'user', None, None, None, None)
+        self.assertUrl(Url(':pass@'), None, '', 'pass', None, None, None)
+        self.assertUrl(Url('host'), None, None, None, 'host', None, None)
+        self.assertUrl(Url(':1234'), None, None, None, None, 1234, None)
+        self.assertUrl(Url('/path'), None, None, None, None, None, 'path')
+
+        for s in ['amqp://', 'user@', ':pass@', ':1234', '/path']:
+            self.assertEqual(s, str(Url(s)))
+
+        for s, full in [
+                ('amqp://', 'amqp://0.0.0.0:amqp'),
+                ('user@', 'amqp://user@0.0.0.0:amqp'),
+                (':pass@', 'amqp://:pass@0.0.0.0:amqp'),
+                (':1234', 'amqp://0.0.0.0:1234'),
+                ('/path', 'amqp://0.0.0.0:amqp/path')]:
+            self.assertEqual(str(Url(s).defaults()), full)
+
+        self.assertRaises(ValueError, Url, '')



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org