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 @@ -239,6 +239,7 @@ return builds( dict, data=binary(max_size=4096), + ctime=optional(datetimes()), status=one_of(just('visible'), just('hidden')), ) @@ -258,6 +259,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 @@ -554,12 +554,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: @@ -628,7 +622,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 @@ -636,10 +630,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': @@ -710,7 +708,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 @@ -723,6 +725,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,11 +18,7 @@ MissingData, Person ) from swh.model.hashutil import hash_to_bytes, MultiHash - -from swh.model.hypothesis_strategies import ( - objects, origins, origin_visits, origin_visit_updates, - skipped_contents_d, timestamps -) +import swh.model.hypothesis_strategies as strategies from swh.model.identifiers import ( directory_identifier, revision_identifier, release_identifier, snapshot_identifier @@ -32,7 +28,7 @@ ) -@given(objects()) +@given(strategies.objects()) def test_todict_inverse_fromdict(objtype_and_obj): (obj_type, obj) = objtype_and_obj @@ -52,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() @@ -60,14 +56,14 @@ 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() assert origin_visit == type(origin_visit).from_dict(obj) -@given(origin_visit_updates()) +@given(strategies.origin_visit_updates()) def test_todict_origin_visit_updates(origin_visit_update): obj = origin_visit_update.to_dict() @@ -76,7 +72,7 @@ # Timestamp -@given(timestamps()) +@given(strategies.timestamps()) def test_timestamps_strategy(timestamp): attr.validate(timestamp) @@ -326,6 +322,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') @@ -356,6 +354,33 @@ 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 + + +def test_content_from_dict_str_ctime(): + # test with ctime as a string + n = datetime.datetime(2020, 5, 6, 12, 34) + content_d = { + 'ctime': n.isoformat(), + 'data': b'', + 'length': 0, + 'sha1': b'\x00', + 'sha256': b'\x00', + 'sha1_git': b'\x00', + 'blake2s256': b'\x00', + } + c = Content.from_dict(content_d) + assert c.ctime == n + + @given(binary(max_size=4096)) def test_content_from_data(data): c = Content.from_data(data) @@ -376,6 +401,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') @@ -386,7 +413,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)