Skip to content

Commit

Permalink
Merge pull request #216 from EC-USGS/v0.2.0
Browse files Browse the repository at this point in the history
Release 0.2.0
  • Loading branch information
jmccreight authored Jul 18, 2023
2 parents f623e5c + be9305c commit 8188a59
Show file tree
Hide file tree
Showing 205 changed files with 246,287 additions and 9,496 deletions.
49 changes: 0 additions & 49 deletions setup.cfg → .flake8
Original file line number Diff line number Diff line change
@@ -1,52 +1,3 @@
[metadata]
name = pywatershed
version = attr: pywatershed.version.__version__
description = pywatershed is a hydrologic model
long_description = pywatershed is a Python package for hydrologic modeling
long_description_content_type = text/markdown
author = USGS Enterprise Capacity Team
author_email = [email protected]
maintainer = James McCreight
maintainer_email = [email protected]
license = CC0
license_files = LICENSE, LICENSE.md
platform = Mac OS-X, Linux
keywords = PRMS, hydrology
classifiers =
Development Status :: 5 - Production/Stable
Intended Audience :: Science/Research
License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
Programming Language :: Python
Programming Language :: Python :: 3 :: Only
Topic :: Scientific/Engineering :: Hydrology
url = https://github.com/EC-USGS/prmsNHMpy
download_url = https://pypi.org/project/pywatershed
project_urls =
Bug Tracker = https://github.com/EC-USGS/pywatershed/issues
Source Code = https://github.com/EC-USGS/pywatershed

[options]
include_package_data = True # includes files listed in MANIFEST.in
zip_safe = False
packages = find:
python_requires = >=3.7
install_requires =
epiweeks
netCDF4
networkx
numpy
numba
pandas
pyyaml
xarray

