Merge lp:~javier.collado/utah/url-doctest into lp:utah

Proposed by Javier Collado
Status: Merged
Approved by: Javier Collado
Approved revision: 760
Merged at revision: 758
Proposed branch: lp:~javier.collado/utah/url-doctest
Merge into: lp:utah
Diff against target: 269 lines (+171/-29)
3 files modified
docs/source/reference.rst (+4/-0)
utah/exceptions.py (+0/-12)
utah/url.py (+167/-17)
To merge this branch: bzr merge lp:~javier.collado/utah/url-doctest
Reviewer Review Type Date Requested Status
Javier Collado (community) Approve
Max Brustkern (community) Needs Information
Review via email: mp+136173@code.launchpad.net

Description of the change

This branch adds documentation and doctests to the utah.url module.

To post a comment you must log in.
Revision history for this message
Max Brustkern (nuclearbob) wrote :

I don't see an ellipsis that would match the +ELLIPSIS directive on line 209 of the merge, so I wonder if that directive is needed.

Other than that, it looks reasonable to me.

review: Needs Information
lp:~javier.collado/utah/url-doctest updated
759. By Javier Collado

Removed unnecessary ELLIPSIS

760. By Javier Collado

Added missing import

Nosetest works fine without the import, but 'make doctest' in the documentation
directory doesn't.

Revision history for this message
Javier Collado (javier.collado) wrote :

You're right, I had a more complex example to show launchpad URLs that
didn't work fine, so I decided to go for something simpler for the
documentation and forgot to remove the ELLIPSIS.

Aside from that, I've added an import that is needed to run the test cases
from the `docs` directory with `make doctest`.

