Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/check_pypi_packaging.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,36 @@ jobs:
pip install build
- name: Build
run: python -m build

# Everything above only proves the artifacts can be produced. This installs
# the built wheel the way a user would and imports it from a directory with
# no source tree in it, so nothing can be satisfied by the checkout: the
# repo uses a flat layout, so running from the repo root would import
# scadnano/ directly and prove nothing about the wheel.
- name: Install the built wheel and import it as a user would
run: |
python -m venv /tmp/wheel-check
/tmp/wheel-check/bin/pip install --quiet dist/*.whl
mkdir -p /tmp/neutral
cd /tmp/neutral
/tmp/wheel-check/bin/python - <<'PY'
import os, pathlib, sys, tomllib
import scadnano, scadnano.modifications, scadnano.origami_rectangle

# Quoted heredoc, so the shell expands nothing; read the workspace path
# from the environment instead.
pyproject = pathlib.Path(os.environ["GITHUB_WORKSPACE"]) / "pyproject.toml"
expected = tomllib.loads(pyproject.read_text())["project"]["version"]

if scadnano.__version__ != expected:
sys.exit(
f"scadnano.__version__ is {scadnano.__version__!r}, but "
f"pyproject.toml declares {expected!r}"
)

# Exercising a real design keeps this from passing on an import alone.
design = scadnano.Design(helices=[scadnano.Helix(max_offset=16)], strands=[])
assert design.to_json(), "design serialized to nothing"

print(f"ok: installed wheel imports and reports version {expected}")
PY
2 changes: 1 addition & 1 deletion .github/workflows/docs-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Install Sphinx
run: |
python -m pip install --upgrade pip
pip install -r doc/requirements.txt
pip install .[docs]
- name: Move to docs folder and build
run: |
cd doc
Expand Down
17 changes: 10 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name: "release"

# On every push to main: tag v{__version__}, create a GitHub release whose body
# On every push to main: tag v{version}, create a GitHub release whose body
# lists commits since the previous release, and publish to PyPI.
#
# Issues are not touched here. main is the default branch, so GitHub itself
# closes any issue referenced by a closing keyword in the commits this push
# brings to main, and records the closing commit on the issue.
#
# INVARIANT: every push to main is a release. If __version__ in
# scadnano/scadnano.py was not bumped, this workflow FAILS (red X) on purpose.
# INVARIANT: every push to main is a release. If the version in pyproject.toml
# was not bumped, this workflow FAILS (red X) on purpose.
#
# NEVER substitute a personal access token (PAT) for GITHUB_TOKEN below.
# Actions taken with GITHUB_TOKEN do not trigger other workflows, and the
Expand Down Expand Up @@ -43,12 +43,15 @@ jobs:
with:
fetch-depth: 0 # full history + all tags, needed for `git log <prev>..HEAD`

- name: Extract version from scadnano/scadnano.py
- name: Extract version from pyproject.toml
id: version
run: |
version=$(sed -nE 's/^__version__ = "([^"]+)".*$/\1/p' scadnano/scadnano.py | head -1)
# Parsed with tomllib rather than a regex so that comments, quoting
# style and key order in pyproject.toml cannot silently change the
# answer. tomllib is stdlib from 3.11; the runner image ships newer.
version=$(python3 -c 'import tomllib, pathlib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error file=scadnano/scadnano.py::could not extract semantic version from the __version__ line (got: '$version')"
echo "::error file=pyproject.toml::could not extract a semantic version from [project] version (got: '$version')"
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
Expand All @@ -63,7 +66,7 @@ jobs:
echo "Tag v$version already exists at this exact commit: re-run of a partially failed workflow. Skipping release creation."
echo "create_release=false" >> "$GITHUB_OUTPUT"
else
echo "::error::Tag v$version already exists (at $tag_commit) but this push is $GITHUB_SHA. __version__ in scadnano/scadnano.py was not bumped before merging to main. Bump it on dev and merge dev to main again (see CONTRIBUTING.md); this red X is the intended reminder."
echo "::error::Tag v$version already exists (at $tag_commit) but this push is $GITHUB_SHA. The version in pyproject.toml was not bumped before merging to main. Bump it on dev and merge dev to main again (see CONTRIBUTING.md); this red X is the intended reminder."
exit 1
fi
else
Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/run_unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
fail-fast: false
matrix:
# "3.x" means the newest released Python, so the latest version is always tested even if this
# list is not kept up to date. setup.py requires >= 3.10.
# list is not kept up to date. pyproject.toml requires >= 3.10.
python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ]

steps:
Expand All @@ -21,7 +21,10 @@ jobs:
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
- name: Install openpyxl,tabulate with pip
run: pip install openpyxl tabulate
# Editable, so the tests still exercise the checked-out source (they read
# data from tests_inputs/ relative to the repo root). The install is what
# registers the distribution metadata that scadnano.__version__ reads.
- name: Install scadnano and test dependencies
run: pip install -e .[tests]
- name: Test with unittest
run: python -m unittest -v tests/scadnano_tests.py
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ __pycache__/
tests_outputs/
.vscode/
dist/
build/
# generated by `python setup.py sdist`; not MANIFEST.in, and nothing reads it
MANIFEST
.mypy_cache/
39 changes: 24 additions & 15 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,23 @@ The scadnano Python package requires at least Python 3.7. See the [README for in

Follow the [installation instructions](README.md#installation) to install the correct version of Python if you don't have it already.

It is actually unnecessary for you to install scadnano via pip, so you can skip that step. In developing, you will have a local version of the package that you run and modify.
Install your clone in editable mode from the repository root: `pip install -e .[tests]`. You still run and modify the local source — editable mode means your edits take effect immediately, with no reinstall — but the install is required, because `scadnano.__version__` is read from the installed distribution's metadata.

I suggest using a powerful IDE such as [PyCharm](https://www.jetbrains.com/pycharm/download/download-thanks.html). [Visual Studio Code](https://code.visualstudio.com/) is also good with the right plugins. The scadnano Python package uses type hints, and these tools are very helpful in giving static analysis warnings about the code that may represent errors that will manifest at run time.

### Keeping the scadnano package simple for users to install

One goal is to make the package as easy to install as possible, even for users who have trouble installing scadnano via pip. For this reason, we have two self-imposed constraints:
One self-imposed constraint remains: keep package dependencies minimal. scadnano depends only on
[openpyxl](https://pypi.org/project/openpyxl/) and [tabulate](https://pypi.org/project/tabulate/),
both declared in [pyproject.toml](pyproject.toml), and it should stay that way unless there is a
strong reason otherwise.

1. There are minimal package dependencies. scadnano can be run in most circumstances with a standard Python 3.7 (or above) installation. (One exception is the package [xlwt](https://pypi.org/project/xlwt/), which is required to call the method [`Design.write_idt_plate_excel_file()`](https://scadnano-python-package.readthedocs.io/#scadnano.Design.write_idt_plate_excel_file).)

2. All the required code is in a single file, [scadnano.py](scadnano/scadnano.py). This is one reason an IDE will help, because navigating a large source code file is easier in an IDE.

These two constraints imply that a user who has trouble installing via pip can simply copy the file scadnano.py into their working directory (or in some directory on their `PYTHONPATH`) and import it as normal.
scadnano is installed as a normal Python package, so `pip install scadnano` is the only supported
way to get it. It used to be possible to copy [scadnano.py](scadnano/scadnano.py) into your working
directory and import it without installing anything, and most of the code is still in that one file,
which is why an IDE helps for navigating it. But that is no longer a supported workflow: the package
reads its own version from the installed distribution's metadata, so importing it requires a real
install.


### git
Expand Down Expand Up @@ -183,9 +187,9 @@ GitHub proposes it as the base, so there is nothing to change. The workflow that
`dev` deliberately skips this one, recognizing it by its `dev` head branch *in this repository*
(a contributor's fork may also have a `dev` branch, and those PRs are retargeted normally).

**Every push to `main` is a release.** The release workflow reads `__version__` from
[scadnano/scadnano.py](scadnano/scadnano.py), creates the tag `v{version}`, creates a GitHub release,
and publishes to PyPI. So you **must bump `__version__` on `dev` before merging `dev` into `main`**.
**Every push to `main` is a release.** The release workflow reads `version` from
[pyproject.toml](pyproject.toml), creates the tag `v{version}`, creates a GitHub release,
and publishes to PyPI. So you **must bump the version on `dev` before merging `dev` into `main`**.
If you forget, the release workflow fails with a red X and an explanatory message, and nothing is
tagged or published; bump the version on `dev` and merge again to recover. (Practically, this means
there is no such thing as a casual push to `main` — even a README typo fix rides along with a version
Expand Down Expand Up @@ -219,16 +223,21 @@ So the steps for committing to the main branch are:
MINOR for backwards-compatible feature additions.
- For the web interface repo scadnano, this is located at the top of the file https://github.com/UC-Davis-molecular-computing/scadnano/blob/main/lib/src/constants.dart
- For the Python library repo scadnano-python-package, there is a single source of truth: the
`__version__` line near the top of the file
[scadnano/scadnano.py](scadnano/scadnano.py) (as `__version__ = "0.9.3"` or something similar).
Keep the trailing `# version line; WARNING: ...` comment intact — `setup.py` finds this line by
searching for that comment, and the release workflow reads the same line.
`version` field under `[project]` in [pyproject.toml](pyproject.toml)
(as `version = "0.9.3"` or something similar). That is the only place to edit.

Everything else derives from it. `scadnano.__version__` — which is what stamps the version
into every `.sc` file the library writes — is read at import time from the installed
distribution's metadata via `importlib.metadata`, and the release workflow parses
`pyproject.toml` directly. One consequence worth knowing: after bumping the version, run
`pip install -e .` again before running any script whose output you care about, or the
metadata (and therefore the version written into `.sc` files) will still be the old one.

The PATCH version numbers are not always synced between the two repos, but, they should stay synced on MAJOR and MINOR versions. **Note:** right now this isn't quite true since MINOR versions deal with backwards-compatible feature additions, and some features are supported on one but not the other; e.g., modifications can be made in the Python package but not the web interface, and calculating helix rolls/positions from crossovers can be done in the web interface but not the Python package. But post-version-1.0.0, the major and minor versions of the should be enforced.

3. Ensure all unit tests pass.

4. In the Python repo, ensure that the documentation is generated without errors. First, run `pip install sphinx sphinx_rtd_theme`. This installs [Sphinx](https://www.sphinx-doc.org/en/main/), which is the most well-supported documentation generator for Python. (It's not very friendly, the syntax for things like links in docstrings is awkward, but it's well supported, so we use it.) Then, from within the subfolder `doc`, run the command `make html` (or `make.bat html` on Windows), ensure there are no errors, and inspect the documentation it generates in the folder `_build`.
4. In the Python repo, ensure that the documentation is generated without errors. First, run `pip install .[docs]` from the repository root. This installs [Sphinx](https://www.sphinx-doc.org/en/main/), which is the most well-supported documentation generator for Python. (It's not very friendly, the syntax for things like links in docstrings is awkward, but it's well supported, so we use it.) Then, from within the subfolder `doc`, run the command `make html` (or `make.bat html` on Windows), ensure there are no errors, and inspect the documentation it generates in the folder `_build`.

5. Create a PR to merge changes from dev into main. `main` is the default base, so there is nothing
to change here.
Expand Down
7 changes: 0 additions & 7 deletions MANIFEST

This file was deleted.

90 changes: 38 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,65 +85,51 @@ If that fails, or reports Python version 3.10 or below, you will have to install

### Installing the scadnano Python package

Once Python is installed, there are two ways you can install the scadnano Python package:
Once Python is installed, use [pip](https://pypi.org/project/pip/) to install the package by executing the following at the command line:

1. pip (recommended)

Use [pip](https://pypi.org/project/pip/) to install the package by executing the following at the command line:
```console
pip install scadnano
```

If it worked, you should be able to open a Python interpreter and import the scadnano module:
```console
pip install scadnano
```

```console
Python 3.7.9 (default, Aug 31 2020, 17:10:11) [MSC v.1916 64 bit (AMD64)] :: Anaconda, Inc. on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import scadnano as sc
>>> print(sc.Domain(helix=1, forward=True, start=0, end=8))
Domain(, helix=1, forward=True, start=0, end=8)
>>>
```
If it worked, you should be able to open a Python interpreter and import the scadnano module:

### Troubleshooting
If the above does not work for you, here are some things to try.
```console
Python 3.12.5 (main, Sep 11 2024, 12:00:00) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import scadnano as sc
>>> print(sc.Domain(helix=1, forward=True, start=0, end=8))
Domain(, helix=1, forward=True, start=0, end=8)
>>>
```

If your Python installation does not already have pip installed, you may have to install it.
Executing [this Python script](https://bootstrap.pypa.io/get-pip.py) should work;
see also
https://docs.python.org/3/installing/index.html
or
https://www.liquidweb.com/kb/install-pip-windows/.
Two optional submodules are installed along with it: `scadnano.modifications`, which contains common
DNA modifications such as biotin and Cy3, and `scadnano.origami_rectangle`, which helps create
origami rectangles.

Once pip is installed, or if you believe it is already installed, check your version of `pip` by typing
```
pip --version
```
It should say something like
```
pip 19.3.1 from ...lib\site-packages\pip (python 3.8)
```
If the version of Python at the end is Python 3.9 or higher, you are good. If it is version 2.7 or lower, type
```
pip3 --version
```
If that works and shows Python 3.9 or higher, you are good, but you should type `pip3` in the subsequent instructions instead of `pip`.
### Troubleshooting
If the above does not work for you, here are some things to try.


2. download
If your Python installation does not already have pip installed, you may have to install it.
Executing [this Python script](https://bootstrap.pypa.io/get-pip.py) should work;
see also
https://docs.python.org/3/installing/index.html
or
https://www.liquidweb.com/kb/install-pip-windows/.

As a simple alternative (in case you run into trouble using pip), you can simply download the scadnano.py file. However, you need to first install two packages that are required by scadnano: Install [openpyxl](https://pypi.org/project/openpyxl/) and [tabulate](https://pypi.org/project/tabulate/) by typing the following at the command line: `pip install openpyxl tabulate`.

Download and place the following files in your [PYTHONPATH](https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH) (e.g., in the same directory as the scripts you are running). **Note:** If you are reading this on the PyPI website or anywhere other than GitHub, the links below won't work. They are relative links intended to be read on the [GitHub README page](https://github.com/UC-Davis-molecular-computing/scadnano-python-package#readme).
Once pip is installed, or if you believe it is already installed, check your version of `pip` by typing
```
pip --version
```
It should say something like
```
pip 19.3.1 from ...lib\site-packages\pip (python 3.8)
```
If the version of Python at the end is Python 3.10 or higher, you are good. If it is version 2.7 or lower, type
```
pip3 --version
```
If that works and shows Python 3.10 or higher, you are good, but you should type `pip3` in the subsequent instructions instead of `pip`.

- *required*: [scadnano.py](scadnano/scadnano.py)
- *optional*: [modifications.py](scadnano/modifications.py); This contains some common DNA modifications such as biotin and Cy3.
- *optional*: [origami_rectangle.py](scadnano/origami_rectangle.py); This can help create origami rectangles, but it is not necessary to use scadnano.

To download them, right-click on "Raw" near the top and select (in Chrome or Firefox) "Save link as...":
![](images/download_raw_screenshot.png)

The scadnano package uses the Python package [xlwt](https://pypi.org/project/xlwt/) to write Excel files, so xlwt must be installed in order to call the method [`Design.write_idt_plate_excel_file()`](https://scadnano-python-package.readthedocs.io/#scadnano.Design.write_idt_plate_excel_file) to export an Excel file with DNA sequences. To install xlwt, type `pip install xlwt` at the command line. (If you instead use pip to install the scadnano package, xlwt will be automatically installed.)



Expand All @@ -161,7 +147,7 @@ The following Python script produces this design.

```python
import scadnano as sc
import modifications as mod
import scadnano.modifications as mod


def create_design() -> sc.Design:
Expand Down
Loading