[options.package_data]
pywatershed =
static/metadata/*.yaml

[sdist]
formats = zip

[flake8]
exclude =
.git
Expand Down
6 changes: 6 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!-- Feel free to remove check-list items aren't relevant to your change -->

- [ ] Closes #xxxx
- [ ] Tests and/or performance benchmarks added
- [ ] User visible changes (including notable bug fixes) are documented in `whats-new.rst`
- [ ] New functions/methods are listed in `api.rst` or it's sub rsts?
127 changes: 127 additions & 0 deletions .github/RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Release guide This document describes release procedures, conventions, and
utilities for `pywatershed`.

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Contents**

- [Conventions](#conventions)
- [Releasing `pywatershed`](#releasing-pywatershed)
- [Utility scripts](#utility-scripts)
- [Updating version numbers](#updating-version-numbers)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Conventions

- Releases follow the [git
flow](https://nvie.com/posts/a-successful-git-branching-model/). - Release
numbers follow [semantic version](https://semver.org/) conventions. - Minor
and major releases branch from `develop`. Patches branch from `main`.

## Releasing `pywatershed`

The release procedure is mostly automated. The workflow is defined in
`.github/workflows/release.yaml` and triggers when a release or patch branch is
pushed to this repo.

To release a new version:

1. Test asv benchmarking with the `-q` flag to ensure it is working (multiple
platformas a bonus).

1. On your local machine, create a release branch from `develop` or a patch
branch from `main`. The branch's name must follow format
`v{major}.{minor}.{patch}` ([semantic version](https://semver.org/) number
with a leading 'v'). For instance, for a minor release, if this repo is an
`upstream` remote and one's local `develop` is up to date with upstream
`develop`, then from `develop` run `git switch -c vx.y.z`.

1. If this is a patch release, make changes/fixes locally. If this is a major or
minor release, no changes are needed.

In either case, add the release version and date to the top of
`doc/whats-new.rst`. If a patch, put it below the pending minor release.

1. Push the branch to this repo. For instance, if this repo is an `upstream`
remote: `git push -u upstream vx.y.z`. This starts a job to:

- Check out the release branch Update version number in `version.txt` and
- `pywatershed/version.py` to match the version in the branch name Build and
- check the Python package Generate a changelog since the last release
- Prepend the changelog to the cumulative `HISTORY.md` Upload the package
- and changelog as artifacts Draft a PR against `main` with the updated
- version files and cumulative changelog. The cumulative `HISTORY.md` is
- version-controlled, release changelogs are not.

1. On all platforms, pull the release from upstream and perform ASV performance
benchmarks against previous release , e.g., ``` asv continuous --verbose
--show-stderr --factor 1.3 previous_release this_release ``` Collect
performance reports from various machines into a single report and use `asv
publish` to generate the static webpages to be included with the release as
artifacts in that step below.

1. Inspect the package and changelog. If they look good, merge the PR to `main`.

**Note**: it is critical to *merge* the PR to `main`, not squash as is
conventional for development PRs. Squashing causes `develop` and `main` to
diverge. Merging to `main` preserves commit history and ensures `develop`
and `main` don't diverge.

Merging the PR to `main` will trigger another job to draft a [GitHub
release](https://github.com/EC-USGS/pywatershed/releases). The release is
not yet publicly visible at this point. The release notes are autofilled as
the changelog since the last release.

1. Inspect the GitHub release. If needed, make any manual edits to the release
notes. If the release looks good, publish it via GitHub UI or CLI. Manually
add the asv static web pages and frozen conda dependencies for each platform.

Publishing the release on GitHub automatically tags the head of `main` with
the release version number (**Note**: release tags, unlike branches, don't
include an initial `v`, as is common in some projects) and triggers jobs to:

- Publish the package to PyPI
- Check out `main`
- Run `.github/scripts/update_version.py -v x.y+1.0.dev0` to update
`version.txt` and `pywatershed/version.py` with the minor version number
incremented. The `.dev0` suffix indicates preliminary development status.
- Draft a PR against `develop` with the updated version files and the
updates previously merged to `main`.

1. In the case of a minor or major release, a couple of manual steps:
- Update the PR against `develop` to add a new minor or major release to
the top of `doc/whats-new.rst`
- Update `main` image on WholeTale to have the current release.

1. Merge the PR to `develop`. As above, it is important to *merge* the PR, not
squash, to preserve history and keep `develop` and `main` from diverging.


## Utility scripts

The automated release procedure uses a few scripts, located in
`.github/scripts`.

### Updating version numbers

The `update_version.py` script can be used to update version numbers embedded in
the repository. The script acquires a file lock to make sure only one process
edits version files at a given time. If the script is run with no arguments,
updated timestamp comments are written but the version number is not changed. To
set the version number, use the `--version` (short `-v`) option.

For instance, to set the version number before a release:

```shell python .github/scripts/update_version.py -a -v 0.1.3 ```

Or to set the version number on `develop` following a release:

```shell python .github/scripts/update_version.py -a -v 0.2.0.dev0 ```

To get the current version number without writing any changes to the
repository's files, use the `--get` (short `-g`) flag:

```shell python .github/scripts/update_version.py -g ```

**Note**: this script should not need to be run manually, as it is run automatically in the release automation.
22 changes: 22 additions & 0 deletions .github/scripts/symlink_gfortran_mac.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash

# get full gfortran version string
# assumes installed via brew as by
# https://github.com/awvwgk/setup-fortran
#
# sed not head for first line, avoid ruby broken pipe issues
# (https://stackoverflow.com/a/2845541/6514033)
full_version=$(brew info gfortran | sed -n 1p | cut -d' ' -f 4)

# get major version
version=$(echo "$full_version" | cut -d'.' -f 1)

# symlink gfortran libraries
old_libdir="/usr/local/opt/gcc/lib/gcc/${version}"
new_libdir="/usr/local/lib/"
mkdir -p "$new_libdir"
if [ -d "$old_libdir" ]
then
sudo ln -fs "$old_libdir/libgfortran.5.dylib" "$new_libdir/libgfortran.5.dylib"
sudo ln -fs "$old_libdir/libquadmath.0.dylib" "$new_libdir/libquadmath.0.dylib"
fi
118 changes: 118 additions & 0 deletions .github/scripts/update_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import argparse
import textwrap
import yaml
from datetime import datetime
from pathlib import Path

from filelock import FileLock
from packaging.version import Version


_project_name = "pywatershed"
_project_root_path = Path(__file__).parent.parent.parent
_version_txt_path = _project_root_path / "version.txt"
_citation_cff_path = _project_root_path / "CITATION.cff"
_version_py_path = _project_root_path / _project_name / "version.py"
_initial_version = Version("0.0.1")
_current_version = Version(_version_txt_path.read_text().strip())


def update_version_txt(version: Version):
with open(_version_txt_path, "w") as f:
f.write(str(version))
print(f"Updated {_version_txt_path} to version {version}")


def get_authors():
citation = yaml.safe_load(_citation_cff_path.read_text())
return citation["authors"]


def update_version_py(timestamp: datetime, version: Version):
lines = open(_version_py_path, "r").readlines() if _version_py_path.exists() else []
authors = get_authors()
with open(_version_py_path, "w") as f:
f.write(
f"# {_project_name} version file automatically created using "
f"{Path(__file__).name} on {timestamp:%B %d, %Y %H:%M:%S}\n\n"
)
f.write(f'__version__ = "{version}"\n')
f.writelines(
[
f"__pakname__ = \"{_project_name}\"\n",
"\n",
"author_dict = {\n",
] + [f" \"{a['given-names']} {a['family-names']}\": \"{a['email']}\",\n" for a in authors] + [
"}\n",
"__author__ = \", \".join(author_dict.keys())\n",
"__author_email__ = \", \".join(s for _, s in author_dict.items())\n",
]
)
f.close()
print(f"Updated {_version_py_path} to version {version}")


def update_version(
timestamp: datetime = datetime.now(),
version: Version = None,
):
lock_path = Path(_version_txt_path.name + ".lock")
try:
lock = FileLock(lock_path)
previous = Version(_version_txt_path.read_text().strip())
version = (
version
if version
else Version(previous.major, previous.minor, previous.micro)
)

with lock:
update_version_txt(version)
update_version_py(timestamp, version)
finally:
try:
lock_path.unlink()
except:
pass


if __name__ == "__main__":
parser = argparse.ArgumentParser(
prog=f"Update {_project_name} version",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent(
"""\
Update version information stored in version.txt in the project root,
as well as several other files in the repository. If --version is not
provided, the version number will not be changed. A file lock is held
to synchronize file access. The version tag must comply with standard
'<major>.<minor>.<patch>' format conventions for semantic versioning.
"""
),
)
parser.add_argument(
"-v",
"--version",
required=False,
help="Specify the release version",
)
parser.add_argument(
"-g",
"--get",
required=False,
action="store_true",
help="Just get the current version number, don't update anything (defaults to false)",
)
args = parser.parse_args()

if args.get:
print(
Version((_project_root_path / "version.txt").read_text().strip())
)
else:
update_version(
timestamp=datetime.now(),
version=Version(args.version)
if args.version
else _current_version,
)
Loading

0 comments on commit 8188a59

Please sign in to comment.