Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
691c67e
feat: hatch build change
kierandidi Dec 1, 2025
82305e1
fix: pip wheel
nscorley Dec 1, 2025
97d2822
fix: pypi release version
nscorley Dec 1, 2025
b4fa5b7
Merge branch 'production' into staging
nscorley Dec 1, 2025
ea325c3
fix: pin requires-python <3.13 due to pyarrow 17.x build failure
daylight-00 Feb 25, 2026
f256f4e
feat: Add ASE LMDB dataset support with loader utilities and tests
amorehead May 5, 2026
cf97a75
feat: Add test for ASELMDBDataset to read real OMol25 test partition …
amorehead May 5, 2026
ac43ddd
feat: Enhance ASE LMDB support with materials science data loader and…
amorehead May 5, 2026
fc43004
refactor: Remove redundant main block from test for ASE materials loader
amorehead May 5, 2026
a8f6c1d
style: Update formatting for HTML JS files in Sphinx configuration
amorehead May 5, 2026
46f34ab
feat: Add additional annotations for atom arrays in ase_atoms_to_atom…
amorehead May 11, 2026
58cb6a6
docs(lmdb): clarify ASE atom array loading
Alex-Abrudan May 12, 2026
ceeafc8
Merge pull request #1 from amorehead/amorehead-lmdb-touchup
amorehead May 12, 2026
665c246
fix: enhance get_row_and_index_by_example_id to handle datasets witho…
amorehead May 22, 2026
53626ef
refactor: run linting
amorehead May 22, 2026
9547bb7
Merge origin/staging; resolve pyproject.toml conflict.
kierandidi Jun 7, 2026
8769ecd
Update urllib3 package based on security alert
Jun 8, 2026
358c4dc
Update tornado due to security risk
Jun 8, 2026
36fae05
Update packages to address security risks
Jun 8, 2026
4ce3a8b
Update formatting
Jun 8, 2026
7ba1b58
Revert torch dependency version back to 2.2.0 in pyproject.toml
woodsh17 Jun 9, 2026
3ed2df4
Merge branch 'production' into feat/add-lmdb-support
amorehead Jun 19, 2026
0ca1bd7
Docs: AtomWorks Cookbook
rclune Jun 29, 2026
643820e
Docs: point version switcher at RosettaCommons Pages
Jul 17, 2026
bc437fd
Docs: deploy archived docs only for tagged releases
Jul 17, 2026
4e463e7
fix(ml): don't require CCD leaving groups when substituting residues
benz0id Jul 30, 2026
fc043e7
feat: add mmseqs server usage
r-krishna Aug 8, 2026
f50d629
remove server tests
r-krishna Aug 8, 2026
d6c52cb
remove init
r-krishna Aug 8, 2026
bdc64fd
chore ruff
r-krishna Aug 8, 2026
332dab9
fix(io): write parse cache atomically
hwendler Aug 13, 2026
1cce0c9
style(docs): apply make format to conf.py
hwendler Aug 13, 2026
e87e7fa
Apply suggestions from code review
hwendler Aug 18, 2026
1367359
fix(io): map cache suffix to compression; drop redundant cache tests
hwendler Aug 18, 2026
2cbb836
docs(installation): Add CCD sync to test setup instructions
Emrys-Merlin Sep 10, 2026
df50559
chore(release): prepare AtomWorks 3.0 public snapshot
nscorley Sep 30, 2026
d151847
docs: add AtomWorks 3.0 migration guides
m1c0l Sep 30, 2026
46b19b3
docs: fix migration index heading
m1c0l Sep 30, 2026
50f6050
docs: focus migration guides on IO changes
m1c0l Sep 30, 2026
c76d9a6
docs: remove migration draft scope section
m1c0l Sep 30, 2026
8aec643
fix(ml): adapt canonical residue matching to 3.0 CCD groups
m1c0l Sep 30, 2026
eb83eb6
docs: update cookbook recipes for AtomWorks 3.0
m1c0l Sep 30, 2026
23f9f4d
docs: clarify separate CCD sync for release 3.0
m1c0l Sep 30, 2026
1e59a6b
Merge pull request #101 from Emrys-Merlin/docs/installation_instructions
m1c0l Sep 30, 2026
5b86399
docs: simplify short migration parser guidance
m1c0l Sep 30, 2026
49851e5
Merge pull request #93 from RosettaCommons/docs/fix-version-switcher-…
m1c0l Oct 1, 2026
ae27527
docs: keep cookbook guidance focused on reader tasks
m1c0l Oct 1, 2026
5fff4bb
test: scope cache interruption regression to the 3.0 parser
m1c0l Oct 1, 2026
5e15154
Merge pull request #98 from hwendler/fix/atomic-parse-cache-writes
m1c0l Oct 1, 2026
4d14911
docs: keep cookbook recipe imports independent
m1c0l Oct 1, 2026
3e45c08
test: consolidate canonical residue matching coverage
m1c0l Oct 1, 2026
930e72c
Merge pull request #95 from benz0id/fix/undesired-res-token-leaving-a…
m1c0l Oct 1, 2026
d60ee57
fix: port security updates to 3.0 with optional audited constraints
m1c0l Oct 1, 2026
048f51b
feat: port remote MSA server to 3.0 with bounded jobs
m1c0l Oct 1, 2026
2ed737d
feat: reconcile materials loading with 3.0 datasets
m1c0l Oct 1, 2026
6501c62
Merge pull request #92 from RosettaCommons/docs_cookbook
m1c0l Oct 1, 2026
e5153b3
Merge pull request #83 from amorehead/feat/add-lmdb-support
m1c0l Oct 1, 2026
23e14d6
fix: apply security dependency floors directly after runtime validation
m1c0l Oct 1, 2026
6530a03
Merge pull request #89 from RosettaCommons/fix/update_package_securit…
m1c0l Oct 1, 2026
9b3f5bf
test: consolidate remote MSA protocol and dispatch coverage
m1c0l Oct 1, 2026
07a7b43
fix: declare CLI and traceback dependencies in core
m1c0l Oct 2, 2026
b1c440e
perf(io): port link validation caches and parser prerequisites
m1c0l Oct 2, 2026
af6309b
docs: add migration guides with link chemistry guidance
m1c0l Oct 2, 2026
392936c
Merge pull request #96 from RosettaCommons/feat/add_msa_server
m1c0l Oct 2, 2026
e0f2e4d
feat: integrate current dev chemistry into public AtomWorks 3.0
kierandidi Oct 2, 2026
eb5cf73
docs: refresh AtomWorks 3 release README and navigation
kierandidi Oct 3, 2026
46fcbcc
ci: publish AtomWorks from verified version tags
kierandidi Oct 4, 2026
c135b2f
ci: run lint on a hosted runner
kierandidi Oct 4, 2026
b878415
ci: expose test helpers to spawned workers
kierandidi Oct 4, 2026
d4a0227
build: prepare AtomWorks 3.0.0 metadata
kierandidi Oct 4, 2026
4090824
docs: preserve public analytics and clarify mmJSON inputs
kierandidi Oct 4, 2026
1007121
docs: name the 2.3 release and use a transparent dark-mode logo
kierandidi Oct 4, 2026
12744d5
release: prepare AtomWorks 2.3.0
kierandidi Oct 4, 2026
5ea0636
fix: retain package sources when rebuilding release wheels
kierandidi Oct 4, 2026
04467a8
ci: validate ready PRs through the pull request workflow
kierandidi Oct 4, 2026
ecbb0b4
Merge branch 'review/aw-23-docs' into ci/public-release-checks
kierandidi Oct 4, 2026
40a8917
docs: resolve migration guide conflicts with release branch
nscorley Oct 4, 2026
1e362a2
Merge pull request #103 from m1c0l/docs/atomworks-3-migration
nscorley Oct 4, 2026
64986a0
chore: name the upcoming release AtomWorks 3.0
kierandidi Oct 4, 2026
e636a9b
chore: name the upcoming release AtomWorks 3.0
kierandidi Oct 4, 2026
f92c214
fix: preserve dependency versions during release rename
kierandidi Oct 4, 2026
206fbee
ci: gate AtomWorks 3 releases on public tests, installed wheels and docs
kierandidi Oct 4, 2026
3bd741f
Merge branch 'review/aw-23-release' into ci/public-release-checks
kierandidi Oct 4, 2026
96913df
Merge branch 'review/aw-23-docs' into ci/public-release-checks
kierandidi Oct 4, 2026
b911d9f
feat: sync remaining dev chemistry and ColabFold updates for 3.0
nscorley Oct 4, 2026
38ae3fd
fix: keep core CLI imports independent of optional ML dependencies
kierandidi Oct 4, 2026
bbe0273
docs: use the minimal parser preset in the quick start
kierandidi Oct 4, 2026
bb08e58
Merge remote-tracking branch 'public/release/atomworks-3-0' into ci/p…
kierandidi Oct 4, 2026
fa8778c
ci: pin regression inputs and deploy release docs explicitly
kierandidi Oct 4, 2026
7747d8e
docs: consolidate AtomWorks release and development guidance
kierandidi Oct 4, 2026
a201fac
refactor: replace duplicate MSA server client with ColabFold workflow
nscorley Oct 4, 2026
6c419b1
docs: preserve explanatory docstrings and examples
kierandidi Oct 4, 2026
23495f9
ci: use one public test bundle and narrow release changes
kierandidi Oct 5, 2026
a71d43f
docs: fix Sphinx warnings while preserving examples
kierandidi Oct 5, 2026
f562b9d
ci: verify optional MSA command imports in core installs
kierandidi Oct 5, 2026
43c79f8
docs: keep warning fixes compatible with release preparation
kierandidi Oct 5, 2026
29d65f1
fix: restore eager MSA imports and original module layout
kierandidi Oct 5, 2026
85c72f7
docs: correct development environment setup
kierandidi Oct 5, 2026
6536d34
fix(io): sync minimal PDB preservation policy from dev
nscorley Oct 5, 2026
0d7cfee
refactor: use dev protonation implementation exclusively
nscorley Oct 5, 2026
9db44b7
Merge pull request #113 from RosettaCommons/codex/sync-public-dev-202…
nscorley Oct 5, 2026
b3bc7ea
Merge release updates preserving dev chemistry and ColabFold client
nscorley Oct 5, 2026
812e970
Merge documentation warning fixes for combined release validation
nscorley Oct 5, 2026
4482cb5
fix: retain existing branch-based Pages deployment
kierandidi Oct 5, 2026
d9b515c
fix: sync final dev insertion-code and documentation updates
nscorley Oct 5, 2026
81d58eb
Merge pull request #114 from RosettaCommons/ci/public-release-checks
nscorley Oct 5, 2026
bffeb79
docs: document MSA CLI and preprocessing APIs
nscorley Oct 5, 2026
e39fd3f
Merge pull request #117 from RosettaCommons/codex/msa-cli-docs-public
nscorley Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
18 changes: 16 additions & 2 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ PDB_MIRROR_PATH=
# Example: /path/to/ccd_mirror/M/MYLIGAND1/MYLIGAND1.cif
CCD_MIRROR_PATH=

