* chore(build): migrate justfile tooling to uv All recipes now run through 'uv run' against .venv, with a shared DEV_EXTRAS variable (dev + vad) because uv syncs the environment exactly and would otherwise uninstall the dev tools between recipes. 'deps' installs dev+vad; new 'deps-all' covers every extra. 'build' switches to 'uv build'; 'version' uses --no-sync. Bump [tool.uv] required-version to >=0.12,<0.13 for uv 0.12.x, and raise the mypy python_version target to 3.12: mypy 1.19 in the lock cannot target 3.9 and numpy 2.x stubs need a 3.12 target to parse (mypy is not run in CI or pre-commit; this only affects 'just typecheck'). * fix(tests): isolate test runs from real XDG state; fix Arch model-path crash Local test runs failed en masse (185 failures) while CI stayed green: tests picked up the developer's real ~/.local/share/vocalinux models via the default XDG paths and switched IBus/keyboard code paths through the desktop session env (XDG_CURRENT_DESKTOP/ XDG_SESSION_TYPE on GNOME/Wayland, issue #478 branches). conftest now points XDG base dirs at a throwaway session-wide directory and clears session variables at import time, mirroring a fresh CI runner (paths like MODELS_DIR freeze at first import during collection, so fixture-time isolation is too late). Also guard the Arch branch of _get_system_model_paths(): it removed /usr/local/share/vocalinux/models unconditionally and crashed with ValueError on Arch hosts when a custom XDG_DATA_DIRS never added that path. Add regression tests, and make the lock-file test assert the actual $XDG_DATA_HOME/vocalinux contract instead of the hardcoded ~/.local/share default. * fix(tests): keep the IBus engine double a plain class Python 3.14's unittest.mock re-instantiates a user subclass when it builds child mocks (klass(name=...)). VocalinuxEngine inherits from whatever IBus.Engine resolves to, and the test module bound that to the MagicMock class itself, so every engine instantiation raised TypeError: VocalinuxEngine.__init__() got an unexpected keyword argument 'name' on 3.14 while passing on 3.13. Replace it with a plain stand-in class that still hands out per-instance MagicMocks for the IBus API surface the engine touches. The same pattern remains in test_ibus_engine.py, test_ibus_engine_utils.py, test_ibus_active_engine_guard.py and test_ibus_xkb_wayland.py; those never instantiate the engine, so they are unaffected for now. * fix(install): build the venv from the system Python install.sh looked up a bare `python3` from PATH. Started from an activated virtualenv — a shell left in uv's .venv after `just deps` — it built venv/ from that interpreter, where distro PyGObject is invisible even with --system-site-packages, and the run aborted with Distro PyGObject (python3-gi / python3-gobject) is not importable in the venv. A mismatched venv/ then survived every retry, because the installer reuses whatever venv it finds. - drop an inherited VIRTUAL_ENV (and its bin from PATH) before any interpreter lookup - select the interpreter explicitly: $SYSTEM_PYTHON (default /usr/bin/python3) or PATH python3, preferring one that can import gi, and create the venv with it instead of `python3` - recreate venv/ when it was built by a different interpreter - name the interpreter in the PyGObject guard's error Documents the two environments (uv's .venv for tooling, venv/ for the installed app) in CONTRIBUTING, the justfile header and AGENTS.md, and adds the mismatch symptom to the INSTALL troubleshooting section. * chore(python): raise the supported floor to 3.11 requires-python promised 3.9 while the CI matrix ran 3.9/3.10/3.11/3.13, the classifiers advertised up to 3.14, mypy targeted 3.12 and install.sh enforced 3.9 — five different answers to "which Python does this support". Settle on 3.11 everywhere: pyproject (requires-python, classifiers, black targets), install.sh's MIN_VERSION, check-system-deps.sh, the distro package map, the CI matrix (now 3.11-3.14, matching the classifiers), the release and nightly workflows, and the docs. mypy now targets the floor instead of 3.12, so type checking covers what we promise; numpy 2.x ships stubs in 3.12 syntax that a 3.11 target cannot parse, so its imports are skipped. (mypy has 71 pre-existing errors either way; no CI job gates on it.) uv.lock and the requirements/* exports are regenerated — dropping 3.9/3.10 removes most of the version-conditional resolution branches. tests/test_python_version_policy.py pins the invariant: install.sh's floor, requires-python, the classifiers and the CI matrix must agree. * ci: run lint and tests from uv.lock The lock file and the hash-pinned exports were not enforced anywhere: CI still built a venv by hand and installed unpinned `PyGObject` plus `-e ".[dev,vad]"`, so the tool versions it checked against had nothing to do with the ones `just lint` uses locally. Both Python jobs now use astral-sh/setup-uv and run through `uv sync --locked` / `uv run --locked`, which also fails the build when uv.lock drifts from pyproject.toml. Partially covers phase 5 of #701 (tests install from the lock, lock freshness gate); trusted publishing, artifact checksums and action SHA pinning are still open there. * ci: lint from a dedicated dependency group The lint job failed on this branch: `uv run --extra dev` syncs the whole project, so it tried to build pyaudio from source on a runner that installs no system dependencies. src/pyaudio/device_api.c:9:10: fatal error: portaudio.h: No such file or directory Move black/isort/flake8 out of the `dev` extra into a `lint` dependency group. CI runs `uv run --locked --only-group lint`, which installs the three pure-Python tools and skips the project entirely — no portaudio or girepository headers needed. `just deps` and DEV_EXTRAS request the group alongside the dev extras, so the local environment still carries test and lint tooling at once and `just lint` matches CI version for version. pre-commit is unaffected: its hooks pin their own tool versions upstream. * fix(install): never configure an engine the venv cannot import vosk became an optional extra in #705, but the installer still writes `"engine": "vosk"` into config.json on the whisper.cpp fallback path even when installing the extra only produced a warning. config.json also survives reinstalls — every writer guards on "file does not exist" — so an engine picked by an earlier attempt outlives the venv that supported it. Either way Vocalinux dies at startup: ModuleNotFoundError: No module named 'vosk' - the fallback now writes the vosk config only when `import vosk` actually succeeds, and otherwise leaves the engine configuration alone - a new verify_configured_engine() runs near the end of the install: if config.json selects vosk and the venv cannot import it, it says so and prints both fixes instead of leaving a broken install behind * feat(models): verify every model download against a pinned digest Pins a sha256/md5 per downloadable speech model in src/vocalinux/utils/model_checksums.txt and verifies against it in both install.sh and the runtime downloaders, before the file is moved into place. Verification fails closed. Covers phase 2.5 of #701; see the PR for details. * docs(agents): rebase AGENTS.md onto the #727 rewrite * fix(install): hash models that are already on disk instead of trusting them * refactor(models): one download path per engine, and fix Whisper's * docs(install): drop distros that cannot meet the 3.11 floor * docs: retire the distros and Python versions the 3.11 floor rules out * fix(install): abort instead of warning when Python is below the floor * fix(install): fall back to the system Python, not the one on PATH * fix(install): match an existing venv by installation, not by X.Y * fix(install): repair a config naming an engine the venv cannot import * fix(models): pin VOSK by the sha256 of bytes we fetched, not upstream's md5 * fix(justfile): request the dev set in every recipe that syncs * test(version): scan the docs and website trees for a stale Python floor * docs: correct the stale model-checksum and distro claims * fix(models): stamp the VOSK tree the app extracts, as the installer does * fix(install): replace an unverified VOSK model only once its successor is ready * docs(install): align the Fedora floor with the rest of the tree * test(install): drop an f-string with no placeholders * test(models): stub the downloader probe the VOSK installer test relies on * docs: drop the long dashes from two model-checksum comments * fix(install): gate on the interpreter, not on the distro release label * fix(justfile): export the lint group into requirements/dev.txt * fix(models): unpack the VOSK tree into staging and stamp it before the swap * fix(whisper): adopt a checkpoint an earlier release stored under the catalog name * docs(settings): state what a stray Whisper copy really means for deletion * fix(models): hash a whisper.cpp model found on disk before it reaches ctypes * docs(contributing): stop telling readers to uninstall their linters * fix(install): stop warning off distros the project documents as supported * docs(distros): file the supported derivatives under supported
189 lines
5.0 KiB
TOML
189 lines
5.0 KiB
TOML
[build-system]
|
|
requires = ["setuptools>=61,<82"]
|
|
build-backend = "setuptools.build_meta"
|
|
|
|
[project]
|
|
name = "vocalinux"
|
|
dynamic = ["version"]
|
|
description = "A seamless voice dictation system for Linux"
|
|
readme = "README.md"
|
|
license = {text = "AGPL-3.0"}
|
|
authors = [
|
|
{name = "Jatin K Malik", email = "jatinkrmalik@gmail.com"}
|
|
]
|
|
maintainers = [
|
|
{name = "Jatin K Malik", email = "jatinkrmalik@gmail.com"}
|
|
]
|
|
requires-python = ">=3.11"
|
|
keywords = [
|
|
"voice", "dictation", "speech", "recognition",
|
|
"linux", "accessibility", "voice-typing", "speech-to-text"
|
|
]
|
|
classifiers = [
|
|
"Development Status :: 5 - Production/Stable",
|
|
"Environment :: X11 Applications :: GTK",
|
|
"Intended Audience :: End Users/Desktop",
|
|
"License :: OSI Approved :: GNU Affero General Public License v3",
|
|
"Operating System :: POSIX :: Linux",
|
|
"Programming Language :: Python :: 3",
|
|
"Programming Language :: Python :: 3.11",
|
|
"Programming Language :: Python :: 3.12",
|
|
"Programming Language :: Python :: 3.13",
|
|
"Programming Language :: Python :: 3.14",
|
|
"Topic :: Desktop Environment :: Gnome",
|
|
"Topic :: Multimedia :: Sound/Audio :: Speech",
|
|
"Topic :: Text Processing",
|
|
"Topic :: Utilities",
|
|
]
|
|
dependencies = [
|
|
"pywhispercpp>=1.5.0,<2",
|
|
"pynput>=1.7.6,<2",
|
|
"evdev>=1.6.0,<2; sys_platform == 'linux'",
|
|
"requests>=2.28.0",
|
|
"numpy>=1.22.0,<3",
|
|
"pyaudio>=0.2.13",
|
|
"PyGObject; sys_platform == 'linux'",
|
|
"psutil>=5.9.0,<8",
|
|
]
|
|
|
|
[project.optional-dependencies]
|
|
vosk = [
|
|
"vosk>=0.3.45",
|
|
]
|
|
whisper = [
|
|
"openai-whisper>=20231117",
|
|
"torch>=2.0.0",
|
|
"torchaudio>=2.0.0",
|
|
]
|
|
vad = [
|
|
"onnxruntime>=1.16.0",
|
|
]
|
|
dev = [
|
|
"pytest>=7.0.0",
|
|
"pytest-cov>=4.0.0",
|
|
"pytest-mock>=3.10.0",
|
|
"pytest-timeout>=2.1.0",
|
|
"pre-commit>=3.0.0",
|
|
"mypy>=1.0.0",
|
|
]
|
|
docs = [
|
|
"sphinx>=6.0.0",
|
|
"sphinx-rtd-theme>=1.2.0",
|
|
]
|
|
|
|
# Formatters and linters, deliberately outside the `dev` extra: `uv sync
|
|
# --only-group lint` installs them without the project, so the lint job needs
|
|
# no portaudio/girepository headers to build pyaudio and PyGObject.
|
|
[dependency-groups]
|
|
lint = [
|
|
"black>=23.0.0",
|
|
"isort>=5.12.0",
|
|
"flake8>=6.0.0",
|
|
]
|
|
|
|
[project.scripts]
|
|
vocalinux = "vocalinux.main:main"
|
|
|
|
[project.gui-scripts]
|
|
vocalinux-gui = "vocalinux.main:main"
|
|
|
|
[project.urls]
|
|
Homepage = "https://github.com/VocaHQ/vocalinux"
|
|
Documentation = "https://github.com/VocaHQ/vocalinux/tree/main/docs"
|
|
Repository = "https://github.com/VocaHQ/vocalinux"
|
|
Issues = "https://github.com/VocaHQ/vocalinux/issues"
|
|
Changelog = "https://github.com/VocaHQ/vocalinux/releases"
|
|
|
|
[tool.uv]
|
|
# Lock/exports are generated with uv; keep the minor pinned so lockfile
|
|
# upgrades are deliberate (see `just lock`).
|
|
required-version = ">=0.12,<0.13"
|
|
|
|
[tool.setuptools]
|
|
packages = ["vocalinux", "vocalinux.resources", "vocalinux.speech_recognition", "vocalinux.text_injection", "vocalinux.ui", "vocalinux.ui.keyboard_backends", "vocalinux.utils"]
|
|
package-dir = {"" = "src"}
|
|
|
|
[tool.setuptools.dynamic]
|
|
version = {attr = "vocalinux.version.__version__"}
|
|
|
|
[tool.setuptools.package-data]
|
|
"*" = ["*.svg", "*.wav", "*.onnx"]
|
|
"vocalinux" = ["resources/**/*"]
|
|
"vocalinux.speech_recognition" = ["data/*.onnx", "data/LICENSE"]
|
|
"vocalinux.utils" = ["model_checksums.txt"]
|
|
|
|
[tool.black]
|
|
line-length = 100
|
|
target-version = ['py311', 'py312', 'py313', 'py314']
|
|
include = '\.pyi?$'
|
|
exclude = '''
|
|
/(
|
|
\.git
|
|
| \.hg
|
|
| \.mypy_cache
|
|
| \.tox
|
|
| \.venv
|
|
| venv
|
|
| _build
|
|
| buck-out
|
|
| build
|
|
| dist
|
|
| node_modules
|
|
)/
|
|
'''
|
|
|
|
[tool.isort]
|
|
profile = "black"
|
|
line_length = 100
|
|
skip_gitignore = true
|
|
skip = [".git", "venv", "node_modules", "build", "dist"]
|
|
|
|
[tool.pytest.ini_options]
|
|
minversion = "7.0"
|
|
addopts = "-ra -q --strict-markers --timeout=10 --timeout_method=thread"
|
|
testpaths = ["tests"]
|
|
python_files = ["test_*.py"]
|
|
python_functions = ["test_*"]
|
|
markers = [
|
|
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
|
"integration: marks tests as integration tests",
|
|
"audio: marks tests that require audio hardware",
|
|
]
|
|
|
|
[tool.coverage.run]
|
|
source = ["src"]
|
|
branch = true
|
|
omit = [
|
|
"*/tests/*",
|
|
"*/__pycache__/*",
|
|
# GTK dialog files are untestable without a real GTK environment
|
|
"*/settings_dialog.py",
|
|
"*/logging_dialog.py",
|
|
]
|
|
|
|
[tool.coverage.report]
|
|
exclude_lines = [
|
|
"pragma: no cover",
|
|
"def __repr__",
|
|
"raise NotImplementedError",
|
|
"if TYPE_CHECKING:",
|
|
"if __name__ == .__main__.:",
|
|
]
|
|
show_missing = true
|
|
|
|
[tool.mypy]
|
|
# Target the supported floor so typechecking covers what requires-python promises.
|
|
python_version = "3.11"
|
|
warn_return_any = true
|
|
warn_unused_configs = true
|
|
ignore_missing_imports = true
|
|
exclude = ["venv", "build", "dist", "node_modules"]
|
|
|
|
# numpy 2.x ships stubs written in 3.12 syntax, which a 3.11 target cannot parse.
|
|
# We only pass arrays through, so skipping the stubs costs little compared to
|
|
# type-checking against the Python version we actually promise to support.
|
|
[[tool.mypy.overrides]]
|
|
module = ["numpy", "numpy.*"]
|
|
follow_imports = "skip"
|
|
follow_imports_for_stubs = true
|