Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/dep-audit.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
# SPDX-FileCopyrightText: 2026 William Jin <AuraMindNest@outlook.com>
#
# SPDX-License-Identifier: BSL-1.0

Expand Down
44 changes: 43 additions & 1 deletion .github/workflows/lint-and-format.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
# SPDX-FileCopyrightText: 2026 William Jin <AuraMindNest@outlook.com>
#
# SPDX-License-Identifier: BSL-1.0

Expand Down Expand Up @@ -45,3 +45,45 @@ jobs:
env:
RUFF_OUTPUT_FORMAT: github
REUSE_OUTPUT_FORMAT: github

test-coverage:
name: Tests and coverage
runs-on: ubuntu-latest
steps:
# actions/checkout v6.0.2
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
# actions/setup-python v6.2.0
- name: Setup Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: '3.14'
# astral-sh/setup-uv v8.1.0
- uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
with:
version: 0.11.12
- name: Install apt dependencies (Weblate venv)
run: sudo ./.github/ci/apt-install
- name: Install dependencies (incl. dev)
run: uv sync --frozen --group dev --group pre-commit
- name: Pytest with coverage
run: >
uv run --group dev --group pre-commit pytest -v --tb=short
--cov=boost_weblate
--cov-report=term-missing
--cov-report=xml:coverage.xml
--cov-report=html:htmlcov
--cov-fail-under=90
- name: Coverage report (summary table)
run: uv run --group dev coverage report
# actions/upload-artifact v4.6.2
- name: Upload coverage artifacts
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: coverage-${{ github.event.pull_request.number || github.run_id }}
path: |
coverage.xml
htmlcov/
.coverage
if-no-files-found: error
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ dist/
# Testing / coverage
.pytest_cache/
.coverage
coverage.xml
htmlcov/
.tox/
.nox/
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
# SPDX-FileCopyrightText: 2026 William Jin <AuraMindNest@outlook.com>
#
# SPDX-License-Identifier: BSL-1.0

Expand Down
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ Run the test suite:
pytest
```

Run with the same coverage gate as CI (terminal + XML + HTML, 90% minimum on `boost_weblate`):

```bash
pytest -v --tb=short \
--cov=boost_weblate \
--cov-report=term-missing \
--cov-report=xml:coverage.xml \
--cov-report=html:htmlcov \
--cov-fail-under=90
coverage report
```

(`coverage.xml`, `htmlcov/`, and `.coverage` are gitignored; open `htmlcov/index.html` locally to browse line coverage.)

Run the same checks CI uses (lint, reuse, workflow lint, and pytest via [prek](https://pypi.org/project/prek/) reading `.pre-commit-config.yaml`):

```bash
Expand Down Expand Up @@ -79,7 +93,7 @@ flowchart TB

- **`src/boost_weblate/utils/`** — **Format-specific logic** with no Weblate import cycle: QuickBook parsing, segment extraction, translate-toolkit storage (`QuickBookFile` / `QuickBookUnit`), and reconstruction (`QuickBookTranslator`). New formats should add a sibling module (or package) here.

- **`tests/`** — **Pytest** layout mirrors `formats/` and `utils/` (`tests/formats/`, `tests/utils/`). Shared fixtures live under `tests/fixtures/`. `tests/conftest.py` configures `sys.path`, sets `DJANGO_SETTINGS_MODULE` to `tests.django_qbk_format_settings`, and calls `django.setup()` so format tests can load Weblate’s Django stack without requiring PostgreSQL.
- **`tests/`** — **Pytest** layout mirrors `src/boost_weblate/` (`tests/formats/`, `tests/utils/`, `tests/endpoint/`). Shared fixtures live under `tests/fixtures/`. `tests/conftest.py` configures `sys.path`, sets `DJANGO_SETTINGS_MODULE` to `tests.django_qbk_format_settings`, and calls `django.setup()` so format tests can load Weblate’s Django stack without requiring PostgreSQL.

