Skip to content

Commit

Permalink
Merge branch '543_support_numeric_value_for_attribute_field' into dev…
Browse files Browse the repository at this point in the history
…elop
  • Loading branch information
chinyeungli committed Oct 19, 2023
2 parents 5df302d + 7c88fba commit f9ff6a9
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 3 deletions.
9 changes: 7 additions & 2 deletions docs/source/specification.rst
Original file line number Diff line number Diff line change
Expand Up @@ -352,14 +352,19 @@ 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 Character fields
-------------------------------------

- attribute: This field can be either in boolean value: ('yes', 'y', 'true',
'x', 'no', 'n', 'false') or a character value field with no more than 2
characters. Defaults to no when absent.

Optional Extension fields
-------------------------

Expand Down
101 changes: 100 additions & 1 deletion src/attributecode/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,105 @@ def __eq__(self, other):
and self.value == other.value)


class BooleanAndTwoCharactersField(SingleLineField):
"""
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):
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 character value.
Default flag to False. Return a list of errors.
"""
errors = super(BooleanAndTwoCharactersField,
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 1 or 2 character 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 or len(value) <= 2:
if value in self.true_flags:
return u'yes'
elif value in self.false_flags:
return u'no'
else:
return value
else:
return False
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):
"""
Expand Down Expand Up @@ -810,7 +909,7 @@ def set_standard_fields(self):
('notice_url', UrlField()),

('redistribute', BooleanField()),
('attribute', BooleanField()),
('attribute', BooleanAndTwoCharactersField()),
('track_changes', BooleanField()),
('modified', BooleanField()),
('internal_use_only', BooleanField()),
Expand Down
47 changes: 47 additions & 0 deletions tests/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,53 @@ def test_About_boolean_value(self):
assert a.redistribute.value is True
assert a.track_changes.value is None

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."
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_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
Expand Down
3 changes: 3 additions & 0 deletions tests/testdata/test_model/parse/boolean_chara_data.about
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
about_resource: .
name: data
attribute: 11
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
about_resource: .
name: test
attribute: abc
7 changes: 7 additions & 0 deletions tests/testdata/test_model/parse/boolean_numeric_data.about
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
about_resource: .
name: boolean_data
attribute: 3
modified: true
internal_use_only: no
redistribute: yes
track_changes:

0 comments on commit f9ff6a9

Please sign in to comment.