# --- Local MSA directories ---
LOCAL_MSA_DIRS=
# --- MSA directories ---
PROTEIN_MSA_DIRS=


# --- External tools ---
Expand All @@ -37,6 +37,20 @@ X3DNA_PATH=
# Example: /path/to/hhsuite/build/bin/hhfilter
HHFILTER_PATH=

# The `HHBLITS_PATH` is a path to the hhblits tool from the HH-suite, which is used for
# CPU-based MSA generation (--backend hhblits).
# Example: /path/to/hhsuite/build/bin/hhblits
HHBLITS_PATH=

# HH-suite database paths (required for --backend hhblits)
# UniRef30 database for HHblits
# Example: /local/databases/uniclust/UniRef30_2022_02
HHBLITS_UNIREF30_DB_PATH=

# BFD database for HHblits (used as fallback when UniRef30 yields insufficient sequences)
# Example: /local/databases/bfd/bfd_metaclust_clu_complete_id30_c90_final_seq.sorted_opt
HHBLITS_BFD_DB_PATH=

# The `MMSEQS2_PATH` is a path to the mmseqs2 tool, which is used for fast sequence searching.
# Example: /path/to/mmseqs-gpu/bin/mmseqs
MMSEQS2_PATH=
Expand Down
31 changes: 25 additions & 6 deletions .github/ci/run_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,41 @@ echo "Running from $PWD"

