Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
135 changes: 107 additions & 28 deletions src/ort/models/hash_algorithm.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,116 @@
# SPDX-FileCopyrightText: 2025 Helio Chissini de Castro <heliocastro@gmail.com>
#
# 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",
)
2 changes: 1 addition & 1 deletion tests/data/example_curations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
42 changes: 21 additions & 21 deletions tests/data/example_simple_curation.yml
Original file line number Diff line number Diff line change
@@ -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'
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading