Diagram

From code completion to apt install ./tux-im.deb: mypy strict, CI evolution, deb packaging.

This is part 8/8 of the TUX IM Dev Log: From Zero to 0.1 series.

Part 8 · Releasing 0.1.0: mypy strict, CI, deb packaging

Code completion doesn’t mean you can ship. Between the last line of code and apt install ./tux-im_0.1.0-12_all.deb, there were 25 commits doing “engineering” — lint, type checking, CI, deb packaging, version management. This post covers that.

mypy strict: How 0 errors were forced

Commit 153587c mypy strict: 0 errors was a milestone — the entire project (27 Python files) passed mypy --strict.

What mypy strict includes:

  • All function parameters and return values must have type annotations.
  • No Any type escaping.
  • No implicit Optional.
  • No abuse of # type: ignore.

Why is it worth it?

  • Refactoring without fear — change a function signature, mypy immediately lists all call sites.
  • Fast onboarding — IDE auto-completion is accurate, no need to read entire files to guess types.
  • Runtime type bugs nearly vanishAttributeError: 'NoneType' object has no attribute 'x' — that classic Python error is essentially impossible to write under strict mode.

Notable fixes:

  • config.dict → config.dictionary — avoid name collision with Python’s built-in dict type.
  • shortcut handler type annotations — previously Callable with no parameter info, mypy couldn’t see it.
  • PEP 563 annotation parsing — after from __future__ import annotations, generics like list[int] in dataclass fields needed typing.List[int] before Python 3.9.
  • ASR module gets ignore_errors as a whole — ctypes cfunc signatures are incompatible with mypy strict. A pragmatic compromise.

How the lint debt was paid off

The project historically had 53 ruff errors. The approach wasn’t a single commit fix, but a three-step process:

  • 74ee0af style: manual lint fixes for the non-fixable 53 ruff errors — manually fixed what couldn’t be auto-fixed.
  • f36218d style: auto-fix ruff lint debtruff check --fix auto-fixed.
  • 2d5daf6 style: rename _MAX_PINYIN_LEN to _max_pinyin_len per ruff N806 — renamed constants per N806 rules.

After that, CI enforces ruff check . on every push — no new debt accumulates.

CI: Evolution of GitHub Actions

Starting from d88808a ci: add GH Actions workflow, the CI platform was built step by step. Each step had its pitfalls.

Pitfall 1: respx is a PyPI package, not apt

Diagram

The first time I wrote the CI, I mistakenly tried to install respx via apt. respx is a PyPI package used for HTTP mocking in tests. This taught me to carefully distinguish between apt and pip dependencies in CI workflows.

Pitfall 2: The deb packaging learning curve

Building a proper .deb package required understanding Debian’s package metadata, control files, and the dpkg-deb toolchain. The key was getting the debian/control, debian/changelog, and debian/rules files right. The final package tux-im_0.1.0-12_all.deb could be installed with a simple apt install ./tux-im.deb.

Pitfall 3: Version numbering discipline

Adopting SemVer with a Debian-compatible version scheme (0.1.0-12 where 12 is the Debian revision number) required careful management. Each build needed an updated changelog entry.

Summary

The engineering work — lint, types, CI, packaging — was invisible to end users but essential for maintainability. The 25 commits between “code works” and “code ships” are the difference between a prototype and a product. Key takeaways:

  1. mypy strict from day one (or at least before the codebase grows too large)
  2. CI that runs on every push prevents regression debt
  3. Proper packaging makes installation trivial for users
  4. Version discipline enables reproducible builds

“Code is only half the story. The other half is making it shippable.”

Last modified: 2026年6月25日

Author

Comments

Write a Reply or Comment

Your email address will not be published.