diff --git a/python/ommx-python-mip-adapter/docs/source/conf.py b/python/ommx-python-mip-adapter/docs/source/conf.py index 4a75960a..eb35105c 100644 --- a/python/ommx-python-mip-adapter/docs/source/conf.py +++ b/python/ommx-python-mip-adapter/docs/source/conf.py @@ -12,7 +12,7 @@ copyright = "2024, Jij Inc." author = "Jij Inc." -version = "1.0.1" +version = "1.1.0" release = version # -- General configuration --------------------------------------------------- diff --git a/python/ommx-python-mip-adapter/pyproject.toml b/python/ommx-python-mip-adapter/pyproject.toml index 4fd31006..2ef8c7c5 100644 --- a/python/ommx-python-mip-adapter/pyproject.toml +++ b/python/ommx-python-mip-adapter/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "ommx_python_mip_adapter" -version = "1.0.1" +version = "1.1.0" description = "An adapter for the Python-MIP from/to OMMX." authors = [ @@ -23,7 +23,7 @@ classifiers = [ "License :: OSI Approved :: MIT License", ] dependencies = [ - "ommx >= 1.0.1, < 2.0.0", + "ommx >= 1.1.0, < 2.0.0", # FIXME: This project requires latest version of Python-MIP (will be 1.16.0?), which does not release yet. # https://github.com/coin-or/python-mip/issues/384 diff --git a/python/ommx/docs/source/conf.py b/python/ommx/docs/source/conf.py index 1344339d..1d16f7d3 100644 --- a/python/ommx/docs/source/conf.py +++ b/python/ommx/docs/source/conf.py @@ -13,7 +13,7 @@ copyright = "2024, Jij Inc." author = "Jij Inc." -version = "1.0.1" +version = "1.1.0" release = version # -- General configuration --------------------------------------------------- diff --git a/python/ommx/ommx/artifact.py b/python/ommx/ommx/artifact.py index 625f125a..b59cd899 100644 --- a/python/ommx/ommx/artifact.py +++ b/python/ommx/ommx/artifact.py @@ -7,24 +7,107 @@ from dataclasses import dataclass from pathlib import Path from dateutil import parser +from abc import ABC, abstractmethod from ._ommx_rust import ( - ArtifactArchive, - ArtifactDir, + ArtifactArchive as _ArtifactArchive, + ArtifactDir as _ArtifactDir, Descriptor, - ArtifactArchiveBuilder, - ArtifactDirBuilder, + ArtifactArchiveBuilder as _ArtifactArchiveBuilder, + ArtifactDirBuilder as _ArtifactDirBuilder, ) from .v1 import Instance, Solution +class ArtifactBase(ABC): + @property + @abstractmethod + def image_name(self) -> str | None: ... + + @property + @abstractmethod + def annotations(self) -> dict[str, str]: ... + + @property + @abstractmethod + def layers(self) -> list[Descriptor]: ... + + @abstractmethod + def get_blob(self, digest: str) -> bytes: ... + + @abstractmethod + def push(self): ... + + +# FIXME: This wrapper class should be defined in Rust binding directly, +# but PyO3 does not support inheriting Python class https://github.com/PyO3/pyo3/issues/991 +@dataclass +class ArtifactArchive(ArtifactBase): + _base: _ArtifactArchive + + @staticmethod + def from_oci_archive(path: str) -> ArtifactArchive: + return ArtifactArchive(_ArtifactArchive.from_oci_archive(path)) + + @property + def image_name(self) -> str | None: + return self._base.image_name + + @property + def annotations(self) -> dict[str, str]: + return self._base.annotations + + @property + def layers(self) -> list[Descriptor]: + return self._base.layers + + def get_blob(self, digest: str) -> bytes: + return self._base.get_blob(digest) + + def push(self): + self._base.push() + + +# FIXME: This wrapper class should be defined in Rust binding directly, +# but PyO3 does not support inheriting Python class https://github.com/PyO3/pyo3/issues/991 +@dataclass +class ArtifactDir(ArtifactBase): + _base: _ArtifactDir + + @staticmethod + def from_oci_dir(path: str) -> ArtifactDir: + return ArtifactDir(_ArtifactDir.from_oci_dir(path)) + + @staticmethod + def from_image_name(image_name: str) -> ArtifactDir: + return ArtifactDir(_ArtifactDir.from_image_name(image_name)) + + @property + def image_name(self) -> str | None: + return self._base.image_name + + @property + def annotations(self) -> dict[str, str]: + return self._base.annotations + + @property + def layers(self) -> list[Descriptor]: + return self._base.layers + + def get_blob(self, digest: str) -> bytes: + return self._base.get_blob(digest) + + def push(self): + self._base.push() + + @dataclass class Artifact: """ Reader for OMMX Artifacts. """ - _base: ArtifactArchive | ArtifactDir + _base: ArtifactBase @staticmethod def load_archive(path: str | Path) -> Artifact: @@ -206,13 +289,82 @@ def get_dataframe(self, descriptor: Descriptor) -> pandas.DataFrame: return pandas.read_parquet(io.BytesIO(blob)) +class ArtifactBuilderBase(ABC): + @abstractmethod + def add_layer( + self, media_type: str, blob: bytes, annotations: dict[str, str] + ) -> Descriptor: ... + + @abstractmethod + def add_annotation(self, key: str, value: str): ... + + @abstractmethod + def build(self) -> ArtifactBase: ... + + +# FIXME: This wrapper class should be defined in Rust binding directly, +# but PyO3 does not support inheriting Python class https://github.com/PyO3/pyo3/issues/991 +@dataclass +class ArtifactArchiveBuilder(ArtifactBuilderBase): + _base: _ArtifactArchiveBuilder + + @staticmethod + def new(path: str, image_name: str) -> ArtifactArchiveBuilder: + return ArtifactArchiveBuilder(_ArtifactArchiveBuilder.new(path, image_name)) + + @staticmethod + def new_unnamed(path: str) -> ArtifactArchiveBuilder: + return ArtifactArchiveBuilder(_ArtifactArchiveBuilder.new_unnamed(path)) + + @staticmethod + def temp() -> ArtifactArchiveBuilder: + return ArtifactArchiveBuilder(_ArtifactArchiveBuilder.temp()) + + def add_layer( + self, media_type: str, blob: bytes, annotations: dict[str, str] = {} + ) -> Descriptor: + return self._base.add_layer(media_type, blob, annotations) + + def add_annotation(self, key: str, value: str): + self._base.add_annotation(key, value) + + def build(self) -> ArtifactArchive: + return ArtifactArchive(self._base.build()) + + +# FIXME: This wrapper class should be defined in Rust binding directly, +# but PyO3 does not support inheriting Python class https://github.com/PyO3/pyo3/issues/991 +@dataclass +class ArtifactDirBuilder(ArtifactBuilderBase): + _base: _ArtifactDirBuilder + + @staticmethod + def new(image_name: str) -> ArtifactDirBuilder: + return ArtifactDirBuilder(_ArtifactDirBuilder.new(image_name)) + + @staticmethod + def for_github(org: str, repo: str, name: str, tag: str) -> ArtifactDirBuilder: + return ArtifactDirBuilder(_ArtifactDirBuilder.for_github(org, repo, name, tag)) + + def add_layer( + self, media_type: str, blob: bytes, annotations: dict[str, str] = {} + ) -> Descriptor: + return self._base.add_layer(media_type, blob, annotations) + + def add_annotation(self, key: str, value: str): + self._base.add_annotation(key, value) + + def build(self) -> ArtifactDir: + return ArtifactDir(self._base.build()) + + @dataclass(frozen=True) class ArtifactBuilder: """ Builder for OMMX Artifacts. """ - _base: ArtifactArchiveBuilder | ArtifactDirBuilder + _base: ArtifactBuilderBase @staticmethod def new_archive_unnamed(path: str | Path) -> ArtifactBuilder: diff --git a/python/ommx/pyproject.toml b/python/ommx/pyproject.toml index afb26d15..f7d98350 100644 --- a/python/ommx/pyproject.toml +++ b/python/ommx/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "maturin" [project] name = "ommx" -version = "1.0.1" +version = "1.1.0" description = "Open Mathematical prograMming eXchange (OMMX)" authors = [{ name="Jij Inc.", email="info@j-ij.com" }] readme = "README.md"