Merge lp:~dooferlad/linaro-license-protection/api-updates into lp:~linaro-automation/linaro-license-protection/trunk

Proposed by James Tunnicliffe
Status: Merged
Approved by: Milo Casagrande
Approved revision: 180
Merged at revision: 179
Proposed branch: lp:~dooferlad/linaro-license-protection/api-updates
Merge into: lp:~linaro-automation/linaro-license-protection/trunk
Diff against target: 306 lines (+150/-91)
3 files modified
license_protected_downloads/tests/test_views.py (+24/-0)
license_protected_downloads/views.py (+22/-11)
scripts/download.py (+104/-80)
To merge this branch: bzr merge lp:~dooferlad/linaro-license-protection/api-updates
Reviewer Review Type Date Requested Status
Milo Casagrande (community) Approve
Review via email: mp+153903@code.launchpad.net

Description of the change

Minor update to the API so <server>/api/ls/<path to file> works. Previously a listing would only work for single directory.

Updated download.py. Now it is a full, interactive download script:

download.py <URL of directory>
 - download all files in directory

download <URL of file>
 - download single file

Both of the above require the user to accept each license once. Once a license has been accepted the digest is stored so they don't have to re-accept an unchanged license.

Have split up download.py so the guts of the work is done in a single function, but set up and some messing around with URLs is moved out into a class. This improves readability. Since it is designed as executable documentation, it seems reasonable to take the cruft out of the main function.

To post a comment you must log in.
Revision history for this message
Milo Casagrande (milo) wrote :

Hey James,

