nano-laser 0.1.0__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.
@@ -0,0 +1,29 @@
1
+ name: PR Checks
2
+
3
+ concurrency:
4
+ group: ${{ github.workflow }}-${{ github.ref }}
5
+ cancel-in-progress: true
6
+
7
+ on:
8
+ pull_request:
9
+ branches:
10
+ - main
11
+ paths:
12
+ - 'src/nano_laser/**'
13
+
14
+ jobs:
15
+ changelog:
16
+ name: CHANGELOG
17
+ runs-on: ubuntu-latest
18
+ if: github.event_name == 'pull_request'
19
+
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ with:
23
+ fetch-depth: 0
24
+
25
+ - name: Check that CHANGELOG has been updated
26
+ run: |
27
+ # If this step fails, this means you haven't updated the CHANGELOG.md
28
+ # file with notes on your contribution.
29
+ git diff --name-only $(git merge-base origin/main HEAD) | grep '^CHANGELOG.md$' && echo "Thanks for helping keep our CHANGELOG up-to-date!"
@@ -0,0 +1,18 @@
1
+ name: Prek checks
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ prek:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.13"
17
+
18
+ - uses: j178/prek-action@v1
@@ -0,0 +1,50 @@
1
+ name: publish
2
+
3
+ # Publishes to PyPI when a release tag is pushed, with trusted publishing: no API token is stored.
4
+ # PyPI side: add a trusted publisher for BurkimbIA/nano-laser, workflow publish.yml, environment pypi.
5
+ on:
6
+ push:
7
+ tags: ["v*.*.*"]
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v5
17
+ with:
18
+ version: "latest"
19
+
20
+ - name: Check that the tag is the project version
21
+ run: |
22
+ version="$(uv version --short)"
23
+ if [ "${GITHUB_REF_NAME}" != "v${version}" ]; then
24
+ echo "Tag ${GITHUB_REF_NAME} does not match project.version ${version}" >&2
25
+ exit 1
26
+ fi
27
+
28
+ - name: Build
29
+ run: uv build
30
+
31
+ - uses: actions/upload-artifact@v4
32
+ with:
33
+ name: dist
34
+ path: dist/
35
+
36
+ publish:
37
+ needs: build
38
+ runs-on: ubuntu-latest
39
+ environment:
40
+ name: pypi
41
+ url: https://pypi.org/project/nano-laser/
42
+ permissions:
43
+ id-token: write
44
+ steps:
45
+ - uses: actions/download-artifact@v4
46
+ with:
47
+ name: dist
48
+ path: dist/
49
+
50
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,42 @@
1
+ name: tests
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ jobs:
9
+ tests:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout code
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up Python
17
+ uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.13"
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v5
23
+ with:
24
+ enable-cache: false
25
+ version: "latest"
26
+
27
+ - name: Cache uv
28
+ uses: actions/cache@v4
29
+ with:
30
+ path: ~/.cache/uv
31
+ key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
32
+ restore-keys: |
33
+ uv-${{ runner.os }}-
34
+
35
+ - name: Install just
36
+ uses: extractions/setup-just@v3
37
+
38
+ - name: Install dependencies (dev)
39
+ run: just install
40
+
41
+ - name: Run tests
42
+ run: just test
@@ -0,0 +1,10 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
@@ -0,0 +1,23 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/uv-pre-commit
3
+ rev: 0.13.4
4
+ hooks:
5
+ - id: uv-lock
6
+
7
+ - repo: https://github.com/astral-sh/ruff-pre-commit
8
+ rev: v0.14.10
9
+ hooks:
10
+ - id: ruff
11
+ args: [ --fix ]
12
+ types_or: [ python, pyi ]
13
+
14
+ - repo: https://github.com/pre-commit/pre-commit-hooks
15
+ rev: v6.0.0
16
+ hooks:
17
+ - id: check-merge-conflict
18
+ - id: mixed-line-ending
19
+
20
+ exclude: |
21
+ unsloth_compiled_cache/.*
22
+ |llms-finetune/gemma3/unsloth_compiled_cache/.*
23
+ |.*\.ipynb$
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## Unreleased
9
+
10
+ ## v0.1.0 - 2026-10-26
11
+
12
+ ### Added
13
+
14
+ - `nano_laser`: LASER2 and LASER3 encoders in plain torch, a drop-in replacement for `laser_encoders` 0.0.2 without fairseq.
15
+ - Parity tests against embeddings from `laser_encoders`, run when the models are cached locally.
16
+ - A Colab notebook comparing `nano_laser` with `laser_encoders` on all 147 LASER3 models; all match.
17
+ - A workflow that publishes to PyPI with trusted publishing when a `vX.Y.Z` tag is pushed.
18
+
19
+ ### Security
20
+
21
+ - Bumped the `uv-pre-commit` hook to uv 0.13.4. Earlier versions ship the compromised emSign ECC Root CA - G3 root, which certifi 2026.10.19 and uv 0.13.4 remove.
@@ -0,0 +1,8 @@
1
+ Contributing
2
+ ============
3
+
4
+ Unless you explicitly state otherwise, any contribution intentionally submitted
5
+ for inclusion in the work by you shall be licensed as in [LICENSE](LICENSE),
6
+ without any additional terms or conditions.
7
+
8
+ See [the readme](README.md#contributing) for contribution workflow suggestions.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, BurkimbIA
4
+ Copyright (c) Facebook, Inc. and its affiliates (LASER, the code adapted in nano_laser)
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.5
2
+ Name: nano-laser
3
+ Version: 0.1.0
4
+ Summary: LASER sentence encoders in plain torch: a drop-in replacement for laser_encoders, without fairseq
5
+ Author: Facebook AI Research
6
+ Author-email: MNIKIEMA <nikiemamahamadi01@gmail.com>, Salif Sawadogo <sawallesalfo@gmail.com>
7
+ License-Expression: BSD-3-Clause
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: numpy>=1.21.3
11
+ Requires-Dist: sacremoses==0.1.0
12
+ Requires-Dist: sentencepiece>=0.1.99
13
+ Requires-Dist: torch>=2.5
14
+ Requires-Dist: unicategories>=0.1.2
15
+ Description-Content-Type: text/markdown
16
+
17
+ # nano-laser
18
+
19
+ The [LASER](https://github.com/facebookresearch/LASER) sentence encoders (LASER2 and the LASER3 language
20
+ models) in plain torch. It is a drop-in replacement for [`laser_encoders`](https://pypi.org/project/laser-encoders/),
21
+ without fairseq: the same API, the same checkpoints, the same embeddings.
22
+
23
+ ## Install
24
+
25
+ The package is not on PyPI yet; install it from GitHub:
26
+
27
+ ```bash
28
+ pip install git+https://github.com/BurkimbIA/nano-laser.git
29
+ # or, in a uv project
30
+ uv add git+https://github.com/BurkimbIA/nano-laser.git
31
+ ```
32
+
33
+ Append `@<commit or tag>` to the URL to pin a version. It needs Python ≥ 3.10 and torch ≥ 2.5.
34
+
35
+ ## Usage
36
+
37
+ Replace the import, nothing else:
38
+
39
+ ```python
40
+ from nano_laser import LaserEncoderPipeline # was: from laser_encoders import LaserEncoderPipeline
41
+
42
+ encoder = LaserEncoderPipeline(lang="mos_Latn")
43
+ embeddings = encoder.encode_sentences(["Laafi bala?"], normalize_embeddings=True) # (1, 1024)
44
+ ```
45
+
46
+ `initialize_tokenizer`, `initialize_encoder`, `SentenceEncoder`, `LaserTokenizer` and the module layout
47
+ (`nano_laser.models`, `nano_laser.laser_tokenizer`, `nano_laser.download_models`, `nano_laser.language_list`)
48
+ mirror `laser_encoders`. The models are downloaded from the same place into the same cache,
49
+ `~/.cache/laser_encoders`, so models already downloaded by `laser_encoders` are reused.
50
+
51
+ Like `laser_encoders`, the encoder takes one sentence per batch by default. Batch on GPU with the
52
+ `SentenceEncoder` options, which `initialize_encoder` and `LaserEncoderPipeline` now pass on:
53
+
54
+ ```python
55
+ encoder = LaserEncoderPipeline(lang="dyu_Latn", max_sentences=64) # or max_tokens=..., cpu=True, fp16=True
56
+ ```
57
+
58
+ ## Parity
59
+
60
+ Against `laser_encoders` 0.0.2 (fairseq 0.12.2, torch 2.1.2), on 85 sentences of 0 to 2,000 tokens:
61
+
62
+ | model | max abs. difference (cpu) | min cosine (cpu and gpu, batched or not) |
63
+ | --- | --- | --- |
64
+ | LASER2 (LSTM) | 1.3e-6 | 0.9999996 |
65
+ | LASER3 mos_Latn, dyu_Latn, fuv_Latn (transformer) | 1.4e-5 | 0.9999998 |
66
+
67
+ Every LASER3 model is also checked, on a Colab T4 with 18 sentences, one at a time and in batches of 8
68
+ ([`notebooks/check_laser3.ipynb`](notebooks/check_laser3.ipynb)): all 147 give the same tokens and embeddings,
69
+ with a min cosine of 0.9999997 and a max abs. difference of 2.0e-5. They come in three kinds:
70
+
71
+ | LASER3 models | languages |
72
+ | --- | --- |
73
+ | 12-layer transformer, `maxpool` pooling | 135 |
74
+ | LSTM, like LASER2 | 10: `als_Latn`, `cym_Latn`, `hye_Armn`, `kat_Geor`, `khk_Cyrl`, `khm_Khmr`, `ltz_Latn`, `mlt_Latn`, `quy_Latn`, `ydd_Hebr` |
75
+ | 12-layer transformer, `cls` pooling on a leading `<s>` | 2: `lij_Latn`, `wol_Latn` |
76
+
77
+ The results per language are in [`scripts/results/laser3_parity.jsonl`](scripts/results/laser3_parity.jsonl).
78
+
79
+ `tests/test_parity.py` checks this against `tests/data/reference.npz` whenever the models are in the cache
80
+ (or in `LASER_MODEL_DIR`); it is skipped otherwise, so CI downloads nothing.
81
+
82
+ ## Differences from laser_encoders
83
+
84
+ - No fairseq: the transformer encoder and fairseq's `Dictionary` are re-implemented in `nano_laser/models.py`.
85
+ - Checkpoints load with `torch.load(weights_only=True)`: they only hold tensors and an `argparse.Namespace`.
86
+ - LASER3 works with `max_sentences > 1`. In `laser_encoders` on torch ≥ 1.12, fairseq's BetterTransformer path fails on
87
+ left-padded batches (`RuntimeError: Mask Type should be defined`).
88
+ - With `sentemb_criterion="cls"`, each sentence of a batch gets its own `<s>` state whatever the padding side.
89
+ `laser_encoders` takes the `<s>` states in position order, which is only right when they share a position:
90
+ true for the two `cls` models (`lij_Latn`, `wol_Latn`), which pad on the right.
91
+ - Importing the package does not call `logging.basicConfig`, and a failed download raises instead of saving the
92
+ error page as the model.
93
+
94
+ ## Development
95
+
96
+ ```bash
97
+ just install # uv sync and the pre-commit hooks
98
+ just test
99
+ just lint
100
+ ```
101
+
102
+ To regenerate the parity reference, see `tests/data/make_reference.py`.
103
+
104
+ ## License
105
+
106
+ [BSD 3-Clause](LICENSE), Copyright (c) 2026, BurkimbIA. The code adapted from LASER keeps its copyright,
107
+ Copyright (c) Facebook, Inc. and its affiliates, also under the BSD license; each such file says so in its header.
108
+ The LASER model weights are downloaded from Facebook and are not covered by this license.
@@ -0,0 +1,92 @@
1
+ # nano-laser
2
+
3
+ The [LASER](https://github.com/facebookresearch/LASER) sentence encoders (LASER2 and the LASER3 language
4
+ models) in plain torch. It is a drop-in replacement for [`laser_encoders`](https://pypi.org/project/laser-encoders/),
5
+ without fairseq: the same API, the same checkpoints, the same embeddings.
6
+
7
+ ## Install
8
+
9
+ The package is not on PyPI yet; install it from GitHub:
10
+
11
+ ```bash
12
+ pip install git+https://github.com/BurkimbIA/nano-laser.git
13
+ # or, in a uv project
14
+ uv add git+https://github.com/BurkimbIA/nano-laser.git
15
+ ```
16
+
17
+ Append `@<commit or tag>` to the URL to pin a version. It needs Python ≥ 3.10 and torch ≥ 2.5.
18
+
19
+ ## Usage
20
+
21
+ Replace the import, nothing else:
22
+
23
+ ```python
24
+ from nano_laser import LaserEncoderPipeline # was: from laser_encoders import LaserEncoderPipeline
25
+
26
+ encoder = LaserEncoderPipeline(lang="mos_Latn")
27
+ embeddings = encoder.encode_sentences(["Laafi bala?"], normalize_embeddings=True) # (1, 1024)
28
+ ```
29
+
30
+ `initialize_tokenizer`, `initialize_encoder`, `SentenceEncoder`, `LaserTokenizer` and the module layout
31
+ (`nano_laser.models`, `nano_laser.laser_tokenizer`, `nano_laser.download_models`, `nano_laser.language_list`)
32
+ mirror `laser_encoders`. The models are downloaded from the same place into the same cache,
33
+ `~/.cache/laser_encoders`, so models already downloaded by `laser_encoders` are reused.
34
+
35
+ Like `laser_encoders`, the encoder takes one sentence per batch by default. Batch on GPU with the
36
+ `SentenceEncoder` options, which `initialize_encoder` and `LaserEncoderPipeline` now pass on:
37
+
38
+ ```python
39
+ encoder = LaserEncoderPipeline(lang="dyu_Latn", max_sentences=64) # or max_tokens=..., cpu=True, fp16=True
40
+ ```
41
+
42
+ ## Parity
43
+
44
+ Against `laser_encoders` 0.0.2 (fairseq 0.12.2, torch 2.1.2), on 85 sentences of 0 to 2,000 tokens:
45
+
46
+ | model | max abs. difference (cpu) | min cosine (cpu and gpu, batched or not) |
47
+ | --- | --- | --- |
48
+ | LASER2 (LSTM) | 1.3e-6 | 0.9999996 |
49
+ | LASER3 mos_Latn, dyu_Latn, fuv_Latn (transformer) | 1.4e-5 | 0.9999998 |
50
+
51
+ Every LASER3 model is also checked, on a Colab T4 with 18 sentences, one at a time and in batches of 8
52
+ ([`notebooks/check_laser3.ipynb`](notebooks/check_laser3.ipynb)): all 147 give the same tokens and embeddings,
53
+ with a min cosine of 0.9999997 and a max abs. difference of 2.0e-5. They come in three kinds:
54
+
55
+ | LASER3 models | languages |
56
+ | --- | --- |
57
+ | 12-layer transformer, `maxpool` pooling | 135 |
58
+ | LSTM, like LASER2 | 10: `als_Latn`, `cym_Latn`, `hye_Armn`, `kat_Geor`, `khk_Cyrl`, `khm_Khmr`, `ltz_Latn`, `mlt_Latn`, `quy_Latn`, `ydd_Hebr` |
59
+ | 12-layer transformer, `cls` pooling on a leading `<s>` | 2: `lij_Latn`, `wol_Latn` |
60
+
61
+ The results per language are in [`scripts/results/laser3_parity.jsonl`](scripts/results/laser3_parity.jsonl).
62
+
63
+ `tests/test_parity.py` checks this against `tests/data/reference.npz` whenever the models are in the cache
64
+ (or in `LASER_MODEL_DIR`); it is skipped otherwise, so CI downloads nothing.
65
+
66
+ ## Differences from laser_encoders
67
+
68
+ - No fairseq: the transformer encoder and fairseq's `Dictionary` are re-implemented in `nano_laser/models.py`.
69
+ - Checkpoints load with `torch.load(weights_only=True)`: they only hold tensors and an `argparse.Namespace`.
70
+ - LASER3 works with `max_sentences > 1`. In `laser_encoders` on torch ≥ 1.12, fairseq's BetterTransformer path fails on
71
+ left-padded batches (`RuntimeError: Mask Type should be defined`).
72
+ - With `sentemb_criterion="cls"`, each sentence of a batch gets its own `<s>` state whatever the padding side.
73
+ `laser_encoders` takes the `<s>` states in position order, which is only right when they share a position:
74
+ true for the two `cls` models (`lij_Latn`, `wol_Latn`), which pad on the right.
75
+ - Importing the package does not call `logging.basicConfig`, and a failed download raises instead of saving the
76
+ error page as the model.
77
+
78
+ ## Development
79
+
80
+ ```bash
81
+ just install # uv sync and the pre-commit hooks
82
+ just test
83
+ just lint
84
+ ```
85
+
86
+ To regenerate the parity reference, see `tests/data/make_reference.py`.
87
+
88
+ ## License
89
+
90
+ [BSD 3-Clause](LICENSE), Copyright (c) 2026, BurkimbIA. The code adapted from LASER keeps its copyright,
91
+ Copyright (c) Facebook, Inc. and its affiliates, also under the BSD license; each such file says so in its header.
92
+ The LASER model weights are downloaded from Facebook and are not covered by this license.
@@ -0,0 +1,100 @@
1
+ # Release process
2
+
3
+ This guide describes how to release `nano-laser`.
4
+
5
+ ## Versioning
6
+
7
+ The project follows [Semantic Versioning](https://semver.org/):
8
+
9
+ - `MAJOR`: breaking API, configuration, or workflow changes.
10
+ - `MINOR`: backwards-compatible features.
11
+ - `PATCH`: backwards-compatible bug fixes, dependency fixes, and security patches.
12
+
13
+ The release version is the value of `project.version` in [`pyproject.toml`](pyproject.toml). The release tag and changelog heading must use the same version, formatted as `vX.Y.Z`.
14
+
15
+ ## Prepare the release
16
+
17
+ Start from an up-to-date `main` branch and create a release branch:
18
+
19
+ ```bash
20
+ git checkout main
21
+ git pull --ff-only origin main
22
+ git checkout -b chore/release-vX.Y.Z
23
+ ```
24
+
25
+ Choose the appropriate version bump:
26
+
27
+ ```bash
28
+ uv version --bump major
29
+ uv version --bump minor
30
+ uv version --bump patch
31
+ ```
32
+
33
+ This updates `pyproject.toml` and `uv.lock`. Do not edit only one of them.
34
+
35
+ Update `CHANGELOG.md` as follows:
36
+
37
+ 1. Move the release-ready entries from `Unreleased` into `## vX.Y.Z - YYYY-MM-DD`.
38
+ 2. Add a new empty `## Unreleased` section directly below the changelog intro.
39
+ 3. Review the entries for user-facing accuracy and remove internal-only detail that does not help users upgrade.
40
+
41
+ ## Verify and open the release PR
42
+
43
+ Run the release checks:
44
+
45
+ ```bash
46
+ uv lock --check
47
+ just format
48
+ just lint
49
+ just typecheck
50
+ just test
51
+ just pre-commit
52
+ ```
53
+
54
+ Review the resulting diff. It should include the intended version change, lockfile update, and changelog update, with any relevant documentation or configuration changes.
55
+
56
+ ```bash
57
+ git add pyproject.toml uv.lock CHANGELOG.md
58
+ git commit -m "chore: release vX.Y.Z"
59
+ git push -u origin chore/release-vX.Y.Z
60
+ ```
61
+
62
+ Open a pull request targeting `main` and merge it after review and all enabled CI checks pass.
63
+
64
+ ## Tag and publish
65
+
66
+ After the release PR is merged, tag the exact `main` commit with an annotated tag, then push it:
67
+
68
+ ```bash
69
+ git checkout main
70
+ git pull --ff-only origin main
71
+ git tag -a vX.Y.Z -m "Release vX.Y.Z"
72
+ git push origin vX.Y.Z
73
+ ```
74
+
75
+ Create the GitHub Release using generated notes, then review and edit the notes before publishing:
76
+
77
+ ```bash
78
+ gh release create vX.Y.Z --title "vX.Y.Z" --generate-notes
79
+ ```
80
+
81
+ ## Post-release checks
82
+
83
+ - Confirm the Git tag points to the merged release commit.
84
+ - Confirm the GitHub Release title, version, and notes are correct.
85
+ - Confirm that `CHANGELOG.md` has an empty `Unreleased` section for the next change.
86
+
87
+ ## Publish to PyPI
88
+
89
+ Pushing the tag runs [`publish.yml`](.github/workflows/publish.yml): it checks that the tag is `v` + `project.version`,
90
+ builds the sdist and wheel with `uv build`, and publishes them to PyPI through trusted publishing.
91
+
92
+ One-time setup, before the first release:
93
+
94
+ - On PyPI, add a trusted publisher for the project `nano-laser`: owner `BurkimbIA`, repository `nano-laser`,
95
+ workflow `publish.yml`, environment `pypi` (a "pending publisher" if the project does not exist yet).
96
+ - On GitHub, create the `pypi` environment (Settings → Environments), optionally with required reviewers so that
97
+ each upload waits for an approval.
98
+
99
+ After the workflow succeeds, confirm that https://pypi.org/project/nano-laser/ shows the new version and that
100
+ `pip install nano-laser==X.Y.Z` installs it.
@@ -0,0 +1,35 @@
1
+ # display help information
2
+ default:
3
+ @just --list
4
+
5
+
6
+ # clean the project
7
+ clean:
8
+ rm -rf .venv/
9
+
10
+
11
+ # install the dependencies
12
+ install:
13
+ uv sync --all-groups
14
+ uvx prek install
15
+
16
+
17
+ # format with ruff
18
+ format:
19
+ uvx ruff format
20
+
21
+ # lint with ruff
22
+ lint:
23
+ uvx ruff check
24
+
25
+ # Typecheck using ty
26
+ typecheck:
27
+ uvx ty check
28
+
29
+ # Run pre-commit [lint, format]"
30
+ pre-commit: lint format
31
+ uvx prek run
32
+
33
+ # test with pytest
34
+ test:
35
+ uv run pytest