PROTON-1853: [Python] Rework Url so it doesn't use proton-c - Improve the str output format to be more RFC compliant - Tests for new string format
Project: http://git-wip-us.apache.org/repos/asf/qpid-proton/repo Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton/commit/55a4a192 Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton/tree/55a4a192 Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton/diff/55a4a192 Branch: refs/heads/go1 Commit: 55a4a192bf6e76fc1cf9dbacc90623b0fe46735b Parents: a04a964 Author: Andrew Stitcher <astitc...@apache.org> Authored: Fri May 25 18:18:27 2018 -0400 Committer: Andrew Stitcher <astitc...@apache.org> Committed: Tue Jun 5 15:52:18 2018 +0100 ---------------------------------------------------------------------- python/proton/_compat.py | 6 ++ python/proton/_url.py | 127 ++++++++++++++++++++++------------ tests/python/proton_tests/url.py | 16 +++-- 3 files changed, 101 insertions(+), 48 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/55a4a192/python/proton/_compat.py ---------------------------------------------------------------------- diff --git a/python/proton/_compat.py b/python/proton/_compat.py index eae4c84..79bd2c1 100644 --- a/python/proton/_compat.py +++ b/python/proton/_compat.py @@ -29,6 +29,12 @@ try: except ImportError: import queue +try: + from urlparse import urlparse, urlunparse + from urllib import quote, unquote +except ImportError: + from urllib.parse import urlparse, urlunparse, quote, unquote + PY3 = sys.version_info[0] == 3 if PY3: http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/55a4a192/python/proton/_url.py ---------------------------------------------------------------------- diff --git a/python/proton/_url.py b/python/proton/_url.py index b4a9a6a..39cf480 100644 --- a/python/proton/_url.py +++ b/python/proton/_url.py @@ -20,12 +20,7 @@ from __future__ import absolute_import import socket - -from cproton import pn_url, pn_url_free, pn_url_parse, pn_url_str, pn_url_get_port, pn_url_get_scheme, \ - pn_url_get_host, pn_url_get_username, pn_url_get_password, pn_url_get_path, pn_url_set_scheme, pn_url_set_host, \ - pn_url_set_username, pn_url_set_password, pn_url_set_port, pn_url_set_path - -from ._common import unicode2utf8 +from ._compat import urlparse, urlunparse, quote, unquote class Url(object): @@ -97,48 +92,98 @@ class Url(object): @param kwargs: scheme, user, password, host, port, path. If specified, replaces corresponding part in url string. """ - if url: - self._url = pn_url_parse(unicode2utf8(str(url))) - if not self._url: raise ValueError("Invalid URL '%s'" % url) + if isinstance(url, Url): + self.scheme = url.scheme + self.username = url.username + self.password = url.password + self.host = url.host + self._port = url._port + self._path = url._path + self._params = url._params + self._query = url._query + self._fragment = url._fragment + elif url: + if not '://' in url: + url = '//' + url + u = urlparse(url) + if not u: raise ValueError("Invalid URL '%s'" % url) + self.scheme = None if not u.scheme else u.scheme + self.username = u.username and unquote(u.username) + self.password = u.password and unquote(u.password) + self.host = None if not u.hostname else u.hostname + self._port = self._parse_port(u.netloc) + self._path = None if not u.path else u.path + self._params = u.params + self._query = u.query + self._fragment = u.fragment else: - self._url = pn_url() + self.scheme = None + self.username = None + self.password = None + self.host = None + self._port = None + self._path = None + self._params = None + self._query = None + self._fragment = None for k in kwargs: # Let kwargs override values parsed from url getattr(self, k) # Check for invalid kwargs setattr(self, k, kwargs[k]) if defaults: self.defaults() - class PartDescriptor(object): - def __init__(self, part): - self.getter = globals()["pn_url_get_%s" % part] - self.setter = globals()["pn_url_set_%s" % part] - - def __get__(self, obj, type=None): return self.getter(obj._url) - - def __set__(self, obj, value): return self.setter(obj._url, str(value)) - - scheme = PartDescriptor('scheme') - username = PartDescriptor('username') - password = PartDescriptor('password') - host = PartDescriptor('host') - path = PartDescriptor('path') - - def _get_port(self): - portstr = pn_url_get_port(self._url) - return portstr and Url.Port(portstr) - - def _set_port(self, value): - if value is None: - pn_url_set_port(self._url, None) - else: - pn_url_set_port(self._url, str(Url.Port(value))) - - port = property(_get_port, _set_port) + @staticmethod + def _parse_port(nl): + netloc = nl.split('@')[-1].split(']')[-1] + if ':' in netloc: + port = netloc.split(':')[1] + if port: + return port + return None + + @property + def path(self): + return self._path if not self._path or self._path[0] != '/' else self._path[1:] + + @path.setter + def path(self, p): + self._path = p if p[0] == '/' else '/' + p + + @property + def port(self): + return self._port and Url.Port(self._port) + + @port.setter + def port(self, p): + self._port = p + + @property + def _netloc(self): + hostport = '' + if self.host: + hostport = self.host + if self._port: + hostport += ':' + hostport += str(self._port) + userpart = '' + if self.username: + userpart += quote(self.username) + if self.password: + userpart += ':' + userpart += quote(self.password) + if self.username or self.password: + userpart += '@' + return userpart + hostport def __str__(self): - return pn_url_str(self._url) + if self.scheme \ + and not self._netloc and not self._path \ + and not self._params and not self._query and not self._fragment: + return self.scheme + '://' + return urlunparse((self.scheme or '', self._netloc or '', self._path or '', + self._params or '', self._query or '', self._fragment or '')) def __repr__(self): - return "Url(%s://%s/%s)" % (self.scheme, self.host, self.path) + return "Url('%s')" % self def __eq__(self, x): return str(self) == str(x) @@ -146,10 +191,6 @@ class Url(object): def __ne__(self, x): return not self == x - def __del__(self): - pn_url_free(self._url) - del self._url - def defaults(self): """ Fill in missing values (scheme, host or port) with defaults @@ -157,5 +198,5 @@ class Url(object): """ 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) + self._port = self._port or self.Port(self.scheme) return self http://git-wip-us.apache.org/repos/asf/qpid-proton/blob/55a4a192/tests/python/proton_tests/url.py ---------------------------------------------------------------------- diff --git a/tests/python/proton_tests/url.py b/tests/python/proton_tests/url.py index 40a257c..5b259c6 100644 --- a/tests/python/proton_tests/url.py +++ b/tests/python/proton_tests/url.py @@ -42,7 +42,7 @@ class UrlTest(common.Test): def testDefaults(self): # Check that we allow None for scheme, port url = Url(username='me', password='secret', host='myhost', path='foobar', defaults=False) - self.assertEqual(str(url), "me:secret@myhost/foobar") + self.assertEqual(str(url), "//me:secret@myhost/foobar") self.assertUrl(url, None, 'me', 'secret', 'myhost', None, 'foobar') self.assertEqual(str(Url("amqp://me:secret@myhost/foobar")), @@ -79,7 +79,7 @@ class UrlTest(common.Test): assert False, "Expected ValueError" except ValueError: pass - self.assertEqual(str(Url("host:amqp", defaults=False)), "host:amqp") + self.assertEqual(str(Url("host:amqp", defaults=False)), "//host:amqp") self.assertEqual(Url("host:amqp", defaults=False).port, 5672) def testArgs(self): @@ -103,8 +103,14 @@ class UrlTest(common.Test): self.assertUrl(Url(':1234', defaults=False), None, None, None, None, 1234, None) self.assertUrl(Url('/path', defaults=False), None, None, None, None, None, 'path') - for s in ['amqp://', 'username@', ':pass@', ':1234', '/path']: - self.assertEqual(s, str(Url(s, defaults=False))) + for s, full in [ + ('amqp://', 'amqp://'), + ('username@','//username@'), + (':pass@', '//:pass@'), + (':1234', '//:1234'), + ('/path','/path') + ]: + self.assertEqual(str(Url(s, defaults=False)), full) for s, full in [ ('amqp://', 'amqp://0.0.0.0:amqp'), @@ -126,7 +132,7 @@ class UrlTest(common.Test): "amqps://me:secret@myhost:amqps/foobar") self.assertPort(Url.Port('amqps'), 5671, 'amqps') - self.assertEqual(str(Url("host:amqps", defaults=False)), "host:amqps") + self.assertEqual(str(Url("host:amqps", defaults=False)), "//host:amqps") self.assertEqual(Url("host:amqps", defaults=False).port, 5671) def testEqual(self): --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@qpid.apache.org For additional commands, e-mail: commits-h...@qpid.apache.org