mcpymol 1.5.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.
- mcpymol-1.5.0/.claude/skills/mcpymol-guide/SKILL.md +182 -0
- mcpymol-1.5.0/.coverage +0 -0
- mcpymol-1.5.0/.github/dependabot.yml +20 -0
- mcpymol-1.5.0/.github/workflows/ci.yml +123 -0
- mcpymol-1.5.0/.github/workflows/dependabot-auto-merge.yml +42 -0
- mcpymol-1.5.0/.github/workflows/release.yml +67 -0
- mcpymol-1.5.0/.gitignore +41 -0
- mcpymol-1.5.0/.pre-commit-config.yaml +86 -0
- mcpymol-1.5.0/.python-version +1 -0
- mcpymol-1.5.0/CHANGELOG.md +179 -0
- mcpymol-1.5.0/CONTRIBUTING.md +142 -0
- mcpymol-1.5.0/KNOWN-ISSUES.md +96 -0
- mcpymol-1.5.0/LICENSE +21 -0
- mcpymol-1.5.0/PKG-INFO +697 -0
- mcpymol-1.5.0/README.md +642 -0
- mcpymol-1.5.0/assets/cinematic_view.png +0 -0
- mcpymol-1.5.0/assets/conservation_view.png +0 -0
- mcpymol-1.5.0/assets/crosslink_view.png +0 -0
- mcpymol-1.5.0/assets/electrostatic_view.png +0 -0
- mcpymol-1.5.0/assets/hydrophobic_surface_view.png +0 -0
- mcpymol-1.5.0/assets/interface_view.png +0 -0
- mcpymol-1.5.0/assets/ligand_view.png +0 -0
- mcpymol-1.5.0/assets/mutation_view.png +0 -0
- mcpymol-1.5.0/assets/nucleosome.png +0 -0
- mcpymol-1.5.0/assets/plddt_view.png +0 -0
- mcpymol-1.5.0/assets/pocket_view.png +0 -0
- mcpymol-1.5.0/assets/poisson_boltzmann_view.png +0 -0
- mcpymol-1.5.0/assets/print_ribbon_view.png +0 -0
- mcpymol-1.5.0/assets/putty_view.png +0 -0
- mcpymol-1.5.0/assets/superposition_view.png +0 -0
- mcpymol-1.5.0/assets/textbook_view.png +0 -0
- mcpymol-1.5.0/assets/turntable.gif +0 -0
- mcpymol-1.5.0/pyproject.toml +116 -0
- mcpymol-1.5.0/src/mcpymol/__init__.py +3 -0
- mcpymol-1.5.0/src/mcpymol/__main__.py +6 -0
- mcpymol-1.5.0/src/mcpymol/analysis.py +538 -0
- mcpymol-1.5.0/src/mcpymol/app.py +11 -0
- mcpymol-1.5.0/src/mcpymol/bridge.py +183 -0
- mcpymol-1.5.0/src/mcpymol/cli.py +148 -0
- mcpymol-1.5.0/src/mcpymol/comparison.py +219 -0
- mcpymol-1.5.0/src/mcpymol/conservation.py +361 -0
- mcpymol-1.5.0/src/mcpymol/pdbtext.py +145 -0
- mcpymol-1.5.0/src/mcpymol/plugin.py +453 -0
- mcpymol-1.5.0/src/mcpymol/primitives.py +1315 -0
- mcpymol-1.5.0/src/mcpymol/printing.py +385 -0
- mcpymol-1.5.0/src/mcpymol/rendering.py +272 -0
- mcpymol-1.5.0/src/mcpymol/server.py +489 -0
- mcpymol-1.5.0/src/mcpymol/structures.py +919 -0
- mcpymol-1.5.0/src/mcpymol/style.py +27 -0
- mcpymol-1.5.0/src/mcpymol/views.py +923 -0
- mcpymol-1.5.0/tests/conftest.py +16 -0
- mcpymol-1.5.0/tests/test_alphafold.py +493 -0
- mcpymol-1.5.0/tests/test_auto_wrappers.py +259 -0
- mcpymol-1.5.0/tests/test_bridge_roundtrip.py +197 -0
- mcpymol-1.5.0/tests/test_cli.py +195 -0
- mcpymol-1.5.0/tests/test_comparison.py +310 -0
- mcpymol-1.5.0/tests/test_conservation.py +568 -0
- mcpymol-1.5.0/tests/test_contacts.py +451 -0
- mcpymol-1.5.0/tests/test_docs.py +116 -0
- mcpymol-1.5.0/tests/test_interface.py +290 -0
- mcpymol-1.5.0/tests/test_live.py +465 -0
- mcpymol-1.5.0/tests/test_measurements.py +170 -0
- mcpymol-1.5.0/tests/test_package_layout.py +165 -0
- mcpymol-1.5.0/tests/test_plugin.py +476 -0
- mcpymol-1.5.0/tests/test_rendering.py +431 -0
- mcpymol-1.5.0/tests/test_server.py +1181 -0
- mcpymol-1.5.0/tests/test_sessions.py +182 -0
- mcpymol-1.5.0/tests/test_structure_info.py +655 -0
- mcpymol-1.5.0/uv.lock +2806 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mcpymol-guide
|
|
3
|
+
description: >-
|
|
4
|
+
How to drive the mcpymol MCP server well — structure prep, the view-preset
|
|
5
|
+
catalogue, PyMOL selection syntax, rendering, and 3D-print STL export.
|
|
6
|
+
TRIGGER when the user asks to visualize/fetch/render a protein structure
|
|
7
|
+
through the mcpymol tools, apply a *_view preset, or produce a 3D-printable
|
|
8
|
+
model/STL. SKIP for unrelated PyMOL-less work.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Driving the mcpymol MCP server
|
|
12
|
+
|
|
13
|
+
The `mcp__mcpymol__*` tools talk to a running PyMOL over a socket. Prefer the
|
|
14
|
+
high-level tools (`fetch_structure`, the `*_view` presets, `show`/`color`/
|
|
15
|
+
`select`) over `execute_pymol_command` — the presets do coloring,
|
|
16
|
+
transparency, H-bonds and labels in one call. Reach for
|
|
17
|
+
`execute_pymol_command` only when no dedicated tool covers the need.
|
|
18
|
+
|
|
19
|
+
## Report first, then draw
|
|
20
|
+
|
|
21
|
+
The presets make pictures; the report tools answer questions with numbers.
|
|
22
|
+
When the user asks *why* or *how much* — what holds this ligand, how big is
|
|
23
|
+
this interface, where do these differ — run the report and quote the numbers,
|
|
24
|
+
then apply the matching view if a picture helps. Answering a quantitative
|
|
25
|
+
question with only a rendering leaves the user to measure it themselves.
|
|
26
|
+
|
|
27
|
+
| The question | Reach for |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| What is this structure? | `structure_info` |
|
|
30
|
+
| What is the sequence / numbering / where are the gaps? | `get_sequence` |
|
|
31
|
+
| What contacts what, and how tightly? | `contact_report` -> `ligand_view` |
|
|
32
|
+
| How big is this interface, which residues matter? | `interface_report` -> `interface_view` |
|
|
33
|
+
| Where do two structures differ? | `superposition_view` |
|
|
34
|
+
| How far / what angle / how much area? | `distance`, `angle`, `dihedral`, `sasa`, `rms_cur` |
|
|
35
|
+
| Does my selection match anything? | `count_atoms` |
|
|
36
|
+
| Occupancy / altloc / per-atom B-factor? | `atom_properties` |
|
|
37
|
+
|
|
38
|
+
`contact_report` classification is heavy-atom geometry (no hydrogens assumed):
|
|
39
|
+
salt bridge <= 4.0 A, H-bond <= 3.5 A between N/O, hydrophobic <= 4.5 A,
|
|
40
|
+
ring centroids <= 5.5 A. Report it as such — a "hydrogen bond" here is a
|
|
41
|
+
donor-acceptor pair with plausible geometry, not one verified against a
|
|
42
|
+
hydrogen. Aromatic detection covers the standard aromatic amino acids only;
|
|
43
|
+
ligand rings appear as hydrophobic contacts.
|
|
44
|
+
|
|
45
|
+
`interface_report` gives buried surface area per side. Under ~400 A^2 usually
|
|
46
|
+
means crystal packing rather than a real interface; over ~1000 A^2 means a
|
|
47
|
+
substantial, likely specific association. Say which regime the number is in.
|
|
48
|
+
|
|
49
|
+
## Core workflow
|
|
50
|
+
|
|
51
|
+
0. **Predicted structures.** `fetch_structure` also accepts a UniProt
|
|
52
|
+
accession (`P69905`, `af-P69905`) and routes it to AlphaFold DB, colouring
|
|
53
|
+
by pLDDT confidence; `fetch_alphafold` is the same thing called directly,
|
|
54
|
+
and takes `model_version` / `fragment` for older or split entries. For predicted models use `plddt_view`, never
|
|
55
|
+
`bfactor_view`/`putty_view` — pLDDT lives in the B-factor column and those
|
|
56
|
+
presets read it backwards (they assume low = rigid; low pLDDT = unreliable).
|
|
57
|
+
0b. **Local files.** `load_structure(file_path, obj_name)` takes a PDB/mmCIF
|
|
58
|
+
off disk — a built model, a docking pose, an MD frame — and applies the
|
|
59
|
+
same multimer heuristic and styling as a fetch. Use it whenever the user
|
|
60
|
+
has a file rather than an accession.
|
|
61
|
+
1. **`fetch_structure(pdb_code)`** — grabs the biological assembly when one
|
|
62
|
+
exists, runs a BFS chain-contact heuristic (`multimer_cutoff`, default
|
|
63
|
+
8.0 Å) so functional multimers stay whole while crystallographic copies
|
|
64
|
+
are dropped, hides waters/additives, and applies the default style. The
|
|
65
|
+
object is named after the PDB code unless `obj_name` is given.
|
|
66
|
+
2. **Ground yourself before composing selections.** Use `list_objects`,
|
|
67
|
+
`list_chains(obj_name)`, `list_ligands(obj_name)` instead of guessing
|
|
68
|
+
object names, chain IDs, or 3-letter ligand codes.
|
|
69
|
+
3. **Apply a view preset** (below) or build the scene manually.
|
|
70
|
+
4. **Check a selection with `count_atoms`** before building a scene on it —
|
|
71
|
+
an empty selection is invisible until the render comes out blank.
|
|
72
|
+
5. **Render with `render()`** — it ray-traces and returns the image directly,
|
|
73
|
+
so you can see the result and iterate. Prefer it over `ray` + `png`, which
|
|
74
|
+
only leave a file on disk. Pass `ray_trace=False` for a fast unshaded
|
|
75
|
+
check of a selection or orientation, and keep the width/height modest —
|
|
76
|
+
the image is inlined into the conversation.
|
|
77
|
+
|
|
78
|
+
## View-preset catalogue
|
|
79
|
+
|
|
80
|
+
All take `obj_name`. Each is a one-call scene.
|
|
81
|
+
|
|
82
|
+
- **Binding / functional sites:** `ligand_view`, `pocket_view`,
|
|
83
|
+
`pharmacophore_view`, `interface_view` (chain–chain), `crosslink_view`
|
|
84
|
+
(disulfides & metal coordination), `mutation_view`.
|
|
85
|
+
- **Per-residue scalar coloring:** `bfactor_view`, `putty_view` (B-factor as
|
|
86
|
+
tube width), `conservation_view` (evolutionary; first call hits the
|
|
87
|
+
ColabFold MMseqs2 API and is slow, then cached).
|
|
88
|
+
- **Surface chemistry / electrostatics:** `hydrophobic_surface_view`,
|
|
89
|
+
`electrostatic_view` (approximate), `poisson_boltzmann_view` (true PB;
|
|
90
|
+
needs `apbs` + `pdb2pqr`).
|
|
91
|
+
- **Illustrative:** `textbook_view` (cel-shaded; needs `ray`),
|
|
92
|
+
`cinematic_view` (fog/shadows; needs `ray`), `pointillist_view`.
|
|
93
|
+
- **Predicted-model confidence:** `plddt_view` (AlphaFold pLDDT in the
|
|
94
|
+
official palette, plus a confidence breakdown).
|
|
95
|
+
- **Comparing two structures:** `superposition_view(mobile, target)` —
|
|
96
|
+
superposes and colours the mobile structure by per-residue shift, so you
|
|
97
|
+
see *where* it moved rather than just an RMSD. Names the worst-shifted
|
|
98
|
+
residues.
|
|
99
|
+
- **3D printing:** `print_ribbon_view` — see below.
|
|
100
|
+
|
|
101
|
+
## Rendering, movies and sessions
|
|
102
|
+
|
|
103
|
+
- `render(width, height, ray_trace, filename)` — the one to reach for. Returns
|
|
104
|
+
the image so you can look at it.
|
|
105
|
+
- `turntable(obj_name, frames, out_dir)` — a 360° PNG sequence plus the
|
|
106
|
+
ffmpeg line to assemble it. Defaults to the fast renderer; `ray_trace=True`
|
|
107
|
+
is for finals only (36 traced frames of a big assembly can take an hour).
|
|
108
|
+
- `save_session(filename)` / `load_session(filename)` — `.pse` round-trips
|
|
109
|
+
the whole scene (objects, colours, camera, scenes). Save before
|
|
110
|
+
experimenting with a scene that took effort to build.
|
|
111
|
+
|
|
112
|
+
## Selection syntax (PyMOL)
|
|
113
|
+
|
|
114
|
+
Selections are an algebra, not just residue lists:
|
|
115
|
+
`chain A`, `resi 10-50`, `resn ATP`, `polymer.protein`, `organic`,
|
|
116
|
+
`ss H` / `ss S` (helix/strand), `byres (... around 5)`, combined with
|
|
117
|
+
`and`/`or`/`not` and parentheses. Cross-object selections are fine:
|
|
118
|
+
`(1abc or 1abc_spine)`.
|
|
119
|
+
|
|
120
|
+
## 3D-print STL export
|
|
121
|
+
|
|
122
|
+
Two-step: configure the scene, then export.
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
print_ribbon_view(obj_name="1ema")
|
|
126
|
+
print_export(obj_name="1ema", groups="1ema=(1ema or 1ema_spine)",
|
|
127
|
+
representation="cartoon", method="voxel", voxel_pitch=0.2)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
`print_ribbon_view` builds chunky β-arrows + a fat helix and a separate
|
|
131
|
+
`<obj>_spine` (`cartoon tube`, ignores secondary structure) that threads
|
|
132
|
+
unbroken through every strand→loop junction — fused on export into one
|
|
133
|
+
watertight solid, with the spine acting as internal rebar for rigidity.
|
|
134
|
+
|
|
135
|
+
`print_export` key arguments:
|
|
136
|
+
|
|
137
|
+
- **`representation`** — `"surface"` (default) exports the molecular
|
|
138
|
+
surface; **`"cartoon"`** exports the *currently displayed* cartoon/tube
|
|
139
|
+
geometry (what `print_ribbon_view` sets up). Using the default with a
|
|
140
|
+
ribbon scene silently gives you a surface blob.
|
|
141
|
+
- **`method`** — `"voxel"` (robust for thin tubular ribbon geometry;
|
|
142
|
+
`voxel_pitch` ~0.2 keeps ribbon detail), `"poisson"` (bulky chains),
|
|
143
|
+
`"auto"`.
|
|
144
|
+
- **`groups`** — `label=selection` pairs, one STL per colour. In cartoon
|
|
145
|
+
mode each group should name whole object(s).
|
|
146
|
+
|
|
147
|
+
### Hard-won verification rules
|
|
148
|
+
|
|
149
|
+
- **Verify the STL itself, not the viewport.** `ray`/`png` show the live
|
|
150
|
+
PyMOL scene, which can look like ribbons while the exported mesh is a
|
|
151
|
+
surface. PyMOL cannot load STL back, so inspect the file with the
|
|
152
|
+
project's own `trimesh` (already a `print`-extra dependency):
|
|
153
|
+
check `is_watertight`, `len(mesh.split())` (want **1** component), and
|
|
154
|
+
to tell ribbon from surface compare bbox-fill fraction and
|
|
155
|
+
area/volume — a ribbon is thin (low fill, high area/volume), a surface
|
|
156
|
+
is a fat shell.
|
|
157
|
+
- **A printable result is watertight AND a single component.** Fragmented,
|
|
158
|
+
non-watertight output won't slice.
|
|
159
|
+
- The STL is in the same coordinate frame across groups — in a slicer,
|
|
160
|
+
load the first then add others as *parts* (don't re-centre).
|
|
161
|
+
|
|
162
|
+
### After editing anything under `src/mcpymol/`
|
|
163
|
+
|
|
164
|
+
The MCP server imports the package at startup. Code changes are **not live**
|
|
165
|
+
until the user reconnects it (`/mcp` → reconnect mcpymol, or restart Claude
|
|
166
|
+
Code) — you cannot restart it yourself. After a reconnect, confirm the new
|
|
167
|
+
behavior from tool output, don't assume it loaded (a stale server returns
|
|
168
|
+
the old result).
|
|
169
|
+
|
|
170
|
+
## Troubleshooting
|
|
171
|
+
|
|
172
|
+
- *"Socket connection failed"* on every tool → PyMOL plugin not running /
|
|
173
|
+
server disconnected. Ask the user to reconnect mcpymol via `/mcp`.
|
|
174
|
+
- Long `get_fastastr`/`get_chains` JSON parse errors → pre-2026-05 build.
|
|
175
|
+
- `poisson_boltzmann_view` fails → missing `apbs`/`pdb2pqr`.
|
|
176
|
+
- 3D-print tools say the `print` extra is missing → `uv sync --extra print`.
|
|
177
|
+
- A tool reports a file "did not appear" → PyMOL and the bridge are not on the
|
|
178
|
+
same machine; they exchange files through the filesystem.
|
|
179
|
+
- `render` returns a size complaint instead of an image → ask for a smaller
|
|
180
|
+
width/height, or raise `MCPYMOL_MAX_IMAGE_BYTES`.
|
|
181
|
+
- A scene comes out blank → check the selection with `count_atoms` before
|
|
182
|
+
assuming the view preset failed.
|
mcpymol-1.5.0/.coverage
ADDED
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
# Python dependencies (pyproject.toml + uv.lock).
|
|
4
|
+
- package-ecosystem: "uv"
|
|
5
|
+
directory: "/"
|
|
6
|
+
schedule:
|
|
7
|
+
interval: "weekly"
|
|
8
|
+
groups:
|
|
9
|
+
python-deps:
|
|
10
|
+
patterns: ["*"]
|
|
11
|
+
open-pull-requests-limit: 5
|
|
12
|
+
|
|
13
|
+
# Keep the SHA-pinned GitHub Actions current.
|
|
14
|
+
- package-ecosystem: "github-actions"
|
|
15
|
+
directory: "/"
|
|
16
|
+
schedule:
|
|
17
|
+
interval: "weekly"
|
|
18
|
+
groups:
|
|
19
|
+
actions:
|
|
20
|
+
patterns: ["*"]
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
# Cancel superseded runs on the same ref (saves CI minutes on rapid pushes).
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ci-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
lockfile:
|
|
18
|
+
name: Lockfile in sync
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
|
|
22
|
+
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
23
|
+
with:
|
|
24
|
+
enable-cache: true
|
|
25
|
+
# Fails fast and unambiguously if uv.lock drifts from pyproject.toml,
|
|
26
|
+
# instead of the error hiding inside every Tests (pyX.Y) install step.
|
|
27
|
+
- name: uv.lock matches pyproject.toml
|
|
28
|
+
run: uv lock --locked
|
|
29
|
+
|
|
30
|
+
lint:
|
|
31
|
+
name: Lint (ruff)
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
|
|
35
|
+
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
36
|
+
with:
|
|
37
|
+
enable-cache: true
|
|
38
|
+
- run: uv sync --locked --all-groups
|
|
39
|
+
- name: ruff check
|
|
40
|
+
run: uv run ruff check --output-format=github .
|
|
41
|
+
- name: ruff format
|
|
42
|
+
run: uv run ruff format --check --diff .
|
|
43
|
+
|
|
44
|
+
typecheck:
|
|
45
|
+
name: Type check (mypy)
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
|
|
49
|
+
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
50
|
+
with:
|
|
51
|
+
enable-cache: true
|
|
52
|
+
# The print extra must be installed or trimesh resolves to Any under
|
|
53
|
+
# ignore_missing_imports, and mypy silently skips the mesh-repair code —
|
|
54
|
+
# the one place in the tree doing real work against a third-party API.
|
|
55
|
+
- run: uv sync --locked --all-groups --extra print
|
|
56
|
+
- run: uv run mypy
|
|
57
|
+
|
|
58
|
+
test:
|
|
59
|
+
name: Tests (py${{ matrix.python-version }})
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
strategy:
|
|
62
|
+
fail-fast: false
|
|
63
|
+
matrix:
|
|
64
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
65
|
+
steps:
|
|
66
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
|
|
67
|
+
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
68
|
+
with:
|
|
69
|
+
enable-cache: true
|
|
70
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
71
|
+
run: uv python install ${{ matrix.python-version }}
|
|
72
|
+
# --locked fails if uv.lock is stale relative to pyproject.toml,
|
|
73
|
+
# catching dependency drift before it reaches a release.
|
|
74
|
+
- name: Install dependencies
|
|
75
|
+
run: uv sync --locked --all-groups
|
|
76
|
+
- name: Run pytest
|
|
77
|
+
run: >
|
|
78
|
+
uv run pytest tests/ -v
|
|
79
|
+
--cov=mcpymol --cov-report=term-missing --cov-report=xml
|
|
80
|
+
|
|
81
|
+
print-extra:
|
|
82
|
+
name: Print extra imports
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
|
|
86
|
+
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
87
|
+
with:
|
|
88
|
+
enable-cache: true
|
|
89
|
+
# The `print` extra pulls heavy native deps (pymeshlab, scipy). This
|
|
90
|
+
# job just proves that code path still imports — it is silent otherwise.
|
|
91
|
+
- name: Install with print extra
|
|
92
|
+
run: uv sync --locked --extra print
|
|
93
|
+
- name: Import print_export
|
|
94
|
+
run: uv run python -c "from mcpymol.server import print_export; print('print_export import OK')"
|
|
95
|
+
|
|
96
|
+
build:
|
|
97
|
+
name: Build & metadata check
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
|
|
101
|
+
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
102
|
+
with:
|
|
103
|
+
enable-cache: true
|
|
104
|
+
- name: Build sdist and wheel
|
|
105
|
+
run: uv build
|
|
106
|
+
- name: Validate package metadata
|
|
107
|
+
run: uv run --with twine twine check dist/*
|
|
108
|
+
|
|
109
|
+
security:
|
|
110
|
+
name: Dependency audit (advisory)
|
|
111
|
+
runs-on: ubuntu-latest
|
|
112
|
+
steps:
|
|
113
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
|
|
114
|
+
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
115
|
+
with:
|
|
116
|
+
enable-cache: true
|
|
117
|
+
- run: uv sync --locked --all-groups
|
|
118
|
+
# Advisory: a CVE in a transitive dep should be visible but should not
|
|
119
|
+
# block unrelated PRs. Step-level continue-on-error keeps the check
|
|
120
|
+
# green; triage via the run log / Dependabot alerts.
|
|
121
|
+
- name: pip-audit
|
|
122
|
+
run: uv run --with pip-audit pip-audit
|
|
123
|
+
continue-on-error: true
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
name: Dependabot auto-merge
|
|
2
|
+
|
|
3
|
+
# Queues Dependabot's patch and minor updates to merge themselves once every
|
|
4
|
+
# required check passes. Major bumps are left for a human, because those are
|
|
5
|
+
# the ones that change behaviour.
|
|
6
|
+
#
|
|
7
|
+
# Auto-merge does not bypass anything: main requires nine green checks and an
|
|
8
|
+
# up-to-date branch, so this only removes the manual click, never the gate.
|
|
9
|
+
|
|
10
|
+
on: pull_request_target
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
pull-requests: write
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
auto-merge:
|
|
18
|
+
name: Enable auto-merge
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
if: github.event.pull_request.user.login == 'dependabot[bot]'
|
|
21
|
+
steps:
|
|
22
|
+
# NB: pull_request_target runs with write access in the base repo's
|
|
23
|
+
# context. Nothing here checks out the PR's code, which is what makes
|
|
24
|
+
# that safe — do not add actions/checkout of the head ref.
|
|
25
|
+
- name: Inspect the update
|
|
26
|
+
id: metadata
|
|
27
|
+
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
|
|
28
|
+
|
|
29
|
+
- name: Enable auto-merge for patch and minor updates
|
|
30
|
+
if: steps.metadata.outputs.update-type != 'version-update:semver-major'
|
|
31
|
+
run: gh pr merge --auto --rebase "$PR_URL"
|
|
32
|
+
env:
|
|
33
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
34
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
35
|
+
|
|
36
|
+
- name: Leave major updates for review
|
|
37
|
+
if: steps.metadata.outputs.update-type == 'version-update:semver-major'
|
|
38
|
+
run: |
|
|
39
|
+
gh pr comment "$PR_URL" --body "Major version bump — not auto-merging. Worth reading the changelog before this lands."
|
|
40
|
+
env:
|
|
41
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
42
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishing is driven by tags, not by pushes to main: the tag is the record
|
|
4
|
+
# of what was released, and a PyPI version is immutable once it exists.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
name: Build & verify
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4
|
|
18
|
+
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
|
|
19
|
+
with:
|
|
20
|
+
enable-cache: true
|
|
21
|
+
|
|
22
|
+
- name: Build sdist and wheel
|
|
23
|
+
run: uv build
|
|
24
|
+
|
|
25
|
+
# A published version can never be replaced, only superseded by another
|
|
26
|
+
# release. Tagging v1.5.0 while pyproject still says 1.4.0 would upload
|
|
27
|
+
# 1.4.0 under a v1.5.0 tag and burn the version number permanently, so
|
|
28
|
+
# this checks the tag against the artefacts that are about to be
|
|
29
|
+
# uploaded rather than against the source it was built from.
|
|
30
|
+
- name: Tag matches built version
|
|
31
|
+
run: |
|
|
32
|
+
version="${GITHUB_REF_NAME#v}"
|
|
33
|
+
if [ ! -f "dist/mcpymol-${version}.tar.gz" ]; then
|
|
34
|
+
echo "::error::Tag ${GITHUB_REF_NAME} implies version ${version}, but that is not what was built. Bump the version in pyproject.toml, or retag. dist/ contains:"
|
|
35
|
+
ls -1 dist/
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
- name: Validate package metadata
|
|
40
|
+
run: uv run --with twine twine check dist/*
|
|
41
|
+
|
|
42
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
43
|
+
with:
|
|
44
|
+
name: dist
|
|
45
|
+
path: dist/
|
|
46
|
+
if-no-files-found: error
|
|
47
|
+
|
|
48
|
+
publish:
|
|
49
|
+
name: Publish to PyPI
|
|
50
|
+
needs: build
|
|
51
|
+
runs-on: ubuntu-latest
|
|
52
|
+
# Trusted Publishing (OIDC): PyPI verifies this exact repository, workflow
|
|
53
|
+
# filename and environment, and hands back a short-lived token. There is
|
|
54
|
+
# no API token stored in the repo to leak or rotate — but it does mean the
|
|
55
|
+
# three names below are load-bearing and must match the pending publisher
|
|
56
|
+
# registered on PyPI. Renaming this file breaks publishing.
|
|
57
|
+
environment:
|
|
58
|
+
name: pypi
|
|
59
|
+
url: https://pypi.org/p/mcpymol
|
|
60
|
+
permissions:
|
|
61
|
+
id-token: write
|
|
62
|
+
steps:
|
|
63
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
64
|
+
with:
|
|
65
|
+
name: dist
|
|
66
|
+
path: dist/
|
|
67
|
+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
mcpymol-1.5.0/.gitignore
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.pytest_cache/
|
|
6
|
+
.mypy_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
env/
|
|
13
|
+
refresh/
|
|
14
|
+
|
|
15
|
+
# Editor / OS / local agent artifacts
|
|
16
|
+
.DS_Store
|
|
17
|
+
.idea/
|
|
18
|
+
.vscode/
|
|
19
|
+
.claude/*
|
|
20
|
+
!.claude/skills/
|
|
21
|
+
.gemini/
|
|
22
|
+
|
|
23
|
+
# Build artifacts
|
|
24
|
+
build/
|
|
25
|
+
dist/
|
|
26
|
+
|
|
27
|
+
# Project stubs / leftovers
|
|
28
|
+
main.py
|
|
29
|
+
*.mc
|
|
30
|
+
|
|
31
|
+
# Structural data (downloaded on demand)
|
|
32
|
+
*.pdb
|
|
33
|
+
*.cif
|
|
34
|
+
*.mmtf
|
|
35
|
+
|
|
36
|
+
# Media (keep README assets)
|
|
37
|
+
*.mp4
|
|
38
|
+
*.gif
|
|
39
|
+
*.png
|
|
40
|
+
!assets/*.png
|
|
41
|
+
!assets/*.gif
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Local gates that mirror CI, so a formatting slip is caught before it becomes
|
|
2
|
+
# a red pipeline (and, in a stacked branch, a rebase of everything above it).
|
|
3
|
+
#
|
|
4
|
+
# Run `pre-commit install` once after cloning. See CONTRIBUTING.md.
|
|
5
|
+
#
|
|
6
|
+
# Why the linters run through `uv run` as `language: system` rather than
|
|
7
|
+
# pre-commit's own ruff/mypy repos: those would pin their own tool versions in
|
|
8
|
+
# this file, giving the project two sources of truth that drift apart until a
|
|
9
|
+
# hook passes locally and CI fails (or worse, the reverse). Going through uv
|
|
10
|
+
# means the hooks use exactly the versions in uv.lock — the same ones CI
|
|
11
|
+
# installs.
|
|
12
|
+
|
|
13
|
+
default_install_hook_types: [pre-commit, pre-push]
|
|
14
|
+
|
|
15
|
+
repos:
|
|
16
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
17
|
+
rev: v6.0.0
|
|
18
|
+
hooks:
|
|
19
|
+
# Whitespace and newline hygiene, which otherwise shows up as noise in
|
|
20
|
+
# unrelated diffs.
|
|
21
|
+
- id: trailing-whitespace
|
|
22
|
+
# Markdown uses two trailing spaces as a hard line break.
|
|
23
|
+
args: [--markdown-linebreak-ext=md]
|
|
24
|
+
- id: end-of-file-fixer
|
|
25
|
+
- id: mixed-line-ending
|
|
26
|
+
args: [--fix=lf]
|
|
27
|
+
# Config files that break the build if malformed.
|
|
28
|
+
- id: check-yaml
|
|
29
|
+
- id: check-toml
|
|
30
|
+
# Things that should never reach a commit.
|
|
31
|
+
- id: check-merge-conflict
|
|
32
|
+
- id: check-case-conflict
|
|
33
|
+
- id: debug-statements
|
|
34
|
+
- id: check-added-large-files
|
|
35
|
+
# Structures and renders are gitignored; this catches the ones that
|
|
36
|
+
# slip past, since a stray PDB or PNG is easy to add and hard to remove.
|
|
37
|
+
# Sized to catch accidents rather than curated media: a downloaded
|
|
38
|
+
# assembly or a ray-traced PNG runs to several MB, while the largest
|
|
39
|
+
# deliberate asset here is an 830 KB turntable GIF.
|
|
40
|
+
args: [--maxkb=1024]
|
|
41
|
+
|
|
42
|
+
- repo: local
|
|
43
|
+
hooks:
|
|
44
|
+
- id: ruff-check
|
|
45
|
+
name: ruff check
|
|
46
|
+
entry: uv run ruff check --force-exclude --fix
|
|
47
|
+
language: system
|
|
48
|
+
types_or: [python, pyi]
|
|
49
|
+
require_serial: true
|
|
50
|
+
|
|
51
|
+
- id: ruff-format
|
|
52
|
+
name: ruff format
|
|
53
|
+
entry: uv run ruff format --force-exclude
|
|
54
|
+
language: system
|
|
55
|
+
types_or: [python, pyi]
|
|
56
|
+
require_serial: true
|
|
57
|
+
|
|
58
|
+
- id: mypy
|
|
59
|
+
name: mypy
|
|
60
|
+
# mypy reads `files` and its settings from pyproject.toml, so it runs
|
|
61
|
+
# over the whole package rather than the staged subset — passing
|
|
62
|
+
# filenames would silently override that config.
|
|
63
|
+
entry: uv run mypy
|
|
64
|
+
language: system
|
|
65
|
+
pass_filenames: false
|
|
66
|
+
files: ^(src/.*\.py|pyproject\.toml)$
|
|
67
|
+
require_serial: true
|
|
68
|
+
|
|
69
|
+
- id: uv-lock-check
|
|
70
|
+
name: uv.lock in sync with pyproject.toml
|
|
71
|
+
# Mirrors the CI "Lockfile in sync" job: blocks the commit if the
|
|
72
|
+
# lockfile is stale so it never reaches a push / the test matrix.
|
|
73
|
+
entry: uv lock --locked
|
|
74
|
+
language: system
|
|
75
|
+
files: ^(pyproject\.toml|uv\.lock)$
|
|
76
|
+
pass_filenames: false
|
|
77
|
+
|
|
78
|
+
- id: pytest
|
|
79
|
+
name: pytest
|
|
80
|
+
# Deliberately pre-push, not pre-commit: the suite takes ~16 s, which
|
|
81
|
+
# is too slow to pay on every commit but unremarkable on a push.
|
|
82
|
+
entry: uv run pytest -q
|
|
83
|
+
language: system
|
|
84
|
+
pass_filenames: false
|
|
85
|
+
stages: [pre-push]
|
|
86
|
+
require_serial: true
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|