Since it looks good to you, I'm merging right away.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'docs/source/reference.rst'
2--- docs/source/reference.rst 2012-11-08 19:36:29 +0000
3+++ docs/source/reference.rst 2012-11-27 10:01:20 +0000
4@@ -41,8 +41,12 @@
5 .. automodule:: utah.timeout
6 :members:
7
8+``utah.url``
9+----------------
10+
11 .. automodule:: utah.url
12 :members:
13+ :member-order: bysource
14
15 ``utah.client``
16 ===============
17
18=== modified file 'utah/exceptions.py'
19--- utah/exceptions.py 2012-08-22 15:20:25 +0000
20+++ utah/exceptions.py 2012-11-27 10:01:20 +0000
21@@ -14,15 +14,3 @@
22 except KeyError:
23 self.retry = False
24 super(UTAHException, self).__init__(*args, **kw)
25-
26-
27-class URLNotFound(UTAHException):
28- """
29- Exception raised when a URL isn't found
30- """
31-
32-
33-class URLNotReadable(UTAHException):
34- """
35- Exception raised when a URL isn't readable
36- """
37
38=== modified file 'utah/url.py'
39--- utah/url.py 2012-10-11 15:11:34 +0000
40+++ utah/url.py 2012-11-27 10:01:20 +0000
41@@ -1,5 +1,8 @@
42-"""
43-URL tools
44+r"""This module provides the classes/functions needed to:
45+
46+ - Check that a URL is valid and readable
47+ - Use a url type in an `argparse.ArgumentParser` object
48+
49 """
50 import os
51 import urllib
52@@ -13,26 +16,71 @@
53 import bzrlib.plugin
54 import bzrlib.errors
55
56-from utah.exceptions import URLNotFound, URLNotReadable
57+from utah.exceptions import UTAHException
58
59
60 # Inspired by: http://stackoverflow.com/a/2070916/183066
61 class HeadRequest(urllib2.Request):
62- """
63- Request that sends HEAD method instead of GET
64- """
65+
66+ """A request that sends HEAD method instead of GET.
67+
68+ .. seealso:: :class:`URLChecker`
69+
70+ """
71+
72 def get_method(self):
73+ """Return Method used to get URL.
74+
75+ :returns: 'HEAD'
76+ :rtype: str
77+
78+ """
79+
80 return 'HEAD'
81
82
83 class URLChecker(urllib.URLopener):
84- """
85- Check that URL exists withou opening it
86- or downloading its contents
87- """
88- def open_http(self, url, data=None):
89- """
90- Check if http URL exists
91+
92+ """An opener to checks a URL is valid and readable.
93+
94+ To use it, create an object instance and call the `open`
95+ method passing the url to be checked as argument.
96+
97+ """
98+
99+ def open_http(self, url):
100+ """Check if http URL exists and is readable.
101+
102+ The check is performed by sending an HTTP HEAD
103+ request, waiting for the response and checking that
104+ the code is 200 OK.
105+
106+ If a redirect response is received, the URL will still
107+ be reported as working fine, but the underlying
108+ implementation will use an HTTP GET method instead, so
109+ it won't be as efficient as in the standard case.
110+
111+ :param url: The HTTP URL to be checked
112+ :type url: `basestring`
113+ :returns: The url passed as argument when it's valid and readable.
114+ :rtype: `basestring`
115+ :throws URLNotFound:
116+ When there's a problem opening the URL or isn't found.
117+
118+ :Example:
119+
120+ >>> from utah.url import URLChecker
121+ >>> opener = URLChecker()
122+ >>> opener.open('http://www.ubuntu.com')
123+ 'http://www.ubuntu.com'
124+
125+ .. note::
126+
127+ This method is called by the `open` method when the URL protocol is
128+ http, so it's not expected to be called directly.
129+
130+ .. seealso:: :meth:`open_local_file`, :class:`URLNotFound`
131+
132 """
133 # This is redundant becuase urllib2 will call urllib
134 # under the hood, but makes code easy to read.
135@@ -52,8 +100,39 @@
136 return url
137
138 def open_local_file(self, url):
139- """
140- Check if local file exists
141+ """Check if local file exists.
142+
143+ :param url: The file URL to be checked
144+ :type url: `basestring`
145+ :returns: The path to the file if it was found and readable.
146+
147+ .. note::
148+ The returned value is a path, not a URL, so it
149+ can be used to open the file the same way as
150+ any other files.
151+ :rtype: `basestring`
152+ :throws URLNotFound:
153+ when the path to the file doesn't exist.
154+ :throws URLNotReadable:
155+ when the user doesn't have read permissions to open the file.
156+
157+ :Example:
158+
159+ >>> import tempfile
160+ >>> with tempfile.NamedTemporaryFile() as f: # doctest: +ELLIPSIS
161+ ... opener = URLChecker()
162+ ... opener.open(f.name)
163+ '/tmp/tmp...'
164+
165+ .. note::
166+
167+ This method is called by the `open` method when the URL protocol is
168+ file, so it's not expected to be called directly.
169+
170+ .. seealso::
171+
172+ :meth:`open_http`, :class:`URLNotFound`, :class:`URLNotReadable`
173+
174 """
175 # Based on urllib.URLopener.open_local_file implementation
176 _host, filename = urllib.splithost(url)
177@@ -69,8 +148,37 @@
178
179
180 def url_argument(url):
181- """
182- Check URL argument in argparse.ArgumentParser
183+ """URL argument to be used in an `argparse.ArgumentParser` object.
184+
185+ :param url: URL as passed to the parser object.
186+
187+ .. note::
188+
189+ The URL passed as argument can be a launchpad URL. In that case,
190+ the file pointed by the URL will be downloaded as when using `bzr
191+ export` and the returned value is the path to the downloaded file.
192+
193+ :type url: `basestring`
194+ :returns: URL or path to local file
195+ :rtype: `basestring`
196+ :throws argparse.ArgumentTypeError:
197+
198+ when the URL is invalid or unreadable. In any case, the error message
199+ will provide information to be displayed by the
200+ `argparse.ArgumentParser` object in the command line.
201+
202+ :Example:
203+
204+ >>> from utah.url import url_argument
205+ >>> import argparse
206+ >>> parser = argparse.ArgumentParser()
207+ >>> parser.add_argument('url', type=url_argument) # doctest: +ELLIPSIS
208+ _StoreAction(... dest='url', ...)
209+ >>> parser.parse_args(['http://www.ubuntu.com'])
210+ Namespace(url='http://www.ubuntu.com')
211+
212+ .. seealso:: :class:`URLChecker`
213+
214 """
215 parse_result = urlparse(url)
216 if parse_result.scheme in ('', 'file', 'http', 'https'):
217@@ -87,6 +195,7 @@
218 bzr_logger.addHandler(logging.NullHandler())
219 bzrlib.plugin.load_plugins() # Enable launchpad URLs in bazaar
220 cmd = bzrlib.builtins.cmd_export()
221+ assert cmd is not None
222 tmp_dir = tempfile.mkdtemp(prefix='utah_')
223 try:
224 cmd.run(tmp_dir, url)
225@@ -104,3 +213,44 @@
226 .format(parse_result.scheme, url))
227
228 return full_url
229+
230+
231+class URLNotFound(UTAHException):
232+
233+ """Exception raised when a URL isn't found.
234+
235+ :Example:
236+
237+ >>> opener = URLChecker()
238+ >>> opener.open('http://localhost/invalid_url')
239+ Traceback (most recent call last):
240+ ...
241+ URLNotFound: http://localhost/invalid_url
242+ >>> opener.open('file:///tmp/invalid_url')
243+ Traceback (most recent call last):
244+ ...
245+ URLNotFound: /tmp/invalid_url
246+
247+ .. seealso:: :class:`URLChecker`
248+
249+ """
250+
251+
252+class URLNotReadable(UTAHException):
253+
254+ """Exception raised when a URL isn't readable.
255+
256+ :Example:
257+
258+ >>> import os
259+ >>> with tempfile.NamedTemporaryFile() as f: # doctest: +ELLIPSIS
260+ ... os.chmod(f.name, 0000)
261+ ... opener = URLChecker()
262+ ... opener.open(f.name)
263+ Traceback (most recent call last):
264+ ...
265+ URLNotReadable: /tmp/tmp...
266+
267+ .. seealso:: :class:`URLChecker`
268+
269+ """

Subscribers

People subscribed via source and target branches