Skip to content

Commit f9ff6a9

Browse files
committed
Merge branch '543_support_numeric_value_for_attribute_field' into develop
2 parents 5df302d + 7c88fba commit f9ff6a9

File tree

6 files changed

+167
-3
lines changed

6 files changed

+167
-3
lines changed

docs/source/specification.rst

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,14 +352,19 @@ Optional Boolean flag fields
352352

353353
- redistribute: Set this flag to yes if the component license requires source code
354354
redistribution. Defaults to no when absent.
355-
- attribute: Set this flag to yes if the component license requires publishing an attribution
356-
or credit notice. Defaults to no when absent.
357355
- track_changes: Set this flag to yes if the component license requires tracking changes made to
358356
a the component. Defaults to no when absent.
359357
- modified: Set this flag to yes if the component has been modified. Defaults to no when absent.
360358
- internal_use_only: Set this flag to yes if the component is used internal only.
361359
Defaults to no when absent.
362360

361+
Optional Boolean and Character fields
362+
-------------------------------------
363+
364+
- attribute: This field can be either in boolean value: ('yes', 'y', 'true',
365+
'x', 'no', 'n', 'false') or a character value field with no more than 2
366+
characters. Defaults to no when absent.
367+
363368
Optional Extension fields
364369
-------------------------
365370

src/attributecode/model.py

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,105 @@ def __eq__(self, other):
720720
and self.value == other.value)
721721

722722

723+
class BooleanAndTwoCharactersField(SingleLineField):
724+
"""
725+
Field with either a boolean value or character(s) value (at most 2
726+
characters). Validated value is False, True, None or character value.
727+
"""
728+
729+
def default_value(self):
730+
return None
731+
732+
true_flags = ('yes', 'y', 'true', 'x')
733+
false_flags = ('no', 'n', 'false')
734+
flag_values = true_flags + false_flags
735+
736+
def _validate(self, *args, **kwargs):
737+
"""
738+
Check that flag are valid with either boolean value or character value.
739+
Default flag to False. Return a list of errors.
740+
"""
741+
errors = super(BooleanAndTwoCharactersField,
742+
self)._validate(*args, ** kwargs)
743+
self.about_file_path = kwargs.get('about_file_path')
744+
flag = self.get_value(self.original_value)
745+
if flag is False:
746+
name = self.name
747+
val = self.original_value
748+
about_file_path = self.about_file_path
749+
flag_values = self.flag_values
750+
msg = (u'Path: %(about_file_path)s - Field %(name)s: Invalid value: %(val)r is not '
751+
u'one of: %(flag_values)s and it is not a 1 or 2 character value.' % locals())
752+
errors.append(Error(ERROR, msg))
753+
self.value = None
754+
elif flag is None:
755+
name = self.name
756+
msg = (u'Field %(name)s: field is present but empty. ' % locals())
757+
errors.append(Error(INFO, msg))
758+
self.value = None
759+
else:
760+
if flag == u'yes' or flag is True:
761+
self.value = True
762+
elif flag == u'no':
763+
self.value = False
764+
else:
765+
self.value = flag
766+
return errors
767+
768+
def get_value(self, value):
769+
"""
770+
Return a normalized existing value if found in the list of
771+
possible values or None if empty or False if not found or original value
772+
if it is not a boolean value
773+
"""
774+
if value is None or value == '':
775+
return None
776+
777+
if isinstance(value, bool):
778+
return value
779+
else:
780+
if isinstance(value, str):
781+
value = value.strip()
782+
if not value:
783+
return None
784+
785+
value = value.lower()
786+
if value in self.flag_values or len(value) <= 2:
787+
if value in self.true_flags:
788+
return u'yes'
789+
elif value in self.false_flags:
790+
return u'no'
791+
else:
792+
return value
793+
else:
794+
return False
795+
else:
796+
return False
797+
798+
@property
799+
def has_content(self):
800+
"""
801+
Return true if it has content regardless of what value, False otherwise
802+
"""
803+
if self.original_value:
804+
return True
805+
return False
806+
807+
def _serialized_value(self):
808+
# default normalized values for serialization
809+
if self.value:
810+
if isinstance(self.value, bool):
811+
return u'yes'
812+
else:
813+
return self.value
814+
elif self.value is False:
815+
return u'no'
816+
else:
817+
# self.value is None
818+
# TODO: should we serialize to No for None???
819+
return u''
820+
821+
723822
def validate_fields(fields, about_file_path, running_inventory, base_dir,
724823
reference_dir=None):
725824
"""
@@ -810,7 +909,7 @@ def set_standard_fields(self):
810909
('notice_url', UrlField()),
811910

812911
('redistribute', BooleanField()),
813-
('attribute', BooleanField()),
912+
('attribute', BooleanAndTwoCharactersField()),
814913
('track_changes', BooleanField()),
815914
('modified', BooleanField()),
816915
('internal_use_only', BooleanField()),

tests/test_model.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,53 @@ def test_About_boolean_value(self):
648648
assert a.redistribute.value is True
649649
assert a.track_changes.value is None
650650

651+
def test_About_boolean_numberic_value(self):
652+
test_file = get_test_loc('test_model/parse/boolean_numeric_data.about')
653+
a = model.About(test_file)
654+
expected_msg = "Field track_changes is present but empty."
655+
assert expected_msg in a.errors[0].message
656+
# Context of the test file
657+
"""
658+
about_resource: .
659+
name: boolean_data
660+
attribute: 3
661+
modified: true
662+
internal_use_only: no
663+
redistribute: yes
664+
track_changes:
665+
"""
666+
assert a.attribute.value == '3'
667+
assert a.modified.value is True
668+
assert a.internal_use_only.value is False
669+
assert a.redistribute.value is True
670+
assert a.track_changes.value is None
671+
672+
def test_About_boolean_character_value(self):
673+
test_file = get_test_loc('test_model/parse/boolean_chara_data.about')
674+
a = model.About(test_file)
675+
# Context of the test file
676+
"""
677+
about_resource: .
678+
name: data
679+
attribute: 11
680+
"""
681+
assert a.attribute.value == '11'
682+
assert len(a.errors) == 0
683+
684+
def test_About_boolean_more_than_2_character_value(self):
685+
test_file = get_test_loc(
686+
'test_model/parse/boolean_more_than_2_chara_data.about')
687+
a = model.About(test_file)
688+
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."
689+
assert expected_msg in a.errors[0].message
690+
# Context of the test file
691+
"""
692+
about_resource: .
693+
name: test
694+
attribute: abc
695+
"""
696+
assert a.attribute.value is None
697+
651698
def test_About_contains_about_file_path(self):
652699
test_file = get_test_loc('test_model/serialize/about.ABOUT')
653700
# TODO: I am not sure this override of the about_file_path makes sense
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
about_resource: .
2+
name: data
3+
attribute: 11
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
about_resource: .
2+
name: test
3+
attribute: abc
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
about_resource: .
2+
name: boolean_data
3+
attribute: 3
4+
modified: true
5+
internal_use_only: no
6+
redistribute: yes
7+
track_changes:

0 commit comments

Comments
 (0)