Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into mps
Browse files Browse the repository at this point in the history
  • Loading branch information
fnhirwa committed Sep 11, 2024
2 parents 3bd4d6d + b497a6b commit c60d324
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 49 deletions.
91 changes: 68 additions & 23 deletions .github/workflows/pypi_release.yml
Original file line number Diff line number Diff line change
@@ -1,40 +1,85 @@
name: PyPI Release

# https://help.github.com/en/actions/reference/events-that-trigger-workflows
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
branches:
- master
release:
types:
- created
types: [published]

jobs:
build:
build_wheels:
name: Build wheels
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: 3.11
python-version: '3.11'

- name: Install poetry
shell: bash
- name: Build wheel
run: |
curl -sSL https://install.python-poetry.org | python3 -
python -m pip install poetry-dynamic-versioning[plugin]
python -m pip install build
python -m build --wheel --sdist --outdir wheelhouse
- name: Store wheels
uses: actions/upload-artifact@v4
with:
name: wheels
path: wheelhouse/*

pytest-nosoftdeps:
name: no-softdeps
runs-on: ${{ matrix.os }}
needs: [build_wheels]
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-13, windows-latest]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

- name: Set poetry path variable
run: echo "/Users/runner/.local/bin" >> $GITHUB_PATH
steps:
- uses: actions/checkout@v4

- name: Build
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Setup macOS
if: runner.os == 'macOS'
run: |
poetry build
brew install libomp # https://github.com/pytorch/pytorch/issues/20030
- name: Publish distribution 📦 to PyPI
if: startsWith(github.event.ref, 'refs/tags') || github.event_name == 'release'
- name: Get full Python version
id: full-python-version
shell: bash
run: echo version=$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info))") >> $GITHUB_OUTPUT

- name: Install dependencies
shell: bash
run: |
poetry publish --username "${{ secrets.PYPI_USER }}" --password "${{ secrets.PYPI_PASSWORD }}"
pip install ".[dev,github-actions]"
- name: Show dependencies
run: python -m pip list

- name: Run pytest
shell: bash
run: python -m pytest tests

upload_wheels:
name: Upload wheels to PyPI
runs-on: ubuntu-latest
needs: [pytest-nosoftdeps]

steps:
- uses: actions/download-artifact@v4
with:
name: wheels
path: wheelhouse

- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
packages-dir: wheelhouse/
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
# Release Notes

## v1.1.1

Hotfix for accidental package name change in `pyproject.toml`.

The package name is now corrected to `pytorch-forecasting`.


## v1.1.0

Maintenance update widening compatibility ranges and consolidating dependencies:

* support for python 3.11 and 3.12, added CI testing
* support for MacOS, added CI testing
* core dependencies have been minimized to `numpy`, `torch`, `lightning`, `scipy`, `pandas`, and `scikit-learn`.
* soft dependencies are available in soft dependency sets: `all_extras` for all soft dependencies, and `tuning` for `optuna` based optimization.

### Dependency changes

* the following are no longer core dependencies and have been changed to optional dependencies : `optuna`, `statsmodels`, `pytorch-optimize`, `matplotlib`. Environments relying on functionality requiring these dependencies need to be updated to instlal these explicitly.
* `optuna` bounds have been updated to `optuna >=3.1.0,<4.0.0`
* `optuna-integrate` is now an additional soft dependency, in case of `optuna >=3.3.0`

### Deprecations and removals

* from 1.2.0, the default optimizer will be changed from `"ranger"` to `"adam"` to avoid non-`torch` dependencies in defaults. `pytorch-optimize` optimizers can still be used. Users should set the optimizer explicitly to continue using `"ranger"`.
* from 1.1.0, the loggers do not log figures if soft dependency `matplotlib` is not present, but will raise no exceptions in this case. To log figures, ensure tha `matplotlib` is installed.

## v1.0.0 Update to pytorch 2.0 (10/04/2023)


Expand Down
25 changes: 9 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,17 @@
![PyTorch Forecasting](./docs/source/_static/logo.svg)

[pypi-image]: https://badge.fury.io/py/pytorch-forecasting.svg
[pypi-url]: https://pypi.python.org/pypi/pytorch-forecasting
[conda-image]: https://img.shields.io/conda/v/conda-forge/pytorch-forecasting
[conda-url]: https://anaconda.org/conda-forge/pytorch-forecasting
[build-image]: https://github.com/jdb78/pytorch-forecasting/actions/workflows/test.yml/badge.svg?branch=master
[build-url]: https://github.com/jdb78/pytorch-forecasting/actions/workflows/test.yml?query=branch%3Amaster
[linter-image]: https://github.com/jdb78/pytorch-forecasting/actions/workflows/lint.yml/badge.svg?event=push
[linter-url]: https://github.com/jdb78/pytorch-forecasting/actions/workflows/code_quality.yml?query=branch%3Amaster
[docs-image]: https://readthedocs.org/projects/pytorch-forecasting/badge/?version=latest
[docs-url]: https://pytorch-forecasting.readthedocs.io
[coverage-image]: https://codecov.io/gh/jdb78/pytorch-forecasting/branch/master/graph/badge.svg
[coverage-url]: https://codecov.io/github/jdb78/pytorch-forecasting?branch=master
_PyTorch Forecasting_ is a PyTorch-based package for forecasting with state-of-the-art deep learning architectures. It provides a high-level API and uses [PyTorch Lightning](https://pytorch-lightning.readthedocs.io/) to scale training on GPU or CPU, with automatic logging.

[![PyPI Version][pypi-image]][pypi-url] [![Conda Version][conda-image]][conda-url] [![Docs Status][docs-image]][docs-url] [![Linter Status][linter-image]][linter-url] [![Build Status][build-image]][build-url] [![Code Coverage][coverage-image]][coverage-url]

**[Documentation](https://pytorch-forecasting.readthedocs.io)** | **[Tutorials](https://pytorch-forecasting.readthedocs.io/en/latest/tutorials.html)** | **[Release Notes](https://pytorch-forecasting.readthedocs.io/en/latest/CHANGELOG.html)**
| | **[Documentation](https://pytorch-forecasting.readthedocs.io)** · **[Tutorials](https://pytorch-forecasting.readthedocs.io/en/latest/tutorials.html)** · **[Release Notes](https://pytorch-forecasting.readthedocs.io/en/latest/CHANGELOG.html)** |
|---|---|
| **Open&#160;Source** | [![MIT](https://img.shields.io/github/license/jdb78/pytorch-forecasting)](https://github.com/jdb78/pytorch-forecasting/blob/master/LICENSE) |
| **Community** | [![!discord](https://img.shields.io/static/v1?logo=discord&label=discord&message=chat&color=lightgreen)](https://discord.com/invite/54ACzaFsn7) [![!slack](https://img.shields.io/static/v1?logo=linkedin&label=LinkedIn&message=news&color=lightblue)](https://www.linkedin.com/company/scikit-time/) |
| **CI/CD** | [![github-actions](https://img.shields.io/github/actions/workflow/status/jdb78/pytorch-forecasting/pypi_release.yml?logo=github)](https://github.com/jdb78/pytorch-forecasting/actions/workflows/pypi_release.yml) [![readthedocs](https://img.shields.io/readthedocs/pytorch-forecasting?logo=readthedocs)](https://pytorch-forecasting.readthedocs.io) [![platform](https://img.shields.io/conda/pn/conda-forge/pytorch-forecasting)](https://github.com/jdb78/pytorch-forecasting) [![Code Coverage][coverage-image]][coverage-url] |
| **Code** | [![!pypi](https://img.shields.io/pypi/v/pytorch-forecasting?color=orange)](https://pypi.org/project/pytorch-forecasting/) [![!conda](https://img.shields.io/conda/vn/conda-forge/pytorch-forecasting)](https://anaconda.org/conda-forge/pytorch-forecasting) [![!python-versions](https://img.shields.io/pypi/pyversions/pytorch-forecasting)](https://www.python.org/) [![!black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) |

_PyTorch Forecasting_ is a PyTorch-based package for forecasting time series with state-of-the-art network architectures. It provides a high-level API for training networks on pandas data frames and leverages
[PyTorch Lightning](https://pytorch-lightning.readthedocs.io/) for scalable training on (multiple) GPUs, CPUs and for automatic logging.
[coverage-image]: https://codecov.io/gh/jdb78/pytorch-forecasting/branch/master/graph/badge.svg
[coverage-url]: https://codecov.io/github/jdb78/pytorch-forecasting?branch=master

---

Expand Down
191 changes: 191 additions & 0 deletions build_tools/changelog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
"""RestructuredText changelog generator."""

from collections import defaultdict
import os

HEADERS = {
"Accept": "application/vnd.github.v3+json",
}

if os.getenv("GITHUB_TOKEN") is not None:
HEADERS["Authorization"] = f"token {os.getenv('GITHUB_TOKEN')}"

OWNER = "jdb78"
REPO = "pytorch-forecasting"
GITHUB_REPOS = "https://api.github.com/repos"


def fetch_merged_pull_requests(page: int = 1) -> list[dict]:
"""Fetch a page of merged pull requests.
Parameters
----------
page : int, optional
Page number to fetch, by default 1.
Returns all merged pull request from the ``page``-th page of closed PRs,
where pages are in descending order of last update.
Returns
-------
list
List of merged pull requests from the ``page``-th page of closed PRs.
Elements of list are dictionaries with PR details, as obtained
from the GitHub API via ``httpx.get``, from the ``pulls`` endpoint.
"""
import httpx

params = {
"base": "main",
"state": "closed",
"page": page,
"per_page": 50,
"sort": "updated",
"direction": "desc",
}
r = httpx.get(
f"{GITHUB_REPOS}/{OWNER}/{REPO}/pulls",
headers=HEADERS,
params=params,
)
return [pr for pr in r.json() if pr["merged_at"]]


def fetch_latest_release(): # noqa: D103
"""Fetch the latest release from the GitHub API.
Returns
-------
dict
Dictionary with details of the latest release.
Dictionary is as obtained from the GitHub API via ``httpx.get``,
for ``releases/latest`` endpoint.
"""
import httpx

response = httpx.get(f"{GITHUB_REPOS}/{OWNER}/{REPO}/releases/latest", headers=HEADERS)

if response.status_code == 200:
return response.json()
else:
raise ValueError(response.text, response.status_code)


def fetch_pull_requests_since_last_release() -> list[dict]:
"""Fetch all pull requests merged since last release.
Returns
-------
list
List of pull requests merged since the latest release.
Elements of list are dictionaries with PR details, as obtained
from the GitHub API via ``httpx.get``, through ``fetch_merged_pull_requests``.
"""
from dateutil import parser

release = fetch_latest_release()
published_at = parser.parse(release["published_at"])
print(f"Latest release {release['tag_name']} was published at {published_at}")

is_exhausted = False
page = 1
all_pulls = []
while not is_exhausted:
pulls = fetch_merged_pull_requests(page=page)
all_pulls.extend([p for p in pulls if parser.parse(p["merged_at"]) > published_at])
is_exhausted = any(parser.parse(p["updated_at"]) < published_at for p in pulls)
page += 1
return all_pulls


def github_compare_tags(tag_left: str, tag_right: str = "HEAD"):
"""Compare commit between two tags."""
import httpx

response = httpx.get(f"{GITHUB_REPOS}/{OWNER}/{REPO}/compare/{tag_left}...{tag_right}")
if response.status_code == 200:
return response.json()
else:
raise ValueError(response.text, response.status_code)


def render_contributors(prs: list, fmt: str = "rst"):
"""Find unique authors and print a list in given format."""
authors = sorted({pr["user"]["login"] for pr in prs}, key=lambda x: x.lower())

header = "Contributors"
if fmt == "github":
print(f"### {header}")
print(", ".join(f"@{user}" for user in authors))
elif fmt == "rst":
print(header)
print("~" * len(header), end="\n\n")
print(",\n".join(f":user:`{user}`" for user in authors))


def assign_prs(prs, categs: list[dict[str, list[str]]]):
"""Assign PR to categories based on labels."""
assigned = defaultdict(list)

for i, pr in enumerate(prs):
for cat in categs:
pr_labels = [label["name"] for label in pr["labels"]]
if not set(cat["labels"]).isdisjoint(set(pr_labels)):
assigned[cat["title"]].append(i)

# if any(l.startswith("module") for l in pr_labels):
# print(i, pr_labels)

assigned["Other"] = list(set(range(len(prs))) - {i for _, j in assigned.items() for i in j})

return assigned


def render_row(pr):
"""Render a single row with PR in restructuredText format."""
print(
"*",
pr["title"].replace("`", "``"),
f"(:pr:`{pr['number']}`)",
f":user:`{pr['user']['login']}`",
)


def render_changelog(prs, assigned):
# sourcery skip: use-named-expression
"""Render changelog."""
from dateutil import parser

for title, _ in assigned.items():
pr_group = [prs[i] for i in assigned[title]]
if pr_group:
print(f"\n{title}")
print("~" * len(title), end="\n\n")

for pr in sorted(pr_group, key=lambda x: parser.parse(x["merged_at"])):
render_row(pr)


if __name__ == "__main__":
categories = [
{"title": "Enhancements", "labels": ["feature", "enhancement"]},
{"title": "Fixes", "labels": ["bug", "fix", "bugfix"]},
{"title": "Maintenance", "labels": ["maintenance", "chore"]},
{"title": "Refactored", "labels": ["refactor"]},
{"title": "Documentation", "labels": ["documentation"]},
]

pulls = fetch_pull_requests_since_last_release()
print(f"Found {len(pulls)} merged PRs since last release")
assigned = assign_prs(pulls, categories)
render_changelog(pulls, assigned)
print()
render_contributors(pulls)

release = fetch_latest_release()
diff = github_compare_tags(release["tag_name"])
if diff["total_commits"] != len(pulls):
raise ValueError(
"Something went wrong and not all PR were fetched. "
f'There are {len(pulls)} PRs but {diff["total_commits"]} in the diff. '
"Please verify that all PRs are included in the changelog."
)
2 changes: 1 addition & 1 deletion docs/source/_static/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit c60d324

Please sign in to comment.