Skip to content

Commit f231fa2

Browse files
committed
Handle validation for about_resource references #300
* Add the `--validate-about-resource` option * Remove `validation` code from `dump` as `dump` should only write ABOUT file and it should not do any validation check Signed-off-by: Chin Yeung Li <[email protected]>
1 parent 23420c9 commit f231fa2

File tree

4 files changed

+68
-48
lines changed

4 files changed

+68
-48
lines changed

src/attributecode/cmd.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,17 @@ def inventory(location, output, quiet, format, show_all):
196196
'for any level.'
197197
)
198198

199+
@click.option('--validate-about-resource', is_flag=True, default=False,
200+
help='Validate the existence of the about resource.'
201+
)
202+
199203
@click.option('-q', '--quiet', is_flag=True,
200204
help='Do not print error or warning messages.')
201205

202206
@click.help_option('-h', '--help')
203207

204-
def gen(location, output, mapping, license_notice_text_location, fetch_license, quiet, show_all):
208+
def gen(location, output, mapping, license_notice_text_location, fetch_license,
209+
quiet, show_all, validate_about_resource):
205210
"""
206211
Generate .ABOUT files in OUTPUT directory from a JSON or CSV inventory of .ABOUT files at LOCATION.
207212
@@ -218,9 +223,9 @@ def gen(location, output, mapping, license_notice_text_location, fetch_license,
218223
click.echo('Generating .ABOUT files...')
219224

220225
errors, abouts = gen_generate(
221-
location=location, base_dir=output, use_mapping=mapping,
226+
location=location, base_dir=output, validate_about_resource=validate_about_resource,
222227
license_notice_text_location=license_notice_text_location,
223-
fetch_license=fetch_license)
228+
fetch_license=fetch_license, use_mapping=mapping)
224229

225230
about_count = len(abouts)
226231
error_count = 0

src/attributecode/gen.py

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import codecs
2222
from collections import OrderedDict
2323
import logging
24-
import posixpath
24+
from posixpath import basename, dirname, exists, join, normpath
2525
import sys
2626

2727
if sys.version_info[0] < 3:
@@ -39,6 +39,7 @@
3939
from attributecode import util
4040
from attributecode.util import add_unc
4141
from attributecode.util import to_posix
42+
from attributecode.util import UNC_PREFIX_POSIX
4243

4344

4445
LOG_FILENAME = 'error.log'
@@ -160,7 +161,7 @@ def load_inventory(location, base_dir, license_notice_text_location=None,
160161
continue
161162
else:
162163
afp = util.to_posix(afp)
163-
loc = posixpath.join(base_dir, afp)
164+
loc = join(base_dir, afp)
164165
about = model.About(about_file_path=afp)
165166
about.location = loc
166167
running_inventory = False
@@ -177,15 +178,17 @@ def load_inventory(location, base_dir, license_notice_text_location=None,
177178
return errors, abouts
178179

179180

180-
def generate(location, base_dir, license_notice_text_location=None,
181-
fetch_license=False, policy=None, conf_location=None,
182-
with_empty=False, with_absent=False, use_mapping=False):
181+
def generate(location, base_dir, validate_about_resource=False,
182+
license_notice_text_location=None, fetch_license=False, policy=None,
183+
conf_location=None, with_empty=False, with_absent=False,
184+
use_mapping=False):
183185
"""
184186
Load ABOUT data from a CSV inventory at `location`. Write ABOUT files to
185187
base_dir using policy flags and configuration file at conf_location.
186188
Policy defines which action to take for merging or overwriting fields and
187189
files. Return errors and about objects.
188190
"""
191+
not_exist_errors = []
189192
api_url = ''
190193
api_key = ''
191194
gen_license = False
@@ -214,7 +217,7 @@ def generate(location, base_dir, license_notice_text_location=None,
214217
for about in abouts:
215218
if about.about_file_path.startswith('/'):
216219
about.about_file_path = about.about_file_path.lstrip('/')
217-
dump_loc = posixpath.join(bdir, about.about_file_path.lstrip('/'))
220+
dump_loc = join(bdir, about.about_file_path.lstrip('/'))
218221

219222
# The following code is to check if there is any directory ends with spaces
220223
split_path = about.about_file_path.split('/')
@@ -234,25 +237,41 @@ def generate(location, base_dir, license_notice_text_location=None,
234237

235238
try:
236239
# Generate value for 'about_resource' if it does not exist
237-
# Note: The `about_resource` and `about_resource_path` have
238-
# OrderDict as the value type from PathField
240+
# Note: The `about_resource` is in ListField class
241+
# and `about_resource_path` is in AboutResourceField class
239242
if not about.about_resource.value:
240-
about.about_resource.value = OrderedDict()
243+
#about.about_resource.value = OrderedDict()
241244
if about.about_file_path.endswith('/'):
242-
about.about_resource.value[u'.'] = None
245+
about.about_resource.value.append(u'.')
243246
about.about_resource.original_value = u'.'
244247
else:
245-
about.about_resource.value[posixpath.basename(about.about_file_path)] = None
246-
about.about_resource.original_value = posixpath.basename(about.about_file_path)
248+
about.about_resource.value.append(basename(about.about_file_path))
249+
about.about_resource.original_value = basename(about.about_file_path)
247250
about.about_resource.present = True
248251

249252
# Generate value for 'about_resource_path' if it does not exist
250253
# Basically, this should be the same as the 'about_resource'
251254
if not about.about_resource_path.value:
252255
about.about_resource_path.value = OrderedDict()
253-
about.about_resource_path.value = about.about_resource.value
256+
for about_resource_value in about.about_resource.value:
257+
about.about_resource_path.value[about_resource_value] = None
254258
about.about_resource_path.present = True
255-
259+
if validate_about_resource:
260+
# Check for the existence of the about_resource
261+
# If the input already have the about_resource_path field, it will
262+
# be validated when creating the about object
263+
loc = util.to_posix(dump_loc)
264+
about_file_loc = loc
265+
for about_resource_value in about.about_resource_path.value:
266+
path = join(dirname(util.to_posix(about_file_loc)),
267+
about_resource_value)
268+
if not exists(path):
269+
path = util.to_posix(path.strip(UNC_PREFIX_POSIX))
270+
path = normpath(path)
271+
msg = (u'The reference file: '
272+
u'%(path)s '
273+
u'does not exist' % locals())
274+
not_exist_errors.append(msg)
256275
if gen_license:
257276
about.license_file.value = OrderedDict()
258277
# Write generated LICENSE file
@@ -276,21 +295,15 @@ def generate(location, base_dir, license_notice_text_location=None,
276295
if about.license_name.value:
277296
about.license_name.present = True
278297

279-
# Write the ABOUT file and check does the referenced file exist
280-
# This function is not purposed to throw error. However, since I've commented
281-
# out the error throwing in FileTextField (See model.py), I have add error handling
282-
# in this function. This error handling should be removed once the fetch-license option
283-
# is treated as a subcommand.
284-
not_exist_errors = about.dump(dump_loc,
285-
with_empty=with_empty,
286-
with_absent=with_absent)
298+
# Write the ABOUT files
299+
about.dump(dump_loc, with_empty=with_empty, with_absent=with_absent)
300+
287301
file_field_not_exist_errors = check_file_field_exist(about, dump_loc)
288302

289303
for e in not_exist_errors:
290304
errors.append(Error(ERROR, e))
291305
for e in file_field_not_exist_errors:
292306
errors.append(Error(ERROR, e))
293-
294307
except Exception as e:
295308
# only keep the first 100 char of the exception
296309
emsg = repr(e)[:100]

src/attributecode/model.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1054,7 +1054,6 @@ def dump(self, location, with_absent=False, with_empty=True):
10541054
If with_absent, include absent (not present) fields.
10551055
If with_empty, include empty fields.
10561056
"""
1057-
errors = []
10581057
loc = util.to_posix(location)
10591058
parent = posixpath.dirname(loc)
10601059