export OPENBLAS_NUM_THREADS=1
export OMP_NUM_THREADS=1
apptainer_path=/net/software/containers/users/ncorley/atomworks/atomworks_dev.sif
tar -xf /net/lab/pub/atomworks/test_pack_latest.tar.gz -C tests/data > /dev/null 2>&1
: "${ATOMWORKS_CI_CONTAINER:?Set the AtomWorks CI container path}"
: "${ATOMWORKS_CI_TEST_PACK:?Set the AtomWorks CI test-pack path}"

# Give the container a job-local /tmp instead of the compute node's.
ci_tmp=$(mktemp -d "$PWD/.ci-tmp.XXXXXX")
trap 'rm -rf "$ci_tmp"' EXIT

run_in_container() {
apptainer exec \
--bind "$PWD:/workspace" \
--bind "$ci_tmp:/tmp" \
--pwd /workspace \
--env PYTHONPATH=/workspace/src \
--env TMPDIR=/tmp \
"$ATOMWORKS_CI_CONTAINER" "$@"
}

# Extract the shared test pack (non-PDB fixtures). The PDB mirror itself is
# resolved by tests/conftest.py, which prefers the lab-wide frozen PDB copy.
mkdir -p tests/data
tar -xf "$ATOMWORKS_CI_TEST_PACK" -C tests/data

