diff --git a/requirements.txt b/requirements.txt --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,10 @@ # Add here external Python modules dependencies, one per line. Module names # should match https://pypi.python.org/pypi names. For the full spec or # dependency lines, see https://pip.readthedocs.org/en/1.1/requirements.html -vcversioner attrs attrs_strict hypothesis -python-dateutil iso8601 +python-dateutil +typing_extensions +vcversioner diff --git a/swh/model/model.py b/swh/model/model.py --- a/swh/model/model.py +++ b/swh/model/model.py @@ -8,6 +8,7 @@ from abc import ABCMeta, abstractmethod from enum import Enum from typing import Dict, List, Optional, Union +from typing_extensions import Final import attr from attrs_strict import type_validator @@ -90,6 +91,8 @@ class Person(BaseModel): """Represents the author/committer of a revision or release.""" + object_type: Final = "person" + fullname = attr.ib(type=bytes, validator=type_validator()) name = attr.ib(type=Optional[bytes], validator=type_validator()) email = attr.ib(type=Optional[bytes], validator=type_validator()) @@ -134,6 +137,8 @@ class Timestamp(BaseModel): """Represents a naive timestamp from a VCS.""" + object_type: Final = "timestamp" + seconds = attr.ib(type=int, validator=type_validator()) microseconds = attr.ib(type=int, validator=type_validator()) @@ -154,6 +159,8 @@ class TimestampWithTimezone(BaseModel): """Represents a TZ-aware timestamp from a VCS.""" + object_type: Final = "timestamp_with_timezone" + timestamp = attr.ib(type=Timestamp, validator=type_validator()) offset = attr.ib(type=int, validator=type_validator()) negative_utc = attr.ib(type=bool, validator=type_validator()) @@ -204,6 +211,8 @@ class Origin(BaseModel): """Represents a software source: a VCS and an URL.""" + object_type: Final = "origin" + url = attr.ib(type=str, validator=type_validator()) @@ -212,6 +221,8 @@ """Represents a visit of an origin at a given point in time, by a SWH loader.""" + object_type: Final = "origin_visit" + origin = attr.ib(type=str, validator=type_validator()) date = attr.ib(type=datetime.datetime, validator=type_validator()) status = attr.ib( @@ -248,6 +259,8 @@ """ + object_type: Final = "origin_visit_status" + origin = attr.ib(type=str, validator=type_validator()) visit = attr.ib(type=int, validator=type_validator()) @@ -287,6 +300,8 @@ class SnapshotBranch(BaseModel): """Represents one of the branches of a snapshot.""" + object_type: Final = "snapshot_branch" + target = attr.ib(type=bytes, validator=type_validator()) target_type = attr.ib(type=TargetType, validator=type_validator()) @@ -307,6 +322,8 @@ class Snapshot(BaseModel, HashableObject): """Represents the full state of an origin at a given point in time.""" + object_type: Final = "snapshot" + branches = attr.ib( type=Dict[bytes, Optional[SnapshotBranch]], validator=type_validator() ) @@ -330,6 +347,8 @@ @attr.s(frozen=True) class Release(BaseModel, HashableObject): + object_type: Final = "release" + name = attr.ib(type=bytes, validator=type_validator()) message = attr.ib(type=Optional[bytes], validator=type_validator()) target = attr.ib(type=Optional[Sha1Git], validator=type_validator()) @@ -380,6 +399,8 @@ @attr.s(frozen=True) class Revision(BaseModel, HashableObject): + object_type: Final = "revision" + message = attr.ib(type=bytes, validator=type_validator()) author = attr.ib(type=Person, validator=type_validator()) committer = attr.ib(type=Person, validator=type_validator()) @@ -425,6 +446,8 @@ @attr.s(frozen=True) class DirectoryEntry(BaseModel): + object_type: Final = "directory_entry" + name = attr.ib(type=bytes, validator=type_validator()) type = attr.ib(type=str, validator=attr.validators.in_(["file", "dir", "rev"])) target = attr.ib(type=Sha1Git, validator=type_validator()) @@ -434,6 +457,8 @@ @attr.s(frozen=True) class Directory(BaseModel, HashableObject): + object_type: Final = "directory" + entries = attr.ib(type=List[DirectoryEntry], validator=type_validator()) id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"") @@ -487,6 +512,8 @@ @attr.s(frozen=True) class Content(BaseContent): + object_type: Final = "content" + sha1 = attr.ib(type=bytes, validator=type_validator()) sha1_git = attr.ib(type=Sha1Git, validator=type_validator()) sha256 = attr.ib(type=bytes, validator=type_validator()) @@ -550,6 +577,8 @@ @attr.s(frozen=True) class SkippedContent(BaseContent): + object_type: Final = "skipped_content" + sha1 = attr.ib(type=Optional[bytes], validator=type_validator()) sha1_git = attr.ib(type=Optional[Sha1Git], validator=type_validator()) sha256 = attr.ib(type=Optional[bytes], validator=type_validator()) 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 @@ -13,6 +13,7 @@ import pytest from swh.model.model import ( + BaseModel, Content, SkippedContent, Directory, @@ -437,3 +438,23 @@ snp_id = hash_to_bytes(snapshot_identifier(snp_dict)) snp_model = Snapshot.from_dict(snp_dict) assert snp_model.id == snp_id + + +@given(strategies.objects(split_content=True)) +def test_object_type(objtype_and_obj): + obj_type, obj = objtype_and_obj + assert obj_type == obj.object_type + + +def test_object_type_is_final(): + object_types = set() + + def check_final(cls): + if not cls.__subclasses__(): + assert cls.object_type not in object_types + object_types.add(cls.object_type) + else: + for subcls in cls.__subclasses__(): + check_final(subcls) + + check_final(BaseModel)