## WEBLATE_FORMATS configuration

Expand All @@ -101,7 +115,9 @@ That path is fixed; Weblate does not scan `DATA_DIR` for arbitrary override file

- **Hooks:** use prek (or classic pre-commit) with `.pre-commit-config.yaml` so local runs match CI (Ruff, YAML/TOML checks, REUSE, actionlint, pytest).

- **Tests:** add tests next to the code you touch (`tests/formats/` or `tests/utils/`). Keep `django.setup()`-friendly patterns; heavy DB or migration suites are intentionally avoided in the bundled Django test settings.
- **Tests:** add tests next to the code you touch (`tests/formats/`, `tests/utils/`, or `tests/endpoint/`). Keep `django.setup()`-friendly patterns; heavy DB or migration suites are intentionally avoided in the bundled Django test settings.

- **CI coverage:** the *Lint and format* workflow runs a **Tests and coverage** job that prints `term-missing` output, runs `coverage report`, writes `coverage.xml` and `htmlcov/`, and uploads those plus `.coverage` as a workflow artifact (download from the run’s *Artifacts* section on GitHub). Coverage is configured in `pyproject.toml` (`[tool.coverage.*]`); the job uses `uv sync --frozen --group dev --group pre-commit` so `pytest-cov` and `coverage[toml]` match the lockfile.

