From fd7ba0a189ea310df77853d37460ac3ea4e57f31 Mon Sep 17 00:00:00 2001 From: Helio Chissini de Castro Date: Mon, 10 Nov 2025 15:04:21 +0100 Subject: [PATCH] fix(hashalgo): Use same aliases as Ort Kotlin upstream Signed-off-by: Helio Chissini de Castro --- pyproject.toml | 2 +- src/ort/models/hash_algorithm.py | 135 ++++++++++++++++++++----- tests/data/example_curations.yml | 2 +- tests/data/example_simple_curation.yml | 42 ++++---- uv.lock | 2 +- 5 files changed, 131 insertions(+), 52 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d094830..5979002 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "uv_build" [project] name = "python-ort" -version = "0.4.0" +version = "0.4.1" description = "A Python Ort model serialization library" readme = "README.md" license = "MIT" diff --git a/src/ort/models/hash_algorithm.py b/src/ort/models/hash_algorithm.py index c51464c..6b85b0c 100644 --- a/src/ort/models/hash_algorithm.py +++ b/src/ort/models/hash_algorithm.py @@ -1,37 +1,116 @@ # SPDX-FileCopyrightText: 2025 Helio Chissini de Castro -# # SPDX-License-Identifier: MIT +from typing import ClassVar -from enum import Enum +from pydantic import BaseModel, Field, model_validator -class HashAlgorithm(Enum): +class HashAlgorithm(BaseModel): """ - An enum of supported hash algorithms. Each algorithm has one or more [aliases] associated to it, - where the first alias is the definite name. - - Attributes: - NONE: No hash algorithm. - UNKNOWN: An unknown hash algorithm. - MD5: The Message-Digest 5 hash algorithm, see [MD5](http://en.wikipedia.org/wiki/MD5). - SHA1: The Secure Hash Algorithm 1, see [SHA-1](https://en.wikipedia.org/wiki/SHA-1). - SHA256: The Secure Hash Algorithm 2 with 256 bits, see [SHA-256](https://en.wikipedia.org/wiki/SHA-256). - SHA384: The Secure Hash Algorithm 2 with 384 bits, see [SHA-384](https://en.wikipedia.org/wiki/SHA-384). - SHA512: The Secure Hash Algorithm 2 with 512 bits, see [SHA-512](https://en.wikipedia.org/wiki/SHA-512). - SHA1GIT: The Secure Hash Algorithm 1, but calculated on a Git "blob" object, see - - https://git-scm.com/book/en/v2/Git-Internals-Git-Objects#_object_storage - - https://docs.softwareheritage.org/devel/swh-model/persistent-identifiers.html#git-compatibility + A Python port of the Kotlin HashAlgorithm enum class. + + Each algorithm has one or more aliases, an empty hash value, + and an 'is_verifiable' flag. """ - NONE = "NONE" - UNKNOWN = "UNKNOWN" - MD5 = "MD5" - SHA1 = "SHA1" - SHA256 = "SHA256" - SHA384 = "SHA384" - SHA512 = "SHA512" - SHA1GIT = ( - ["SHA-1-GIT", "SHA1-GIT", "SHA1GIT", "SWHID"], - "e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", - ) + aliases: list[str] = Field(default_factory=list) + empty_value: str = "" + is_verifiable: bool = True + + # ---- known algorithms ---- + NONE: ClassVar["HashAlgorithm"] + UNKNOWN: ClassVar["HashAlgorithm"] + MD5: ClassVar["HashAlgorithm"] + SHA1: ClassVar["HashAlgorithm"] + SHA256: ClassVar["HashAlgorithm"] + SHA384: ClassVar["HashAlgorithm"] + SHA512: ClassVar["HashAlgorithm"] + SHA1GIT: ClassVar["HashAlgorithm"] + + # ---- derived property ---- + @property + def size(self) -> int: + """The length of the empty hash string for this algorithm.""" + return len(self.empty_value) + + # ---- validation ---- + @model_validator(mode="before") + @classmethod + def _from_alias(cls, value): + """Allow initialization from alias string.""" + if isinstance(value, str): + algo = cls.from_string(value) + return algo.model_dump() + return value + + # ---- class methods ---- + @classmethod + def from_string(cls, alias: str) -> "HashAlgorithm": + """Find a HashAlgorithm by alias name (case-insensitive).""" + alias_upper = alias.upper() + for algo in cls._entries(): + if any(a.upper() == alias_upper for a in algo.aliases): + return algo + return cls.UNKNOWN + + @classmethod + def create(cls, value: str) -> "HashAlgorithm": + """ + Create a HashAlgorithm from a hash value string, based on its length. + Returns NONE if value is blank, UNKNOWN otherwise. + """ + if not value.strip(): + return cls.NONE + for algo in cls._entries(): + if len(value) == algo.size: + return algo + return cls.UNKNOWN + + @classmethod + def _entries(cls) -> list["HashAlgorithm"]: + """Return the list of all defined algorithms.""" + return [ + cls.NONE, + cls.UNKNOWN, + cls.MD5, + cls.SHA1, + cls.SHA256, + cls.SHA384, + cls.SHA512, + cls.SHA1GIT, + ] + + def __str__(self) -> str: + return self.aliases[0] if self.aliases else "" + + +HashAlgorithm.NONE = HashAlgorithm(aliases=[""], empty_value="", is_verifiable=False) +HashAlgorithm.UNKNOWN = HashAlgorithm(aliases=["UNKNOWN"], empty_value="", is_verifiable=False) +HashAlgorithm.MD5 = HashAlgorithm( + aliases=["MD5"], + empty_value="d41d8cd98f00b204e9800998ecf8427e", +) +HashAlgorithm.SHA1 = HashAlgorithm( + aliases=["SHA-1", "SHA1"], + empty_value="da39a3ee5e6b4b0d3255bfef95601890afd80709", +) +HashAlgorithm.SHA256 = HashAlgorithm( + aliases=["SHA-256", "SHA256"], + empty_value="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", +) +HashAlgorithm.SHA384 = HashAlgorithm( + aliases=["SHA-384", "SHA384"], + empty_value=("38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"), +) +HashAlgorithm.SHA512 = HashAlgorithm( + aliases=["SHA-512", "SHA512"], + empty_value=( + "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce" + "47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" + ), +) +HashAlgorithm.SHA1GIT = HashAlgorithm( + aliases=["SHA-1-GIT", "SHA1-GIT", "SHA1GIT", "SWHID"], + empty_value="e69de29bb2d1d6434b8b29ae775ad8c2e48c5391", +) diff --git a/tests/data/example_curations.yml b/tests/data/example_curations.yml index e5c4290..6cc8470 100644 --- a/tests/data/example_curations.yml +++ b/tests/data/example_curations.yml @@ -31,7 +31,7 @@ curations: comment: 'Repository moved to https://gitlab.ow2.org.' vcs: - type: 'Giot' + type: 'Git' url: 'https://gitlab.ow2.org/asm/asm.git' - id: 'NPM::ast-traverse:0.1.0' diff --git a/tests/data/example_simple_curation.yml b/tests/data/example_simple_curation.yml index 48896d8..533b892 100644 --- a/tests/data/example_simple_curation.yml +++ b/tests/data/example_simple_curation.yml @@ -1,33 +1,33 @@ -- id: "Maven:com.example.app:example:0.0.1" +- id: 'Maven:com.example.app:example:0.0.1' curations: - comment: "An explanation why the curation is needed or the reasoning for a license conclusion" - purl: "pkg:Maven/com.example.app/example@0.0.1?arch=arm64-v8a#src/main" + comment: 'An explanation why the curation is needed or the reasoning for a license conclusion' + purl: 'pkg:Maven/com.example.app/example@0.0.1?arch=arm64-v8a#src/main' authors: - - "Name of one author" - - "Name of another author" - cpe: "cpe:2.3:a:example-org:example-package:0.0.1:*:*:*:*:*:*:*" - concluded_license: "Valid SPDX license expression to override the license findings." + - 'Name of one author' + - 'Name of another author' + cpe: 'cpe:2.3:a:example-org:example-package:0.0.1:*:*:*:*:*:*:*' + concluded_license: 'Valid SPDX license expression to override the license findings.' declared_license_mapping: - "license a": "Apache-2.0" - description: "Curated description." - homepage_url: "http://example.com" + 'license a': 'Apache-2.0' + description: 'Curated description.' + homepage_url: 'http://example.com' binary_artifact: - url: "http://example.com/binary.zip" + url: 'http://example.com/binary.zip' hash: - value: "ddce269a1e3d054cae349621c198dd52" - algorithm: "MD5" + value: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + algorithm: 'SHA256' source_artifact: - url: "http://example.com/sources.zip" + url: 'http://example.com/sources.zip' hash: - value: "ddce269a1e3d054cae349621c198dd52" - algorithm: "MD5" + value: 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + algorithm: 'SHA-256' vcs: - type: "Git" - url: "http://example.com/repo.git" - revision: "1234abc" - path: "subdirectory" + type: 'Git' + url: 'http://example.com/repo.git' + revision: '1234abc' + path: 'subdirectory' is_metadata_only: true is_modified: true source_code_origins: [ARTIFACT, VCS] labels: - my-key: "my-value" + my-key: 'my-value' diff --git a/uv.lock b/uv.lock index 6f6fe87..09c54ea 100644 --- a/uv.lock +++ b/uv.lock @@ -644,7 +644,7 @@ wheels = [ [[package]] name = "python-ort" -version = "0.4.0" +version = "0.4.1" source = { editable = "." } dependencies = [ { name = "pydantic" },