Merge lp:~james-w/pkgme-devportal/better-api into lp:pkgme-devportal

Proposed by James Westby
Status: Merged
Approved by: James Westby
Approved revision: 109
Merged at revision: 108
Proposed branch: lp:~james-w/pkgme-devportal/better-api
Merge into: lp:pkgme-devportal
Diff against target: 442 lines (+99/-129)
6 files modified
devportalbinary/binary.py (+3/-2)
devportalbinary/metadata.py (+39/-43)
devportalbinary/pdf.py (+5/-4)
devportalbinary/tests/test_binary.py (+2/-4)
devportalbinary/tests/test_metadata.py (+44/-65)
devportalbinary/tests/test_pdf.py (+6/-11)
To merge this branch: bzr merge lp:~james-w/pkgme-devportal/better-api
Reviewer Review Type Date Requested Status
Jonathan Lange Approve
Review via email: mp+123788@code.launchpad.net

Commit message

Move the want stuff to class methods and split some of the code out.

Description of the change

Hi,

This moves want() to be a classmethod, because it's part of a thing you
do before you use a backend. Maybe it should be moved from the class altogether.

It also moves some logic to a more generic function, and then uses that function.

It also drops some unused code (we never used the code to load a single field
from a metadata file, we just load the whole thing and use it as a dict.)

Thanks,

James

To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

Looks great. Dovetails nicely with what I'm doing.

load_json needs a proper docstring before landing though.

Thanks
jml

review: Approve
109. By James Westby

