diff --git a/swh/model/hypothesis_strategies.py b/swh/model/hypothesis_strategies.py --- a/swh/model/hypothesis_strategies.py +++ b/swh/model/hypothesis_strategies.py @@ -221,6 +221,7 @@ return builds( dict, data=binary(max_size=4096), + ctime=optional(datetimes()), status=one_of(just('visible'), just('hidden')), ) @@ -240,6 +241,7 @@ result[k] = None result['reason'] = draw(pgsql_text()) result['status'] = 'absent' + result['ctime'] = draw(optional(datetimes())) return result diff --git a/swh/model/model.py b/swh/model/model.py --- a/swh/model/model.py +++ b/swh/model/model.py @@ -527,12 +527,6 @@ return d - def to_dict(self): - content = super().to_dict() - if content['ctime'] is None: - del content['ctime'] - return content - @classmethod def from_dict(cls, d, use_subclass=True): if use_subclass: @@ -601,7 +595,7 @@ return content @classmethod - def from_data(cls, data, status='visible') -> 'Content': + def from_data(cls, data, status='visible', ctime=None) -> 'Content': """Generate a Content from a given `data` byte string. This populates the Content with the hashes and length for the data @@ -609,10 +603,14 @@ """ d = cls._hash_data(data) d['status'] = status + d['ctime'] = ctime return cls(**d) @classmethod def from_dict(cls, d): + if isinstance(d.get('ctime'), str): + d = d.copy() + d['ctime'] = dateutil.parser.parse(d['ctime']) return super().from_dict(d, use_subclass=False) def with_data(self) -> 'Content': @@ -683,7 +681,11 @@ return content @classmethod - def from_data(cls, data, reason: str) -> 'SkippedContent': + def from_data( + cls, + data: bytes, + reason: str, + ctime: Optional[datetime.datetime] = None) -> 'SkippedContent': """Generate a SkippedContent from a given `data` byte string. This populates the SkippedContent with the hashes and length for the @@ -696,6 +698,7 @@ del d['data'] d['status'] = 'absent' d['reason'] = reason + d['ctime'] = ctime return cls(**d) @classmethod diff --git a/swh/model/tests/test_hypothesis_strategies.py b/swh/model/tests/test_hypothesis_strategies.py --- a/swh/model/tests/test_hypothesis_strategies.py +++ b/swh/model/tests/test_hypothesis_strategies.py @@ -44,15 +44,13 @@ (obj_type, object_) = obj_type_and_obj assert_nested_dict(object_) if obj_type == 'content': + COMMON_KEYS = set(DEFAULT_ALGORITHMS) | {'length', 'status', 'ctime'} if object_['status'] == 'visible': - assert set(object_) <= \ - set(DEFAULT_ALGORITHMS) | {'length', 'status', 'data'} + assert set(object_) <= COMMON_KEYS | {'data'} elif object_['status'] == 'absent': - assert set(object_) == \ - set(DEFAULT_ALGORITHMS) | {'length', 'status', 'reason'} + assert set(object_) == COMMON_KEYS | {'reason'} elif object_['status'] == 'hidden': - assert set(object_) <= \ - set(DEFAULT_ALGORITHMS) | {'length', 'status', 'data'} + assert set(object_) <= COMMON_KEYS | {'data'} else: assert False, object_ elif obj_type == 'release': @@ -68,15 +66,13 @@ obj_dict = object_.to_dict() assert_nested_dict(obj_dict) if obj_type == 'content': + COMMON_KEYS = set(DEFAULT_ALGORITHMS) | {'length', 'status', 'ctime'} if obj_dict['status'] == 'visible': - assert set(obj_dict) == \ - set(DEFAULT_ALGORITHMS) | {'length', 'status', 'data'} + assert set(obj_dict) == COMMON_KEYS | {'data'} elif obj_dict['status'] == 'absent': - assert set(obj_dict) == \ - set(DEFAULT_ALGORITHMS) | {'length', 'status', 'reason'} + assert set(obj_dict) == COMMON_KEYS | {'reason'} elif obj_dict['status'] == 'hidden': - assert set(obj_dict) == \ - set(DEFAULT_ALGORITHMS) | {'length', 'status', 'data'} + assert set(obj_dict) == COMMON_KEYS | {'data'} else: assert False, obj_dict elif obj_type == 'release': diff --git a/swh/model/tests/test_model.py b/swh/model/tests/test_model.py --- a/swh/model/tests/test_model.py +++ b/swh/model/tests/test_model.py @@ -18,8 +18,7 @@ MissingData, Person ) from swh.model.hashutil import hash_to_bytes, MultiHash -from swh.model.hypothesis_strategies import timestamps, skipped_contents_d -from swh.model.hypothesis_strategies import objects, origins, origin_visits +import swh.model.hypothesis_strategies as strategies from swh.model.identifiers import ( directory_identifier, revision_identifier, release_identifier, snapshot_identifier @@ -29,7 +28,7 @@ ) -@given(objects()) +@given(strategies.objects()) def test_todict_inverse_fromdict(objtype_and_obj): (obj_type, obj) = objtype_and_obj @@ -49,7 +48,7 @@ assert obj_as_dict == type(obj).from_dict(obj_as_dict).to_dict() -@given(origins()) +@given(strategies.origins()) def test_todict_origins(origin): obj = origin.to_dict() @@ -57,7 +56,7 @@ assert type(origin)(url=origin.url) == type(origin).from_dict(obj) -@given(origin_visits()) +@given(strategies.origin_visits()) def test_todict_origin_visits(origin_visit): obj = origin_visit.to_dict() @@ -66,7 +65,7 @@ # Timestamp -@given(timestamps()) +@given(strategies.timestamps()) def test_timestamps_strategy(timestamp): attr.validate(timestamp) @@ -316,6 +315,8 @@ assert expected_person == Person.from_fullname(person) +# Content + def test_content_get_hash(): hashes = dict( sha1=b'foo', sha1_git=b'bar', sha256=b'baz', blake2s256=b'qux') @@ -346,6 +347,17 @@ c.with_data() +@given(strategies.present_contents_d()) +def test_content_from_dict(content_d): + c = Content.from_data(**content_d) + assert c + assert c.ctime == content_d['ctime'] + + content_d2 = c.to_dict() + c2 = Content.from_dict(content_d2) + assert c2.ctime == c.ctime + + @given(binary(max_size=4096)) def test_content_from_data(data): c = Content.from_data(data) @@ -366,6 +378,8 @@ assert getattr(c, key) == value +# SkippedContent + @given(binary(max_size=4096)) def test_skipped_content_from_data(data): c = SkippedContent.from_data(data, reason='reason') @@ -376,7 +390,7 @@ assert getattr(c, key) == value -@given(skipped_contents_d()) +@given(strategies.skipped_contents_d()) def test_skipped_content_origin_is_str(skipped_content_d): assert SkippedContent.from_dict(skipped_content_d)