- **Pull requests:** open PRs against the default branch on GitHub. Keep changes focused; ensure CI is green (build/wheel checks, lint, tests). Respond to review feedback on the PR thread; for design questions or bug reports, use [Issues](https://github.com/cppalliance/cppa-weblate-plugin/issues).

Expand Down
22 changes: 22 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ build-backend = "uv_build"
requires = ["uv_build>=0.8.18,<0.12.0"]

[dependency-groups]
dev = [
"coverage[toml]>=7.6.0",
"pytest-cov>=7.1.0"
]
lint = [
{include-group = "pre-commit"}
]
Expand Down Expand Up @@ -56,7 +60,9 @@ version = "0.1.0"

[project.optional-dependencies]
dev = [
"coverage[toml]>=7.6.0",
"prek==0.3.13",
"pytest-cov>=7.1.0",
"pytest>=8.3"
]

Expand All @@ -72,6 +78,20 @@ ignore = [
".editorconfig"
]

[tool.coverage]
Comment thread
AuraMindNest marked this conversation as resolved.

[tool.coverage.report]
fail_under = 90
omit = [
"*/migrations/*",
"*/tests/*",
"*/__pycache__/*"
]
show_missing = true

[tool.coverage.run]
source = ["boost_weblate"]

# liccheck: regex on PyPI license classifiers (as_regex).
[tool.liccheck]
as_regex = true
Expand Down Expand Up @@ -104,6 +124,8 @@ level = "cautious"
unauthorized_licenses = []

[tool.pytest.ini_options]
python_classes = ["Test*"]
python_files = ["test_*.py", "*_test.py"]
pythonpath = ["src", "."]
testpaths = ["tests"]

Expand Down
23 changes: 16 additions & 7 deletions src/boost_weblate/settings_override.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,17 @@
regex-slicing ``FormatsConf.FORMATS``. That avoids ``import weblate.formats.models``,
which pulls in Django ORM classes during settings import and raises
``AppRegistryNotReady``. The slice is **layout-sensitive**: it assumes ``FORMATS = (``
inside ``FormatsConf`` is followed by ``class Meta:`` at the same indent; if upstream
reformats that class, update the pattern here.
inside ``FormatsConf`` (same file) is followed by ``class Meta:`` at the same indent;
if upstream reformats ``FormatsConf`` or moves ``FORMATS`` / ``Meta``, update
``_FORMATS_BLOCK`` below.

``INSTALLED_APPS`` is extended via ``globals().get("INSTALLED_APPS")`` when this file
is ``exec``'d (Docker): the list exists in the settings namespace. Importing this
module for tests still defines ``WEBLATE_FORMATS`` on the module without mutating
Django settings.
When this file is ``exec``'d into Weblate's settings namespace (Docker),
``INSTALLED_APPS`` is taken from ``globals()`` and extended. Upstream
``weblate.settings_docker`` uses a **list**; the override appends in place with
``+=``. Settings that use an **immutable tuple** instead get a new tuple assigned
back to ``globals()["INSTALLED_APPS"]``. Importing this module without
``INSTALLED_APPS`` in the namespace (typical unit tests) still defines
``WEBLATE_FORMATS`` and skips the apps mutation.
"""

from __future__ import annotations
Expand Down Expand Up @@ -67,4 +71,9 @@ def weblate_formats_with_quickbook() -> tuple[str, ...]:

_INSTALLED_APPS = globals().get("INSTALLED_APPS")
if _INSTALLED_APPS is not None:
_INSTALLED_APPS += (_ENDPOINT_APP_CONFIG,)
# Tuple += creates a new object; assign back so exec namespace / settings see it.
# List += mutates in place, matching Weblate/Docker settings namespaces.
if isinstance(_INSTALLED_APPS, tuple):
globals()["INSTALLED_APPS"] = _INSTALLED_APPS + (_ENDPOINT_APP_CONFIG,)
else:
_INSTALLED_APPS += (_ENDPOINT_APP_CONFIG,)
44 changes: 44 additions & 0 deletions tests/formats/test_quickbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,46 @@ def test_existing_units_merge_orangutan(tmp_path: Path) -> None:
assert testfile.read_text(encoding="utf-8") == _EXPECTED_AFTER_EXISTING_UNITS


def test_convertfile_merge_duplicates_uses_merge_style(tmp_path: Path) -> None:
"""``merge_duplicates`` selects ``duplicate_style="merge"`` in ``convertfile``."""
from boost_weblate.formats.quickbook import QuickBookFormat

template_path = tmp_path / "tpl_merge.qbk"
translation_path = tmp_path / "tr_merge.qbk"
template_path.write_text("[h2 One]\n", encoding="utf-8")
translation_path.write_text("[h2 Jedna]\n", encoding="utf-8")

storage = QuickBookFormat(
str(translation_path),
template_store=QuickBookFormat(str(template_path), is_template=True),
file_format_params={"merge_duplicates": True},
)
assert len(storage.content_units) == 1
assert storage.content_units[0].source == "One"


def test_save_content_resolves_template_path_via_storefile_name(
tmp_path: Path,
) -> None:
"""Template ``storefile`` may be a binary handle; ``save`` uses ``.name``."""
from boost_weblate.formats.quickbook import QuickBookFormat

template_path = tmp_path / "tpl_handle.qbk"
translation_path = tmp_path / "tr_handle.qbk"
template_path.write_text("[h2 Hello]\n", encoding="utf-8")
translation_path.write_text("[h2 Ahoj]\n", encoding="utf-8")

with open(template_path, "rb") as template_file:
template_fmt = QuickBookFormat(template_file, is_template=True)
storage = QuickBookFormat(
str(translation_path),
template_store=template_fmt,
)
storage.save()

assert translation_path.read_text(encoding="utf-8") == "[h2 Ahoj]\n"


def main(argv: list[str]) -> int:
_bootstrap_django()

Expand All @@ -233,6 +273,10 @@ def main(argv: list[str]) -> int:
print("import existing (cs/cs2): OK")
test_existing_units_merge_orangutan(p)
print("existing_units merge: OK")
test_convertfile_merge_duplicates_uses_merge_style(p)
print("merge_duplicates convertfile: OK")
test_save_content_resolves_template_path_via_storefile_name(p)
print("save_content template storefile.name: OK")

return 0

Expand Down
2 changes: 1 addition & 1 deletion tests/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
# SPDX-FileCopyrightText: 2026 William Jin <AuraMindNest@outlook.com>
#
# SPDX-License-Identifier: BSL-1.0
Loading
Loading