Better docstring. Thanks jml.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'devportalbinary/binary.py'
2--- devportalbinary/binary.py 2012-09-11 15:21:46 +0000
3+++ devportalbinary/binary.py 2012-09-11 16:56:39 +0000
4@@ -405,8 +405,9 @@
5 package_name = self.get_package_name(metadata)
6 return guess_extra_debian_rules_targets(package_name, self.path)
7
8- def want_with_metadata(self, metadata):
9- if list(iter_binaries(self.path)):
10+ @classmethod
11+ def want_with_metadata(cls, path, metadata):
12+ if list(iter_binaries(path)):
13 return {
14 'score': 10,
15 'reason': 'Has ELF binaries and a metadata file'}
16
17=== modified file 'devportalbinary/metadata.py'
18--- devportalbinary/metadata.py 2012-09-11 15:21:46 +0000
19+++ devportalbinary/metadata.py 2012-09-11 16:56:39 +0000
20@@ -92,30 +92,6 @@
21 }
22
23
24-_no_field = object()
25-def get_metadata(field_name=None, default=_no_field, path=None):
26- """Return the value of ``field_name`` in metadata.
27-
28- :param field_name: The field to look up. If None, then return all the
29- metadata as a dict.
30- :param default: If provided, then this value will be returned if
31- 'field_name' is not present in the metadata.
32- :param path: The path to the metadata file, if unspecified, look for
33- ``MetadataBackend.METADATA_FILE`` in the current working directory.
34- :return: The value of the field.
35- """
36- if path is None:
37- path = MetadataBackend.METADATA_FILE
38- with open(path) as f:
39- metadata = json.load(f)
40- if field_name is None:
41- return metadata
42- value = metadata.get(field_name, default)
43- if value is _no_field:
44- raise KeyError(field_name)
45- return value
46-
47-
48 def get_install_basedir(package_name):
49 return '/opt/%s' % package_name
50
51@@ -210,6 +186,25 @@
52 return int(w)
53
54
55+def load_json(path):
56+ """Load JSON from `path`.
57+
58+ :param path: the path to load the JSON from.
59+ :return: (json, err) where json is the deserialized contents of the file,
60+ or None if it didn't exist or didn't contain json. If json is
61+ None then err will be a description of why it couldn't be loaded.
62+ """
63+ if not os.path.exists(path):
64+ return None, 'No metadata file'
65+ try:
66+ with open(path) as f:
67+ metadata = json.load(f)
68+ except ValueError:
69+ # Not a JSON file.
70+ return None, 'Metadata file is not valid JSON'
71+ return metadata, None
72+
73+
74 class MetadataBackend(object):
75 """A backend that is mostly powered by metadata from MyApps."""
76
77@@ -428,7 +423,12 @@
78 """Get the version of the package."""
79 return metadata.get(self.SUGGESTED_VERSION, None)
80
81- def get_metadata(self):
82+ @classmethod
83+ def get_metadata_path(cls, path):
84+ return os.path.join(path, cls.METADATA_FILE)
85+
86+ @classmethod
87+ def get_metadata(cls, path):
88 """Get the metadata for this backend.
89
90 Looks for the metadata in a file called ``METADATA_FILE`` in the
91@@ -436,7 +436,10 @@
92
93 :return: A dict of metadata.
94 """
95- return get_metadata(path=self.metadata_path)
96+ metadata, err = load_json(cls.get_metadata_path(path))
97+ if metadata is None:
98+ raise AssertionError(err)
99+ return metadata
100
101 def get_info(self, metadata):
102 """Return a dict of InfoElements given 'metadata'.
103@@ -484,22 +487,16 @@
104 ExtraFiles, metadata, info[PackageName])
105 return info
106
107- def has_metadata_file(self):
108- """True if a metadata file exists."""
109- return os.path.exists(self.metadata_path)
110-
111- def want(self):
112+ @classmethod
113+ def want(cls, path):
114 """How relevant this backend is."""
115- if not self.has_metadata_file():
116- return {'score': 0, 'reason': 'No metadata file'}
117- try:
118- metadata = self.get_metadata()
119- except ValueError:
120- # Not a JSON file.
121- return {'score': 0, 'reason': 'Metadata file is not valid JSON'}
122- return self.want_with_metadata(metadata)
123+ metadata, err = load_json(cls.get_metadata_path(path))
124+ if err is not None:
125+ return {'score': 0, 'reason': err}
126+ return cls.want_with_metadata(path, metadata)
127
128- def want_with_metadata(self, metadata):
129+ @classmethod
130+ def want_with_metadata(self, path, metadata):
131 """How relevant this backend is, after metadata has been found.
132
133 Specific backends should override this.
134@@ -509,16 +506,15 @@
135
136 def make_want_fn(backend_cls):
137 def metadata_want(path):
138- backend = backend_cls(path)
139- return backend.want()
140+ return backend_cls.want(path)
141 metadata_want.__name__ = '{}_want'.format(backend_cls.__name__)
142 return backend_script(metadata_want)
143
144
145 def make_all_info_fn(backend_cls):
146 def metadata_all_info(path):
147+ metadata = backend_cls.get_metadata(path)
148 backend = backend_cls(path)
149- metadata = backend.get_metadata()
150 info = backend.get_info(metadata)
151 return convert_info(info)
152 metadata_all_info.__name__ = '{}_all_info'.format(backend_cls.__name__)
153
154=== modified file 'devportalbinary/pdf.py'
155--- devportalbinary/pdf.py 2012-09-11 15:21:46 +0000
156+++ devportalbinary/pdf.py 2012-09-11 16:56:39 +0000
157@@ -46,14 +46,15 @@
158 return None
159 return '/usr/bin/xdg-open /opt/%s/%s' % (package_name, pdf_filename)
160
161- def want_with_metadata(self, metadata):
162+ @classmethod
163+ def want_with_metadata(cls, path, metadata):
164 # XXX: This implies that we're doing exact string matching on the
165 # paths in the metadata and the results of os.listdir. We actually
166 # want to exclude the icons if they are the same file, not if the
167 # strings happen to be equal.
168- excluded_files = set([self.METADATA_FILE, 'debian'])
169- excluded_files |= set(metadata.get(self.ICONS, {}).values())
170- files = os.listdir(self.path)
171+ excluded_files = set([cls.METADATA_FILE, 'debian'])
172+ excluded_files |= set(metadata.get(cls.ICONS, {}).values())
173+ files = os.listdir(path)
174 files = [f for f in files if f not in excluded_files]
175 # By default, we don't want it and give no reason. Sane default in
176 # case of buggy code below.
177
178=== modified file 'devportalbinary/tests/test_binary.py'
179--- devportalbinary/tests/test_binary.py 2012-09-06 20:04:30 +0000
180+++ devportalbinary/tests/test_binary.py 2012-09-11 16:56:39 +0000
181@@ -293,20 +293,18 @@
182 # top-level.
183 path = self.useFixture(MetadataFixture({})).path
184 self.useFixture(BinaryFileFixture(path))
185- backend = self.make_backend(path)
186 self.assertEqual(
187 {'score': 10,
188 'reason': 'Has ELF binaries and a metadata file'},
189- backend.want())
190+ BinaryBackend.want(path))
191
192 def test_want_with_just_metadata(self):
193 # If something just has a metadata file but no binaries it is
194 # not wanted
195 path = self.useFixture(MetadataFixture({})).path
196- backend = self.make_backend(path)
197 self.assertEqual(
198 {'score': 0,
199- 'reason': 'No ELF binaries found.'}, backend.want())
200+ 'reason': 'No ELF binaries found.'}, BinaryBackend.want(path))
201
202 def test_description(self):
203 # The binary backend uses the package description that's in the
204
205=== modified file 'devportalbinary/tests/test_metadata.py'
206--- devportalbinary/tests/test_metadata.py 2012-09-11 15:21:46 +0000
207+++ devportalbinary/tests/test_metadata.py 2012-09-11 16:56:39 +0000
208@@ -37,14 +37,13 @@
209 make_tree,
210 )
211
212-from devportalbinary.backend import convert_info
213 from devportalbinary.metadata import (
214 format_install_map,
215 get_desktop_file,
216 get_install_basedir,
217 get_install_file,
218 get_install_map,
219- get_metadata,
220+ load_json,
221 make_all_info_fn,
222 make_want_fn,
223 MetadataBackend,
224@@ -80,10 +79,42 @@
225 def get_executable(self, package_name):
226 return 'executable:' + package_name
227
228- def want_with_metadata(self, metadata):
229+ @classmethod
230+ def want_with_metadata(self, path, metadata):
231 return {'score': 20, 'reason': "I'm a dummy", 'metadata': metadata}
232
233
234+class TestLoadJson(TestCase):
235+
236+ def test_load_json_all_fields(self):
237+ metadata = {
238+ 'foo': self.getUniqueString(),
239+ 'bar': self.getUniqueInteger(),
240+ }
241+ path = self.useFixture(MetadataFixture(metadata)).metadata_path
242+ found_metadata, err = load_json(path)
243+ self.assertEqual(metadata, found_metadata)
244+ self.assertEqual(None, err)
245+
246+ def test_load_json_not_present(self):
247+ # load_json returns None and a description when the file is
248+ # missing
249+ filename = 'metdata.json'
250+ path = self.useFixture(FileTree({})).join(filename)
251+ found_metadata, err = load_json(path)
252+ self.assertEqual(None, found_metadata)
253+ self.assertEqual('No metadata file', err)
254+
255+ def test_load_json_not_json(self):
256+ # load_json returns None and a description when the file
257+ # does not contain json
258+ filename = 'metdata.json'
259+ path = self.useFixture(FileTree({filename: {}})).join(filename)
260+ found_metadata, err = load_json(path)
261+ self.assertEqual(None, found_metadata)
262+ self.assertEqual('Metadata file is not valid JSON', err)
263+
264+
265 class MetadataTests(BackendTests):
266
267 BACKEND = DummyBackend
268@@ -101,57 +132,6 @@
269 def test_metadata_file(self):
270 self.assertEqual('devportal-metadata.json', MetadataBackend.METADATA_FILE)
271
272- def test_get_metadata_field_present(self):
273- # get_metadata returns the metadata value of the requested field.
274- metadata = {
275- 'foo': self.getUniqueString(),
276- 'bar': self.getUniqueInteger(),
277- }
278- path = self.useFixture(MetadataFixture(metadata)).metadata_path
279- foo = get_metadata('foo', path=path)
280- self.assertEqual(metadata['foo'], foo)
281-
282- def test_get_metadata_default_file(self):
283- # By default, get_metadata looks for the METADATA_FILE in the current
284- # working directory.
285- metadata = {
286- 'foo': self.getUniqueString(),
287- 'bar': self.getUniqueInteger(),
288- }
289- path = self.useFixture(MetadataFixture(metadata)).path
290- self.addCleanup(os.chdir, os.getcwd())
291- os.chdir(path)
292- foo = get_metadata('foo')
293- self.assertEqual(metadata['foo'], foo)
294-
295- def test_get_metadata_field_not_present_default_provided(self):
296- # get_metadata returns the provided default value if the field is not
297- # present in the metadata.
298- metadata = {}
299- path = self.useFixture(MetadataFixture(metadata)).metadata_path
300- default = object()
301- foo = get_metadata('foo', default, path=path)
302- self.assertIs(default, foo)
303-
304- def test_get_metadata_field_not_present_no_default(self):
305- # get_metadata raises an exception if the field isn't there and no
306- # default was provided.
307- metadata = {}
308- field = self.getUniqueString()
309- path = self.useFixture(MetadataFixture(metadata)).metadata_path
310- e = self.assertRaises(KeyError, get_metadata, field, path=path)
311- self.assertEqual(repr(field), str(e))
312-
313- def test_get_metadata_all_fields(self):
314- # get_metadata returns the metadata value of the requested field.
315- metadata = {
316- 'foo': self.getUniqueString(),
317- 'bar': self.getUniqueInteger(),
318- }
319- path = self.useFixture(MetadataFixture(metadata)).metadata_path
320- found_metadata = get_metadata(path=path)
321- self.assertEqual(metadata, found_metadata)
322-
323 def test_get_extra_files_install_file(self):
324 # ExtraFiles includes an install file generated by get_install_file.
325 package_name = self.getUniqueString()
326@@ -408,32 +388,30 @@
327
328 def test_want_with_no_metadata(self):
329 # If there's no metadata file, we don't want it.
330- backend = self.make_backend()
331+ path = self.useFixture(TempDir()).path
332 self.assertEqual(
333 {'score': 0, 'reason': 'No metadata file'},
334- backend.want())
335+ DummyBackend.want(path))
336
337 def test_want_with_invalid_json(self):
338 # If there's a metadata file, but it's not valid JSON, we don't want
339 # it.
340- backend = self.make_backend()
341- make_tree(backend.path, from_rough_spec(
342- [(MetadataBackend.METADATA_FILE, 'not valid json')]))
343+ path = self.useFixture(FileTree(from_rough_spec(
344+ [(MetadataBackend.METADATA_FILE, 'not valid json')]))).path
345 self.assertEqual(
346 {'score': 0, 'reason': 'Metadata file is not valid JSON'},
347- backend.want())
348+ DummyBackend.want(path))
349
350 def test_want_with_valid_metadata(self):
351 # If there's a metadata file containing valid JSON, we delegate to the
352 # backend's want_with_metadata method. See
353 # DummyBackend.want_with_metadata.
354- backend = self.make_backend()
355 metadata = {'foo': 'bar'}
356- make_tree(backend.path, from_rough_spec(
357- [(MetadataBackend.METADATA_FILE, json.dumps(metadata))]))
358+ path = self.useFixture(FileTree(from_rough_spec(
359+ [(MetadataBackend.METADATA_FILE, json.dumps(metadata))]))).path
360 self.assertEqual(
361 {'score': 20, 'reason': "I'm a dummy", 'metadata': metadata},
362- backend.want())
363+ DummyBackend.want(path))
364
365
366 class DesktopFileTests(TestCase):
367@@ -661,7 +639,8 @@
368 backend = DummyBackend(path)
369 output = StringIO()
370 want(path, output)
371- self.assertEqual(backend.want(), json.loads(output.getvalue()))
372+ self.assertEqual(DummyBackend.want(path),
373+ json.loads(output.getvalue()))
374
375 def test_names_fn(self):
376 want = make_want_fn(DummyBackend)
377
378=== modified file 'devportalbinary/tests/test_pdf.py'
379--- devportalbinary/tests/test_pdf.py 2012-08-23 12:05:12 +0000
380+++ devportalbinary/tests/test_pdf.py 2012-09-11 16:56:39 +0000
381@@ -27,27 +27,24 @@
382 # If we detect the metadata file and a pdf, then we score 20.
383 filename = self.getUniqueString() + ".pdf"
384 path = self.make_tree([VALID_METADATA_FILE, filename])
385- backend = PdfBackend(path)
386- self.assertEqual({'score': 20, 'reason': None}, backend.want())
387+ self.assertEqual({'score': 20, 'reason': None}, PdfBackend.want(path))
388
389 def test_want_with_single_non_pdf(self):
390 # If we detect a metadata file and a single other file that's not a
391 # PDF, we score 0.
392 filename = self.getUniqueString() + ".not-pdf"
393 path = self.make_tree([VALID_METADATA_FILE, filename])
394- backend = PdfBackend(path)
395 self.assertEqual(
396 {'score': 0,
397 'reason': 'File is not a PDF: %r' % (filename,),
398- }, backend.want())
399+ }, PdfBackend.want(path))
400
401 def test_want_with_just_metadata(self):
402 # If we detect just the metadata file then we score 0.
403 path = self.make_tree([VALID_METADATA_FILE])
404- backend = PdfBackend(path)
405 self.assertEqual(
406 {'score': 0, 'reason': 'No files found, just metadata'},
407- backend.want())
408+ PdfBackend.want(path))
409
410 def test_want_with_extra_files(self):
411 # If we detect other files then we score 0. This is so that
412@@ -56,19 +53,17 @@
413 filename = self.getUniqueString() + ".pdf"
414 other_filename = self.getUniqueString() + ".foo"
415 path = self.make_tree([VALID_METADATA_FILE, filename, other_filename])
416- backend = PdfBackend(path)
417 self.assertEqual(
418 {'score': 0, 'reason': 'More files than just a PDF: %r' %
419 (sorted([filename, other_filename]),)},
420- backend.want())
421+ PdfBackend.want(path))
422
423 def test_want_with_debian_dir(self):
424 # If the other file is a debian dir then we score 20 still.
425 # This just makes local testing of the backend easier.
426 filename = self.getUniqueString() + ".pdf"
427 path = self.make_tree([VALID_METADATA_FILE, filename, 'debian/'])
428- backend = PdfBackend(path)
429- self.assertEqual({'score': 20, 'reason': None}, backend.want())
430+ self.assertEqual({'score': 20, 'reason': None}, PdfBackend.want(path))
431
432 def test_want_with_icons(self):
433 icon_file = self.getUniqueString() + '.png'
434@@ -77,7 +72,7 @@
435 path = self.make_tree([
436 (MetadataBackend.METADATA_FILE, metadata), icon_file, filename])
437 backend = PdfBackend(path)
438- self.assertEqual({'score': 20, 'reason': None}, backend.want())
439+ self.assertEqual({'score': 20, 'reason': None}, PdfBackend.want(path))
440
441 def test_architecture(self):
442 # The pdf backend set architecture to all

Subscribers

People subscribed via source and target branches