# Get max processes from environment variable, default to 24 if not set
N_CPU=${N_CPU:-24}
echo "Running tests with max $N_CPU CPUs"

# Ensure we can collect all tests (i.e. imports succeed)
echo "Testing imports by trying to collect all tests"
apptainer exec --bind $PWD:/workspace --pwd /workspace --env PYTHONPATH=/workspace/src $apptainer_path pytest -m "not benchmark" --collect-only tests/
run_in_container pytest -m "not benchmark" --collect-only tests/

# Run the tests in coverage mode (with 24 CPUs)
apptainer exec --bind $PWD:/workspace --pwd /workspace --env PYTHONPATH=/workspace/src $apptainer_path pytest -m "not benchmark" --cov=atomworks --cov-report=xml -n=auto --maxprocesses=$N_CPU --dist=worksteal tests/
run_in_container pytest -m "not benchmark" --cov=atomworks --cov-report=xml -n=auto --maxprocesses=$N_CPU --dist=worksteal tests/

# Require at least 80% coverage
apptainer exec --bind $PWD:/workspace --pwd /workspace --env PYTHONPATH=/workspace/src $apptainer_path coverage report --fail-under=80
run_in_container coverage report --fail-under=80

# Output the coverage in a format GitLab can parse
apptainer exec --bind $PWD:/workspace --pwd /workspace --env PYTHONPATH=/workspace/src $apptainer_path coverage report | tail -n 1 | awk '{print "TOTAL", $NF}'
run_in_container coverage report | tail -n 1 | awk '{print "TOTAL", $NF}'
10 changes: 2 additions & 8 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
## 📋 PR Checklist

