diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 13614d2..4229aac 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -10,6 +10,13 @@ jobs: - name: Check out repository code uses: actions/checkout@v4 + - name: Install uv with cache + uses: astral-sh/setup-uv@v5 + with: + version: "0.6.2" + enable-cache: true + cache-dependency-glob: "uv.lock" + - name: Install nox uses: wntrblm/nox@2025.02.09 diff --git a/.gitignore b/.gitignore index 739f35e..f3b7177 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ *__pycache__/ poetry.lock dist/ -.idea/ \ No newline at end of file +.idea/ +.venv/ +uv.lock +ifdo.egg-info/ +build/ \ No newline at end of file diff --git a/ifdo/models.py b/ifdo/models.py index 8c93059..a348844 100644 --- a/ifdo/models.py +++ b/ifdo/models.py @@ -53,7 +53,8 @@ from pydantic import BaseModel, Field, field_validator from stringcase import spinalcase -from yaml import safe_dump, safe_load +from yaml import safe_dump as yaml_dump, safe_load as yaml_load +from json import dump as json_dump, load as json_load from ifdo.model import model @@ -962,8 +963,8 @@ class iFDO: # noqa: N801 image_set_items (dict[str, list[ImageData]]): A dictionary mapping keys to lists of ImageData objects. Methods: - load(path: str | Path) -> 'iFDO': Class method to load an iFDO object from a YAML file. - save(path: str | Path) -> None: Instance method to save the iFDO object to a YAML file. + load(path: str | Path) -> 'iFDO': Class method to load an iFDO object from a YAML or JSON file. + save(path: str | Path) -> None: Instance method to save the iFDO object to a YAML or JSON file. Example: # Load an existing iFDO from a YAML file @@ -983,26 +984,47 @@ class iFDO: # noqa: N801 @classmethod def load(cls, path: str | Path) -> "iFDO": """ - Load an iFDO from a YAML file. + Load an iFDO from a YAML or JSON file. Args: - path: Path to the YAML file. + path: Path to the file. Should have a suffix of `.yaml`, `.yml`, or `.json`. Returns: The loaded iFDO object. + + Raises: + ValueError: If the file format is not supported. """ path = Path(path) # Ensure Path object with path.open() as f: - d = safe_load(f) + suffix = path.suffix.lower().lstrip(".") + if suffix in ("yaml", "yml"): + d = yaml_load(f) + elif suffix == "json": + d = json_load(f) + else: + raise ValueError("Unsupported file format. Use YAML (.yaml, .yml) or JSON (.json).") + return cls.from_dict(d) def save(self, path: str | Path) -> None: """ - Save to a YAML file. + Save to a YAML or JSON file. Should have a suffix of `.yaml`, `.yml`, or `.json`. Args: - path: Path to the YAML file. + path: Path to the file. + + Raises: + ValueError: If the file format is not supported """ path = Path(path) # Ensure Path object with path.open("w") as f: - safe_dump(self.to_dict(), f, sort_keys=False) + suffix = path.suffix.lower().lstrip(".") + if suffix in ("yaml", "yml"): + yaml_dump(self.to_dict(), f, sort_keys=False) + elif suffix == "json": + json_dump(self.to_dict(), f, indent=2) + else: + raise ValueError( + "Unsupported file format. Use YAML (.yaml, .yml) or JSON (.json).", + ) diff --git a/noxfile.py b/noxfile.py index 6c0cc10..bfcafda 100644 --- a/noxfile.py +++ b/noxfile.py @@ -1,6 +1,9 @@ -from nox import session +import nox -@session(python=["3.10", "3.11", "3.12", "3.13"]) + +nox.options.default_venv_backend = "uv" + +@nox.session(python=["3.10", "3.11", "3.12", "3.13"]) def tests(session): session.install("pytest", "jsonschema", ".") session.run("pytest") diff --git a/pyproject.toml b/pyproject.toml index b31f88e..d657c26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,31 +1,38 @@ -[tool.poetry] +[project] name = "ifdo" version = "1.2.5" description = "Python iFDO implementation" +readme = "README.md" authors = [ - "Kevin Barnard ", - "Chris Jackett ", + {name = "Kevin Barnard", email = "kbarnard@mbari.org"}, + {name = "Chris Jackett", email = "chris.jackett@csiro.au"}, + {name = "Karsten Schimpf", email = "karsten.schimpf@awi.de"}, +] +license = {text = "MIT"} +requires-python = ">=3.10" + +dependencies = [ + "stringcase<2.0.0,>=1.2.0", + "pyyaml<7.0,>=6.0", + "pydantic<3.0.0,>=2.4.2", +] + +[dependency-groups] +dev = [ + "pre-commit<5.0.0,>=4.0.1", + "ruff<1.0.0,>=0.7.3", + "black<25.0.0,>=24.10.0", + "mypy<2.0.0,>=1.13.0", + "bandit<2.0.0,>=1.7.10", + "types-pyyaml<7.0.0.0,>=6.0.12.20240917", + "pytest<9.0.0,>=8.3.4", + "nox<2026.0.0,>=2025.2.9", + "jsonschema<5.0.0,>=4.23.0" ] -license = "MIT" -readme = "README.md" -[tool.poetry.dependencies] -python = "^3.10" -stringcase = "^1.2.0" -pyyaml = "^6.0" -pydantic = "^2.4.2" +[tool.uv] +package=true -[tool.poetry.group.dev.dependencies] -pre-commit = "^4.0.1" -ruff = "^0.7.3" -black = "^24.10.0" -mypy = "^1.13.0" -bandit = "^1.7.10" -types-pyyaml = "^6.0.12.20240917" -pytest = "^8.3.4" -nox = "^2025.2.9" -jsonschema = "^4.23.0" +[tool.setuptools.packages.find] +include = ["ifdo*"] -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api"