From c1a44ea4f7d514861a23f488a36e446d599e2a5e Mon Sep 17 00:00:00 2001 From: z4r <24erre@gmail.com> Date: Wed, 20 Nov 2013 09:24:56 +0100 Subject: [PATCH 01/17] urlquote on content text! This closes #34 --- .travis.yml | 1 + README.rst | 16 ++++++++-------- rtkit/__init__.py | 2 +- rtkit/forms.py | 5 +++-- rtkit/tests/tkt.py | 6 +++--- test_requirements.txt | 1 - 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4a4a455..8c34a9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,4 +9,5 @@ before_script: script: - py.test --doctest-modules --pep8 rtkit -v --cov rtkit --cov-report term-missing after_success: + - pip install python-coveralls --use-mirrors - coveralls diff --git a/README.rst b/README.rst index 53b920a..be6f816 100644 --- a/README.rst +++ b/README.rst @@ -137,8 +137,8 @@ Create ticket content = { 'content': { 'Queue': 1,#'', 2 - 'Subject' : 'New Ticket', - 'Text' : 'My useless\ntext on\nthree lines.', + 'Subject': 'New Ticket', + 'Text': 'My useless\ntext on\nthree lines.', } } try: @@ -287,13 +287,13 @@ Usually your requests will be something like this. try: params = { - 'content' :{ - 'Action' : 'comment', - 'Text' : 'Comment with attach', - 'Attachment' : 'x.txt, 140x105.jpg', + 'content': { + 'Action': 'comment', + 'Text': 'Comment with attach', + 'Attachment': 'x.txt, 140x105.jpg', }, - 'attachment_1' : file('x.txt'), - 'attachment_2' : file('140x105.jpg'), + 'attachment_1': file('x.txt'), + 'attachment_2': file('140x105.jpg'), } response = resource.post(path='ticket/16/comment', payload=params,) for r in response.parsed: diff --git a/rtkit/__init__.py b/rtkit/__init__.py index 76dbeae..ccf661f 100644 --- a/rtkit/__init__.py +++ b/rtkit/__init__.py @@ -1,5 +1,5 @@ __author__ = 'Andrea De Marco <24erre@gmail.com>' -__version__ = '0.6.0' +__version__ = '0.6.1' __classifiers__ = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', diff --git a/rtkit/forms.py b/rtkit/forms.py index ce52bd1..ecc59af 100644 --- a/rtkit/forms.py +++ b/rtkit/forms.py @@ -148,10 +148,11 @@ def _content_encode(value): ... 'Action' : 'comment', ... 'Text' : 'Comment\nwith\nseveral\nlines', ... }) - 'Action: comment\nText: Comment\n with\n several\n lines' + 'Action: comment\nText: Comment%0A+with%0A+several%0A+lines' """ if 'Text' in value: value['Text'] = '\n '.join(value['Text'].splitlines()) + value['Text'] = url_quote(value['Text']) return '\n'.join(['{0}: {1}'.format(k, v) for k, v in value.iteritems()]) @@ -161,7 +162,7 @@ def url_quote(s, charset='utf-8', safe='/:'): s = s.encode(charset) elif not isinstance(s, str): s = str(s) - return urllib.quote(s, safe=safe) + return urllib.quote_plus(s, safe=safe) def to_bytestring(s): diff --git a/rtkit/tests/tkt.py b/rtkit/tests/tkt.py index 16f0241..e87e5cc 100644 --- a/rtkit/tests/tkt.py +++ b/rtkit/tests/tkt.py @@ -17,7 +17,7 @@ def setUp(self): 'Text': 'My useless\ntext on\nthree lines.', } } - self.req_body = 'content=Queue: 1\nText: My useless\n text on\n three lines.\nSubject: New Ticket' + self.req_body = 'content=Queue: 1\nText: My+useless%0A+text+on%0A+three+lines.\nSubject: New Ticket' self.req_headers_get = { 'connection': 'close', 'user-agent': 'rtkit-ua', @@ -27,7 +27,7 @@ def setUp(self): } self.req_headers_post = self.req_headers_get.copy() self.req_headers_post.update({ - 'content-length': '76', + 'content-length': '80', 'content-type': 'application/x-www-form-urlencoded; charset=utf-8', }) @@ -221,7 +221,7 @@ def test_tkt_comment_with_attach(self): parsed=[[]], status_int=200, status='200 Ok', - req_body='--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="content"\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 77\r\n\r\nAction: comment\nText: Comment with attach\nAttachment: x1.txt, x2.txt, 1x1.gif\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_2"; filename="rtkit/tests/attach/x2.txt"\r\nContent-Type: text/plain\r\nContent-Length: 15\r\n\r\nHello World!\n2\n\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_1"; filename="rtkit/tests/attach/x1.txt"\r\nContent-Type: text/plain\r\nContent-Length: 15\r\n\r\nHello World!\n1\n\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_3"; filename="rtkit/tests/attach/1x1.gif"\r\nContent-Type: image/gif\r\nContent-Length: 35\r\n\r\nGIF87a\x01\x00\x01\x00\x80\x00\x00\xcc\xcc\xcc\x96\x96\x96,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;\r\n--xXXxXXyYYzzz--\r\n', + req_body='--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="content"\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 77\r\n\r\nAction: comment\nText: Comment+with+attach\nAttachment: x1.txt, x2.txt, 1x1.gif\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_2"; filename="rtkit/tests/attach/x2.txt"\r\nContent-Type: text/plain\r\nContent-Length: 15\r\n\r\nHello World!\n2\n\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_1"; filename="rtkit/tests/attach/x1.txt"\r\nContent-Type: text/plain\r\nContent-Length: 15\r\n\r\nHello World!\n1\n\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_3"; filename="rtkit/tests/attach/1x1.gif"\r\nContent-Type: image/gif\r\nContent-Length: 35\r\n\r\nGIF87a\x01\x00\x01\x00\x80\x00\x00\xcc\xcc\xcc\x96\x96\x96,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;\r\n--xXXxXXyYYzzz--\r\n', req_headers=self.req_headers_post, ) self.assertPost( diff --git a/test_requirements.txt b/test_requirements.txt index 0433281..c163a22 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -2,4 +2,3 @@ pytest pytest-pep8 pytest-cov httpretty -python-coveralls From 9381585783cb0eb473fb5010479cad0751b8bcbb Mon Sep 17 00:00:00 2001 From: z4r <24erre@gmail.com> Date: Wed, 20 Nov 2013 09:30:03 +0100 Subject: [PATCH 02/17] pytest deps failed on travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8c34a9b..856530b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ python: install: - pip install -r requirements.txt --use-mirrors before_script: - - pip install -r test_requirements.txt --use-mirrors + - pip install -U -r test_requirements.txt --use-mirrors script: - py.test --doctest-modules --pep8 rtkit -v --cov rtkit --cov-report term-missing after_success: From ca04310eddad72eb57172236f13d5beff0ab009d Mon Sep 17 00:00:00 2001 From: Bitdeli Chef Date: Wed, 20 Nov 2013 08:51:48 +0000 Subject: [PATCH 03/17] Add a Bitdeli badge to README --- README.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.rst b/README.rst index be6f816..2e2d455 100644 --- a/README.rst +++ b/README.rst @@ -337,3 +337,9 @@ References .. _Best Practical RT: http://bestpractical.com/rt/ .. _Request Tracker Wiki: http://requesttracker.wikia.com/wiki/REST + + +.. image:: https://d2weczhvl823v0.cloudfront.net/z4r/python-rtkit/trend.png + :alt: Bitdeli badge + :target: https://bitdeli.com/free + From b81798a99387da53f48eb61e2822c2ce43829fc6 Mon Sep 17 00:00:00 2001 From: z4r <24erre@gmail.com> Date: Mon, 25 Nov 2013 08:56:51 +0100 Subject: [PATCH 04/17] Avoid URLEncode on Multipart. This commit fixes #36. Thx @garw --- rtkit/forms.py | 14 ++++++++++---- rtkit/tests/tkt.py | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/rtkit/forms.py b/rtkit/forms.py index ecc59af..dc93883 100644 --- a/rtkit/forms.py +++ b/rtkit/forms.py @@ -132,7 +132,7 @@ def encode_unreadable_value(self, value): def encode(value, headers): if len(value) == 1 and 'content' in value: - value = 'content={0}'.format(_content_encode(value['content'])) + value = 'content={0}'.format(_content_encode(value['content'], quote=True)) headers.setdefault('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8') else: mf = MultipartForm(value, BOUNDARY) @@ -142,17 +142,23 @@ def encode(value, headers): return value -def _content_encode(value): +def _content_encode(value, quote=False): r""" >>> _content_encode({ ... 'Action' : 'comment', ... 'Text' : 'Comment\nwith\nseveral\nlines', - ... }) + ... }, quote=True) 'Action: comment\nText: Comment%0A+with%0A+several%0A+lines' + >>> _content_encode({ + ... 'Action' : 'comment', + ... 'Text' : 'Comment\nwith\nseveral\nlines', + ... }) + 'Action: comment\nText: Comment\n with\n several\n lines' """ if 'Text' in value: value['Text'] = '\n '.join(value['Text'].splitlines()) - value['Text'] = url_quote(value['Text']) + if quote: + value['Text'] = url_quote(value['Text']) return '\n'.join(['{0}: {1}'.format(k, v) for k, v in value.iteritems()]) diff --git a/rtkit/tests/tkt.py b/rtkit/tests/tkt.py index e87e5cc..d5fe3c8 100644 --- a/rtkit/tests/tkt.py +++ b/rtkit/tests/tkt.py @@ -221,7 +221,7 @@ def test_tkt_comment_with_attach(self): parsed=[[]], status_int=200, status='200 Ok', - req_body='--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="content"\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 77\r\n\r\nAction: comment\nText: Comment+with+attach\nAttachment: x1.txt, x2.txt, 1x1.gif\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_2"; filename="rtkit/tests/attach/x2.txt"\r\nContent-Type: text/plain\r\nContent-Length: 15\r\n\r\nHello World!\n2\n\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_1"; filename="rtkit/tests/attach/x1.txt"\r\nContent-Type: text/plain\r\nContent-Length: 15\r\n\r\nHello World!\n1\n\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_3"; filename="rtkit/tests/attach/1x1.gif"\r\nContent-Type: image/gif\r\nContent-Length: 35\r\n\r\nGIF87a\x01\x00\x01\x00\x80\x00\x00\xcc\xcc\xcc\x96\x96\x96,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;\r\n--xXXxXXyYYzzz--\r\n', + req_body='--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="content"\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 77\r\n\r\nAction: comment\nText: Comment with attach\nAttachment: x1.txt, x2.txt, 1x1.gif\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_2"; filename="rtkit/tests/attach/x2.txt"\r\nContent-Type: text/plain\r\nContent-Length: 15\r\n\r\nHello World!\n2\n\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_1"; filename="rtkit/tests/attach/x1.txt"\r\nContent-Type: text/plain\r\nContent-Length: 15\r\n\r\nHello World!\n1\n\r\n--xXXxXXyYYzzz\r\nContent-Disposition: form-data; name="attachment_3"; filename="rtkit/tests/attach/1x1.gif"\r\nContent-Type: image/gif\r\nContent-Length: 35\r\n\r\nGIF87a\x01\x00\x01\x00\x80\x00\x00\xcc\xcc\xcc\x96\x96\x96,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;\r\n--xXXxXXyYYzzz--\r\n', req_headers=self.req_headers_post, ) self.assertPost( From 80221db9e7e23b21f373ec07b240ffb5771a5409 Mon Sep 17 00:00:00 2001 From: z4r <24erre@gmail.com> Date: Mon, 25 Nov 2013 09:01:36 +0100 Subject: [PATCH 05/17] Bump [ci skip] --- rtkit/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtkit/__init__.py b/rtkit/__init__.py index ccf661f..d1d267b 100644 --- a/rtkit/__init__.py +++ b/rtkit/__init__.py @@ -1,5 +1,5 @@ __author__ = 'Andrea De Marco <24erre@gmail.com>' -__version__ = '0.6.1' +__version__ = '0.6.2' __classifiers__ = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', From 5395e0a5fc0931d85d1f0afd42bb121b037d7c86 Mon Sep 17 00:00:00 2001 From: Andrea de Marco Date: Mon, 1 Dec 2014 20:35:35 +0100 Subject: [PATCH 06/17] Query String Authenticator/Opener and Test --- rtkit/authenticators.py | 43 +++++++++++++++++++++++++++++++++++++++++ rtkit/tests/auth.py | 22 ++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/rtkit/authenticators.py b/rtkit/authenticators.py index 968c443..b3f32c8 100644 --- a/rtkit/authenticators.py +++ b/rtkit/authenticators.py @@ -4,6 +4,7 @@ * And the current implementations are: * :py:class:`~rtkit.authenticators.BasicAuthenticator` * :py:class:`~rtkit.authenticators.CookieAuthenticator` + * :py:class:`~rtkit.authenticators.QueryStringAuthenticator` * :py:class:`~rtkit.authenticators.KerberosAuthenticator` .. seealso:: @@ -16,6 +17,7 @@ import urllib import urllib2 import cookielib +from urlparse import urlparse, urlsplit, parse_qs, urlunsplit __all__ = [ 'BasicAuthenticator', @@ -112,6 +114,47 @@ def _login(self): ) +class QueryStringAuthenticator(AbstractAuthenticator): + """Authenticate against server using a querystring + + .. doctest:: + + from rtkit.resource import RTResource + from rtkit.authenticators import QueryStringAuthenticator + from rtkit.errors import RTResourceError + + from rtkit import set_logging + import logging + set_logging('debug') + logger = logging.getLogger('rtkit') + + resource = RTResource('http:///REST/1.0/', '', '', QueryStringAuthenticator) + """ + + def __init__(self, username, password, url): + super(QueryStringAuthenticator, self).__init__(username, password, url, QueryStringAuthHandler(username, password)) + + +class QueryStringAuthHandler(urllib2.BaseHandler): + def __init__(self, username, password): + self.username = username + self.password = password + + def default_open(self, request): + scheme, netloc, path, query_string, fragment = urlsplit(request.get_full_url()) + query_params = parse_qs(query_string) + query_params['user'] = self.username + query_params['pass'] = self.password + + request = urllib2.Request( + url=urlunsplit((scheme, netloc, path, urllib.urlencode(query_params, doseq=True), fragment)), + data=request.data, + headers=request.headers + ) + + return urllib2.urlopen(request) + + class KerberosAuthenticator(AbstractAuthenticator): """Authenticate using Kerberos diff --git a/rtkit/tests/auth.py b/rtkit/tests/auth.py index ba72b0b..67e6102 100644 --- a/rtkit/tests/auth.py +++ b/rtkit/tests/auth.py @@ -2,7 +2,7 @@ import unittest from httpretty import httprettified, HTTPretty from rtkit.resource import RTResource -from rtkit.authenticators import BasicAuthenticator, CookieAuthenticator +from rtkit.authenticators import BasicAuthenticator, CookieAuthenticator, QueryStringAuthenticator class BasicAuthTestCase(unittest.TestCase): @@ -60,3 +60,23 @@ def test_auth(self): self.assertEqual(HTTPretty.latest_requests[1].path, '/ticket/1') self.assertEqual(HTTPretty.latest_requests[1].method, 'GET') self.assertEqual(HTTPretty.latest_requests[1].body, '') + + +class QueryStringAuthTestCase(unittest.TestCase): + def setUp(self): + self.resource = RTResource('http://rtkit.test/', 'USER', 'PASS', QueryStringAuthenticator) + + @httprettified + def test_auth(self): + HTTPretty.register_uri( + HTTPretty.GET, + 'http://rtkit.test/ticket/1', + responses=[ + HTTPretty.Response(body='RT/3.8.10 200 Ok\n\n# Ticket 1 does not exist.\n\n\n', status=200), + ] + ) + self.resource.get( + path='ticket/1', + headers={'User-Agent': 'rtkit-ua', } + ) + self.assertEqual(HTTPretty.latest_requests[0].path, '/ticket/1?user=USER&pass=PASS') From 05e33eb7e0ed983e4e8db39a06bbd9897d14441a Mon Sep 17 00:00:00 2001 From: Andrea de Marco Date: Tue, 2 Dec 2014 12:54:30 +0100 Subject: [PATCH 07/17] Add Readme and Bump Version --- README.rst | 16 ++++++++++++++++ rtkit/__init__.py | 2 +- rtkit/authenticators.py | 3 ++- setup.py | 8 ++++---- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 2e2d455..62baa5f 100644 --- a/README.rst +++ b/README.rst @@ -106,6 +106,22 @@ Cookie-based Authentication resource = RTResource('http:///REST/1.0/', '', '', CookieAuthenticator) +QueryString-based Authentication +--------------------------- + +:: + + from rtkit.resource import RTResource + from rtkit.authenticators import QueryStringAuthenticator + from rtkit.errors import RTResourceError + + from rtkit import set_logging + import logging + set_logging('debug') + logger = logging.getLogger('rtkit') + + resource = RTResource('http:///REST/1.0/', '', '', QueryStringAuthenticator) + Kerberos Authentication --------------------------- diff --git a/rtkit/__init__.py b/rtkit/__init__.py index d1d267b..743d4b9 100644 --- a/rtkit/__init__.py +++ b/rtkit/__init__.py @@ -1,5 +1,5 @@ __author__ = 'Andrea De Marco <24erre@gmail.com>' -__version__ = '0.6.2' +__version__ = '0.7.0' __classifiers__ = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', diff --git a/rtkit/authenticators.py b/rtkit/authenticators.py index b3f32c8..da8658b 100644 --- a/rtkit/authenticators.py +++ b/rtkit/authenticators.py @@ -17,11 +17,12 @@ import urllib import urllib2 import cookielib -from urlparse import urlparse, urlsplit, parse_qs, urlunsplit +from urlparse import urlsplit, parse_qs, urlunsplit __all__ = [ 'BasicAuthenticator', 'CookieAuthenticator', + 'QueryStringAuthenticator', 'KerberosAuthenticator', ] if os.environ.get('__GEN_DOCS__', None): diff --git a/setup.py b/setup.py index 71f920d..a986196 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ version = pkg.__version__ classifiers = pkg.__classifiers__ -readme = open(os.path.join(wd, 'README.rst'),'r').readlines() +readme = open(os.path.join(wd, 'README.rst'), 'r').readlines() description = readme[1] long_description = ''.join(readme) @@ -35,8 +35,8 @@ description=description, long_description=long_description, classifiers=classifiers, - install_requires = reqs, + install_requires=reqs, packages=find_packages(), - license = 'Apache License 2.0', - keywords ='RequestTracker REST', + license='Apache License 2.0', + keywords='RequestTracker REST', ) From 6615c76ebea9069b90d24867190d53639d5bd38d Mon Sep 17 00:00:00 2001 From: Andrea de Marco Date: Tue, 2 Dec 2014 13:01:53 +0100 Subject: [PATCH 08/17] Add Readme and Bump Version --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 62baa5f..04b9fbd 100644 --- a/README.rst +++ b/README.rst @@ -106,7 +106,7 @@ Cookie-based Authentication resource = RTResource('http:///REST/1.0/', '', '', CookieAuthenticator) -QueryString-based Authentication +QueryString Authentication --------------------------- :: From 571d83d255f70131466087bb1f3d195fb46c741a Mon Sep 17 00:00:00 2001 From: 2*yo Date: Wed, 21 Jan 2015 10:00:28 +0100 Subject: [PATCH 09/17] Typo in custom fields --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 04b9fbd..7c3cc47 100644 --- a/README.rst +++ b/README.rst @@ -330,7 +330,7 @@ To create or update a tkt with Custom Fields you must use this notation:: 'Queue': 1, 'Subject' : 'New Ticket', 'Text' : 'My useless\ntext on\nthree lines.', - 'CR.{Need For Approval}': 'Yes' + 'CF.{Need For Approval}': 'Yes' } } From 3626fb931df66c51ae68d9fbf716d4e3be70f571 Mon Sep 17 00:00:00 2001 From: Jann Haber Date: Sat, 13 Feb 2016 16:30:27 +0100 Subject: [PATCH 10/17] add Support for Python 3.x --- .gitignore | 1 + rtkit/authenticators.py | 25 +++++++++++++++++++------ rtkit/comment.py | 2 +- rtkit/parser.py | 14 +++++++++----- rtkit/resource.py | 6 +++++- rtkit/tracker.py | 8 ++++++-- 6 files changed, 41 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index d8c8e61..f57d890 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ ENV venv .cache docs/_build/ +build/ diff --git a/rtkit/authenticators.py b/rtkit/authenticators.py index da8658b..48daa47 100644 --- a/rtkit/authenticators.py +++ b/rtkit/authenticators.py @@ -14,10 +14,23 @@ """ import os -import urllib -import urllib2 -import cookielib -from urlparse import urlsplit, parse_qs, urlunsplit +# for compatibility with Python 3.x +try: + from urllib.parse import urlencode +except ImportError: + from urllib import urlencode +try: + import urllib.request as urllib2 +except ImportError: + import urllib2 +try: + import http.cookiejar as cookielib +except ImportError: + import cookielib +try: + from urllib.parse import urlsplit, parse_qs, urlunsplit +except ImportError: + from urlparse import urlsplit, parse_qs, urlunsplit __all__ = [ 'BasicAuthenticator', @@ -111,7 +124,7 @@ def __init__(self, username, password, url): def _login(self): data = {'user': self.username, 'pass': self.password} self.opener.open( - urllib2.Request(self.url, urllib.urlencode(data)) + urllib2.Request(self.url, urlencode(data)) ) @@ -148,7 +161,7 @@ def default_open(self, request): query_params['pass'] = self.password request = urllib2.Request( - url=urlunsplit((scheme, netloc, path, urllib.urlencode(query_params, doseq=True), fragment)), + url=urlunsplit((scheme, netloc, path, urlencode(query_params, doseq=True), fragment)), data=request.data, headers=request.headers ) diff --git a/rtkit/comment.py b/rtkit/comment.py index ead6dfd..4fc5b1d 100644 --- a/rtkit/comment.py +++ b/rtkit/comment.py @@ -1,5 +1,5 @@ import re -from errors import * +from .errors import * UNKNOWN_PATTERN = '# Unknown object type: (?P.+)' UNKNOWN = re.compile(UNKNOWN_PATTERN) diff --git a/rtkit/parser.py b/rtkit/parser.py index fee12b1..d06869a 100644 --- a/rtkit/parser.py +++ b/rtkit/parser.py @@ -1,4 +1,8 @@ -from itertools import ifilterfalse +# for compatibility with Python 3.x +try: + from itertools import filterfalse as ifilterfalse +except ImportError: + from itertools import ifilterfalse import re from rtkit import comment @@ -6,10 +10,10 @@ class RTParser(object): """ RFC5322 Parser - see https://tools.ietf.org/html/rfc5322""" - HEADER = re.compile(r'^RT/(?P.+)\s+(?P(?P\d+).+)') - COMMENT = re.compile(r'^#\s+.+$') - SYNTAX_COMMENT = re.compile(r'^>>\s+.+$') - SECTION = re.compile(r'^--', re.M | re.U) + HEADER = re.compile(b'^RT/(?P.+)\s+(?P(?P\d+).+)') + COMMENT = re.compile(b'^#\s+.+$') + SYNTAX_COMMENT = re.compile(b'^>>\s+.+$') + SECTION = re.compile(b'^--', re.M | re.U) @classmethod def parse(cls, body, decoder): diff --git a/rtkit/resource.py b/rtkit/resource.py index 53eb90c..003e2c6 100644 --- a/rtkit/resource.py +++ b/rtkit/resource.py @@ -1,7 +1,11 @@ import logging import re import os -from urllib2 import Request, HTTPError +try: + from urllib.request import Request + from urllib.error import HTTPError +except ImportError: + from urllib2 import Request, HTTPError from rtkit import forms, errors from rtkit.parser import RTParser diff --git a/rtkit/tracker.py b/rtkit/tracker.py index 74e53d8..84e15be 100644 --- a/rtkit/tracker.py +++ b/rtkit/tracker.py @@ -1,5 +1,9 @@ import urllib -import urllib2 +# For compatibility with python 3.x +try: + from urllib.request import Request +except ImportError: + from urllib2 import Request from rtkit.resource import RTResource from rtkit.entities import * @@ -30,7 +34,7 @@ def search_tickets(self, query, order=None): content = {'query': query, 'format': 'l'} if order: content['orderby'] = order - req = urllib2.Request( + req = Request( url=self.auth.url + 'search/ticket', data=urllib.urlencode(content), ) From b3e0f16783b6a5829e6afd982f2711b474ed9efd Mon Sep 17 00:00:00 2001 From: Jann Haber Date: Sat, 13 Feb 2016 17:02:51 +0100 Subject: [PATCH 11/17] fix some issues with unicode and re --- rtkit/parser.py | 14 +++++++------- rtkit/resource.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/rtkit/parser.py b/rtkit/parser.py index d06869a..4193550 100644 --- a/rtkit/parser.py +++ b/rtkit/parser.py @@ -10,10 +10,10 @@ class RTParser(object): """ RFC5322 Parser - see https://tools.ietf.org/html/rfc5322""" - HEADER = re.compile(b'^RT/(?P.+)\s+(?P(?P\d+).+)') - COMMENT = re.compile(b'^#\s+.+$') - SYNTAX_COMMENT = re.compile(b'^>>\s+.+$') - SECTION = re.compile(b'^--', re.M | re.U) + HEADER = re.compile(r'^RT/(?P.+)\s+(?P(?P\d+).+)') + COMMENT = re.compile(r'^#\s+.+$') + SYNTAX_COMMENT = re.compile(r'^>>\s+.+$') + SECTION = re.compile(r'^--', re.M | re.U) @classmethod def parse(cls, body, decoder): @@ -66,7 +66,7 @@ def decode(cls, lines): """ try: lines = ifilterfalse(cls.COMMENT.match, lines) - return [(k, v.strip(' ')) for k, v in [l.split(':', 1) for l in lines]] + return [(k.encode('utf-8'), v.strip(' ').encode('utf-8')) for k, v in [l.split(':', 1) for l in lines]] except (ValueError, IndexError): return [] @@ -84,7 +84,7 @@ def decode_comment(cls, lines): flines = filter(cls.COMMENT.match, lines) if len(flines) == 1 and flines[0] == '# Syntax error.': flines = [l.strip('>> ') for l in filter(cls.SYNTAX_COMMENT.match, lines)] - return [(k.strip('# '), v.strip(' ')) for k, v in [l.split(':', 1) for l in flines]] + return [(k.strip('# ').encode('utf-8'), v.strip(' ').encode('utf-8')) for k, v in [l.split(':', 1) for l in flines]] @classmethod def build(cls, body): @@ -121,4 +121,4 @@ def build_section(section): else: logic_lines.append(line) return logic_lines - return [build_section(b) for b in cls.SECTION.split(body)] + return [build_section(b) for b in cls.SECTION.split(body.decode('utf-8'))] diff --git a/rtkit/resource.py b/rtkit/resource.py index 003e2c6..75aa3cc 100644 --- a/rtkit/resource.py +++ b/rtkit/resource.py @@ -89,9 +89,9 @@ def __init__(self, request, response): self.logger.info(request.get_method()) self.logger.info(request.get_full_url()) self.logger.debug('HTTP_STATUS: {0}'.format(self.status)) - r = RTParser.HEADER.match(self.body) + r = RTParser.HEADER.match(self.body.decode('utf-8')) if r: - self.status = r.group('s') + self.status = r.group('s').encode('utf-8') self.status_int = int(r.group('i')) else: self.logger.error('"{0}" is not valid'.format(self.body)) From f7c6999f4064025c61648d5e498724a94b70bc20 Mon Sep 17 00:00:00 2001 From: Georges Toth Date: Wed, 1 Feb 2017 13:20:32 +0100 Subject: [PATCH 12/17] Fix attachment request if content type is application/octet-stream --- rtkit/parser.py | 2 +- rtkit/resource.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rtkit/parser.py b/rtkit/parser.py index 4193550..1ecee52 100644 --- a/rtkit/parser.py +++ b/rtkit/parser.py @@ -121,4 +121,4 @@ def build_section(section): else: logic_lines.append(line) return logic_lines - return [build_section(b) for b in cls.SECTION.split(body.decode('utf-8'))] + return [build_section(b) for b in cls.SECTION.split(body.decode('utf-8', 'ignore'))] diff --git a/rtkit/resource.py b/rtkit/resource.py index 75aa3cc..540c9c9 100644 --- a/rtkit/resource.py +++ b/rtkit/resource.py @@ -89,7 +89,7 @@ def __init__(self, request, response): self.logger.info(request.get_method()) self.logger.info(request.get_full_url()) self.logger.debug('HTTP_STATUS: {0}'.format(self.status)) - r = RTParser.HEADER.match(self.body.decode('utf-8')) + r = RTParser.HEADER.match(self.body.decode('utf-8', 'ignore')) if r: self.status = r.group('s').encode('utf-8') self.status_int = int(r.group('i')) From 331dd3d3f72779369922b4a8536b5ae2d1c744b7 Mon Sep 17 00:00:00 2001 From: Georges Toth Date: Wed, 1 Feb 2017 14:48:15 +0100 Subject: [PATCH 13/17] remove --use-mirrors as requested in #49 --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 856530b..77b07c8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,11 +3,11 @@ python: - "2.6" - "2.7" install: - - pip install -r requirements.txt --use-mirrors + - pip install -r requirements.txt before_script: - - pip install -U -r test_requirements.txt --use-mirrors + - pip install -U -r test_requirements.txt script: - py.test --doctest-modules --pep8 rtkit -v --cov rtkit --cov-report term-missing after_success: - - pip install python-coveralls --use-mirrors + - pip install python-coveralls - coveralls From 636c2a6795f694ef067d2b70eb8838a9d270acf7 Mon Sep 17 00:00:00 2001 From: z4r <24erre@gmail.com> Date: Fri, 24 Feb 2017 11:24:03 +0100 Subject: [PATCH 14/17] Doctest fix --- pytest.ini | 2 +- rtkit/parser.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pytest.ini b/pytest.ini index 927590e..ded72d5 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,2 @@ [pytest] -pep8ignore = E501 +pep8ignore = E501 E402 diff --git a/rtkit/parser.py b/rtkit/parser.py index 1ecee52..c6dba6a 100644 --- a/rtkit/parser.py +++ b/rtkit/parser.py @@ -1,4 +1,3 @@ -# for compatibility with Python 3.x try: from itertools import filterfalse as ifilterfalse except ImportError: @@ -109,7 +108,7 @@ def build(cls, body): ... a -- b ... ''' >>> RTParser.build(body) - [['# a\\nb', 'spam: 1', 'ham: 2,\\n3'], ['# c', 'spam: 4', 'ham:'], ['a -- b']] + [[u'# a\\nb', u'spam: 1', u'ham: 2,\\n3'], [u'# c', u'spam: 4', u'ham:'], [u'a -- b']] """ def build_section(section): logic_lines = [] From 1661f78b0f7277d4c6b09fb0bcb556f20b5f688d Mon Sep 17 00:00:00 2001 From: z4r <24erre@gmail.com> Date: Fri, 24 Feb 2017 11:35:51 +0100 Subject: [PATCH 15/17] bump version [ci skip] --- rtkit/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtkit/__init__.py b/rtkit/__init__.py index 743d4b9..8da9149 100644 --- a/rtkit/__init__.py +++ b/rtkit/__init__.py @@ -1,5 +1,5 @@ __author__ = 'Andrea De Marco <24erre@gmail.com>' -__version__ = '0.7.0' +__version__ = '0.7.1' __classifiers__ = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', From a6a7262c09597779d9d9ec8b8d20105d3e3565c6 Mon Sep 17 00:00:00 2001 From: Igor Stroh Date: Wed, 14 Jun 2017 18:02:55 +0200 Subject: [PATCH 16/17] Fix vertical tab handling in parser. Python's string.splitlines() also splits on vertical tabs, which can be used as content in tickets. Resolves #52 --- rtkit/parser.py | 9 +++++++-- rtkit/tests/parser.py | 9 +++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/rtkit/parser.py b/rtkit/parser.py index c6dba6a..7f48dec 100644 --- a/rtkit/parser.py +++ b/rtkit/parser.py @@ -1,7 +1,9 @@ try: from itertools import filterfalse as ifilterfalse + from cStringIO import StringIO except ImportError: from itertools import ifilterfalse + from io import StringIO import re from rtkit import comment @@ -112,8 +114,11 @@ def build(cls, body): """ def build_section(section): logic_lines = [] - for line in filter(None, section.splitlines()): - if cls.HEADER.match(line): + for line in StringIO(section): + # strip trailing newline + if line and line[-1] == '\n': + line = line.rstrip('\n') + if not line or cls.HEADER.match(line): continue if line[0].isspace(): logic_lines[-1] += '\n' + line.strip(' ') diff --git a/rtkit/tests/parser.py b/rtkit/tests/parser.py index 85447c6..59ff8e6 100644 --- a/rtkit/tests/parser.py +++ b/rtkit/tests/parser.py @@ -29,3 +29,12 @@ def test_parse_kw_syntax_err(self): ''' parsed = RTParser.parse(body, RTParser.decode_comment) self.assertEqual(parsed, [[('queue', 'You may not create requests in that queue.')]]) + + def test_vertical_tab(self): + body = '''RT/3.8.7 200 Ok +Field: first line + second\vline + third line +''' + parsed = RTParser.parse(body, RTParser.decode) + self.assertEqual(parsed, [[('Field', 'first line\nsecond\x0bline\nthird line')]]) From a31ee69fa4e7a3ad4670edf5c5be2f3d675e3091 Mon Sep 17 00:00:00 2001 From: Andrea de Marco <24erre@gmail.com> Date: Fri, 16 Jun 2017 10:31:15 +0200 Subject: [PATCH 17/17] bump version [ci skip] --- rtkit/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtkit/__init__.py b/rtkit/__init__.py index 8da9149..08369ea 100644 --- a/rtkit/__init__.py +++ b/rtkit/__init__.py @@ -1,5 +1,5 @@ __author__ = 'Andrea De Marco <24erre@gmail.com>' -__version__ = '0.7.1' +__version__ = '0.7.2' __classifiers__ = [ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', @@ -11,7 +11,7 @@ 'Topic :: Internet :: WWW/HTTP', 'Topic :: Software Development :: Libraries', ] -__copyright__ = "2011, %s " % __author__ +__copyright__ = "2011 - 2017, %s " % __author__ __license__ = """ Copyright (C) %s