- [ ] This PR is tagged as a [draft](https://github.blog/news-insights/product-news/introducing-draft-pull-requests/) if it is still under development and not ready for review.
> This avoids auto-triggering the slower tests in the CI and needlessly wasting resources.
> Drafts still run CI.

- [ ] I have ensured that all my commits follow [angular commit message conventions](https://www.conventionalcommits.org/en/v1.0.0-beta.4/).
> Format: `<type>[optional scope]: <subject>`
> Example: `fix(af3): add missing crop transform to the af3 pipeline`
>
> This affects semantic versioning as follows:
> - `fix`: patch version increment (0.0.1 → 0.0.2)
> - `feat`: minor version increment (0.0.1 → 0.1.0)
> - `BREAKING CHANGE`: major version increment (0.0.1 → 1.0.0)
> - All other types do not affect versioning
>
> The format ensures readable changelogs through auto-generation from commit messages.
> Releases use the explicit version in `pyproject.toml` and a matching `v<version>` tag.

- [ ] I have run `make format` on the codebase before submitting the PR (this autoformats the code and lints it).

Expand Down
101 changes: 101 additions & 0 deletions .github/release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Validate AtomWorks release tags and prepare versioned documentation."""

import json
import os
import shutil
import sys
import tarfile
import tomllib
import zipfile
from pathlib import Path

from packaging.version import Version

DOCS_URL = "https://rosettacommons.github.io/atomworks"


def verify_distributions(dist: Path, source: Path) -> None:
"""Require every package source and data file in both release archives, byte for byte."""
(wheel,) = dist.glob("*.whl")
(sdist,) = dist.glob("*.tar.gz")
files = [path for path in source.rglob("*") if path.is_file() and "__pycache__" not in path.parts]
if not files:
raise ValueError(f"No package files found in {source}")
with zipfile.ZipFile(wheel) as binary, tarfile.open(sdist) as archive:
for path in files:
relative = path.relative_to(source).as_posix()
member = f"{sdist.name.removesuffix('.tar.gz')}/src/{relative}"
archived = archive.extractfile(member)
expected = path.read_bytes()
if archived is None or archived.read() != expected or binary.read(relative) != expected:
raise ValueError(f"Release archive differs from source: {relative}")


def release_version(tag: str) -> Version:
"""Accept canonical version tags suitable for a public package release."""
version = Version(tag.removeprefix("v"))
if tag != f"v{version}" or version.is_devrelease or version.local:
raise ValueError(f"Expected a canonical release tag, got {tag!r}")
return version


def verify_tag(tag: str, project_file: Path) -> Version:
"""Require the release tag to match the package's declared version exactly."""
version = release_version(tag)
project_version = tomllib.loads(project_file.read_text())["project"]["version"]
if tag != f"v{project_version}":
raise ValueError(f"Tag {tag!r} does not match project.version {project_version!r}")
return version


def prepare_docs(tag: str, html: Path, pages: Path) -> None:
"""Keep published version pages intact and point latest at the newest stable release."""
release_version(tag)
if not (html / "index.html").is_file():
raise FileNotFoundError(f"Documentation index missing from {html}")
destination = pages / tag
if not destination.exists():
shutil.copytree(html, destination)
versions = []
for directory in pages.iterdir():
if not directory.is_dir() or not (directory / "index.html").is_file():
continue
try:
version = release_version(directory.name)
except ValueError:
continue
versions.append((version, directory.name))
versions.sort(reverse=True)
stable = next((name for version, name in versions if not version.is_prerelease), None)
latest = pages / "latest"
if stable:
if latest.exists():
shutil.rmtree(latest)
shutil.copytree(pages / stable, latest)
switcher = [{"version": "latest", "url": f"{DOCS_URL}/latest/index.html", "name": "latest"}] if stable else []
switcher.extend(
{"version": str(version), "url": f"{DOCS_URL}/{name}/index.html", "name": name, "preferred": name == stable}
for version, name in versions
)
targets = [pages, *(pages / name for _, name in versions)]
if stable:
targets.append(latest)
for directory in targets:
switcher_path = directory / "_static" / "switcher.json"
switcher_path.parent.mkdir(parents=True, exist_ok=True)
switcher_path.write_text(json.dumps(switcher, indent=2) + "\n")


if __name__ == "__main__":
if sys.argv[1:] == ["artifacts"]:
verify_distributions(Path("dist"), Path("src"))
raise SystemExit(0)
tag = os.environ["GITHUB_REF_NAME"]
version = verify_tag(tag, Path("pyproject.toml"))
if sys.argv[1:] == ["verify"]:
with Path(os.environ["GITHUB_OUTPUT"]).open("a") as output:
output.write(f"prerelease={str(version.is_prerelease).lower()}\n")
elif sys.argv[1:] == ["docs"]:
prepare_docs(tag, Path("docs/_build/html"), Path("gh-pages"))
else:
raise SystemExit("Usage: python .github/release.py {verify|docs|artifacts}")
98 changes: 98 additions & 0 deletions .github/tests/test_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Check package contents, tag consistency, and docs promotion without publishing."""

import json
import sys
import tarfile
import tempfile
import unittest
import zipfile
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from release import prepare_docs, verify_distributions, verify_tag


class ReleaseTests(unittest.TestCase):
def setUp(self) -> None: # noqa: N802
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.project = self.root / "pyproject.toml"
self.html = self.root / "html"
self.html.mkdir()
(self.html / "index.html").write_text("new documentation")
self.pages = self.root / "pages"
self.pages.mkdir()

def test_tag_must_match_project_version(self) -> None:
for value in ("2.3.0", "2.3.0rc1"):
with self.subTest(value=value):
self.project.write_text(f'[project]\nversion = "{value}"\n')
self.assertEqual(str(verify_tag(f"v{value}", self.project)), value)
with self.assertRaises(ValueError):
verify_tag("v9.0.0", self.project)

def test_distribution_contents(self) -> None:
source = self.root / "src"
package = source / "atomworks"
package.mkdir(parents=True)
(package / "__init__.py").write_text('"""AtomWorks."""\n')
(package / "parameters.json").write_text('{"charge": -1}\n')
dist = self.root / "dist"
dist.mkdir()
for case in ("complete", "empty-wheel", "missing-data", "changed-data", "relocated-source"):
with self.subTest(case=case):
with zipfile.ZipFile(dist / "atomworks-2.3.0-py3-none-any.whl", "w") as wheel:
for path in package.iterdir():
if case == "empty-wheel" or (case == "missing-data" and path.suffix == ".json"):
continue
data = b"{}" if case == "changed-data" and path.suffix == ".json" else path.read_bytes()
wheel.writestr(path.relative_to(source).as_posix(), data)
with tarfile.open(dist / "atomworks-2.3.0.tar.gz", "w:gz") as archive:
prefix = "" if case == "relocated-source" else "src/"
archive.add(package, arcname=f"atomworks-2.3.0/{prefix}atomworks")
if case == "complete":
verify_distributions(dist, source)
else:
with self.assertRaises((KeyError, ValueError)):
verify_distributions(dist, source)

def test_rejects_nonrelease_tags(self) -> None:
self.project.write_text('[project]\nversion = "2.3.0"\n')
for tag in ("2.3.0", "production", "v2.3.0.dev1", "v2.3.0+local", "v2.3.0/extra"):
with self.subTest(tag=tag), self.assertRaises(ValueError):
verify_tag(tag, self.project)

def test_old_release_and_prerelease_do_not_replace_latest(self) -> None:
for version in ("v2.2.10", "v2.2.9", "v2.3.0rc1"):
(self.html / "index.html").write_text(version)
prepare_docs(version, self.html, self.pages)
self.assertEqual((self.pages / "latest/index.html").read_text(), "v2.2.10")
switcher = json.loads((self.pages / "_static/switcher.json").read_text())
self.assertEqual([entry["name"] for entry in switcher], ["latest", "v2.3.0rc1", "v2.2.10", "v2.2.9"])
self.assertEqual([entry["name"] for entry in switcher if entry.get("preferred")], ["v2.2.10"])
self.assertEqual(switcher, json.loads((self.pages / "v2.2.9/_static/switcher.json").read_text()))

def test_new_stable_release_updates_latest_without_stale_files(self) -> None:
prepare_docs("v2.0.0", self.html, self.pages)
(self.pages / "latest/removed.html").write_text("old page")
(self.html / "index.html").write_text("new stable")
prepare_docs("v2.3.0", self.html, self.pages)
self.assertEqual((self.pages / "latest/index.html").read_text(), "new stable")
self.assertFalse((self.pages / "latest/removed.html").exists())

def test_retry_preserves_published_version_pages(self) -> None:
prepare_docs("v2.3.0", self.html, self.pages)
(self.html / "index.html").write_text("different build")
prepare_docs("v2.3.0", self.html, self.pages)
self.assertEqual((self.pages / "v2.3.0/index.html").read_text(), "new documentation")

def test_first_prerelease_has_switcher_without_stable_alias(self) -> None:
prepare_docs("v2.3.0rc1", self.html, self.pages)
self.assertFalse((self.pages / "latest").exists())
switcher = json.loads((self.pages / "_static/switcher.json").read_text())
self.assertEqual([entry["name"] for entry in switcher], ["v2.3.0rc1"])


if __name__ == "__main__":
unittest.main()
Loading
Loading