Skip to content

Commit

Permalink
Add support for Python 3.9 (#338)
Browse files Browse the repository at this point in the history
* Add python3.9 markers in pyproject.toml

Signed-off-by: Mihai Maruseac <[email protected]>

* Use `Optional[T]` instead of `T | None`

The union type extension only exists since Python 3.10

Signed-off-by: Mihai Maruseac <[email protected]>

* Use `if` instead of `match`

PEP 636 which introduces structural matching (`match` statement) is
valid only from Python 3.10

Signed-off-by: Mihai Maruseac <[email protected]>

---------

Signed-off-by: Mihai Maruseac <[email protected]>
  • Loading branch information
mihaimaruseac authored Jan 15, 2025
1 parent e97ac69 commit a19b00d
Show file tree
Hide file tree
Showing 11 changed files with 59 additions and 53 deletions.
11 changes: 6 additions & 5 deletions benchmarks/exp_hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,12 @@ def _human_size(size: int) -> str:


def _get_hasher(hash_algorithm: str) -> hashing.StreamingHashEngine:
match hash_algorithm:
case "sha256":
return memory.SHA256()
case "blake2":
return memory.BLAKE2()
# TODO: Once Python 3.9 support is deprecated revert to using `match`
if hash_algorithm == "sha256":
return memory.SHA256()
if hash_algorithm == "blake2":
return memory.BLAKE2()

raise ValueError(f"Cannot convert {hash_algorithm} to a hash engine")


Expand Down
3 changes: 2 additions & 1 deletion benchmarks/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import argparse
import itertools
import pathlib
from typing import Optional

import numpy as np

Expand Down Expand Up @@ -45,7 +46,7 @@ def create_file_of_given_size(path: str, size: int) -> None:


def generate_file_sizes(
total_size: int, count: int, weights: list[int] | None = None
total_size: int, count: int, weights: Optional[list[int]] = None
) -> list[int]:
"""Generate file sizes splitting a total size into multiple files.
Expand Down
22 changes: 12 additions & 10 deletions benchmarks/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ def get_hash_engine_factory(
Raises:
ValueError: if the algorithm is not implemented/not valid.
"""
match hash_algorithm:
case "sha256":
return memory.SHA256
case "blake2":
return memory.BLAKE2
# TODO: Once Python 3.9 support is deprecated revert to using `match`
if hash_algorithm == "sha256":
return memory.SHA256
if hash_algorithm == "blake2":
return memory.BLAKE2

raise ValueError(f"Cannot convert {hash_algorithm} to a hash engine")

Expand Down Expand Up @@ -152,14 +152,16 @@ def run(args: argparse.Namespace) -> None:
if args.single_digest:
in_toto_builder = in_toto.SingleDigestIntotoPayload
else:
match (args.digest_of_digests, args.use_shards):
case (True, True):
# TODO: Once Python 3.9 support is deprecated revert to `match`
if args.digest_of_digests:
if args.use_shards:
in_toto_builder = in_toto.DigestOfShardDigestsIntotoPayload
case (True, False):
else:
in_toto_builder = in_toto.DigestOfDigestsIntotoPayload
case (False, True):
else:
if args.use_shards:
in_toto_builder = in_toto.ShardDigestsIntotoPayload
case (False, False):
else:
in_toto_builder = in_toto.DigestsIntotoPayload

in_toto_builder = in_toto_builder.from_manifest
Expand Down
7 changes: 4 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ classifiers = [
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand All @@ -32,7 +33,7 @@ dependencies = [
"sigstore",
"typing_extensions",
]
requires-python = ">=3.10"
requires-python = ">=3.9"
keywords = [
"machine learning",
"artificial intelligence",
Expand Down Expand Up @@ -64,7 +65,7 @@ randomize = true
extra-args = ["-m", "not integration"]

[[tool.hatch.envs.hatch-test.matrix]]
python = ["3.10", "3.11", "3.12", "3.13"]
python = ["3.9", "3.10", "3.11", "3.12", "3.13"]

[tool.hatch.envs.bench]
description = """Custom environment for running benchmarks.
Expand All @@ -76,7 +77,7 @@ extra-dependencies = [
]

[[tool.hatch.envs.bench.matrix]]
python = ["3.10", "3.11", "3.12", "3.13"]
python = ["3.9", "3.10", "3.11", "3.12", "3.13"]

[tool.hatch.envs.bench.scripts]
generate = "python benchmarks/generate.py {args}"
Expand Down
32 changes: 15 additions & 17 deletions src/model_signing/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import os
import pathlib
import sys
from typing import Literal, cast
from typing import Literal, Optional, cast

from model_signing.hashing import file
from model_signing.hashing import hashing
Expand Down Expand Up @@ -78,7 +78,7 @@ def verify(
signature_path: os.PathLike,
*,
identity: str,
oidc_issuer: str | None = None,
oidc_issuer: Optional[str] = None,
use_staging: bool = False,
):
"""Verifies that a model conforms to a signature.
Expand Down Expand Up @@ -149,15 +149,13 @@ def _build_stream_hasher(
Returns:
An instance of the requested hasher.
"""
match hashing_algorithm:
case "sha256":
return memory.SHA256()
case "blake2":
return memory.BLAKE2()
case _:
raise ValueError(
f"Unsupported hashing method {hashing_algorithm}"
)
# TODO: Once Python 3.9 support is deprecated revert to using `match`
if hashing_algorithm == "sha256":
return memory.SHA256()
if hashing_algorithm == "blake2":
return memory.BLAKE2()

raise ValueError(f"Unsupported hashing method {hashing_algorithm}")

def _build_file_hasher_factory(
self,
Expand Down Expand Up @@ -223,7 +221,7 @@ def set_serialize_by_file_to_manifest(
*,
hashing_algorithm: Literal["sha256", "blake2"] = "sha256",
chunk_size: int = 8192,
max_workers: int | None = None,
max_workers: Optional[int] = None,
allow_symlinks: bool = False,
) -> Self:
"""Configures serialization to a manifest pairing files with hashes.
Expand Down Expand Up @@ -302,7 +300,7 @@ def set_serialize_by_file_shard_to_manifest(
hashing_algorithm: Literal["sha256", "blake2"] = "sha256",
chunk_size: int = 8192,
shard_size: int = 1000000,
max_workers: int | None = None,
max_workers: Optional[int] = None,
allow_symlinks: bool = False,
) -> Self:
"""Configures serialization to a manifest of (file shard, hash) pairs.
Expand Down Expand Up @@ -343,7 +341,7 @@ def set_serialize_by_file_shard_to_digest(
merge_algorithm: Literal["sha256", "blake2"] = "sha256",
chunk_size: int = 8192,
shard_size: int = 1000000,
max_workers: int | None = None,
max_workers: Optional[int] = None,
allow_symlinks: bool = False,
) -> Self:
"""Configures serialization to a single digest, at shard granularity.
Expand Down Expand Up @@ -467,10 +465,10 @@ def set_sigstore_signer(
self,
*,
sign_dsse: bool = True,
oidc_issuer: str | None = None,
oidc_issuer: Optional[str] = None,
use_ambient_credentials: bool = True,
use_staging: bool = False,
identity_token: str | None = None,
identity_token: Optional[str] = None,
) -> Self:
"""Configures the signing to be performed with Sigstore.
Expand Down Expand Up @@ -556,7 +554,7 @@ def set_sigstore_dsse_verifier(
self,
*,
identity: str,
oidc_issuer: str | None = None,
oidc_issuer: Optional[str] = None,
use_staging: bool = False,
) -> Self:
"""Configures the verification of a Sigstore signature over DSSE.
Expand Down
8 changes: 4 additions & 4 deletions src/model_signing/hashing/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
import hashlib
import pathlib
import sys
from typing import BinaryIO
from typing import BinaryIO, Optional

from typing_extensions import override

Expand Down Expand Up @@ -84,7 +84,7 @@ def __init__(
content_hasher: hashing.StreamingHashEngine,
*,
chunk_size: int = 8192,
digest_name_override: str | None = None,
digest_name_override: Optional[str] = None,
):
"""Initializes an instance to hash a file with a specific `HashEngine`.
Expand Down Expand Up @@ -161,7 +161,7 @@ def __init__(
file_descriptor: BinaryIO,
*,
algorithm: str = "sha256",
digest_name_override: str | None = None,
digest_name_override: Optional[str] = None,
):
"""Initializes an instance to hash a file with a specific algorithm.
Expand Down Expand Up @@ -237,7 +237,7 @@ def __init__(
end: int,
chunk_size: int = 8192,
shard_size: int = 1000000,
digest_name_override: str | None = None,
digest_name_override: Optional[str] = None,
):
"""Initializes an instance to hash a file with a specific `HashEngine`.
Expand Down
6 changes: 3 additions & 3 deletions src/model_signing/serialization/serialize_by_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import concurrent.futures
import itertools
import pathlib
from typing import cast
from typing import Optional, cast

from typing_extensions import override

Expand Down Expand Up @@ -110,7 +110,7 @@ def __init__(
self,
file_hasher_factory: Callable[[pathlib.Path], file.FileHasher],
*,
max_workers: int | None = None,
max_workers: Optional[int] = None,
allow_symlinks: bool = False,
):
"""Initializes an instance to serialize a model with this serializer.
Expand Down Expand Up @@ -252,7 +252,7 @@ class _FileDigestTree:
"""

def __init__(
self, path: pathlib.PurePath, digest: hashing.Digest | None = None
self, path: pathlib.PurePath, digest: Optional[hashing.Digest] = None
):
"""Builds a node in the digest tree.
Expand Down
6 changes: 3 additions & 3 deletions src/model_signing/serialization/serialize_by_file_shard.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import concurrent.futures
import itertools
import pathlib
from typing import cast
from typing import Optional, cast

from typing_extensions import override

Expand Down Expand Up @@ -90,7 +90,7 @@ def __init__(
[pathlib.Path, int, int], file.ShardedFileHasher
],
*,
max_workers: int | None = None,
max_workers: Optional[int] = None,
allow_symlinks: bool = False,
):
"""Initializes an instance to serialize a model with this serializer.
Expand Down Expand Up @@ -269,7 +269,7 @@ def __init__(
],
merge_hasher: hashing.StreamingHashEngine,
*,
max_workers: int | None = None,
max_workers: Optional[int] = None,
allow_symlinks: bool = False,
):
"""Initializes an instance to serialize a model with this serializer.
Expand Down
6 changes: 4 additions & 2 deletions src/model_signing/signature/key.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# limitations under the License.
"""Functionality to sign and verify models with keys."""

from typing import Optional

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.hashes import SHA256
Expand All @@ -30,7 +32,7 @@


def load_ec_private_key(
path: str, password: str | None = None
path: str, password: Optional[str] = None
) -> ec.EllipticCurvePrivateKey:
private_key: ec.EllipticCurvePrivateKey
with open(path, "rb") as fd:
Expand All @@ -48,7 +50,7 @@ def __init__(self, private_key: ec.EllipticCurvePrivateKey):
self._private_key = private_key

@classmethod
def from_path(cls, private_key_path: str, password: str | None = None):
def from_path(cls, private_key_path: str, password: Optional[str] = None):
private_key = load_ec_private_key(private_key_path, password)
return cls(private_key)

Expand Down
6 changes: 3 additions & 3 deletions src/model_signing/signature/pki.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.
"""Functionality to sign and verify models with certificates."""

from typing import Self
from typing import Optional, Self

import certifi
from cryptography import x509
Expand Down Expand Up @@ -120,14 +120,14 @@ class PKIVerifier(Verifier):
"""Provides a verifier based on root certificates."""

def __init__(
self, root_certs: list[x509.Certificate] | None = None
self, root_certs: Optional[list[x509.Certificate]] = None
) -> None:
self._store = ssl_crypto.X509Store()
for c in root_certs:
self._store.add_cert(ssl_crypto.X509.from_cryptography(c))

@classmethod
def from_paths(cls, root_cert_paths: list[str] | None = None) -> Self:
def from_paths(cls, root_cert_paths: Optional[list[str]] = None) -> Self:
crypto_trust_roots: list[x509.Certificate] = []
if root_cert_paths:
crypto_trust_roots = _load_multiple_certs(root_cert_paths)
Expand Down
5 changes: 3 additions & 2 deletions src/model_signing/signing/sign_sigstore.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import json
import pathlib
import sys
from typing import Optional

from google.protobuf import json_format
from sigstore import dsse as sigstore_dsse
Expand Down Expand Up @@ -99,10 +100,10 @@ class SigstoreSigner(signing.Signer):
def __init__(
self,
*,
oidc_issuer: str | None = None,
oidc_issuer: Optional[str] = None,
use_ambient_credentials: bool = True,
use_staging: bool = False,
identity_token: str | None = None,
identity_token: Optional[str] = None,
):
"""Initializes Sigstore signers.
Expand Down

0 comments on commit a19b00d

Please sign in to comment.