trimbed 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. trimbed-0.0.1/.gitattributes +12 -0
  2. trimbed-0.0.1/.github/workflows/ci.yml +114 -0
  3. trimbed-0.0.1/.github/workflows/docs.yml +93 -0
  4. trimbed-0.0.1/.github/workflows/hub-tests.yml +57 -0
  5. trimbed-0.0.1/.github/workflows/publish.yml +35 -0
  6. trimbed-0.0.1/.gitignore +77 -0
  7. trimbed-0.0.1/.pre-commit-config.yaml +76 -0
  8. trimbed-0.0.1/.python-version +1 -0
  9. trimbed-0.0.1/CONTRIBUTING.md +103 -0
  10. trimbed-0.0.1/LICENSE +201 -0
  11. trimbed-0.0.1/Makefile +77 -0
  12. trimbed-0.0.1/PKG-INFO +141 -0
  13. trimbed-0.0.1/README.md +106 -0
  14. trimbed-0.0.1/codecov.yml +13 -0
  15. trimbed-0.0.1/docs/api/backends/base.md +3 -0
  16. trimbed-0.0.1/docs/api/backends/bpe.md +3 -0
  17. trimbed-0.0.1/docs/api/backends/registry.md +11 -0
  18. trimbed-0.0.1/docs/api/backends/unigram.md +3 -0
  19. trimbed-0.0.1/docs/api/backends/wordlevel.md +3 -0
  20. trimbed-0.0.1/docs/api/backends/wordpiece.md +3 -0
  21. trimbed-0.0.1/docs/api/bytelevel.md +3 -0
  22. trimbed-0.0.1/docs/api/cli/count.md +3 -0
  23. trimbed-0.0.1/docs/api/cli/inspect.md +3 -0
  24. trimbed-0.0.1/docs/api/cli/presets.md +3 -0
  25. trimbed-0.0.1/docs/api/cli/router.md +3 -0
  26. trimbed-0.0.1/docs/api/cli/trim.md +3 -0
  27. trimbed-0.0.1/docs/api/config.md +3 -0
  28. trimbed-0.0.1/docs/api/counting.md +3 -0
  29. trimbed-0.0.1/docs/api/exceptions.md +3 -0
  30. trimbed-0.0.1/docs/api/loading.md +3 -0
  31. trimbed-0.0.1/docs/api/model_trim.md +3 -0
  32. trimbed-0.0.1/docs/api/pipeline.md +3 -0
  33. trimbed-0.0.1/docs/api/presets.md +3 -0
  34. trimbed-0.0.1/docs/api/remap.md +3 -0
  35. trimbed-0.0.1/docs/api/report.md +3 -0
  36. trimbed-0.0.1/docs/api/selection.md +3 -0
  37. trimbed-0.0.1/docs/api/sidecar.md +3 -0
  38. trimbed-0.0.1/docs/api/spec.md +3 -0
  39. trimbed-0.0.1/docs/api/tokenizer_trim.md +3 -0
  40. trimbed-0.0.1/docs/api/verify.md +3 -0
  41. trimbed-0.0.1/docs/cli.md +100 -0
  42. trimbed-0.0.1/docs/configuration.md +103 -0
  43. trimbed-0.0.1/docs/examples.md +24 -0
  44. trimbed-0.0.1/docs/extending.md +62 -0
  45. trimbed-0.0.1/docs/hooks.py +139 -0
  46. trimbed-0.0.1/docs/index.md +88 -0
  47. trimbed-0.0.1/docs/output.md +66 -0
  48. trimbed-0.0.1/docs/overrides/main.html +8 -0
  49. trimbed-0.0.1/docs/overrides/python/material/_source_link.html.jinja +20 -0
  50. trimbed-0.0.1/docs/overrides/python/material/class.html.jinja +25 -0
  51. trimbed-0.0.1/docs/overrides/python/material/function.html.jinja +12 -0
  52. trimbed-0.0.1/docs/selection.md +92 -0
  53. trimbed-0.0.1/docs/trimming-a-model.md +78 -0
  54. trimbed-0.0.1/examples/01_inspect_tokenizer.py +58 -0
  55. trimbed-0.0.1/examples/02_trim_tokenizer_only.py +66 -0
  56. trimbed-0.0.1/examples/03_trim_with_corpus.py +89 -0
  57. trimbed-0.0.1/examples/04_custom_preset.py +84 -0
  58. trimbed-0.0.1/examples/05_low_level_api.py +68 -0
  59. trimbed-0.0.1/examples/README.md +27 -0
  60. trimbed-0.0.1/mkdocs.yml +212 -0
  61. trimbed-0.0.1/pyproject.toml +155 -0
  62. trimbed-0.0.1/src/trimbed/__init__.py +75 -0
  63. trimbed-0.0.1/src/trimbed/_logging.py +45 -0
  64. trimbed-0.0.1/src/trimbed/backends/__init__.py +84 -0
  65. trimbed-0.0.1/src/trimbed/backends/base.py +85 -0
  66. trimbed-0.0.1/src/trimbed/backends/bpe.py +63 -0
  67. trimbed-0.0.1/src/trimbed/backends/unigram.py +35 -0
  68. trimbed-0.0.1/src/trimbed/backends/wordlevel.py +17 -0
  69. trimbed-0.0.1/src/trimbed/backends/wordpiece.py +18 -0
  70. trimbed-0.0.1/src/trimbed/bytelevel.py +71 -0
  71. trimbed-0.0.1/src/trimbed/cli/__init__.py +7 -0
  72. trimbed-0.0.1/src/trimbed/cli/__main__.py +51 -0
  73. trimbed-0.0.1/src/trimbed/cli/count_tokens.py +85 -0
  74. trimbed-0.0.1/src/trimbed/cli/inspect_tokenizer.py +65 -0
  75. trimbed-0.0.1/src/trimbed/cli/list_presets.py +48 -0
  76. trimbed-0.0.1/src/trimbed/cli/trim_vocab.py +167 -0
  77. trimbed-0.0.1/src/trimbed/config.py +389 -0
  78. trimbed-0.0.1/src/trimbed/counting.py +260 -0
  79. trimbed-0.0.1/src/trimbed/exceptions.py +27 -0
  80. trimbed-0.0.1/src/trimbed/loading.py +164 -0
  81. trimbed-0.0.1/src/trimbed/model_trim.py +176 -0
  82. trimbed-0.0.1/src/trimbed/pipeline.py +290 -0
  83. trimbed-0.0.1/src/trimbed/presets.py +373 -0
  84. trimbed-0.0.1/src/trimbed/remap.py +133 -0
  85. trimbed-0.0.1/src/trimbed/report.py +267 -0
  86. trimbed-0.0.1/src/trimbed/selection.py +394 -0
  87. trimbed-0.0.1/src/trimbed/sidecar.py +112 -0
  88. trimbed-0.0.1/src/trimbed/spec.py +367 -0
  89. trimbed-0.0.1/src/trimbed/tokenizer_trim.py +150 -0
  90. trimbed-0.0.1/src/trimbed/verify.py +220 -0
  91. trimbed-0.0.1/tests/conftest.py +299 -0
  92. trimbed-0.0.1/tests/qwen3_chat_template.jinja +89 -0
  93. trimbed-0.0.1/tests/test_backends.py +129 -0
  94. trimbed-0.0.1/tests/test_chat_template.py +180 -0
  95. trimbed-0.0.1/tests/test_cli.py +258 -0
  96. trimbed-0.0.1/tests/test_config.py +172 -0
  97. trimbed-0.0.1/tests/test_counting.py +124 -0
  98. trimbed-0.0.1/tests/test_docs.py +92 -0
  99. trimbed-0.0.1/tests/test_errors_and_edges.py +250 -0
  100. trimbed-0.0.1/tests/test_examples.py +135 -0
  101. trimbed-0.0.1/tests/test_integration.py +371 -0
  102. trimbed-0.0.1/tests/test_loading.py +76 -0
  103. trimbed-0.0.1/tests/test_logging.py +42 -0
  104. trimbed-0.0.1/tests/test_model_trim.py +282 -0
  105. trimbed-0.0.1/tests/test_pipeline.py +271 -0
  106. trimbed-0.0.1/tests/test_presets.py +156 -0
  107. trimbed-0.0.1/tests/test_remap.py +55 -0
  108. trimbed-0.0.1/tests/test_report.py +124 -0
  109. trimbed-0.0.1/tests/test_selection.py +250 -0
  110. trimbed-0.0.1/tests/test_sidecar.py +73 -0
  111. trimbed-0.0.1/tests/test_spec.py +125 -0
  112. trimbed-0.0.1/tests/test_tokenizer_trim.py +162 -0
  113. trimbed-0.0.1/tests/test_verify.py +214 -0
  114. trimbed-0.0.1/uv.lock +2158 -0
