diff --git a/.gitignore b/.gitignore index 4b7e182..bff27ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,21 @@ .superpowers/ .bounded .DS_Store + +# Python +__pycache__/ +*.py[cod] +*.egg-info/ +build/ +dist/ +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.coverage +htmlcov/ +.venv/ +.env + +# Проект +arm_statements/VERSION +.codegraph/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6f3bc05 --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +BIN := $(CURDIR)/.venv/bin + +.PHONY: help venv lint test check golden-update + +help: ## Список целей + @grep -E '^[a-z-]+:.*##' $(MAKEFILE_LIST) | sed 's/:.*##/ —/' + +venv: ## Создать .venv и поставить зависимости (с dev) + uv venv --python 3.11 --allow-existing .venv + uv pip install --python .venv/bin/python -r pyproject.toml --extra dev + +lint: ## ruff check, ruff format --check, mypy + $(BIN)/ruff check . + $(BIN)/ruff format --check . + $(BIN)/mypy arm_statements + +test: ## pytest + $(BIN)/pytest + +check: lint test ## lint + test + +golden-update: ## Обновить эталоны golden-теста (задача 20) + GOLDEN_UPDATE=1 $(BIN)/pytest arm_statements/tests/test_golden.py diff --git a/README.md b/README.md index 208fa1d..b5b0bbe 100644 --- a/README.md +++ b/README.md @@ -3,3 +3,13 @@ Общий код нормализации форм ББ/ОФР (пакет `arm_statements`) для tabular-parser, scan-gateway и arm-core. Раскладывается копией в `api/arm_statements/` хостов скриптом `sync-statements` из arm-ka-docs. Спека: arm-ka-docs, `openspec/changes/unified-statement-normalization/`. + +Копия раскладывается `make -C docs sync-statements`; прямые правки копии в хостах запрещены. + +## Как проверить + +```bash +make venv && make check +``` + +Раздел «Публичный API» дописывается в задаче 20. diff --git a/arm_statements/__init__.py b/arm_statements/__init__.py new file mode 100644 index 0000000..6339e26 --- /dev/null +++ b/arm_statements/__init__.py @@ -0,0 +1,5 @@ +"""Общий код нормализации форм ББ/ОФР.""" + +from arm_statements.version import statements_version + +__all__ = ["statements_version"] diff --git a/arm_statements/tests/__init__.py b/arm_statements/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/arm_statements/tests/conftest.py b/arm_statements/tests/conftest.py new file mode 100644 index 0000000..c3a717e --- /dev/null +++ b/arm_statements/tests/conftest.py @@ -0,0 +1 @@ +"""Общие фикстуры тестов пакета (фикстура `rules` появится в задаче 3).""" diff --git a/arm_statements/tests/test_version.py b/arm_statements/tests/test_version.py new file mode 100644 index 0000000..be00685 --- /dev/null +++ b/arm_statements/tests/test_version.py @@ -0,0 +1,21 @@ +from pathlib import Path + +from arm_statements.version import statements_version + +FULL_HASH = "abc1234def5678abc1234def5678abc1234def56" + + +def test_version_absent_in_source() -> None: + assert statements_version() is None + + +def test_version_read_from_file(tmp_path: Path) -> None: + marker = tmp_path / "VERSION" + marker.write_text(f"{FULL_HASH}\n", encoding="utf-8") + assert statements_version(marker) == FULL_HASH + + +def test_version_empty_file_is_none(tmp_path: Path) -> None: + marker = tmp_path / "VERSION" + marker.write_text(" \n", encoding="utf-8") + assert statements_version(marker) is None diff --git a/arm_statements/version.py b/arm_statements/version.py new file mode 100644 index 0000000..d9e2ad5 --- /dev/null +++ b/arm_statements/version.py @@ -0,0 +1,14 @@ +"""Маркер версии копии пакета: файл VERSION пишет только sync-statements в хосте.""" + +from pathlib import Path + +_DEFAULT_MARKER = Path(__file__).parent / "VERSION" + + +def statements_version(path: Path | None = None) -> str | None: + """Вернуть версию копии (хеш коммита) или None, если файла нет или он пуст.""" + marker = _DEFAULT_MARKER if path is None else path + if not marker.is_file(): + return None + text = marker.read_text(encoding="utf-8").strip() + return text or None diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..119e89a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,34 @@ +[project] +name = "arm-statements" +version = "0" +requires-python = ">=3.11,<3.12" +dependencies = ["PyYAML==6.0.3"] + +[project.optional-dependencies] +dev = [ + "ruff==0.16.10", + "mypy==2.4.0", + "pytest==9.1.1", + "types-PyYAML==6.0.12.20260906", +] + +[tool.ruff] +line-length = 120 +target-version = "py311" + +[tool.ruff.lint] +select = ["E", "F", "I", "UP", "B", "PL"] + +[tool.ruff.lint.isort] +known-first-party = ["arm_statements"] + +[tool.mypy] +python_version = "3.11" +strict = true +mypy_path = "." +explicit_package_bases = true + +[tool.pytest.ini_options] +testpaths = ["arm_statements/tests"] +pythonpath = ["."] +addopts = "--tb=short --strict-markers"