diff --git a/karapace/dependency.py b/karapace/dependency.py index 074263af7..a289e1f25 100644 --- a/karapace/dependency.py +++ b/karapace/dependency.py @@ -45,7 +45,7 @@ def to_dict(self) -> JsonData: return { "name": self.name, "subject": self.subject, - "version": self.version, + "version": self.version.value, } def identifier(self) -> str: diff --git a/karapace/in_memory_database.py b/karapace/in_memory_database.py index 81bf474d4..01e0153c1 100644 --- a/karapace/in_memory_database.py +++ b/karapace/in_memory_database.py @@ -101,7 +101,7 @@ def _get_from_hash_cache(self, *, typed_schema: TypedSchema) -> TypedSchema: return self._hash_to_schema.setdefault(typed_schema.fingerprint(), typed_schema) def get_next_version(self, *, subject: Subject) -> Version: - return Versioner.V(max(self.subjects[subject].schemas) + 1) + return Versioner.V(max(self.subjects[subject].schemas).value + 1) def insert_schema_version( self, @@ -224,7 +224,7 @@ def find_subject_schemas(self, *, subject: Subject, include_deleted: bool) -> di return self.subjects[subject].schemas with self.schema_lock_thread: return { - Versioner.V(version_id): schema_version + version_id: schema_version for version_id, schema_version in self.subjects[subject].schemas.items() if schema_version.deleted is False } diff --git a/karapace/schema_reader.py b/karapace/schema_reader.py index f591ac3a5..0e81253d0 100644 --- a/karapace/schema_reader.py +++ b/karapace/schema_reader.py @@ -471,7 +471,7 @@ def _handle_msg_delete_subject(self, key: dict, value: dict | None) -> None: # return subject = value["subject"] - version = value["version"] + version = Version(value["version"]) if self.database.find_subject(subject=subject) is None: LOG.warning("Subject: %r did not exist, should have", subject) else: @@ -479,7 +479,7 @@ def _handle_msg_delete_subject(self, key: dict, value: dict | None) -> None: # self.database.delete_subject(subject=subject, version=version) def _handle_msg_schema_hard_delete(self, key: dict) -> None: - subject, version = key["subject"], key["version"] + subject, version = key["subject"], Version(key["version"]) if self.database.find_subject(subject=subject) is None: LOG.warning("Hard delete: Subject %s did not exist, should have", subject) @@ -501,7 +501,7 @@ def _handle_msg_schema(self, key: dict, value: dict | None) -> None: schema_str = value["schema"] schema_subject = value["subject"] schema_id = value["id"] - schema_version = value["version"] + schema_version = Version(value["version"]) schema_deleted = value.get("deleted", False) schema_references = value.get("references", None) resolved_references: list[Reference] | None = None diff --git a/karapace/schema_references.py b/karapace/schema_references.py index 9973b0ccb..0eae47141 100644 --- a/karapace/schema_references.py +++ b/karapace/schema_references.py @@ -60,7 +60,7 @@ def to_dict(self) -> JsonData: return { "name": self.name, "subject": self.subject, - "version": self.version, + "version": self.version.value, } @staticmethod diff --git a/karapace/schema_registry.py b/karapace/schema_registry.py index 2c6d46bfd..e4d24db2e 100644 --- a/karapace/schema_registry.py +++ b/karapace/schema_registry.py @@ -248,7 +248,7 @@ def subject_version_get(self, subject: Subject, version: Version, *, include_del ret: JsonObject = { "subject": subject, - "version": resolved_version, + "version": resolved_version.value, "id": schema_id, "schema": schema.schema_str, } @@ -427,11 +427,11 @@ def send_schema_message( deleted: bool, references: Sequence[Reference] | None, ) -> None: - key = {"subject": subject, "version": version, "magic": 1, "keytype": "SCHEMA"} + key = {"subject": subject, "version": version.value, "magic": 1, "keytype": "SCHEMA"} if schema: value = { "subject": subject, - "version": version, + "version": version.value, "id": schema_id, "schema": str(schema), "deleted": deleted, @@ -461,5 +461,5 @@ def resolve_references( def send_delete_subject_message(self, subject: Subject, version: Version) -> None: key = {"subject": subject, "magic": 0, "keytype": "DELETE_SUBJECT"} - value = {"subject": subject, "version": version} + value = {"subject": subject, "version": version.value} self.producer.send_message(key=key, value=value) diff --git a/karapace/schema_registry_apis.py b/karapace/schema_registry_apis.py index b1515ab8d..4ef7b884a 100644 --- a/karapace/schema_registry_apis.py +++ b/karapace/schema_registry_apis.py @@ -466,7 +466,7 @@ async def schemas_list(self, content_type: str, *, request: HTTPRequest, user: U for schema_version in schema_versions: response_schema = { "subject": schema_version.subject, - "version": schema_version.version, + "version": schema_version.version.value, "id": schema_version.schema_id, "schemaType": schema_version.schema.schema_type, } @@ -571,7 +571,12 @@ async def schemas_get_versions( subject = subject_version["subject"] if self._auth and not self._auth.check_authorization(user, Operation.Read, f"Subject:{subject}"): continue - subject_versions.append(subject_version) + subject_versions.append( + { + "subject": subject_version["subject"], + "version": subject_version["version"].value, + } + ) self.r(subject_versions, content_type) async def schemas_types(self, content_type: str) -> None: @@ -733,7 +738,7 @@ async def subject_delete( if are_we_master: try: version_list = await self.schema_registry.subject_delete_local(subject=subject, permanent=permanent) - self.r(version_list, content_type, status=HTTPStatus.OK) + self.r([version.value for version in version_list], content_type, status=HTTPStatus.OK) except (SubjectNotFoundException, SchemasNotFoundException): self.r( body={ @@ -951,7 +956,8 @@ async def subject_versions_list( deleted = request.query.get("deleted", "false").lower() == "true" try: schema_versions = self.schema_registry.subject_get(subject, include_deleted=deleted) - self.r(list(schema_versions), content_type, status=HTTPStatus.OK) + version_list = [version.value for version in schema_versions] + self.r(version_list, content_type, status=HTTPStatus.OK) except (SubjectNotFoundException, SchemasNotFoundException): self.r( body={ @@ -1170,7 +1176,7 @@ async def subjects_schema_post( if parsed_typed_schema.schema_type == new_schema.schema_type and schema_valid: ret = { "subject": subject, - "version": schema_version.version, + "version": schema_version.version.value, "id": schema_version.schema_id, "schema": parsed_typed_schema.schema_str, } diff --git a/karapace/typing.py b/karapace/typing.py index 40b29fa2d..02b14d4c1 100644 --- a/karapace/typing.py +++ b/karapace/typing.py @@ -9,6 +9,8 @@ from typing import ClassVar, Dict, List, Mapping, NewType, Sequence, Union from typing_extensions import TypeAlias +import functools + JsonArray: TypeAlias = List["JsonData"] JsonObject: TypeAlias = Dict[str, "JsonData"] JsonScalar: TypeAlias = Union[str, int, float, None] @@ -59,23 +61,41 @@ class Mode(StrEnum): readwrite = "READWRITE" -class Version(int): +@functools.total_ordering +class Version: LATEST_VERSION_TAG: ClassVar[str] = "latest" MINUS_1_VERSION_TAG: ClassVar[int] = -1 - def __new__(cls, version: int) -> Version: + def __init__(self, version: int) -> None: if not isinstance(version, int): raise InvalidVersion(f"Invalid version {version}") - if (version < cls.MINUS_1_VERSION_TAG) or (version == 0): + if (version < Version.MINUS_1_VERSION_TAG) or (version == 0): raise InvalidVersion(f"Invalid version {version}") - return super().__new__(cls, version) + self._value = version def __str__(self) -> str: - return f"{int(self)}" + return f"{int(self._value)}" def __repr__(self) -> str: - return f"Version={int(self)}" + return f"Version({int(self._value)})" + + def __lt__(self, other: object) -> bool: + if isinstance(other, Version): + return self._value < other.value + return NotImplemented + + def __eq__(self, other: object) -> bool: + if isinstance(other, Version): + return self._value == other.value + return NotImplemented + + def __hash__(self) -> int: + return hash(self._value) + + @property + def value(self) -> int: + return self._value @property def is_latest(self) -> bool: - return self == self.MINUS_1_VERSION_TAG + return self.value == self.MINUS_1_VERSION_TAG diff --git a/tests/unit/test_schema_models.py b/tests/unit/test_schema_models.py index fc1590da9..9b27d27a0 100644 --- a/tests/unit/test_schema_models.py +++ b/tests/unit/test_schema_models.py @@ -25,9 +25,9 @@ def version(self): return Versioner.V(1) def test_version(self, version: Version): - assert version == 1 + assert version == Version(1) assert isinstance(version, Version) - assert issubclass(Version, int) + assert isinstance(version.value, int) def test_tags(self, version: Version): assert version.LATEST_VERSION_TAG == "latest" @@ -47,7 +47,7 @@ def test_is_latest(self, version: Version, is_latest: bool): def test_text_formating(self, version: Version): assert f"{version}" == "1" - assert f"{version!r}" == "Version=1" + assert f"{version!r}" == "Version(1)" @pytest.mark.parametrize( "version, to_compare, comparer, valid",