Modernize tooling: uv, ruff, ty, Python 3.13+ and full typing - #31
Merged
Conversation
Adopt the same project setup as the observ repo: - Require Python 3.13+ and drop the `tomli` dependency in favour of stdlib `tomllib`. Unpin `keyring` (~=24.3 -> >=25.6) and `shellingham`. - Switch the build backend from flit to hatchling and move dev dependencies from an optional-dependencies extra to PEP 735 dependency groups (dev/ruff/ty), with uv as the package manager. - Derive `__version__` from package metadata so the version is declared in one place only. - Extend the ruff lint selection (isort, flake8-debugger, flake8-print) and drop the ignores that no longer apply. - Add ty type checking and a pre-commit config running format, lint, typecheck and tests. Annotate `error()` as `NoReturn`, which is what it always was and what ty needs to see to narrow correctly. - Reorganize CI into separate lint, typecheck, test, build and publish jobs driven by uv, upgrade all actions to their latest versions and pin third-party actions by commit SHA. Tests now run on Python 3.13 and 3.14; the matrix stays on Windows because the suite needs an OS keyring that unlocks unattended. - Document the development workflow and the 3.13+ requirement in the README. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nTpsbs48Pcky9GVthXnWv
Annotate every function, parameter and module-level variable in `keycmd`, and ship a `py.typed` marker so downstream consumers get the types too. Typing the config surface required describing its shape, so the [keys] and [aliases] tables are now `TypedDict`s. `load_conf` casts to that shape at the boundary where user authored TOML enters the program, which is where the promise is actually made. Enforcement, so this does not decay: - ruff's `ANN` rules require annotations (the test suite is exempt). - ty's off-by-default rules are enabled: `missing-type-argument`, `possibly-missing-attribute`, `possibly-missing-import`, `possibly-unresolved-reference` and `division-by-zero`. Suppressions must name a rule and must be needed (`blanket-ignore-comment`, `unused-ignore-comment`). - CI and pre-commit run `ty check --error-on-warning`, so warn-level diagnostics fail rather than scroll by. Two fixes fell out of making the types honest: - `shell.exec` passed `env=None` straight to `os.execvpe`, which requires a mapping and would have raised `TypeError`. It now falls back to `os.environ`, matching what the subprocess branch already did. - `creds.get_env` bound both the loop variable and the looked up key data to the name `key`, so the two had different types under one name. They are now `key`/`data` and `src`/`alias_src`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nTpsbs48Pcky9GVthXnWv
`uv sync --only-group ty` installs ty but not the project, so ty could not resolve the `keyring` and `shellingham` imports and failed with `unresolved-import`. The pattern was copied from observ, where it is correct because that project has no runtime dependencies. keycmd has two, so the typecheck job needs the project installed as well. It still skips the rest of the dev group. The lint job keeps using `--only-group ruff`: ruff is purely syntactic and needs nothing installed to resolve. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011nTpsbs48Pcky9GVthXnWv
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the project setup in line with fork-tongue/observ, drops support for everything below Python 3.13, and fully annotates the package.
Packaging
requires-python = ">=3.13", andtomliis gone in favour of stdlibtomllib.keyring~=24.3.0→>=25.6,shellingham~=1.5.4→>=1.5.4.[project.optional-dependencies] dev→ PEP 735[dependency-groups]split intodev/ruff/tyso each CI job installs only what it needs.__version__now comes fromimportlib.metadata, so the version is declared once inpyproject.tomlinstead of being kept in sync by hand.license = "MIT"+license-files.Typing
Every function, parameter and module-level variable in
keycmdis annotated, and the package ships apy.typedmarker so consumers get the types.Typing the config surface meant describing its shape, so
[keys]and[aliases]are nowTypedDicts (KeyConf,AliasConf,Conf) withNotRequiredfor the optionalb64/formatfields — the schema the README documents, expressed in code.load_confcasts at the boundary where user-authored TOML enters the program, which is where the promise is actually made;get_envstill reports violations as user errors at runtime.find_filegot@overloads, since its return type genuinely depends onfirst_only.Enforcement, so this does not decay:
ANNrules require annotations (the test suite is exempt).missing-type-argument,possibly-missing-attribute,possibly-missing-import,possibly-unresolved-reference,division-by-zero. Suppressions must name a rule and must be needed (blanket-ignore-comment,unused-ignore-comment).ty check --error-on-warning, so warn-level diagnostics fail rather than scroll by.Two bugs fell out of making the types honest
shell.execpassedenv=Nonestraight toos.execvpe, which requires a mapping and would have raisedTypeError. Reachable viarun_shell()/run_cmd()with no env on POSIX; the tests never hit it because they force the subprocess branch. It now falls back toos.environ, matching what the subprocess branch already did.creds.get_envbound both the loop variable and the looked-up key data to the namekey, giving one name two types. Split intokey/dataandsrc/alias_src.Linting
Ruff's selection now matches observ's — adds isort (
I),T10,T20andANN, keepsB. The old ignore list (E501,E731,B019,RUF012) is gone; nothing in the codebase needed it.Added
.pre-commit-config.yamlrunning format, lint, typecheck and tests.CI
Restructured from four ad-hoc jobs into
lint/typecheck/test/build/publish, all uv-driven with caching:actions/checkout@v4→@v7,upload-artifact@v4→@v7,download-artifact@v4→@v8;setup-pythonreplaced byastral-sh/setup-uv. Third-party actions pinned by commit SHA.uv build+uvx twine checkinstead of the hand-rolled sdist-install-and-git reset --harddance.Verification
ruff check,ruff format --checkandty check --error-on-warningare all clean.twine check;py.typedis present in both. The sdist installs into a clean 3.13 venv with a workingkeycmd --version.test_shell.py::test_run_cmdfails only when run on Linux, because it assertscmd.exe's exit code1for an unknown command while bash returns127. Pre-existing and platform-specific, so it is green on the Windows runners.ANNerrors, and a probe consumer confirms the types catch real misuse (assigningfind_file(..., first_only=False)to astr, akeysentry missingcredential, a non-strenv value), while validload_conf()→get_env()stays clean.Notes
secrets.PYPI_PASSWORD. observ uses trusted publishing (OIDC), which is better, but switching requires configuring a publisher on PyPI first, and doing it here would break the next release. Happy to switch once that exists.uv.lockis gitignored, matching observ — appropriate for a library.keycmdonly and the tests are exempt fromANN, so "fully typed" means the package, not the test suite.Generated by Claude Code