@@ -1070,17 +1069,6 @@ def dump(self, location, with_absent=False, with_empty=True):
10701069
about_file_path = add_unc(about_file_path)
10711070
with codecs.open(about_file_path, mode='wb', encoding='utf-8') as dumped:
10721071
dumped.write(self.dumps(with_absent, with_empty))
1073-
# FIXME: Why do I have to check here? Shouldn't it be checked during validation?
1074-
for about_resource_value in self.about_resource_path.value:
1075-
path = posixpath.join(dirname(util.to_posix(about_file_path)), about_resource_value)
1076-
if not posixpath.exists(path):
1077-
path = util.to_posix(path.strip(UNC_PREFIX_POSIX))
1078-
path = os.path.normpath(path)
1079-
msg = (u'The reference file : '
1080-
u'%(path)s '
1081-
u'does not exist' % locals())
1082-
errors.append(msg)
1083-
return errors
10841072

10851073
def dump_lic(self, location, license_dict):
10861074
"""

tests/test_gen.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -111,31 +111,45 @@ def test_generation_with_no_about_resource(self):
111111
location = get_test_loc('gen/inv2.csv')
112112
base_dir = get_temp_dir()
113113
errors, abouts = gen.generate(location, base_dir)
114-
expected_dict = OrderedDict()
115-
expected_dict[u'.'] = None
116-
assert abouts[0].about_resource.value == expected_dict
114+
expected = [u'.']
115+
assert abouts[0].about_resource.value == expected
117116
assert len(errors) == 0
118117

119118
def test_generation_with_no_about_resource_reference(self):
120119
location = get_test_loc('gen/inv3.csv')
121120
base_dir = get_temp_dir()
121+
validate_about_resource = True
122122

123-
errors, abouts = gen.generate(location, base_dir)
124-
expected_dict = OrderedDict()
125-
expected_dict[u'test.tar.gz'] = None
123+
errors, abouts = gen.generate(location, base_dir, validate_about_resource)
124+
expected = [u'test.tar.gz']
126125

127-
assert abouts[0].about_resource.value == expected_dict
126+
assert abouts[0].about_resource.value == expected
128127
assert len(errors) == 1
129128
msg = u'The reference file'
130129
assert msg in errors[0].message
131130

131+
def test_generation_with_no_about_resource_reference_no_resource_validation(self):
132+
location = get_test_loc('gen/inv3.csv')
133+
base_dir = get_temp_dir()
134+
validate_about_resource = False
135+
136+
errors, abouts = gen.generate(location, base_dir, validate_about_resource)
137+
expected = [u'test.tar.gz']
138+
139+
assert abouts[0].about_resource.value == expected
140+
assert len(errors) == 0
141+
132142
def test_generate(self):
133143
location = get_test_loc('gen/inv.csv')
134144
base_dir = get_temp_dir()
145+
validate_about_resource = True
135146

136-
errors, abouts = gen.generate(location, base_dir)
137-
expected_errors = [Error(INFO, u'Field custom1 is not a supported field and is ignored.')]
138-
assert expected_errors == errors
147+
errors, abouts = gen.generate(location, base_dir, validate_about_resource)
148+
msg1 = u'Field custom1 is not a supported field and is ignored.'
149+
msg2 = u'The reference file'
150+
151+
assert msg1 in errors[0].message
152+
assert msg2 in errors[1].message
139153

140154
in_mem_result = [a.dumps(with_absent=False, with_empty=False)
141155
for a in abouts][0]

0 commit comments

Comments
 (0)