Skip to content

Commit 179663d

Browse files
committed
Merge branch 'load_json_issue' into develop
2 parents c2b0bcd + cb71eb3 commit 179663d

File tree

14 files changed

+378
-54
lines changed

14 files changed

+378
-54
lines changed

src/attributecode/attrib.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,18 +143,20 @@ def generate_from_file(abouts, template_loc=None):
143143
return generate(abouts, template_string=tpls)
144144

145145

146-
def generate_and_save(abouts, output_location, use_mapping=False,
146+
def generate_and_save(abouts, output_location, use_mapping=False, mapping_file=None,
147147
template_loc=None, inventory_location=None):
148148
"""
149149
Generate attribution file using the `abouts` list of About object
150150
at `output_location`.
151151
152+
Optionally use the mapping.config file if `use_mapping` is True.
153+
154+
Optionally use the custom mapping file if mapping_file is set.
155+
152156
Use the optional `template_loc` custom temaplte or a default template.
153157
154158
Optionally filter `abouts` object based on the inventory JSON or
155159
CSV at `inventory_location`.
156-
157-
Optionally use the mapping.config file is `use_mapping` is True
158160
"""
159161
updated_abouts = []
160162
lstrip_afp = []
@@ -178,7 +180,7 @@ def generate_and_save(abouts, output_location, use_mapping=False,
178180
try:
179181
# Return a list which contains only the about file path
180182
about_list = attributecode.util.get_about_file_path(
181-
inventory_location, use_mapping=use_mapping)
183+
inventory_location, use_mapping=use_mapping, mapping_file=mapping_file)
182184
# FIXME: why catching all exceptions?
183185
except Exception:
184186
# 'about_file_path' key/column doesn't exist

src/attributecode/cmd.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ def cli():
115115
@click.option('--mapping', is_flag=True,
116116
help='Use file mapping.config to collect the defined not supported fields in ABOUT files.')
117117

118+
@click.option('--mapping-file', metavar='FILE', nargs=1,
119+
type=click.Path(exists=True, dir_okay=True, readable=True, resolve_path=True),
120+
help='Use a custom mapping file with mapping between input keys and ABOUT field names.')
121+
118122
@click.option('--show-all', is_flag=True, default=False,
119123
help='Show all errors and warnings. '
120124
'By default, the tool only prints these '
@@ -128,7 +132,7 @@ def cli():
128132

129133
@click.help_option('-h', '--help')
130134

131-
def inventory(location, output, mapping, quiet, format, show_all):
135+
def inventory(location, output, mapping, mapping_file, quiet, format, show_all):
132136
"""
133137
Collect a JSON or CSV inventory of components from .ABOUT files.
134138
@@ -151,7 +155,7 @@ def inventory(location, output, mapping, quiet, format, show_all):
151155
# accept zipped ABOUT files as input
152156
location = extract_zip(location)
153157

154-
errors, abouts = model.collect_inventory(location, use_mapping=mapping)
158+
errors, abouts = model.collect_inventory(location, use_mapping=mapping, mapping_file=mapping_file)
155159

156160
write_errors = model.write_output(abouts, output, format)
157161
for err in write_errors:
@@ -176,7 +180,7 @@ def inventory(location, output, mapping, quiet, format, show_all):
176180
@click.argument('output', nargs=1, required=True,
177181
type=click.Path(exists=True, writable=True, dir_okay=True, resolve_path=True))
178182

179-
@click.option('--fetch-license', type=str, nargs=2, metavar='<key>',
183+
@click.option('--fetch-license', type=str, nargs=2, metavar='KEY',
180184
help=('Fetch licenses text from a DejaCode API. and create <license>.LICENSE side-by-side '
181185
'with the generated .ABOUT file using data fetched from a DejaCode License Library. '
182186
'The "license" key is needed in the input. '
@@ -193,7 +197,11 @@ def inventory(location, output, mapping, quiet, format, show_all):
193197
help="Copy the 'license_file' from the directory to the generated location.")
194198

195199
@click.option('--mapping', is_flag=True,
196-
help='Use file mapping.config with mapping between input keys and ABOUT field names.')
200+
help='Use the default file mapping.config with mapping between input keys and ABOUT field names.')
201+
202+
@click.option('--mapping-file', metavar='FILE', nargs=1,
203+
type=click.Path(exists=True, dir_okay=True, readable=True, resolve_path=True),
204+
help='Use a custom mapping file with mapping between input keys and ABOUT field names.')
197205

198206
@click.option('--show-all', is_flag=True, default=False,
199207
help='Show all errors and warnings. '
@@ -208,7 +216,7 @@ def inventory(location, output, mapping, quiet, format, show_all):
208216

209217
@click.help_option('-h', '--help')
210218

211-
def gen(location, output, mapping, license_notice_text_location, fetch_license,
219+
def gen(location, output, mapping, mapping_file, license_notice_text_location, fetch_license,
212220
quiet, show_all):
213221
"""
214222
Generate .ABOUT files in OUTPUT directory from a JSON or CSV inventory of .ABOUT files at LOCATION.
@@ -227,7 +235,7 @@ def gen(location, output, mapping, license_notice_text_location, fetch_license,
227235

228236
errors, abouts = gen_generate(
229237
location=location, base_dir=output, license_notice_text_location=license_notice_text_location,
230-
fetch_license=fetch_license, use_mapping=mapping)
238+
fetch_license=fetch_license, use_mapping=mapping, mapping_file=mapping_file)
231239

232240
about_count = len(abouts)
233241
error_count = 0
@@ -266,6 +274,10 @@ def gen(location, output, mapping, license_notice_text_location, fetch_license,
266274
help='Use the file "mapping.config" with mappings between the CSV '
267275
'inventory columns names and .ABOUT field names.')
268276

277+
@click.option('--mapping-file', metavar='FILE', nargs=1,
278+
type=click.Path(exists=True, dir_okay=True, readable=True, resolve_path=True),
279+
help='Use a custom mapping file with mapping between input keys and ABOUT field names.')
280+
269281
@click.option('--show-all', is_flag=True, default=False,
270282
help='Show all errors and warnings. '
271283
'By default, the tool only prints these '
@@ -282,7 +294,7 @@ def gen(location, output, mapping, license_notice_text_location, fetch_license,
282294

283295
@click.help_option('-h', '--help')
284296

285-
def attrib(location, output, template, mapping, inventory, quiet, show_all):
297+
def attrib(location, output, template, mapping, mapping_file, inventory, quiet, show_all):
286298
"""
287299
Generate an attribution document at OUTPUT using .ABOUT files at LOCATION.
288300
@@ -297,10 +309,10 @@ def attrib(location, output, template, mapping, inventory, quiet, show_all):
297309
if location.lower().endswith('.zip'):
298310
location = extract_zip(location)
299311

300-
inv_errors, abouts = model.collect_inventory(location, use_mapping=mapping)
312+
inv_errors, abouts = model.collect_inventory(location, use_mapping=mapping, mapping_file=mapping_file)
301313
no_match_errors = attrib_generate_and_save(
302314
abouts=abouts, output_location=output,
303-
use_mapping=mapping, template_loc=template,
315+
use_mapping=mapping, mapping_file=mapping_file, template_loc=template,
304316
inventory_location=inventory)
305317

306318
if not no_match_errors:

src/attributecode/gen.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,15 @@ def check_duplicated_about_file_path(inventory_dict):
107107

108108

109109
def load_inventory(location, base_dir, license_notice_text_location=None,
110-
use_mapping=False):
110+
use_mapping=False, mapping_file=None):
111111
"""
112112
Load the inventory file at `location` for ABOUT and LICENSE files
113113
stored in the `base_dir`. Return a list of errors and a list of
114114
About objects validated against the base_dir.
115115
Optionally use `license_notice_text_location` as the location of
116116
license and notice texts.
117-
Optionally use mappings for field names if `use_mapping` is True.
117+
Optionally use mappings for field names if `use_mapping` is True
118+
or a custom mapping_file if provided.
118119
"""
119120
errors = []
120121
abouts = []
@@ -124,9 +125,9 @@ def load_inventory(location, base_dir, license_notice_text_location=None,
124125
if dup_cols_err:
125126
errors.extend(dup_cols_err)
126127
return errors, abouts
127-
inventory = util.load_csv(location, use_mapping)
128+
inventory = util.load_csv(location, use_mapping, mapping_file)
128129
else:
129-
inventory = util.load_json(location, use_mapping)
130+
inventory = util.load_json(location, use_mapping, mapping_file)
130131

131132
try:
132133
dup_about_paths_err = check_duplicated_about_file_path(inventory)
@@ -165,7 +166,9 @@ def load_inventory(location, base_dir, license_notice_text_location=None,
165166
about = model.About(about_file_path=afp)
166167
about.location = loc
167168
running_inventory = False
168-
ld_errors = about.load_dict(fields, base_dir, running_inventory, use_mapping, license_notice_text_location, with_empty=False)
169+
ld_errors = about.load_dict(fields, base_dir, running_inventory,
170+
use_mapping, mapping_file, license_notice_text_location,
171+
with_empty=False)
169172
# 'about_resource' field will be generated during the process.
170173
# No error need to be raise for the missing 'about_resource'.
171174
for e in ld_errors:
@@ -180,7 +183,7 @@ def load_inventory(location, base_dir, license_notice_text_location=None,
180183

181184
def generate(location, base_dir, license_notice_text_location=None,
182185
fetch_license=False, policy=None, conf_location=None,
183-
with_empty=False, with_absent=False, use_mapping=False):
186+
with_empty=False, with_absent=False, use_mapping=False, mapping_file=None):
184187
"""
185188
Load ABOUT data from a CSV inventory at `location`. Write ABOUT files to
186189
base_dir using policy flags and configuration file at conf_location.
@@ -203,7 +206,8 @@ def generate(location, base_dir, license_notice_text_location=None,
203206
location=location,
204207
base_dir=bdir,
205208
license_notice_text_location=license_notice_text_location,
206-
use_mapping=use_mapping)
209+
use_mapping=use_mapping,
210+
mapping_file=mapping_file)
207211

208212
if gen_license:
209213
license_dict, err = model.pre_process_and_fetch_license_dict(abouts, api_url, api_key)

src/attributecode/model.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ def create_fields(self):
710710
field.name = name
711711
setattr(self, name, field)
712712

713-
def __init__(self, location=None, about_file_path=None, use_mapping=False):
713+
def __init__(self, location=None, about_file_path=None, use_mapping=False, mapping_file=None):
714714
self.create_fields()
715715
self.custom_fields = OrderedDict()
716716

@@ -724,7 +724,7 @@ def __init__(self, location=None, about_file_path=None, use_mapping=False):
724724
self.base_dir = None
725725
if self.location:
726726
self.base_dir = os.path.dirname(location)
727-
self.load(location, use_mapping)
727+
self.load(location, use_mapping, mapping_file)
728728

729729
def __repr__(self):
730730
return repr(self.all_fields())
@@ -842,7 +842,7 @@ def as_dict(self, with_paths=False, with_absent=True, with_empty=True):
842842
as_dict[field.name] = field.serialized_value()
843843
return as_dict
844844

845-
def hydrate(self, fields, use_mapping=False):
845+
def hydrate(self, fields, use_mapping=False, mapping_file=None):
846846
"""
847847
Process an iterable of field (name, value) tuples. Update or create
848848
Fields attributes and the fields and custom fields dictionaries.
@@ -852,9 +852,8 @@ def hydrate(self, fields, use_mapping=False):
852852
seen_fields = OrderedDict()
853853

854854
mapping = {}
855-
if use_mapping:
856-
mapping = util.get_mapping()
857-
855+
if use_mapping or mapping_file:
856+
mapping = util.get_mapping(mapping_file)
858857
for name, value in fields:
859858
orig_name = name
860859
name = name.lower()
@@ -879,7 +878,7 @@ def hydrate(self, fields, use_mapping=False):
879878
standard_field.present = True
880879
continue
881880

882-
if not use_mapping:
881+
if not use_mapping and not mapping_file:
883882
if not name == self.about_file_path_attr:
884883
msg = (u'Field %(orig_name)s is not a supported field and is ignored.')
885884
errors.append(Error(INFO, msg % locals()))
@@ -935,7 +934,8 @@ def hydrate(self, fields, use_mapping=False):
935934
return errors
936935

937936
def process(self, fields, about_file_path, running_inventory=False,
938-
base_dir=None, license_notice_text_location=None, use_mapping=False):
937+
base_dir=None, license_notice_text_location=None,
938+
use_mapping=False, mapping_file=None):
939939
"""
940940
Hydrate and validate a sequence of field name/value tuples from an
941941
ABOUT file. Return a list of errors.
@@ -944,7 +944,7 @@ def process(self, fields, about_file_path, running_inventory=False,
944944
self.license_notice_text_location = license_notice_text_location
945945
afp = self.about_file_path
946946
errors = []
947-
hydratation_errors = self.hydrate(fields, use_mapping=use_mapping)
947+
hydratation_errors = self.hydrate(fields, use_mapping=use_mapping, mapping_file=mapping_file)
948948
errors.extend(hydratation_errors)
949949

950950
# We want to copy the license_files before the validation
@@ -965,7 +965,7 @@ def process(self, fields, about_file_path, running_inventory=False,
965965
# self.about_resource.resolve(self.about_file_path)
966966
return errors
967967

968-
def load(self, location, use_mapping=False):
968+
def load(self, location, use_mapping=False, mapping_file=None):
969969
"""
970970
Read, parse and process the ABOUT file at location.
971971
Return a list of errors and update self with errors.
@@ -988,7 +988,7 @@ def load(self, location, use_mapping=False):
988988
and then join with the 'about_resource_path'
989989
"""
990990
running_inventory = True
991-
errs = self.load_dict(saneyaml.load(input_text), base_dir, running_inventory, use_mapping)
991+
errs = self.load_dict(saneyaml.load(input_text), base_dir, running_inventory, use_mapping, mapping_file)
992992
errors.extend(errs)
993993
except Exception as e:
994994
msg = 'Cannot load invalid ABOUT file: %(location)r: %(e)r\n' + str(e)
@@ -1018,7 +1018,8 @@ def load_lines(self, lines, base_dir):
10181018
self.errors = errors
10191019
return errors
10201020

1021-
def load_dict(self, fields_dict, base_dir, running_inventory=False, use_mapping=False,
1021+
def load_dict(self, fields_dict, base_dir, running_inventory=False,
1022+
use_mapping=False, mapping_file=None,
10221023
license_notice_text_location=None, with_empty=True):
10231024
"""
10241025
Load the ABOUT file from a fields name/value mapping.
@@ -1032,7 +1033,7 @@ def load_dict(self, fields_dict, base_dir, running_inventory=False, use_mapping=
10321033
fields = [(n, v) for n, v in fields_dict.items() if v]
10331034
errors = self.process(
10341035
fields, about_file_path, running_inventory, base_dir,
1035-
license_notice_text_location, use_mapping)
1036+
license_notice_text_location, use_mapping, mapping_file)
10361037
self.errors = errors
10371038
return errors
10381039

@@ -1190,7 +1191,7 @@ def parse(lines):
11901191
return errors, fields
11911192

11921193

1193-
def collect_inventory(location, use_mapping=False):
1194+
def collect_inventory(location, use_mapping=False, mapping_file=None):
11941195
"""
11951196
Collect ABOUT files at location and return a list of errors and a list of
11961197
About objects.
@@ -1205,7 +1206,7 @@ def collect_inventory(location, use_mapping=False):
12051206
abouts = []
12061207
for about_loc in about_locations:
12071208
about_file_path = util.get_relative_path(input_location, about_loc)
1208-
about = About(about_loc, about_file_path, use_mapping)
1209+
about = About(about_loc, about_file_path, use_mapping, mapping_file)
12091210
# Insert about_file_path reference to the error
12101211
for severity, message in about.errors:
12111212
msg = (about_file_path + ": " + message)

0 commit comments

Comments
 (0)