diff --git a/CHANGELOG.md b/CHANGELOG.md index a8513468..a77069ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,12 @@ and this project adheres to `RACKSDB_EXTENSIONS`, and to set default schema, extensions, and database paths when the matching `racksdb` / `racksdb-web` option is omitted (#149). - web: Support the same environment variables as CLI. -- pkgs: Add tests dependency on `RFL.build` >= 1.8.0 for parameterized unit - tests. +- pkgs: + - Add tests dependency on `RFL.build` >= 1.8.0 for parameterized unit tests. + - Install `schemas/racksdb.yml` and `schemas/drawings.yml` with the Python + package (#158). +- lib: Resolve default schema paths for pip/source installs and source checkouts + as a fallback for FHS distribution paths. - docs: - Mention support of RHEL 10, Fedora 43, SLES and openSUSE 15 and 16, Ubuntu 26.04 LTS. diff --git a/pyproject.toml b/pyproject.toml index ac0403c7..7498a543 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -52,6 +52,12 @@ racksdb-web = "racksdb.web.app:RacksDBWebApp.run" [tool.setuptools.packages.find] include = ["racksdb*"] +[tool.setuptools.data-files] +"share/racksdb/schemas" = [ + "schemas/racksdb.yml", + "schemas/drawings.yml", +] + [project.urls] "Homepage" = "https://github.com/rackslab/RacksDB" "Bug Tracker" = "https://github.com/rackslab/RacksDB/issues" diff --git a/racksdb/__init__.py b/racksdb/__init__.py index 944ac0d1..1ed5b7bd 100644 --- a/racksdb/__init__.py +++ b/racksdb/__init__.py @@ -17,11 +17,12 @@ RacksDBNotFoundError, ) from . import bases +from .paths import schema_file class RacksDB(GenericDB): DEFAULT_DB = "/var/lib/racksdb" - DEFAULT_SCHEMA = "/usr/share/racksdb/schemas/racksdb.yml" + DEFAULT_SCHEMA = str(schema_file("racksdb.yml")) DEFAULT_EXT = "/etc/racksdb/extensions.yml" DEFAULT_UI = "/usr/share/racksdb/frontend" PREFIX = "RacksDB" diff --git a/racksdb/drawers/parameters.py b/racksdb/drawers/parameters.py index e7f27e10..e26f13c6 100644 --- a/racksdb/drawers/parameters.py +++ b/racksdb/drawers/parameters.py @@ -9,10 +9,11 @@ from ..generic.schema import Schema, SchemaFileLoader, SchemaDefinedTypeLoader from ..generic.db import GenericDB +from ..paths import schema_file class DrawingParameters(GenericDB): - DEFAULT_SCHEMA = "/usr/share/racksdb/schemas/drawings.yml" + DEFAULT_SCHEMA = str(schema_file("drawings.yml")) PREFIX = "DrawingParameters" DEFINED_TYPES_MODULE = "racksdb.drawers.dtypes" diff --git a/racksdb/paths.py b/racksdb/paths.py new file mode 100644 index 00000000..ea6698ea --- /dev/null +++ b/racksdb/paths.py @@ -0,0 +1,41 @@ +# Copyright (c) 2026 Rackslab +# +# This file is part of RacksDB. +# +# SPDX-License-Identifier: MIT + +"""Resolve default paths for schema files shipped with RacksDB.""" + +import sys +from pathlib import Path + +_PACKAGE_ROOT = Path(__file__).resolve().parent +_SOURCE_TREE_SCHEMAS = _PACKAGE_ROOT.parent / "schemas" +_FHS_SCHEMAS_DIR = Path("/usr/share/racksdb/schemas") + + +def schema_file(name: str) -> Path: + """Return the best available path for a schema *name*. + + Resolution order: + + 1. Distribution FHS path /usr/share/racksdb/schemas/ + 2. Pip-installed data files under /share/racksdb/schemas/ + 3. Source tree schemas/ next to the repository root (editable checkout) + + If none of the above paths exist, the distribution path is returned anyway to + preserve documented path in error messages. + """ + fhs = _FHS_SCHEMAS_DIR / name + if fhs.is_file(): + return fhs + + pip_path = Path(sys.prefix) / "share" / "racksdb" / "schemas" / name + if pip_path.is_file(): + return pip_path + + dev_path = _SOURCE_TREE_SCHEMAS / name + if dev_path.is_file(): + return dev_path + + return fhs diff --git a/racksdb/tests/test_paths.py b/racksdb/tests/test_paths.py new file mode 100644 index 00000000..0909c7b8 --- /dev/null +++ b/racksdb/tests/test_paths.py @@ -0,0 +1,56 @@ +# Copyright (c) 2026 Rackslab +# +# This file is part of RacksDB. +# +# SPDX-License-Identifier: MIT + +import sys +import unittest +from pathlib import Path +from unittest import mock + +from racksdb import RacksDB +from racksdb.drawers.parameters import DrawingParameters +from racksdb.paths import _SOURCE_TREE_SCHEMAS, schema_file + + +class TestSchemaFile(unittest.TestCase): + FHS = Path("/usr/share/racksdb/schemas/racksdb.yml") + + def test_prefers_existing_fhs_path(self): + with mock.patch.object(Path, "is_file", return_value=True): + result = schema_file("racksdb.yml") + self.assertEqual(result, self.FHS) + + def test_uses_pip_data_when_fhs_missing(self): + pip_path = Path(sys.prefix) / "share" / "racksdb" / "schemas" / "racksdb.yml" + + def is_file(self): + return self == pip_path + + with mock.patch.object(Path, "is_file", is_file): + result = schema_file("racksdb.yml") + self.assertEqual(result, pip_path) + + def test_uses_source_tree_when_fhs_and_pip_missing(self): + dev_path = _SOURCE_TREE_SCHEMAS / "racksdb.yml" + + def is_file(self): + return self == dev_path + + with mock.patch.object(Path, "is_file", is_file): + result = schema_file("racksdb.yml") + self.assertEqual(result, dev_path) + + def test_returns_fhs_path_when_nothing_found(self): + with mock.patch.object(Path, "is_file", return_value=False): + result = schema_file("racksdb.yml") + self.assertEqual(result, self.FHS) + + +class TestDefaultSchemaPaths(unittest.TestCase): + def test_racksdb_default_schema_exists(self): + self.assertTrue(Path(RacksDB.DEFAULT_SCHEMA).is_file()) + + def test_drawing_parameters_default_schema_exists(self): + self.assertTrue(Path(DrawingParameters.DEFAULT_SCHEMA).is_file())