alphex 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.
alphex-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marielle Russo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
alphex-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.4
2
+ Name: alphex
3
+ Version: 0.1.0
4
+ Summary: Biological sequence alphabet orderings and letter-preserving conversions between them.
5
+ Keywords: bioinformatics,protein,alphabet,tokenizer,esm,proteinmpnn,alphafold
6
+ Author: Marielle Russo
7
+ Author-email: Marielle Russo <67157875+maraxen@users.noreply.github.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
17
+ Classifier: Typing :: Typed
18
+ Requires-Dist: numpy
19
+ Requires-Dist: alphex[cli,mcp] ; extra == 'agent'
20
+ Requires-Dist: cyclopts>=3 ; extra == 'cli'
21
+ Requires-Dist: jax ; extra == 'jax'
22
+ Requires-Dist: fastmcp>=2 ; extra == 'mcp'
23
+ Requires-Dist: cisternal>=0.1.3 ; python_full_version >= '3.13' and extra == 'mcp'
24
+ Requires-Python: >=3.11
25
+ Project-URL: Homepage, https://github.com/maraxen/alphex
26
+ Project-URL: Repository, https://github.com/maraxen/alphex
27
+ Project-URL: Issues, https://github.com/maraxen/alphex/issues
28
+ Provides-Extra: agent
29
+ Provides-Extra: cli
30
+ Provides-Extra: jax
31
+ Provides-Extra: mcp
32
+ Description-Content-Type: text/markdown
33
+
34
+ # alphex
35
+
36
+ Biological sequence alphabet orderings, and letter-preserving conversions between them.
37
+
38
+ ```bash
39
+ uv add alphex
40
+ ```
41
+
42
+ ## Why
43
+
44
+ Three different amino-acid orderings are in common use, under at least five different names:
45
+
46
+ | ordering | letters | used by |
47
+ |---|---|---|
48
+ | ProteinMPNN | `ACDEFGHIKLMNPQRSTVWY` | ProteinMPNN, and most "canonical" alphabets |
49
+ | AlphaFold | `ARNDCQEGHILKMFPSTWYV` | AlphaFold `restypes`, LG/JTT substitution models, IQ-TREE |
50
+ | ESM | `LAGVSERTIDPKQNFYMHWC` | ESM3 / ESM-C, at offset 4 |
51
+
52
+ ProteinMPNN and AlphaFold order have **exactly three fixed points** — `A`, `S` and `T`. Build a
53
+ lookup table from one and label it with the other, and 17 of 20 residues are silently permuted.
54
+ The result is shape-valid, dtype-valid, and wrong; nothing raises. That bug has shipped.
55
+
56
+ An ecosystem census that motivated this package found those three orderings declared at **30
57
+ sites across four repositories**, in six different sentinel conventions (no sentinel; `X` at 20;
58
+ gap at 20; gap at 0 with residues shifted to 1; gap at 20 with `X` at 21; `X` at 20 with gap at
59
+ 21). Every site was an independent opportunity to get it wrong.
60
+
61
+ `alphex` holds one declaration of each, and one way to convert between them.
62
+
63
+ ## What it does
64
+
65
+ ```python
66
+ from alphex import Policy, SpecialKind, known, perm
67
+
68
+ # A table from ProteinMPNN order (gap at 20) into ESM's vocabulary.
69
+ table = perm(known.MPNN_GAP_21, known.ESM_C, policy=Policy.RAISE)
70
+
71
+ table.shape # (21,) -- the whole source domain, never just the 20 residues
72
+ int(table[0]) # 5 -- 'A'
73
+ int(table[20]) # 30 -- the gap, which ESM declares
74
+ ```
75
+
76
+ Four entry points, because four genuinely different things get converted and using the wrong
77
+ one scrambles letter identity silently:
78
+
79
+ | | |
80
+ |---|---|
81
+ | `relation(src, dst)` | classify first: `IDENTITY`, `EXTENSION`, `PERMUTATION`, `INCOMPATIBLE`, plus `lossy` and which letters moved |
82
+ | `perm(src, dst, *, policy)` | build the lookup table |
83
+ | `convert(codes, src, dst, *, policy)` | relabel sequence codes |
84
+ | `reindex(data, src, dst, *, policy, axes)` | move a posterior or substitution-matrix axis |
85
+
86
+ ## The design commitments
87
+
88
+ **Letters, not indices.** The invariant is that the amino acid at each position is unchanged and
89
+ only its integer moves. Index equality cannot catch this bug class — the broken table and the
90
+ correct one both round-trip perfectly within themselves.
91
+
92
+ **Every index means something.** A declaration must account for every index in `[0, size)` as a
93
+ residue, a named special, or explicitly `unclaimed`. A table shorter than its domain is how a
94
+ JAX gather clamped a gap index onto valine.
95
+
96
+ **No silent defaults.** `policy` is keyword-only and has no default, anywhere. It is keyed per
97
+ `SpecialKind`, so ESM's eight specials cannot collapse onto one destination index without you
98
+ writing that down.
99
+
100
+ **No in-band sentinels.** `Policy.MASK` returns a `MaskedPerm`, a distinct type — because every
101
+ in-band marker is a valid index somewhere. `-1` selects the last element of the destination
102
+ axis; `size` is clamped rather than rejected by JAX.
103
+
104
+ **Declarations carry provenance.** `citation` is required and must be non-empty. An ordering
105
+ without a source is how one gets relabelled.
106
+
107
+ **Names are not identity.** Two `Alphabet`s compare equal when their index spaces match,
108
+ whatever they are called. That is what makes an alias collision detectable instead of invisible.
109
+
110
+ ## CLI and MCP
111
+
112
+ Optional surfaces, for asking the two questions whose absence let a mislabelled table ship —
113
+ "which ordering is this?" and "what does the table look like?" — without writing a script.
114
+
115
+ ```bash
116
+ uv add "alphex[cli]" # or [mcp], or [agent] for both
117
+
118
+ alphex list # every shipped declaration
119
+ alphex show MPNN_GAP_21 # symbols, specials, citation, warnings
120
+ alphex relation MPNN_20 AF_20 # classify before converting
121
+ alphex perm MPNN_GAP_21 ESM_C # the table, plus a letter-preservation check
122
+ alphex lint # declarations with conflated sentinels
123
+ ```
124
+
125
+ Every command takes `--json-out`. The MCP server (`alphex-mcp`) exposes the same five as tools —
126
+ `list_alphabets`, `show_alphabet`, `relation`, `perm`, `lint` — registered through
127
+ [cisternal](https://pypi.org/project/cisternal/), which also ships the `using-alphex` skill and
128
+ project rules to Claude Code, Cursor, Copilot and Antigravity via `.praxia/manifest.toml`.
129
+
130
+ Both surfaces answer from one module (`alphex._surface`) so they cannot drift, and both take a
131
+ single uniform `policy`: the per-`SpecialKind` mapping form exists precisely so ESM's eight
132
+ specials cannot be collapsed onto one index by accident, and squeezing that through a CLI flag
133
+ would make doing so easy. Anything needing per-kind policy calls `perm` directly.
134
+
135
+ *(The `mcp` extra pulls cisternal, which requires Python ≥3.13. alphex itself supports ≥3.11 and
136
+ will keep doing so, so the dependency is marker-gated: below 3.13 the library and CLI work
137
+ normally and `alphex-mcp` exits with a message saying why.)*
138
+
139
+ ## Dependencies
140
+
141
+ `numpy`, and nothing else. That ceiling is the reason this is its own distribution rather than a
142
+ module inside a larger library: extras can only *add* to a base install, never subtract, so the
143
+ only way to make "just the alphabets" cheap for a consumer is for it to ship separately.
144
+
145
+ JAX consumers pass the result through `jnp.asarray` — the table is a small constant.
146
+
147
+ ## Status
148
+
149
+ Alpha. The value type, the shipped declarations and the conversion kernel are implemented and
150
+ tested. Not yet implemented: aliases for degenerate and non-standard residues (`B`, `Z`, `J`,
151
+ `U`, `O`), the substitution/rate-matrix asset layer, and the plugin registry for third-party
152
+ alphabets. The entry-point group names (`alphabet_contract.v1.*`) are reserved and deliberately
153
+ independent of this distribution's name.
154
+
155
+ ## Provenance
156
+
157
+ The orderings are facts about published tools, cited per declaration in `known.py`: ProteinMPNN
158
+ (Dauparas et al. 2022, MIT), AlphaFold `residue_constants` (Apache-2.0), ESM3/ESM-C
159
+ `SEQUENCE_VOCAB` (MIT). No code from those projects is included or derived from.
160
+
161
+ MIT licensed.
alphex-0.1.0/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # alphex
2
+
3
+ Biological sequence alphabet orderings, and letter-preserving conversions between them.
4
+
5
+ ```bash
6
+ uv add alphex
7
+ ```
8
+
9
+ ## Why
10
+
11
+ Three different amino-acid orderings are in common use, under at least five different names:
12
+
13
+ | ordering | letters | used by |
14
+ |---|---|---|
15
+ | ProteinMPNN | `ACDEFGHIKLMNPQRSTVWY` | ProteinMPNN, and most "canonical" alphabets |
16
+ | AlphaFold | `ARNDCQEGHILKMFPSTWYV` | AlphaFold `restypes`, LG/JTT substitution models, IQ-TREE |
17
+ | ESM | `LAGVSERTIDPKQNFYMHWC` | ESM3 / ESM-C, at offset 4 |
18
+
19
+ ProteinMPNN and AlphaFold order have **exactly three fixed points** — `A`, `S` and `T`. Build a
20
+ lookup table from one and label it with the other, and 17 of 20 residues are silently permuted.
21
+ The result is shape-valid, dtype-valid, and wrong; nothing raises. That bug has shipped.
22
+
23
+ An ecosystem census that motivated this package found those three orderings declared at **30
24
+ sites across four repositories**, in six different sentinel conventions (no sentinel; `X` at 20;
25
+ gap at 20; gap at 0 with residues shifted to 1; gap at 20 with `X` at 21; `X` at 20 with gap at
26
+ 21). Every site was an independent opportunity to get it wrong.
27
+
28
+ `alphex` holds one declaration of each, and one way to convert between them.
29
+
30
+ ## What it does
31
+
32
+ ```python
33
+ from alphex import Policy, SpecialKind, known, perm
34
+
35
+ # A table from ProteinMPNN order (gap at 20) into ESM's vocabulary.
36
+ table = perm(known.MPNN_GAP_21, known.ESM_C, policy=Policy.RAISE)
37
+
38
+ table.shape # (21,) -- the whole source domain, never just the 20 residues
39
+ int(table[0]) # 5 -- 'A'
40
+ int(table[20]) # 30 -- the gap, which ESM declares
41
+ ```
42
+
43
+ Four entry points, because four genuinely different things get converted and using the wrong
44
+ one scrambles letter identity silently:
45
+
46
+ | | |
47
+ |---|---|
48
+ | `relation(src, dst)` | classify first: `IDENTITY`, `EXTENSION`, `PERMUTATION`, `INCOMPATIBLE`, plus `lossy` and which letters moved |
49
+ | `perm(src, dst, *, policy)` | build the lookup table |
50
+ | `convert(codes, src, dst, *, policy)` | relabel sequence codes |
51
+ | `reindex(data, src, dst, *, policy, axes)` | move a posterior or substitution-matrix axis |
52
+
53
+ ## The design commitments
54
+
55
+ **Letters, not indices.** The invariant is that the amino acid at each position is unchanged and
56
+ only its integer moves. Index equality cannot catch this bug class — the broken table and the
57
+ correct one both round-trip perfectly within themselves.
58
+
59
+ **Every index means something.** A declaration must account for every index in `[0, size)` as a
60
+ residue, a named special, or explicitly `unclaimed`. A table shorter than its domain is how a
61
+ JAX gather clamped a gap index onto valine.
62
+
63
+ **No silent defaults.** `policy` is keyword-only and has no default, anywhere. It is keyed per
64
+ `SpecialKind`, so ESM's eight specials cannot collapse onto one destination index without you
65
+ writing that down.
66
+
67
+ **No in-band sentinels.** `Policy.MASK` returns a `MaskedPerm`, a distinct type — because every
68
+ in-band marker is a valid index somewhere. `-1` selects the last element of the destination
69
+ axis; `size` is clamped rather than rejected by JAX.
70
+
71
+ **Declarations carry provenance.** `citation` is required and must be non-empty. An ordering
72
+ without a source is how one gets relabelled.
73
+
74
+ **Names are not identity.** Two `Alphabet`s compare equal when their index spaces match,
75
+ whatever they are called. That is what makes an alias collision detectable instead of invisible.
76
+
77
+ ## CLI and MCP
78
+
79
+ Optional surfaces, for asking the two questions whose absence let a mislabelled table ship —
80
+ "which ordering is this?" and "what does the table look like?" — without writing a script.
81
+
82
+ ```bash
83
+ uv add "alphex[cli]" # or [mcp], or [agent] for both
84
+
85
+ alphex list # every shipped declaration
86
+ alphex show MPNN_GAP_21 # symbols, specials, citation, warnings
87
+ alphex relation MPNN_20 AF_20 # classify before converting
88
+ alphex perm MPNN_GAP_21 ESM_C # the table, plus a letter-preservation check
89
+ alphex lint # declarations with conflated sentinels
90
+ ```
91
+
92
+ Every command takes `--json-out`. The MCP server (`alphex-mcp`) exposes the same five as tools —
93
+ `list_alphabets`, `show_alphabet`, `relation`, `perm`, `lint` — registered through
94
+ [cisternal](https://pypi.org/project/cisternal/), which also ships the `using-alphex` skill and
95
+ project rules to Claude Code, Cursor, Copilot and Antigravity via `.praxia/manifest.toml`.
96
+
97
+ Both surfaces answer from one module (`alphex._surface`) so they cannot drift, and both take a
98
+ single uniform `policy`: the per-`SpecialKind` mapping form exists precisely so ESM's eight
99
+ specials cannot be collapsed onto one index by accident, and squeezing that through a CLI flag
100
+ would make doing so easy. Anything needing per-kind policy calls `perm` directly.
101
+
102
+ *(The `mcp` extra pulls cisternal, which requires Python ≥3.13. alphex itself supports ≥3.11 and
103
+ will keep doing so, so the dependency is marker-gated: below 3.13 the library and CLI work
104
+ normally and `alphex-mcp` exits with a message saying why.)*
105
+
106
+ ## Dependencies
107
+
108
+ `numpy`, and nothing else. That ceiling is the reason this is its own distribution rather than a
109
+ module inside a larger library: extras can only *add* to a base install, never subtract, so the
110
+ only way to make "just the alphabets" cheap for a consumer is for it to ship separately.
111
+
112
+ JAX consumers pass the result through `jnp.asarray` — the table is a small constant.
113
+
114
+ ## Status
115
+
116
+ Alpha. The value type, the shipped declarations and the conversion kernel are implemented and
117
+ tested. Not yet implemented: aliases for degenerate and non-standard residues (`B`, `Z`, `J`,
118
+ `U`, `O`), the substitution/rate-matrix asset layer, and the plugin registry for third-party
119
+ alphabets. The entry-point group names (`alphabet_contract.v1.*`) are reserved and deliberately
120
+ independent of this distribution's name.
121
+
122
+ ## Provenance
123
+
124
+ The orderings are facts about published tools, cited per declaration in `known.py`: ProteinMPNN
125
+ (Dauparas et al. 2022, MIT), AlphaFold `residue_constants` (Apache-2.0), ESM3/ESM-C
126
+ `SEQUENCE_VOCAB` (MIT). No code from those projects is included or derived from.
127
+
128
+ MIT licensed.
@@ -0,0 +1,102 @@
1
+ [project]
2
+ name = "alphex"
3
+ version = "0.1.0"
4
+ description = "Biological sequence alphabet orderings and letter-preserving conversions between them."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ requires-python = ">=3.11"
9
+ keywords = [
10
+ "bioinformatics",
11
+ "protein",
12
+ "alphabet",
13
+ "tokenizer",
14
+ "esm",
15
+ "proteinmpnn",
16
+ "alphafold",
17
+ ]
18
+ classifiers = [
19
+ "Development Status :: 5 - Production/Stable",
20
+ "Intended Audience :: Science/Research",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.11",
23
+ "Programming Language :: Python :: 3.12",
24
+ "Programming Language :: Python :: 3.13",
25
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
26
+ "Typing :: Typed",
27
+ ]
28
+ dependencies = ["numpy"]
29
+
30
+ [[project.authors]]
31
+ name = "Marielle Russo"
32
+ email = "67157875+maraxen@users.noreply.github.com"
33
+
34
+ [project.urls]
35
+ Homepage = "https://github.com/maraxen/alphex"
36
+ Repository = "https://github.com/maraxen/alphex"
37
+ Issues = "https://github.com/maraxen/alphex/issues"
38
+
39
+ [project.optional-dependencies]
40
+ jax = ["jax"]
41
+ cli = ["cyclopts>=3"]
42
+ mcp = [
43
+ "fastmcp>=2",
44
+ "cisternal>=0.1.3; python_version >= '3.13'",
45
+ ]
46
+ agent = ["alphex[cli,mcp]"]
47
+
48
+ [project.scripts]
49
+ alphex = "alphex.cli:main"
50
+ alphex-mcp = "alphex.mcp:mcp_server"
51
+
52
+ [dependency-groups]
53
+ dev = [
54
+ "pytest>=8",
55
+ "hypothesis>=6",
56
+ "ruff>=0.14",
57
+ "ty>=0.0.1a21",
58
+ ]
59
+
60
+ [tool.uv]
61
+ prerelease = "allow"
62
+
63
+ [tool.ruff]
64
+ line-length = 100
65
+ indent-width = 2
66
+
67
+ [tool.ruff.lint]
68
+ select = ["ALL"]
69
+ ignore = [
70
+ "D203",
71
+ "D213",
72
+ "COM812",
73
+ "TRY003",
74
+ "EM101",
75
+ "EM102",
76
+ "CPY001",
77
+ ]
78
+
79
+ [tool.ruff.lint.per-file-ignores]
80
+ "src/alphex/cli.py" = ["T201"]
81
+ "scripts/*.py" = [
82
+ "T201",
83
+ "INP001",
84
+ "C901",
85
+ ]
86
+ "tests/*.py" = [
87
+ "S101",
88
+ "PLR2004",
89
+ "INP001",
90
+ "D103",
91
+ ]
92
+
93
+ [tool.ty.rules]
94
+ call-non-callable = "ignore"
95
+ unused-ignore-comment = "ignore"
96
+
97
+ [tool.pytest.ini_options]
98
+ testpaths = ["tests"]
99
+
100
+ [build-system]
101
+ requires = ["uv_build>=0.11.21,<0.12.0"]
102
+ build-backend = "uv_build"
@@ -0,0 +1,144 @@
1
+ [project]
2
+ name = "alphex"
3
+ # 0.1.0 is the stable contract for the kernel: `Alphabet`, the shipped declarations, and
4
+ # `relation` / `perm` / `convert` / `reindex`. The v0.2 items -- degenerate and non-standard
5
+ # residue aliases (B/Z/J/U/O), the asset layer, the plugin registry -- are additive and not
6
+ # implemented in 0.1.0.
7
+ version = "0.1.0"
8
+ description = "Biological sequence alphabet orderings and letter-preserving conversions between them."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [
13
+ { name = "Marielle Russo", email = "67157875+maraxen@users.noreply.github.com" }
14
+ ]
15
+ requires-python = ">=3.11"
16
+ keywords = ["bioinformatics", "protein", "alphabet", "tokenizer", "esm", "proteinmpnn", "alphafold"]
17
+ classifiers = [
18
+ "Development Status :: 5 - Production/Stable",
19
+ "Intended Audience :: Science/Research",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
25
+ "Typing :: Typed",
26
+ ]
27
+
28
+ # INTENTIONALLY MINIMAL -- see CLAUDE.md "The dependency contract".
29
+ #
30
+ # numpy is the only runtime dependency, and even that is a deliberate ceiling rather
31
+ # than a starting point. This package exists because the alternative homes for it
32
+ # (proxide, aminx, proteinsmc) all carry a base install that a consumer needing only
33
+ # alphabet conversions cannot afford: proxide's base pulls jax, jax-md, hydride, grain
34
+ # and array_record on top of a maturin-built Rust extension. Optional-dependency
35
+ # extras cannot fix that -- extras only ADD to a base install, they never subtract
36
+ # from it -- so the only way to make "just the alphabets" cheap is for it to be its
37
+ # own distribution. That is this one.
38
+ #
39
+ # Do NOT add jax here. Consumers that want jnp-backed permutation tables should get
40
+ # them by passing this package's plain integer arrays into their own jnp.asarray, or
41
+ # via a `jax` extra that stays strictly optional and lazily imported.
42
+ dependencies = [
43
+ "numpy",
44
+ ]
45
+
46
+ # Keep table headers BELOW every bare `[project]` key. A `[project.urls]` block placed above
47
+ # `dependencies` silently captures it, and the resulting error ("invalid type: sequence,
48
+ # expected a string") points at the list rather than at the header that swallowed it.
49
+ [project.urls]
50
+ Homepage = "https://github.com/maraxen/alphex"
51
+ Repository = "https://github.com/maraxen/alphex"
52
+ Issues = "https://github.com/maraxen/alphex/issues"
53
+
54
+ [project.optional-dependencies]
55
+ # Anything here must be imported only by a module that `alphex/__init__.py` does NOT
56
+ # import, and must fail with an actionable message naming the extra -- never with a
57
+ # bare ImportError. `alphex.cli` and `alphex.mcp` both do this; importing `alphex`
58
+ # itself still pulls nothing but numpy, which is the whole reason this package exists
59
+ # as its own distribution.
60
+ jax = ["jax"]
61
+ cli = ["cyclopts>=3"]
62
+ # The marker is load-bearing, and matches myxcel's precedent for the same dependency.
63
+ # cisternal requires Python >=3.13; alphex supports >=3.11 and must keep doing so, because a
64
+ # cheap install on the widest range of interpreters is the reason this package exists at all.
65
+ # Narrowing `requires-python` to satisfy an optional telemetry dependency would trade the
66
+ # package's purpose for a feature nobody needs to import the library.
67
+ #
68
+ # Consequence, stated rather than discovered: on 3.11/3.12 the `mcp` extra installs fastmcp
69
+ # but NOT cisternal, so `alphex-mcp` exits with the message in alphex/mcp.py naming the 3.13
70
+ # floor. The CLI has no such floor and works everywhere.
71
+ mcp = ["fastmcp>=2", "cisternal>=0.1.3; python_version >= '3.13'"]
72
+ agent = ["alphex[cli,mcp]"]
73
+
74
+ [project.scripts]
75
+ alphex = "alphex.cli:main"
76
+ alphex-mcp = "alphex.mcp:mcp_server"
77
+
78
+ [dependency-groups]
79
+ dev = [
80
+ "pytest>=8",
81
+ "hypothesis>=6",
82
+ "ruff>=0.14",
83
+ "ty>=0.0.1a21",
84
+ ]
85
+
86
+ [tool.uv]
87
+ # cisternal>=0.1.1a11 pins fastmcp==4.0.0a2 (a pre-release), so the 3.13+ fork of the lock
88
+ # needs pre-releases allowed; allow here so `uv lock`/`uv sync` work without a flag. The
89
+ # 3.11/3.12 forks carry no cisternal and stay on fastmcp 3.4.x.
90
+ prerelease = "allow"
91
+
92
+ [build-system]
93
+ requires = ["uv_build>=0.11.21,<0.12.0"]
94
+ build-backend = "uv_build"
95
+
96
+ [tool.ruff]
97
+ line-length = 100
98
+ indent-width = 2
99
+
100
+ [tool.ruff.lint]
101
+ select = ["ALL"]
102
+ ignore = [
103
+ "D203",
104
+ "D213",
105
+ "COM812",
106
+ "TRY003",
107
+ "EM101", "EM102",
108
+ "CPY001", # no copyright headers anywhere in this ecosystem
109
+ ]
110
+
111
+ [tool.ruff.lint.per-file-ignores]
112
+ # T201: a command-line program's output IS print. Routing it through `logging` would send it
113
+ # to stderr and make `alphex perm ... | jq` stop working, which is the main way this surface
114
+ # is meant to be used.
115
+ "src/alphex/cli.py" = ["T201"]
116
+ # CLI scripts print to stdout by design, are not an importable package, and their
117
+ # report-building main() is a flat sequence rather than genuinely branchy.
118
+ "scripts/*.py" = ["T201", "INP001", "C901"]
119
+ # Tests assert (that is their job) and compare against literal indices from the census,
120
+ # which are the values under test -- extracting them to constants would hide the thing
121
+ # being asserted behind a name.
122
+ # D103: test names here are full sentences (`test_declaration_rejects_duplicate_symbols`),
123
+ # so a docstring restating the name is noise. Tests whose *reason for existing* is
124
+ # non-obvious -- the ones guarding a specific past defect -- carry a docstring anyway.
125
+ "tests/*.py" = ["S101", "PLR2004", "INP001", "D103"]
126
+
127
+ [tool.ty.rules]
128
+ # Two rules disabled, both for the same underlying cause: `cisternal` is an OPTIONAL,
129
+ # marker-gated dependency (Python >=3.13), so its presence differs across the CI matrix and
130
+ # no single source-level state is correct on all three interpreters.
131
+ #
132
+ # cisternal assembles its public surface dynamically, so ty resolves `cisternal.wire` to
133
+ # `object` and reports the call as non-callable. bathos hit the identical wall on
134
+ # `app.run()` and demoted the same rule. Scoped by the fact that this library has exactly
135
+ # one call into an untyped third-party object; if that stops being true, narrow this.
136
+ call-non-callable = "ignore"
137
+ # The `ty: ignore[unresolved-import]` on the cisternal import is REQUIRED on 3.11/3.12,
138
+ # where the marker excludes it, and UNUSED on 3.13+, where it installs. Disabling the
139
+ # meta-rule is the only state that holds across the matrix; the alternative is a green
140
+ # 3.13 and a red 3.11, or vice versa.
141
+ unused-ignore-comment = "ignore"
142
+
143
+ [tool.pytest.ini_options]
144
+ testpaths = ["tests"]
@@ -0,0 +1,60 @@
1
+ """Amino-acid alphabet orderings and letter-preserving conversions between them.
2
+
3
+ This package exists because a silent data-corruption bug shipped: a token table built from the
4
+ AlphaFold residue ordering while named for the ProteinMPNN ordering, permuting every residue
5
+ that is not a fixed point of the permutation (only A, S and T are). Being shape-valid, it
6
+ raised nothing.
7
+
8
+ The unit of value here is the **declaration**, not the encoding. See
9
+ `.praxia/docs/specs/260814_alphabet-contract.md` for the contract and
10
+ `260814_alphabet-api-surface.md` for the shape.
11
+
12
+ v0.1 is Phase 0 (decision D4): the `Alphabet` value type and the shipped declarations, for
13
+ **dev-dependency-only conformance testing** in each consumer repo, plus the Phase 1 conversion
14
+ kernel (`relation`, `perm`, `convert`, `reindex`). No asset layer and no plugin registry (D5)
15
+ yet.
16
+ """
17
+
18
+ from alphex import known
19
+ from alphex.alphabet import Alphabet, SpecialKind
20
+ from alphex.convert import MaskedPerm, Policy, PolicySpec, convert, perm, reindex
21
+ from alphex.errors import (
22
+ AliasCollisionError,
23
+ AlphabetDeclarationError,
24
+ AlphabetError,
25
+ DegenerateSymbolError,
26
+ IncompatibleAlphabetError,
27
+ MissingSpecialError,
28
+ MultiCharTokenError,
29
+ NonStandardResidueError,
30
+ ReducedAlphabetError,
31
+ UnmappableSymbolError,
32
+ UnsupportedFeatureError,
33
+ )
34
+ from alphex.relation import Relation, RelationKind, relation
35
+
36
+ __all__ = [
37
+ "AliasCollisionError",
38
+ "Alphabet",
39
+ "AlphabetDeclarationError",
40
+ "AlphabetError",
41
+ "DegenerateSymbolError",
42
+ "IncompatibleAlphabetError",
43
+ "MaskedPerm",
44
+ "MissingSpecialError",
45
+ "MultiCharTokenError",
46
+ "NonStandardResidueError",
47
+ "Policy",
48
+ "PolicySpec",
49
+ "ReducedAlphabetError",
50
+ "Relation",
51
+ "RelationKind",
52
+ "SpecialKind",
53
+ "UnmappableSymbolError",
54
+ "UnsupportedFeatureError",
55
+ "convert",
56
+ "known",
57
+ "perm",
58
+ "reindex",
59
+ "relation",
60
+ ]