thanks for working on this, it looks good to go.
For the download.py file, as long as it is a demo, it looks OK too, otherwise there might be a couple of changes to apply. I hope we can provide a good CLI-client for this in the future.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'license_protected_downloads/tests/test_views.py'
--- license_protected_downloads/tests/test_views.py 2013-03-12 15:03:35 +0000
+++ license_protected_downloads/tests/test_views.py 2013-03-18 18:32:28 +0000
@@ -290,6 +290,30 @@
290290
291 self.assertEqual(mtime, file_info["mtime"])291 self.assertEqual(mtime, file_info["mtime"])
292292
293 def test_api_get_listing_single_file(self):
294 url = "/api/ls/build-info/snowball-blob.txt"
295 response = self.client.get(url)
296 self.assertEqual(response.status_code, 200)
297
298 data = json.loads(response.content)["files"]
299
300 # Should be a listing for a single file
301 self.assertEqual(len(data), 1)
302
303 # For each file listed, check some key attributes
304 for file_info in data:
305 file_path = os.path.join(TESTSERVER_ROOT,
306 file_info["url"].lstrip("/"))
307 if file_info["type"] == "folder":
308 self.assertTrue(os.path.isdir(file_path))
309 else:
310 self.assertTrue(os.path.isfile(file_path))
311
312 mtime = datetime.fromtimestamp(
313 os.path.getmtime(file_path)).strftime('%d-%b-%Y %H:%M')
314
315 self.assertEqual(mtime, file_info["mtime"])
316
293 def test_api_get_listing_404(self):317 def test_api_get_listing_404(self):
294 url = "/api/ls/buld-info"318 url = "/api/ls/buld-info"
295 response = self.client.get(url)319 response = self.client.get(url)
296320
=== modified file 'license_protected_downloads/views.py'
--- license_protected_downloads/views.py 2013-03-11 15:31:31 +0000
+++ license_protected_downloads/views.py 2013-03-18 18:32:28 +0000
@@ -68,23 +68,23 @@
68 files.sort()68 files.sort()
69 listing = []69 listing = []
7070
71 for file in files:71 for file_name in files:
72 if _hidden_file(file):72 if _hidden_file(file_name):
73 continue73 continue
7474
75 name = file75 name = file_name
76 file = os.path.join(path, file)76 file_name = os.path.join(path, file_name)
7777
78 if os.path.exists(file):78 if os.path.exists(file_name):
79 mtime = datetime.fromtimestamp(79 mtime = datetime.fromtimestamp(
80 os.path.getmtime(file)).strftime('%d-%b-%Y %H:%M')80 os.path.getmtime(file_name)).strftime('%d-%b-%Y %H:%M')
81 else:81 else:
82 # If the file we are looking at doesn't exist (broken symlink for82 # If the file we are looking at doesn't exist (broken symlink for
83 # example), it doesn't have a mtime.83 # example), it doesn't have a mtime.
84 mtime = 084 mtime = 0
8585
86 target_type = "other"86 target_type = "other"
87 if os.path.isdir(file):87 if os.path.isdir(file_name):
88 target_type = "folder"88 target_type = "folder"
89 else:89 else:
90 type_tuple = guess_type(name)90 type_tuple = guess_type(name)
@@ -92,8 +92,8 @@
92 if type_tuple[0].split('/')[0] == "text":92 if type_tuple[0].split('/')[0] == "text":
93 target_type = "text"93 target_type = "text"
9494
95 if os.path.exists(file):95 if os.path.exists(file_name):
96 size = os.path.getsize(file)96 size = os.path.getsize(file_name)
97 else:97 else:
98 # If the file we are looking at doesn't exist (broken symlink for98 # If the file we are looking at doesn't exist (broken symlink for
99 # example), it doesn't have a size99 # example), it doesn't have a size
@@ -512,11 +512,22 @@
512 target_type = result[0]512 target_type = result[0]
513 path = result[1]513 path = result[1]
514514
515 if target_type == "dir":515 if target_type:
516 if target_type == "file":
517 file_url = url
518 if file_url[0] != "/":
519 file_url = "/" + file_url
520 path = os.path.dirname(path)
521 url = os.path.dirname(url)
522
516 listing = dir_list(url, path)523 listing = dir_list(url, path)
517524
518 clean_listing = []525 clean_listing = []
519 for entry in listing:526 for entry in listing:
527 if target_type == "file" and file_url != entry["url"]:
528 # If we are getting a listing for a single file, skip the rest
529 continue
530
520 if len(entry["license_list"]) == 0:531 if len(entry["license_list"]) == 0:
521 entry["license_list"] = ["Open"]532 entry["license_list"] = ["Open"]
522533
@@ -545,7 +556,7 @@
545556
546 if target_type == "dir":557 if target_type == "dir":
547 data = json.dumps({"licenses":558 data = json.dumps({"licenses":
548 ["File not found."]})559 ["ERROR: License only shown for a single file."]})
549 else:560 else:
550 license_digest_list = is_protected(path)561 license_digest_list = is_protected(path)
551 license_list = License.objects.all_with_hashes(license_digest_list)562 license_list = License.objects.all_with_hashes(license_digest_list)
552563
=== modified file 'scripts/download.py'
--- scripts/download.py 2013-03-12 14:59:02 +0000
+++ scripts/download.py 2013-03-18 18:32:28 +0000
@@ -6,84 +6,108 @@
6import urllib26import urllib2
7import os7import os
8from html2text import html2text8from html2text import html2text
99import sys
10# Example of how to use the API to download all files in a directory. This is10import xdg.BaseDirectory as xdgBaseDir
11# written as one procedural script without functions11
12directory_url = "http://localhost:8001/build-info"12
1313def download(api_urls, accepted_licenses):
14# Generate the URL that will return the license information. This is the URL14 """Example of how to use the API to download a/all files in a directory."""
15# of the file with /api/license prepended to the path.15
1616 # Get listing for file(s) pointed to by URL we were given
17# Unfortunately urlsplit returns an immutable object. Convert it to an array17 request = urllib2.urlopen(api_urls.ls())
18# so we can modify the path section (index 2)18 listing = json.loads(request.read())["files"]
19parsed_url = [c for c in urlparse.urlsplit(directory_url)]19
20url_path_section = parsed_url[2]20 for file_info in listing:
2121 if file_info["type"] == "folder":
22parsed_url[2] = "/api/ls" + url_path_section22 # Skip folders...
23listing_url = urlparse.urlunsplit(parsed_url)23 continue
2424
25u = urllib2.urlopen(listing_url)25 # Get the licenses. They are returned as a JSON document in the form:
26data = json.loads(u.read())["files"]26 # {"licenses":
2727 # [{"text": "<license text>", "digest": "<digest of license>"},
28for file_info in data:28 # {"text": "<license text>", "digest": "<digest of license>"},
29 if file_info["type"] == "folder":29 # ...
30 # Skip folders...30 # ]}
31 continue31 # Each license has a digest associated with it.
3232 request = urllib2.urlopen(api_urls.license(file_info["url"]))
33 parsed_url[2] = "/api/license" + file_info["url"]33 licenses = json.loads(request.read())["licenses"]
34 license_url = urlparse.urlunsplit(parsed_url)34
3535 if licenses[0] == "Open":
36 parsed_url[2] = file_info["url"]36 headers = {}
37 file_url = urlparse.urlunsplit(parsed_url)37 else:
3838 # Present each license to the user...
39 # Get the licenses. They are returned as a JSON document in the form:39 for lic in licenses:
40 # {"licenses":40 if lic["digest"] not in accepted_licenses:
41 # [{"text": "<license text>", "digest": "<digest of license>"},41 # Licenses are stored as HTML. Convert them to markdown
42 # {"text": "<license text>", "digest": "<digest of license>"},42 # (text) and print it to the terminal.
43 # ...43 print html2text(lic["text"])
44 # ]}44
45 # Each license has a digest associated with it.45 # Ask the user if they accept the license. If they don't we
46 u = urllib2.urlopen(license_url)46 # terminate the script.
47 data = json.loads(u.read())["licenses"]47 user_response = raw_input(
4848 "Do you accept this license? (y/N)")
49 if data[0] == "Open":49 if user_response != "y":
50 headers = {}50 exit(1)
51
52 # Remember this license acceptance for another download.
53 accepted_licenses.append(lic["digest"])
54
55 # To accept a license, place the digest in the LICENSE_ACCEPTED
56 # header. For multiple licenses, they are stored space separated.
57 digests = [lic["digest"] for lic in licenses]
58 headers = {"LICENSE_ACCEPTED": " ".join(digests)}
59
60 # Once the header has been generated, just download the file.
61 req = urllib2.urlopen(urllib2.Request(api_urls.file(file_info["url"]),
62 headers=headers))
63 with open(os.path.basename(file_info["url"]), 'wb') as fp:
64 shutil.copyfileobj(req, fp)
65
66
67class ApiUrls():
68 """Since we want to manipulate URLS, but urlsplit returns an immutable
69 object this is a convenience object to perform the manipulations for us"""
70 def __init__(self, input_url):
71 self.parsed_url = [c for c in urlparse.urlsplit(input_url)]
72 self.path = self.parsed_url[2]
73
74 def ls(self, path=None):
75 if not path:
76 path = self.path
77 self.parsed_url[2] = "/api/ls" + path
78 return urlparse.urlunsplit(self.parsed_url)
79
80 def license(self, path):
81 self.parsed_url[2] = "/api/license" + path
82 return urlparse.urlunsplit(self.parsed_url)
83
84 def file(self, path):
85 self.parsed_url[2] = path
86 return urlparse.urlunsplit(self.parsed_url)
87
88
89if __name__ == '__main__':
90 if len(sys.argv) != 2:
91 # Check that a URL has been supplied.
92 print >> sys.stderr, "Usage: download.py <URL>"
93 exit(1)
94
95 accepted_licenses_path = os.path.join(xdgBaseDir.xdg_data_home,
96 "linaro",
97 "accepted_licenses")
98
99 # Later we ask the user to accept each license in turn. Store which
100 # licenses are accepted so the user only has to accept them once.
101 if os.path.isfile(accepted_licenses_path):
102 with open(accepted_licenses_path) as accepted_licenses_file:
103 accepted_licenses = accepted_licenses_file.read().split()
51 else:104 else:
52 # If this were a command line client designed to ask the user to accept105 accepted_licenses = []
53 # each license, you could use this code to ask the user to accept each106
54 # license in turn. In this example we store which licenses are accepted107 api_urls = ApiUrls(sys.argv[1])
55 # so the user only has to accept them once.108
56 if os.path.isfile("accepted_licenses"):109 download(api_urls, accepted_licenses)
57 with open("accepted_licenses") as accepted_licenses_file:110
58 accepted_licenses = accepted_licenses_file.read().split()111 # Store the licenses that the user accepted
59 else:112 with open(accepted_licenses_path, "w") as accepted_licenses_file:
60 accepted_licenses = []113 accepted_licenses_file.write(" ".join(accepted_licenses))
61
62 # Present each license to the user...
63 for d in data:
64 if d["digest"] not in accepted_licenses:
65 # Licenses are stored as HTML. Convert them to markdown (text)
66 # and print it to the terminal.
67 print html2text(d["text"])
68
69 # Ask the user if they accept the license. If they don't we
70 # terminate the script.
71 user_response = raw_input("Do you accept this license? (y/N)")
72 if user_response != "y":
73 exit(1)
74
75 accepted_licenses.append(d["digest"])
76
77 # Store the licenses that the user accepted
78 with open("accepted_licenses", "w") as accepted_licenses_file:
79 accepted_licenses_file.write(" ".join(accepted_licenses))
80
81 # To accept a license, place the digest in the LICENSE_ACCEPTED header.
82 # For multiple licenses, they are stored space separated.
83 digests = [d["digest"] for d in data]
84 headers = {"LICENSE_ACCEPTED": " ".join(digests)}
85
86 # Once the header has been generated, just download the file.
87 req = urllib2.urlopen(urllib2.Request(file_url, headers=headers))
88 with open(os.path.basename(parsed_url[2]), 'wb') as fp:
89 shutil.copyfileobj(req, fp)

Subscribers

People subscribed via source and target branches