Skip to content

Commit 8188a59

Browse files
authored
Merge pull request #216 from EC-USGS/v0.2.0
Release 0.2.0
2 parents f623e5c + be9305c commit 8188a59

File tree

205 files changed

+246287
-9496
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

205 files changed

+246287
-9496
lines changed

setup.cfg .flake8

-49
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,3 @@
1-
[metadata]
2-
name = pywatershed
3-
version = attr: pywatershed.version.__version__
4-
description = pywatershed is a hydrologic model
5-
long_description = pywatershed is a Python package for hydrologic modeling
6-
long_description_content_type = text/markdown
7-
author = USGS Enterprise Capacity Team
8-
author_email = [email protected]
9-
maintainer = James McCreight
10-
maintainer_email = [email protected]
11-
license = CC0
12-
license_files = LICENSE, LICENSE.md
13-
platform = Mac OS-X, Linux
14-
keywords = PRMS, hydrology
15-
classifiers =
16-
Development Status :: 5 - Production/Stable
17-
Intended Audience :: Science/Research
18-
License :: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
19-
Programming Language :: Python
20-
Programming Language :: Python :: 3 :: Only
21-
Topic :: Scientific/Engineering :: Hydrology
22-
url = https://github.com/EC-USGS/prmsNHMpy
23-
download_url = https://pypi.org/project/pywatershed
24-
project_urls =
25-
Bug Tracker = https://github.com/EC-USGS/pywatershed/issues
26-
Source Code = https://github.com/EC-USGS/pywatershed
27-
28-
[options]
29-
include_package_data = True # includes files listed in MANIFEST.in
30-
zip_safe = False
31-
packages = find:
32-
python_requires = >=3.7
33-
install_requires =
34-
epiweeks
35-
netCDF4
36-
networkx
37-
numpy
38-
numba
39-
pandas
40-
pyyaml
41-
xarray
42-
43-
[options.package_data]
44-
pywatershed =
45-
static/metadata/*.yaml
46-
47-
[sdist]
48-
formats = zip
49-
501
[flake8]
512
exclude =
523
.git

.github/PULL_REQUEST_TEMPLATE.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<!-- Feel free to remove check-list items aren't relevant to your change -->
2+
3+
- [ ] Closes #xxxx
4+
- [ ] Tests and/or performance benchmarks added
5+
- [ ] User visible changes (including notable bug fixes) are documented in `whats-new.rst`
6+
- [ ] New functions/methods are listed in `api.rst` or it's sub rsts?

.github/RELEASE.md

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Release guide This document describes release procedures, conventions, and
2+
utilities for `pywatershed`.
3+
4+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
5+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
6+
**Contents**
7+
8+
- [Conventions](#conventions)
9+
- [Releasing `pywatershed`](#releasing-pywatershed)
10+
- [Utility scripts](#utility-scripts)
11+
- [Updating version numbers](#updating-version-numbers)
12+
13+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
14+
15+
## Conventions
16+
17+
- Releases follow the [git
18+
flow](https://nvie.com/posts/a-successful-git-branching-model/). - Release
19+
numbers follow [semantic version](https://semver.org/) conventions. - Minor
20+
and major releases branch from `develop`. Patches branch from `main`.
21+
22+
## Releasing `pywatershed`
23+
24+
The release procedure is mostly automated. The workflow is defined in
25+
`.github/workflows/release.yaml` and triggers when a release or patch branch is
26+
pushed to this repo.
27+
28+
To release a new version:
29+
30+
1. Test asv benchmarking with the `-q` flag to ensure it is working (multiple
31+
platformas a bonus).
32+
33+
1. On your local machine, create a release branch from `develop` or a patch
34+
branch from `main`. The branch's name must follow format
35+
`v{major}.{minor}.{patch}` ([semantic version](https://semver.org/) number
36+
with a leading 'v'). For instance, for a minor release, if this repo is an
37+
`upstream` remote and one's local `develop` is up to date with upstream
38+
`develop`, then from `develop` run `git switch -c vx.y.z`.
39+
40+
1. If this is a patch release, make changes/fixes locally. If this is a major or
41+
minor release, no changes are needed.
42+
43+
In either case, add the release version and date to the top of
44+
`doc/whats-new.rst`. If a patch, put it below the pending minor release.
45+
46+
1. Push the branch to this repo. For instance, if this repo is an `upstream`
47+
remote: `git push -u upstream vx.y.z`. This starts a job to:
48+
49+
- Check out the release branch Update version number in `version.txt` and
50+
- `pywatershed/version.py` to match the version in the branch name Build and
51+
- check the Python package Generate a changelog since the last release
52+
- Prepend the changelog to the cumulative `HISTORY.md` Upload the package
53+
- and changelog as artifacts Draft a PR against `main` with the updated
54+
- version files and cumulative changelog. The cumulative `HISTORY.md` is
55+
- version-controlled, release changelogs are not.
56+
57+
1. On all platforms, pull the release from upstream and perform ASV performance
58+
benchmarks against previous release , e.g., ``` asv continuous --verbose
59+
--show-stderr --factor 1.3 previous_release this_release ``` Collect
60+
performance reports from various machines into a single report and use `asv
61+
publish` to generate the static webpages to be included with the release as
62+
artifacts in that step below.
63+
64+
1. Inspect the package and changelog. If they look good, merge the PR to `main`.
65+
66+
**Note**: it is critical to *merge* the PR to `main`, not squash as is
67+
conventional for development PRs. Squashing causes `develop` and `main` to
68+
diverge. Merging to `main` preserves commit history and ensures `develop`
69+
and `main` don't diverge.
70+
71+
Merging the PR to `main` will trigger another job to draft a [GitHub
72+
release](https://github.com/EC-USGS/pywatershed/releases). The release is
73+
not yet publicly visible at this point. The release notes are autofilled as
74+
the changelog since the last release.
75+
76+
1. Inspect the GitHub release. If needed, make any manual edits to the release
77+
notes. If the release looks good, publish it via GitHub UI or CLI. Manually
78+
add the asv static web pages and frozen conda dependencies for each platform.
79+
80+
Publishing the release on GitHub automatically tags the head of `main` with
81+
the release version number (**Note**: release tags, unlike branches, don't
82+
include an initial `v`, as is common in some projects) and triggers jobs to:
83+
84+
- Publish the package to PyPI
85+
- Check out `main`
86+
- Run `.github/scripts/update_version.py -v x.y+1.0.dev0` to update
87+
`version.txt` and `pywatershed/version.py` with the minor version number
88+
incremented. The `.dev0` suffix indicates preliminary development status.
89+
- Draft a PR against `develop` with the updated version files and the
90+
updates previously merged to `main`.
91+
92+
1. In the case of a minor or major release, a couple of manual steps:
93+
- Update the PR against `develop` to add a new minor or major release to
94+
the top of `doc/whats-new.rst`
95+
- Update `main` image on WholeTale to have the current release.
96+
97+
1. Merge the PR to `develop`. As above, it is important to *merge* the PR, not
98+
squash, to preserve history and keep `develop` and `main` from diverging.
99+
100+
101+
## Utility scripts
102+
103+
The automated release procedure uses a few scripts, located in
104+
`.github/scripts`.
105+
106+
### Updating version numbers
107+
108+
The `update_version.py` script can be used to update version numbers embedded in
109+
the repository. The script acquires a file lock to make sure only one process
110+
edits version files at a given time. If the script is run with no arguments,
111+
updated timestamp comments are written but the version number is not changed. To
112+
set the version number, use the `--version` (short `-v`) option.
113+
114+
For instance, to set the version number before a release:
115+
116+
```shell python .github/scripts/update_version.py -a -v 0.1.3 ```
117+
118+
Or to set the version number on `develop` following a release:
119+
120+
```shell python .github/scripts/update_version.py -a -v 0.2.0.dev0 ```
121+
122+
To get the current version number without writing any changes to the
123+
repository's files, use the `--get` (short `-g`) flag:
124+
125+
```shell python .github/scripts/update_version.py -g ```
126+
127+
**Note**: this script should not need to be run manually, as it is run automatically in the release automation.
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env bash
2+
3+
# get full gfortran version string
4+
# assumes installed via brew as by
5+
# https://github.com/awvwgk/setup-fortran
6+
#
7+
# sed not head for first line, avoid ruby broken pipe issues
8+
# (https://stackoverflow.com/a/2845541/6514033)
9+
full_version=$(brew info gfortran | sed -n 1p | cut -d' ' -f 4)
10+
11+
# get major version
12+
version=$(echo "$full_version" | cut -d'.' -f 1)
13+
14+
# symlink gfortran libraries
15+
old_libdir="/usr/local/opt/gcc/lib/gcc/${version}"
16+
new_libdir="/usr/local/lib/"
17+
mkdir -p "$new_libdir"
18+
if [ -d "$old_libdir" ]
19+
then
20+
sudo ln -fs "$old_libdir/libgfortran.5.dylib" "$new_libdir/libgfortran.5.dylib"
21+
sudo ln -fs "$old_libdir/libquadmath.0.dylib" "$new_libdir/libquadmath.0.dylib"
22+
fi

.github/scripts/update_version.py

+118
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import argparse
2+
import textwrap
3+
import yaml
4+
from datetime import datetime
5+
from pathlib import Path
6+
7+
from filelock import FileLock
8+
from packaging.version import Version
9+
10+
11+
_project_name = "pywatershed"
12+
_project_root_path = Path(__file__).parent.parent.parent
13+
_version_txt_path = _project_root_path / "version.txt"
14+
_citation_cff_path = _project_root_path / "CITATION.cff"
15+
_version_py_path = _project_root_path / _project_name / "version.py"
16+
_initial_version = Version("0.0.1")
17+
_current_version = Version(_version_txt_path.read_text().strip())
18+
19+
20+
def update_version_txt(version: Version):
21+
with open(_version_txt_path, "w") as f:
22+
f.write(str(version))
23+
print(f"Updated {_version_txt_path} to version {version}")
24+
25+
26+
def get_authors():
27+
citation = yaml.safe_load(_citation_cff_path.read_text())
28+
return citation["authors"]
29+
30+
31+
def update_version_py(timestamp: datetime, version: Version):
32+
lines = open(_version_py_path, "r").readlines() if _version_py_path.exists() else []
33+
authors = get_authors()
34+
with open(_version_py_path, "w") as f:
35+
f.write(
36+
f"# {_project_name} version file automatically created using "
37+
f"{Path(__file__).name} on {timestamp:%B %d, %Y %H:%M:%S}\n\n"
38+
)
39+
f.write(f'__version__ = "{version}"\n')
40+
f.writelines(
41+
[
42+
f"__pakname__ = \"{_project_name}\"\n",
43+
"\n",
44+
"author_dict = {\n",
45+
] + [f" \"{a['given-names']} {a['family-names']}\": \"{a['email']}\",\n" for a in authors] + [
46+
"}\n",
47+
"__author__ = \", \".join(author_dict.keys())\n",
48+
"__author_email__ = \", \".join(s for _, s in author_dict.items())\n",
49+
]
50+
)
51+
f.close()
52+
print(f"Updated {_version_py_path} to version {version}")
53+
54+
55+
def update_version(
56+
timestamp: datetime = datetime.now(),
57+
version: Version = None,
58+
):
59+
lock_path = Path(_version_txt_path.name + ".lock")
60+
try:
61+
lock = FileLock(lock_path)
62+
previous = Version(_version_txt_path.read_text().strip())
63+
version = (
64+
version
65+
if version
66+
else Version(previous.major, previous.minor, previous.micro)
67+
)
68+
69+
with lock:
70+
update_version_txt(version)
71+
update_version_py(timestamp, version)
72+
finally:
73+
try:
74+
lock_path.unlink()
75+
except:
76+
pass
77+
78+
79+
if __name__ == "__main__":
80+
parser = argparse.ArgumentParser(
81+
prog=f"Update {_project_name} version",
82+
formatter_class=argparse.RawDescriptionHelpFormatter,
83+
epilog=textwrap.dedent(
84+
"""\
85+
Update version information stored in version.txt in the project root,
86+
as well as several other files in the repository. If --version is not
87+
provided, the version number will not be changed. A file lock is held
88+
to synchronize file access. The version tag must comply with standard
89+
'<major>.<minor>.<patch>' format conventions for semantic versioning.
90+
"""
91+
),
92+
)
93+
parser.add_argument(
94+
"-v",
95+
"--version",
96+
required=False,
97+
help="Specify the release version",
98+
)
99+
parser.add_argument(
100+
"-g",
101+
"--get",
102+
required=False,
103+
action="store_true",
104+
help="Just get the current version number, don't update anything (defaults to false)",
105+
)
106+
args = parser.parse_args()
107+
108+
if args.get:
109+
print(
110+
Version((_project_root_path / "version.txt").read_text().strip())
111+
)
112+
else:
113+
update_version(
114+
timestamp=datetime.now(),
115+
version=Version(args.version)
116+
if args.version
117+
else _current_version,
118+
)

0 commit comments

Comments
 (0)