From 024da7337ef8a464e0b30e78972d9fec68030992 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Wed, 18 Oct 2023 17:18:16 +0800 Subject: [PATCH 1/2] Fixed #543 - Add numeric value support for 'attribute' field Signed-off-by: Chin Yeung Li --- docs/source/specification.rst | 8 +- src/attributecode/model.py | 104 +++++++++++++++++- tests/test_model.py | 21 ++++ .../parse/boolean_numeric_data.about | 7 ++ 4 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 tests/testdata/test_model/parse/boolean_numeric_data.about diff --git a/docs/source/specification.rst b/docs/source/specification.rst index dd05990f..487b435c 100644 --- a/docs/source/specification.rst +++ b/docs/source/specification.rst @@ -352,14 +352,18 @@ Optional Boolean flag fields - redistribute: Set this flag to yes if the component license requires source code redistribution. Defaults to no when absent. -- attribute: Set this flag to yes if the component license requires publishing an attribution - or credit notice. Defaults to no when absent. - track_changes: Set this flag to yes if the component license requires tracking changes made to a the component. Defaults to no when absent. - modified: Set this flag to yes if the component has been modified. Defaults to no when absent. - internal_use_only: Set this flag to yes if the component is used internal only. Defaults to no when absent. +Optional Boolean and Numberic fields +------------------------------------ + +- attribute: This field can be either in boolean value: ('yes', 'y', 'true', + 'x', 'no', 'n', 'false') or numeric value field. Defaults to no when absent. + Optional Extension fields ------------------------- diff --git a/src/attributecode/model.py b/src/attributecode/model.py index 0ea0ae6d..e6e74aec 100644 --- a/src/attributecode/model.py +++ b/src/attributecode/model.py @@ -720,6 +720,108 @@ def __eq__(self, other): and self.value == other.value) +class BooleanAndNumbericField(SingleLineField): + """ + Field with either a boolean value or a numeric value. Validated value is + False, True, None or numeric value. + """ + + def default_value(self): + return None + + true_flags = ('yes', 'y', 'true', 'x') + false_flags = ('no', 'n', 'false') + flag_values = true_flags + false_flags + + def _validate(self, *args, **kwargs): + """ + Check that flag are valid with either boolean value or numeric value. Default flag to + False. Return a list of errors. + """ + errors = super(BooleanAndNumbericField, + self)._validate(*args, ** kwargs) + self.about_file_path = kwargs.get('about_file_path') + flag = self.get_value(self.original_value) + if flag is False: + name = self.name + val = self.original_value + about_file_path = self.about_file_path + flag_values = self.flag_values + msg = (u'Path: %(about_file_path)s - Field %(name)s: Invalid value: %(val)r is not ' + u'one of: %(flag_values)s and it is not a numeric value.' % locals()) + errors.append(Error(ERROR, msg)) + self.value = None + elif flag is None: + name = self.name + msg = (u'Field %(name)s: field is present but empty. ' % locals()) + errors.append(Error(INFO, msg)) + self.value = None + else: + if flag == u'yes' or flag is True: + self.value = True + elif flag == u'no': + self.value = False + else: + self.value = flag + return errors + + def get_value(self, value): + """ + Return a normalized existing value if found in the list of + possible values or None if empty or False if not found or original value + if it is not a boolean value + """ + if value is None or value == '': + return None + + if isinstance(value, bool): + return value + else: + if isinstance(value, str): + value = value.strip() + if not value: + return None + + value = value.lower() + if value in self.flag_values: + if value in self.true_flags: + return u'yes' + else: + return u'no' + else: + if value.isdigit(): + return value + else: + return False + elif isinstance(value, int): + return value + else: + return False + + @property + def has_content(self): + """ + Return true if it has content regardless of what value, False otherwise + """ + if self.original_value: + return True + return False + + def _serialized_value(self): + # default normalized values for serialization + if self.value: + if isinstance(self.value, bool): + return u'yes' + else: + return self.value + elif self.value is False: + return u'no' + else: + # self.value is None + # TODO: should we serialize to No for None??? + return u'' + + def validate_fields(fields, about_file_path, running_inventory, base_dir, reference_dir=None): """ @@ -810,7 +912,7 @@ def set_standard_fields(self): ('notice_url', UrlField()), ('redistribute', BooleanField()), - ('attribute', BooleanField()), + ('attribute', BooleanAndNumbericField()), ('track_changes', BooleanField()), ('modified', BooleanField()), ('internal_use_only', BooleanField()), diff --git a/tests/test_model.py b/tests/test_model.py index 251fb5c0..b5f2056b 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -648,6 +648,27 @@ def test_About_boolean_value(self): assert a.redistribute.value is True assert a.track_changes.value is None + def test_About_boolean_numeric_value(self): + test_file = get_test_loc('test_model/parse/boolean_numeric_data.about') + a = model.About(test_file) + expected_msg = "Field track_changes is present but empty." + assert expected_msg in a.errors[0].message + # Context of the test file + """ + about_resource: . + name: boolean_data + attribute: 3 + modified: true + internal_use_only: no + redistribute: yes + track_changes: + """ + assert a.attribute.value == '3' + assert a.modified.value is True + assert a.internal_use_only.value is False + assert a.redistribute.value is True + assert a.track_changes.value is None + def test_About_contains_about_file_path(self): test_file = get_test_loc('test_model/serialize/about.ABOUT') # TODO: I am not sure this override of the about_file_path makes sense diff --git a/tests/testdata/test_model/parse/boolean_numeric_data.about b/tests/testdata/test_model/parse/boolean_numeric_data.about new file mode 100644 index 00000000..627f95f0 --- /dev/null +++ b/tests/testdata/test_model/parse/boolean_numeric_data.about @@ -0,0 +1,7 @@ +about_resource: . +name: boolean_data +attribute: 3 +modified: true +internal_use_only: no +redistribute: yes +track_changes: From 7c88fba4c11d71a5d406a92610ce8f078c4f7f4a Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Thu, 19 Oct 2023 15:37:51 +0800 Subject: [PATCH 2/2] Fixed# 543 - attribute field to support boolean and character (at most 2 chars) * Updated tests * Update from supporting numeric to characters (at most 2 chars) Signed-off-by: Chin Yeung Li --- docs/source/specification.rst | 7 +++-- src/attributecode/model.py | 29 +++++++++---------- tests/test_model.py | 28 +++++++++++++++++- .../test_model/parse/boolean_chara_data.about | 3 ++ .../boolean_more_than_2_chara_data.about | 3 ++ 5 files changed, 50 insertions(+), 20 deletions(-) create mode 100644 tests/testdata/test_model/parse/boolean_chara_data.about create mode 100644 tests/testdata/test_model/parse/boolean_more_than_2_chara_data.about diff --git a/docs/source/specification.rst b/docs/source/specification.rst index 487b435c..de01f642 100644 --- a/docs/source/specification.rst +++ b/docs/source/specification.rst @@ -358,11 +358,12 @@ Optional Boolean flag fields - internal_use_only: Set this flag to yes if the component is used internal only. Defaults to no when absent. -Optional Boolean and Numberic fields ------------------------------------- +Optional Boolean and Character fields +------------------------------------- - attribute: This field can be either in boolean value: ('yes', 'y', 'true', - 'x', 'no', 'n', 'false') or numeric value field. Defaults to no when absent. + 'x', 'no', 'n', 'false') or a character value field with no more than 2 + characters. Defaults to no when absent. Optional Extension fields ------------------------- diff --git a/src/attributecode/model.py b/src/attributecode/model.py index e6e74aec..9ba6f1e8 100644 --- a/src/attributecode/model.py +++ b/src/attributecode/model.py @@ -720,10 +720,10 @@ def __eq__(self, other): and self.value == other.value) -class BooleanAndNumbericField(SingleLineField): +class BooleanAndTwoCharactersField(SingleLineField): """ - Field with either a boolean value or a numeric value. Validated value is - False, True, None or numeric value. + Field with either a boolean value or character(s) value (at most 2 + characters). Validated value is False, True, None or character value. """ def default_value(self): @@ -735,10 +735,10 @@ def default_value(self): def _validate(self, *args, **kwargs): """ - Check that flag are valid with either boolean value or numeric value. Default flag to - False. Return a list of errors. + Check that flag are valid with either boolean value or character value. + Default flag to False. Return a list of errors. """ - errors = super(BooleanAndNumbericField, + errors = super(BooleanAndTwoCharactersField, self)._validate(*args, ** kwargs) self.about_file_path = kwargs.get('about_file_path') flag = self.get_value(self.original_value) @@ -748,7 +748,7 @@ def _validate(self, *args, **kwargs): about_file_path = self.about_file_path flag_values = self.flag_values msg = (u'Path: %(about_file_path)s - Field %(name)s: Invalid value: %(val)r is not ' - u'one of: %(flag_values)s and it is not a numeric value.' % locals()) + u'one of: %(flag_values)s and it is not a 1 or 2 character value.' % locals()) errors.append(Error(ERROR, msg)) self.value = None elif flag is None: @@ -783,18 +783,15 @@ def get_value(self, value): return None value = value.lower() - if value in self.flag_values: + if value in self.flag_values or len(value) <= 2: if value in self.true_flags: return u'yes' - else: + elif value in self.false_flags: return u'no' - else: - if value.isdigit(): - return value else: - return False - elif isinstance(value, int): - return value + return value + else: + return False else: return False @@ -912,7 +909,7 @@ def set_standard_fields(self): ('notice_url', UrlField()), ('redistribute', BooleanField()), - ('attribute', BooleanAndNumbericField()), + ('attribute', BooleanAndTwoCharactersField()), ('track_changes', BooleanField()), ('modified', BooleanField()), ('internal_use_only', BooleanField()), diff --git a/tests/test_model.py b/tests/test_model.py index b5f2056b..fbc8576a 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -648,7 +648,7 @@ def test_About_boolean_value(self): assert a.redistribute.value is True assert a.track_changes.value is None - def test_About_boolean_numeric_value(self): + def test_About_boolean_numberic_value(self): test_file = get_test_loc('test_model/parse/boolean_numeric_data.about') a = model.About(test_file) expected_msg = "Field track_changes is present but empty." @@ -669,6 +669,32 @@ def test_About_boolean_numeric_value(self): assert a.redistribute.value is True assert a.track_changes.value is None + def test_About_boolean_character_value(self): + test_file = get_test_loc('test_model/parse/boolean_chara_data.about') + a = model.About(test_file) + # Context of the test file + """ + about_resource: . + name: data + attribute: 11 + """ + assert a.attribute.value == '11' + assert len(a.errors) == 0 + + def test_About_boolean_more_than_2_character_value(self): + test_file = get_test_loc( + 'test_model/parse/boolean_more_than_2_chara_data.about') + a = model.About(test_file) + expected_msg = "Path: None - Field attribute: Invalid value: 'abc' is not one of: ('yes', 'y', 'true', 'x', 'no', 'n', 'false') and it is not a 1 or 2 character value." + assert expected_msg in a.errors[0].message + # Context of the test file + """ + about_resource: . + name: test + attribute: abc + """ + assert a.attribute.value is None + def test_About_contains_about_file_path(self): test_file = get_test_loc('test_model/serialize/about.ABOUT') # TODO: I am not sure this override of the about_file_path makes sense diff --git a/tests/testdata/test_model/parse/boolean_chara_data.about b/tests/testdata/test_model/parse/boolean_chara_data.about new file mode 100644 index 00000000..f1b0bac5 --- /dev/null +++ b/tests/testdata/test_model/parse/boolean_chara_data.about @@ -0,0 +1,3 @@ +about_resource: . +name: data +attribute: 11 diff --git a/tests/testdata/test_model/parse/boolean_more_than_2_chara_data.about b/tests/testdata/test_model/parse/boolean_more_than_2_chara_data.about new file mode 100644 index 00000000..4ccf4619 --- /dev/null +++ b/tests/testdata/test_model/parse/boolean_more_than_2_chara_data.about @@ -0,0 +1,3 @@ +about_resource: . +name: test +attribute: abc