@@ -0,0 +1,12 @@
1
+ # Line-ending policy: LF in the repository, LF in every working tree.
2
+ #
3
+ # `text=auto` lets Git detect which files are text (binary content is left untouched),
4
+ # and `eol=lf` pins the checkout ending on every platform rather than following
5
+ # core.autocrlf. Without this, files committed from Windows keep their CRLF, which shows
6
+ # up as whole-file diffs and breaks tools that parse the repo's own config from a shell
7
+ # (hence the `tr -d '\r'` guard in the Makefile's `test-matrix` target).
8
+ * text=auto eol=lf
9
+
10
+ # Generated by uv and only ever rewritten wholesale; collapse it in diffs and keep it out
11
+ # of review noise.
12
+ uv.lock linguist-generated=true
@@ -0,0 +1,114 @@
1
+ name: CI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches: [main]
7
+ paths:
8
+ - "**.py"
9
+ - "pyproject.toml"
10
+ - "uv.lock"
11
+ - ".pre-commit-config.yaml"
12
+ - ".github/workflows/ci.yml"
13
+ - "Makefile"
14
+ - "mkdocs.yml"
15
+ - "docs/**"
16
+ pull_request:
17
+ branches: [main]
18
+ paths:
19
+ - "**.py"
20
+ - "pyproject.toml"
21
+ - "uv.lock"
22
+ - ".pre-commit-config.yaml"
23
+ - ".github/workflows/ci.yml"
24
+ - "Makefile"
25
+ - "mkdocs.yml"
26
+ - "docs/**"
27
+
28
+ # Cancel any in-progress run for the same branch/PR when a new push arrives.
29
+ concurrency:
30
+ group: ${{ github.workflow }}-${{ github.ref }}
31
+ cancel-in-progress: true
32
+
33
+ jobs:
34
+ quality:
35
+ name: Code quality
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ # fetch-depth: 0 everywhere because hatch-vcs derives the version from the tags.
39
+ - uses: actions/checkout@v4
40
+ with:
41
+ fetch-depth: 0
42
+
43
+ - uses: astral-sh/setup-uv@v5
44
+ with:
45
+ enable-cache: true
46
+ python-version: "3.12"
47
+
48
+ - name: Install dependencies
49
+ run: uv sync --locked --group dev
50
+
51
+ # The hooks already cover ruff lint, ruff format and `uv lock --check`, so there is
52
+ # no separate lint step here. There is deliberately no type-checking step: type
53
+ # hints in this project are documentation, not a contract.
54
+ - name: Run pre-commit
55
+ run: uv run pre-commit run --all-files --show-diff-on-failure
56
+
57
+ # Validation only: builds the site into a throwaway directory and discards it. Nothing
58
+ # is published here -- docs.yml is the only workflow that touches gh-pages, and it only
59
+ # runs on a release. The point is to catch broken cross-references, dead anchors and
60
+ # missing nav entries on the pull request that introduces them rather than at release
61
+ # time.
62
+ docs:
63
+ name: Docs build
64
+ runs-on: ubuntu-latest
65
+ steps:
66
+ - uses: actions/checkout@v4
67
+ with:
68
+ fetch-depth: 0
69
+
70
+ - uses: astral-sh/setup-uv@v5
71
+ with:
72
+ enable-cache: true
73
+ python-version: "3.12"
74
+
75
+ - name: Install docs dependencies
76
+ run: uv sync --locked --group docs
77
+
78
+ - name: Build docs (strict)
79
+ run: uv run mkdocs build --strict --site-dir "${RUNNER_TEMP}/site"
80
+
81
+ test:
82
+ name: Tests (Python ${{ matrix.python-version }})
83
+ runs-on: ubuntu-latest
84
+ strategy:
85
+ fail-fast: false
86
+ matrix:
87
+ # Not 3.14: `datasets` pulls in `multiprocess`, which ships no cp314 wheels and
88
+ # fails to import there, and `datasets` is a hard runtime dependency.
89
+ python-version: ["3.12", "3.13"]
90
+ steps:
91
+ - uses: actions/checkout@v4
92
+ with:
93
+ fetch-depth: 0
94
+
95
+ - uses: astral-sh/setup-uv@v5
96
+ with:
97
+ enable-cache: true
98
+ python-version: ${{ matrix.python-version }}
99
+
100
+ - name: Install dependencies
101
+ run: uv sync --locked --group dev
102
+
103
+ # The offline suite: no Hub access, no real weights. See hub-tests.yml for those.
104
+ - name: Run tests
105
+ run: make test
106
+
107
+ - name: Upload coverage
108
+ uses: codecov/codecov-action@v5
109
+ with:
110
+ token: ${{ secrets.CODECOV_TOKEN }}
111
+ files: coverage.xml
112
+ fail_ci_if_error: true
113
+ disable_search: true
114
+ disable_telem: true
@@ -0,0 +1,93 @@
1
+ name: Publish docs to GitHub Pages
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+ inputs:
8
+ release_tag:
9
+ description: Git tag to publish docs for, for example v0.3.0
10
+ required: true
11
+ type: string
12
+
13
+ # Cancel any in-progress docs deploy for the same branch when a new push arrives.
14
+ concurrency:
15
+ group: ${{ github.workflow }}-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ permissions:
19
+ contents: write
20
+
21
+ jobs:
22
+ deploy:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ with:
27
+ fetch-depth: 0
28
+ fetch-tags: true
29
+
30
+ - name: Resolve docs tag and version
31
+ id: docs_ref
32
+ run: |
33
+ if [ "${{ github.event_name }}" = "release" ]; then
34
+ DOCS_GIT_TAG="${{ github.event.release.tag_name }}"
35
+ else
36
+ DOCS_GIT_TAG="${{ inputs.release_tag }}"
37
+ fi
38
+
39
+ if [ -z "${DOCS_GIT_TAG}" ]; then
40
+ echo "A release tag is required to publish docs." >&2
41
+ exit 1
42
+ fi
43
+
44
+ if ! git rev-parse --verify "refs/tags/${DOCS_GIT_TAG}" >/dev/null 2>&1; then
45
+ echo "Git tag '${DOCS_GIT_TAG}' does not exist." >&2
46
+ exit 1
47
+ fi
48
+
49
+ DOCS_VERSION="${DOCS_GIT_TAG#v}"
50
+ if [ -z "${DOCS_VERSION}" ] || [ "${DOCS_VERSION}" = "${DOCS_GIT_TAG}" ]; then
51
+ echo "Release tag must start with 'v'; got '${DOCS_GIT_TAG}'." >&2
52
+ exit 1
53
+ fi
54
+
55
+ echo "docs_git_tag=${DOCS_GIT_TAG}" >> "$GITHUB_OUTPUT"
56
+ echo "docs_version=${DOCS_VERSION}" >> "$GITHUB_OUTPUT"
57
+ # docs/hooks.py reads this to point every "View source on GitHub" link at the
58
+ # tag these docs were built from instead of at main.
59
+ echo "DOCS_SOURCE_REF=${DOCS_GIT_TAG}" >> "$GITHUB_ENV"
60
+
61
+ - name: Check out docs tag
62
+ run: git checkout "${{ steps.docs_ref.outputs.docs_git_tag }}"
63
+
64
+ # Docs *content* comes from the tag; the tooling that renders it always comes from
65
+ # main, so a rendering fix reaches published docs without re-tagging. Pinned to main
66
+ # rather than GITHUB_SHA because GITHUB_SHA is the tag commit on a release but the
67
+ # branch tip on a workflow_dispatch, which would make a manual rerun build a
68
+ # different site than the release did.
69
+ - name: Use current docs build tooling from main
70
+ run: |
71
+ git checkout origin/main -- \
72
+ pyproject.toml uv.lock mkdocs.yml docs/hooks.py docs/overrides
73
+
74
+ - uses: astral-sh/setup-uv@v5
75
+ with:
76
+ enable-cache: true
77
+
78
+ - name: Install docs dependencies
79
+ run: uv sync --locked --group docs
80
+
81
+ - name: Configure git identity
82
+ run: |
83
+ git config user.name "github-actions[bot]"
84
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
85
+
86
+ - name: Fetch gh-pages branch for mike
87
+ run: git fetch origin gh-pages --depth=1 || true
88
+
89
+ - name: Deploy release docs (tag -> version + set as latest)
90
+ run: |
91
+ VERSION="${{ steps.docs_ref.outputs.docs_version }}"
92
+ uv run mike deploy --push --update-aliases "${VERSION}" latest
93
+ uv run mike set-default --push latest
@@ -0,0 +1,57 @@
1
+ # The tests that reach the Hugging Face Hub, kept off the pull-request path on purpose: a
2
+ # Hub outage or a several-gigabyte download must never be what blocks a merge. Run them
3
+ # on a schedule and on demand instead.
4
+ name: Hub tests
5
+
6
+ on:
7
+ workflow_dispatch:
8
+ schedule:
9
+ # Mondays, 05:40 UTC
10
+ - cron: "40 5 * * 1"
11
+
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ tokenizers:
18
+ name: Hub tokenizer trims
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ with:
23
+ fetch-depth: 0
24
+
25
+ - uses: astral-sh/setup-uv@v5
26
+ with:
27
+ enable-cache: true
28
+ python-version: "3.12"
29
+
30
+ # --all-extras: the mT5 case needs trimbed[convert] to read a bare spiece.model.
31
+ - name: Install dependencies
32
+ run: uv sync --locked --all-extras --group dev
33
+
34
+ - name: Run Hub tokenizer tests
35
+ run: make test-network
36
+
37
+ models:
38
+ name: Hub model trims
39
+ runs-on: ubuntu-latest
40
+ # Minutes per case, and every one of them downloads a real checkpoint. A failure here
41
+ # is worth seeing but is not a reason to call the run red.
42
+ continue-on-error: true
43
+ steps:
44
+ - uses: actions/checkout@v4
45
+ with:
46
+ fetch-depth: 0
47
+
48
+ - uses: astral-sh/setup-uv@v5
49
+ with:
50
+ enable-cache: true
51
+ python-version: "3.12"
52
+
53
+ - name: Install dependencies
54
+ run: uv sync --locked --all-extras --group dev
55
+
56
+ - name: Run Hub model tests
57
+ run: make test-slow
@@ -0,0 +1,35 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ pypi-publish:
9
+ name: Build and Publish
10
+ runs-on: ubuntu-latest
11
+
12
+ environment: release
13
+
14
+ permissions:
15
+ id-token: write
16
+ contents: read
17
+
18
+ steps:
19
+ # fetch-depth: 0 so hatch-vcs sees the tag and builds a released version rather
20
+ # than a development one.
21
+ - uses: actions/checkout@v4
22
+ with:
23
+ fetch-depth: 0
24
+
25
+ - name: Install uv
26
+ uses: astral-sh/setup-uv@v5
27
+ with:
28
+ enable-cache: true
29
+
30
+ - name: Build package
31
+ run: uv build
32
+
33
+ # Trusted publishing: the OIDC token above stands in for an API token.
34
+ - name: Publish to PyPI
35
+ run: uv publish
@@ -0,0 +1,77 @@
1
+ .ruff_cache/
2
+ papers/
3
+ data/
4
+ configs/
5
+
6
+ # Python caches and bytecode
7
+ __pycache__/
8
+ *.py[cod]
9
+ *$py.class
10
+
11
+ # Virtual environments
12
+ .venv/
13
+ venv/
14
+ env/
15
+ # without slash because we are using a sym-link as the venv
16
+ # lives on scratch
17
+ .venv
18
+
19
+ # Build / packaging artifacts
20
+ build/
21
+ dist/
22
+ *.egg-info/
23
+
24
+ # Test / coverage artifacts
25
+ .pytest_cache/
26
+ .coverage
27
+ .coverage.*
28
+ coverage.xml
29
+ report.xml
30
+ htmlcov/
31
+
32
+ # uv's cache, relocated into the project directory by CI
33
+ .uv-cache/
34
+
35
+ # One venv per interpreter, written by `make test-matrix`
36
+ .venvs/
37
+
38
+ # mkdocs output; the published site lives on the gh-pages branch
39
+ /site
40
+
41
+ # Jupyter checkpoints
42
+ .ipynb_checkpoints/
43
+
44
+ # Training outputs and checkpoints
45
+ runs/
46
+ checkpoints/
47
+
48
+ # Model and large binary artifacts
49
+ *.pt
50
+ *.bin
51
+ *.safetensors
52
+
53
+ # System/editor files
54
+ .DS_Store
55
+ .vscode/
56
+ .idea/
57
+
58
+ # Logs
59
+ logs/
60
+ *.log
61
+
62
+ # Downloaded / derived data
63
+ data/dev_samples/
64
+
65
+ # Local planning notes
66
+ plan.md
67
+
68
+ # Claude Code local configuration
69
+ .claude/
70
+ CLAUDE.md
71
+ .agents/
72
+
73
+ # upstream bug reports
74
+ bugs/
75
+
76
+ # Trimmed artefacts (the default output_dir in README/examples)
77
+ trimmed/
@@ -0,0 +1,76 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v6.0.0
4
+ hooks:
5
+ - id: check-added-large-files
6
+ - id: check-case-conflict
7
+ - id: check-merge-conflict
8
+ - id: check-toml
9
+ - id: check-yaml
10
+ # Backs up .gitattributes: that pins what lands in the index, this catches CRLF in
11
+ # the working tree before it gets there.
12
+ - id: mixed-line-ending
13
+ args: [--fix=lf]
14
+ - id: end-of-file-fixer
15
+ - id: trailing-whitespace
16
+
17
+ - repo: local
18
+ hooks:
19
+ # mkdocstrings renders docstrings as Markdown, so a Sphinx role survives as literal
20
+ # ":class:" text on the site. Use [`Name`][full.dotted.path].
21
+ - id: no-rst-roles
22
+ name: no Sphinx/RST roles in docstrings
23
+ language: pygrep
24
+ entry: ':(class|meth|func|mod|attr|obj|exc|data|ref):`'
25
+ types: [python]
26
+ files: ^src/trimbed/
27
+ # Griffe only documents an attribute that carries a real docstring, so a "#:"
28
+ # comment renders as nothing at all. Put a """docstring""" under the assignment.
29
+ - id: no-sphinx-attribute-comments
30
+ name: no Sphinx "#:" attribute comments
31
+ language: pygrep
32
+ entry: '^\s*#:'
33
+ types: [python]
34
+ files: ^src/trimbed/
35
+ # Only the staged files are passed in, restricted to our own source dirs by
36
+ # `files` below. Linting whole directories instead makes pre-commit's
37
+ # stash-and-restore of unstaged edits conflict with the hooks' own auto-fixes.
38
+ - id: ruff-lint
39
+ name: ruff lint
40
+ entry: uv run --frozen ruff check --fix --exit-non-zero-on-fix --force-exclude
41
+ language: system
42
+ types_or: [python, pyi]
43
+ files: ^(src/trimbed|tests|examples|docs)/
44
+ # ruff parallelises internally, so hand it the whole file list in one process
45
+ # rather than letting pre-commit fan it out across several.
46
+ require_serial: true
47
+ - id: ruff-format
48
+ name: ruff format
49
+ entry: uv run --frozen ruff format --force-exclude
50
+ language: system
51
+ # ruff formats the Python blocks inside Markdown too, which is what keeps the
52
+ # examples in docs/ from drifting out of the project's own style.
53
+ types_or: [python, pyi, markdown]
54
+ files: ^(src/trimbed|tests|examples|docs)/
55
+ require_serial: true
56
+ # `uv` is already on PATH and every other hook runs under `uv run --frozen`, so a
57
+ # pyproject.toml edit without a matching re-lock breaks all of them at once.
58
+ - id: uv-lock-check
59
+ name: uv lock check
60
+ entry: uv lock --check
61
+ language: system
62
+ files: ^(pyproject\.toml|uv\.lock)$
63
+ pass_filenames: false
64
+
65
+ # Pre-push only, and only when Python actually changed: runs the offline suite
66
+ # against every interpreter in the CI matrix, so version-specific breakage is caught
67
+ # here instead of by a red job after the push. Requires
68
+ # `pre-commit install --hook-type pre-push` once per clone; `pre-commit run
69
+ # --all-files` (what CI runs) skips it, since that defaults to the pre-commit stage.
70
+ - id: test-matrix
71
+ name: pytest on every CI Python version
72
+ entry: make test-matrix
73
+ language: system
74
+ stages: [pre-push]
75
+ types: [python]
76
+ pass_filenames: false
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,103 @@
1
+ # Contributing to trimbed
2
+
3
+ ## Setup
4
+
5
+ The project is managed with [uv](https://docs.astral.sh/uv/). One command gets you a
6
+ working environment with every development tool:
7
+
8
+ ```bash
9
+ uv sync --locked --group dev
10
+ uv run pre-commit install
11
+ uv run pre-commit install --hook-type pre-push
12
+ ```
13
+
14
+ The second `pre-commit install` is not optional if you want the matrix check below to run.
15
+ It is a separate hook stage, so it needs its own registration once per clone.
16
+
17
+ Python 3.12 or 3.13. Not 3.14 yet: `datasets` pulls in `multiprocess`, which has no cp314
18
+ wheels and fails to import there, so `requires-python` caps at `<3.14`.
19
+
20
+ Runtime dependencies are capped at the next minor rather than the next major, because
21
+ trimbed works against internals of `transformers`, `tokenizers`, `skeletoken` and `torch`
22
+ that move on minor releases. Raising a cap is its own change, with `make test-all` behind
23
+ it, not something to fold into an unrelated pull request.
24
+
25
+ ## The checks
26
+
27
+ ```bash
28
+ make style # ruff check --fix + ruff format
29
+ make quality # the non-mutating equivalent
30
+ make test # the offline suite, with coverage
31
+ make test-network # + the Hub tokenizer trims
32
+ make test-slow # + the Hub model trims; minutes per case
33
+ make test-all # everything
34
+ make build-docs # mkdocs build --strict
35
+ make serve-docs # live preview on localhost
36
+ ```
37
+
38
+ Run `make style`, `make test` and `make build-docs` before finishing any change.
39
+
40
+ The suite is offline by default and covers **100% of statements and branches**. Keep it
41
+ there. The gate is Codecov rather than a number in `pyproject.toml`: `codecov.yml` fails a
42
+ pull request whose project coverage drops more than 3% against the base branch, or whose
43
+ own changed lines are less than 90% covered. A handful of genuinely unreachable guards
44
+ carry `# pragma: no cover` with a comment saying which invariant makes them unreachable.
45
+ Prefer a real test over a new pragma.
46
+
47
+ `make test-matrix` runs the offline suite once per interpreter in the CI matrix. It reads
48
+ the version list out of `.github/workflows/ci.yml`, so the two cannot drift. It is also the
49
+ pre-push hook, which is why version-specific breakage is caught before it becomes a red job.
50
+
51
+ ## Style
52
+
53
+ - Python 3.12+: `|` unions, `type` aliases, PEP 695 generics, `Self`.
54
+ - Concise Google-style docstrings on every public function, enforced by ruff's `D` rules.
55
+ - Type hints are documentation, not a contract. There is no mypy, no ruff `ANN` rules and
56
+ no `py.typed`, and none of the three is wanted.
57
+ - Raise built-in exceptions. `MissingDependencyError` is the only custom one, and it
58
+ subclasses `ImportError`.
59
+ - No `# noqa` in the source. A rule worth suppressing in more than one place is ignored in
60
+ `pyproject.toml`, with the reason next to it.
61
+ - Comments explain *why*, not what.
62
+
63
+ ## Documentation
64
+
65
+ The site is MkDocs Material with mkdocstrings. Guides live in `docs/*.md`; the API
66
+ reference is one stub per module under `docs/api/`, each holding a title and a single
67
+ `::: trimbed.<module>` directive, and each listed in `mkdocs.yml`'s `nav`.
68
+
69
+ Two rules keep it navigable:
70
+
71
+ - **Cross-reference trimbed's own symbols** as ``[`Name`][trimbed.module.Name]``. Sphinx
72
+ roles (`` :class:`Name` ``) render as literal text on the site and are blocked by a
73
+ pre-commit hook. External names (`save_pretrained`, `tokenizers.Tokenizer`) stay as
74
+ plain backticks.
75
+ - **Document an attribute with a docstring**, not a `#:` comment. Griffe only picks up the
76
+ former; the latter renders as nothing at all. Also blocked by a pre-commit hook.
77
+
78
+ Adding a module means adding its stub page and its nav entry in the same pass.
79
+ `tests/test_docs.py` fails if either is missing, and `mkdocs build --strict` fails on any
80
+ cross-reference that does not resolve.
81
+
82
+ `examples/` and `tests/` must be kept in sync with `src/` the same way.
83
+ `tests/test_examples.py` executes every example against tiny in-process fixtures, so a
84
+ stale example fails the suite rather than rotting quietly.
85
+
86
+ ## Pull requests
87
+
88
+ CI runs on every pull request to `main`: pre-commit over the whole tree, a strict docs
89
+ build, and the offline suite on Python 3.12 and 3.13 with coverage uploaded to Codecov.
90
+ The Hub tests are a separate scheduled workflow, deliberately off the pull-request path so
91
+ that a Hub outage or a several-gigabyte download never blocks a merge. You can run them on
92
+ demand from the Actions tab.
93
+
94
+ ## Releases
95
+
96
+ Versions come from git tags through hatch-vcs; nothing is hand-edited.
97
+
98
+ 1. Tag the commit `vX.Y.Z` and push the tag.
99
+ 2. Publish a GitHub Release for it.
100
+
101
+ That fires two workflows in parallel: `publish.yml` builds and uploads to PyPI through
102
+ trusted publishing, and `docs.yml` deploys the docs for that version with mike and moves
103
+ the `latest` alias. The tag must start with `v`, which the docs workflow checks.