Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Require Python 3.7+, fix test failures, test against Py 3.10 and 3.11, README/RELEASE update #19

Merged
merged 12 commits into from
Nov 1, 2022
Merged
11 changes: 2 additions & 9 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ jobs:
fail-fast: false
matrix:
include:
- python-version: "3.6"
- python-version: "3.7"
- python-version: "3.8"
- python-version: "3.9"
Expand All @@ -43,15 +42,9 @@ jobs:
with:
python-version: "${{ matrix.python-version }}"

- name: Install
run: |
pip install -r dev-requirements.txt
pip install -e .
- run: pip install ".[test]"

- name: Test
run: |
pytest --maxfail=3 --log-cli-level=DEBUG --cov=simpervisor tests/
- run: pytest --cov=simpervisor

# GitHub action reference: https://github.com/codecov/codecov-action
- uses: codecov/codecov-action@v3
if: github.ref == 'refs/heads/main'
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repos:
hooks:
- id: pyupgrade
args:
- --py36-plus
- --py37-plus

# Autoformat: Python code
- repo: https://github.com/pycqa/isort
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@
[![Discourse](https://img.shields.io/badge/help_forum-discourse-blue?logo=discourse)](https://discourse.jupyter.org/c/jupyterhub)
[![Gitter](https://img.shields.io/badge/social_chat-gitter-blue?logo=gitter)](https://gitter.im/jupyterhub/jupyterhub)

Simple Python3 Supervisor library
simpervisor provides the SupervisedProcess class that provides async methods
`start`, `ready`, `terminate`, and `kill` to manage it. As an example of how it
can be used, see [how jupyterhub/jupyter-server-proxy uses it][].

[how jupyterhub/jupyter-server-proxy uses it]: https://github.com/jupyterhub/jupyter-server-proxy/blob/969850eb0be2f8d016974104497109e0d13ddc94/jupyter_server_proxy/handlers.py#L650-L660
108 changes: 29 additions & 79 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -1,100 +1,50 @@
# How to make a release

`simpervisor` is a package [available on
PyPI](https://pypi.org/project/jupyterhub-simpervisor/) and
[conda-forge](https://conda-forge.org/). These are instructions on how to make a
release on PyPI. The PyPI release is done automatically by a GitHub workflow
when a tag is pushed.
`simpervisor` is a package available on [PyPI][] and [conda-forge][].
These are instructions on how to make a release.

For you to follow along according to these instructions, you need:
## Pre-requisites

- To have push rights to the [simpervisor GitHub
repository](https://github.com/jupyterhub/simpervisor).
- Push rights to [github.com/jupyterhub/simpervisor][]
- Push rights to [conda-forge/simpervisor-feedstock][]

## Steps to make a release

1. Update [CHANGELOG.md](CHANGELOG.md). Doing this can be made easier with the
help of the
[choldgraf/github-activity](https://github.com/choldgraf/github-activity)
utility to list merged PRs and generate a list of contributors.
1. Create a PR updating `CHANGELOG.md` with [github-activity][] and continue
only when its merged.

```bash
github-activity jupyterhub/simpervisor --output tmp-changelog-prep.md
```

1. Once the changelog is up to date, checkout main and make sure it is up to date and clean.
1. Checkout main and make sure it is up to date.

```bash
ORIGIN=${ORIGIN:-origin} # set to the canonical remote, e.g. 'upstream' if 'origin' is not the official repo
```shell
git checkout main
git fetch $ORIGIN main
git reset --hard $ORIGIN/main
# WARNING! This next command deletes any untracked files in the repo
git clean -xfd
git fetch origin main
git reset --hard origin/main
```

1. Set the `version` field in [setup.py](setup.py) appropriately and make a
commit.

```bash
git add setup.py
VERSION=... # e.g. 1.2.3
git commit -m "release $VERSION"
```

1. Reset the `version` field in [setup.py](setup.py) appropriately with an
incremented patch version and a `dev` element, then make a commit.

```bash
git add setup.py
git commit -m "back to dev"
```
1. Update the version, make commits, and push a git tag with `tbump`.

1. Push your two commits to main.
```shell
pip install tbump
tbump --dry-run ${VERSION}

```bash
# first push commits without a tags to ensure the
# commits comes through, because a tag can otherwise
# be pushed all alone without company of rejected
# commits, and we want have our tagged release coupled
# with a specific commit in main
git push $ORIGIN main
# run
tbump ${VERSION}
```

1. Create a git tag for the pushed release commit and push it.
Following this, the [CI system][] will build and publishe a release.

```bash
git tag -a $VERSION -m $VERSION HEAD~1
1. Reset the version back to dev, e.g. `1.0.1.dev` after releasing `1.0.0`.

# then verify you tagged the right commit
git log

# then push it
git push $ORIGIN refs/tags/$VERSION
```

1. Push your two commits to main along with the annotated tags referencing
commits on main. A GitHub Workflow will trigger automatic deployment of the
pushed tag.

```bash
# pushing the commits standalone allows you to
# ensure you don't end up only pushing the tag
# because the commit were rejected but the tag
# wasn't
git push $ORIGIN main

# if you could push the commits without issues
# go ahead and push the tag also
git push --follow-tags $ORIGIN main
```shell
tbump --no-tag ${NEXT_VERSION}.dev
```

1. Verify that [the GitHub
workflow](https://github.com/jupyterhub/simpervisor/actions?query=workflow%3ARelease)
triggers and succeeds and that that PyPI received a [new
release](https://pypi.org/project/simpervisor/).

1. Following the release to PyPI, an automated PR should arrive to
[conda-forge/simpervisor-feedstock](https://github.com/conda-forge/simpervisor-feedstock),
check for the tests to succeed on this PR and then merge it to successfully
update the package for `conda` on the conda-forge channel.
[conda-forge/simpervisor-feedstock][] with instructions.

[github-activity]: https://github.com/executablebooks/github-activity
[github.com/jupyterhub/simpervisor]: https://github.com/jupyterhub/simpervisor
[pypi]: https://pypi.org/project/simpervisor/
[conda-forge]: https://anaconda.org/conda-forge/simpervisor
[conda-forge/simpervisor-feedstock]: https://github.com/conda-forge/simpervisor-feedstock
[ci system]: https://github.com/jupyterhub/simpervisor/actions/workflows/release.yaml
4 changes: 0 additions & 4 deletions dev-requirements.txt

This file was deleted.

86 changes: 85 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,59 @@
# build-system
# - ref 1: https://peps.python.org/pep-0517/
#
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"


# hatch ref: https://hatch.pypa.io/latest/
#
[tool.hatch.version]
path = "simpervisor/_version.py"


# project
# - ref 1: https://peps.python.org/pep-0621/
# - ref 2: https://hatch.pypa.io/latest/config/metadata/#project-metadata
#
[project]
name = "simpervisor"
description = "Simple async process supervisor"
readme = "README.md"
requires-python = ">=3.7"
license = {file = "LICENSE"}
keywords = ["async", "process", "supervisor"]
authors = [
{name = "Yuvi Panda", email = "yuvipanda@gmail.com"},
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
dependencies = []
dynamic = ["version"]

[project.urls]
Documentation = "https://github.com/jupyterhub/simpervisor#readme"
Issues = "https://github.com/jupyterhub/simpervisor/issues"
Source = "https://github.com/jupyterhub/simpervisor"

[project.optional-dependencies]
test = [
"aiohttp",
"pytest",
"pytest-asyncio",
"pytest-cov",
]


# pytest is used for running Python based tests
#
# ref: https://docs.pytest.org/en/stable/
Expand All @@ -13,7 +69,6 @@ asyncio_mode = "auto"
#
[tool.black]
target_version = [
"py36",
"py37",
"py38",
"py39",
Expand All @@ -28,3 +83,32 @@ target_version = [
#
[tool.isort]
profile = "black"


# tbump is used to simplify and standardize the release process when updating
# the version, making a git commit and tag, and pushing changes.
#
# ref: https://github.com/your-tools/tbump#readme
#
[tool.tbump]
github_url = "https://github.com/jupyterhub/simpervisor"

[tool.tbump.version]
current = "1.0.0.dev0"
regex = '''
(?P<major>\d+)
\.
(?P<minor>\d+)
\.
(?P<patch>\d+)
(?P<pre>((a|b|rc)\d+)|)
\.?
(?P<dev>(?<=\.)dev\d*|)
'''

[tool.tbump.git]
message_template = "Bump to {new_version}"
tag_template = "{new_version}"

[[tool.tbump.file]]
src = "simpervisor/_version.py"
2 changes: 0 additions & 2 deletions setup.cfg

This file was deleted.

12 changes: 0 additions & 12 deletions setup.py

This file was deleted.

3 changes: 2 additions & 1 deletion simpervisor/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from simpervisor.process import KilledProcessError, SupervisedProcess
from ._version import __version__
from .process import KilledProcessError, SupervisedProcess
4 changes: 4 additions & 0 deletions simpervisor/_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# __version__ should be updated using tbump, based on configuration in
# pyproject.toml, according to instructions in RELEASE.md.
#
__version__ = "1.0.0.dev0"
10 changes: 5 additions & 5 deletions simpervisor/process.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import signal
import time

from simpervisor import atexitasync
from .atexitasync import add_handler, remove_handler


class KilledProcessError(Exception):
Expand Down Expand Up @@ -76,7 +76,7 @@ def _debug_log(self, action, message, extras=None, *args):
def _handle_signal(self, signal):
# Child processes should handle SIGTERM / SIGINT & close,
# which should trigger self._restart_process_if_needed
# We don't explicitly reap child processe
# We don't explicitly reap child processes
self.proc.send_signal(signal)
# Don't restart process after it is reaped
self._killed = True
Expand Down Expand Up @@ -117,7 +117,7 @@ async def start(self):
)

# This handler is removed when process stops
atexitasync.add_handler(self._handle_signal)
add_handler(self._handle_signal)

async def _restart_process_if_needed(self):
"""
Expand All @@ -128,7 +128,7 @@ async def _restart_process_if_needed(self):
"""
retcode = await self.proc.wait()
# FIXME: Do we need to aquire a lock somewhere in this method?
atexitasync.remove_handler(self._handle_signal)
remove_handler(self._handle_signal)
self._debug_log(
"exited", "{} exited with code {}", {"code": retcode}, self.name, retcode
)
Expand Down Expand Up @@ -160,7 +160,7 @@ async def _signal_and_wait(self, signum):
await self.proc.wait()
self.running = False
# Remove signal handler *after* the process is done
atexitasync.remove_handler(self._handle_signal)
remove_handler(self._handle_signal)

async def terminate(self):
"""
Expand Down
4 changes: 2 additions & 2 deletions tests/child_scripts/signalprinter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import sys
from functools import partial

from simpervisor import atexitasync
from simpervisor.atexitasync import add_handler


def _handle_sigterm(number, received_signum):
Expand All @@ -15,7 +15,7 @@ def _handle_sigterm(number, received_signum):

handlercount = int(sys.argv[1])
for i in range(handlercount):
atexitasync.add_handler(partial(_handle_sigterm, i))
add_handler(partial(_handle_sigterm, i))

loop = asyncio.get_event_loop()
try:
Expand Down
6 changes: 1 addition & 5 deletions tests/child_scripts/signalsupervisor.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,7 @@ async def main():
loop.run_forever()
finally:
# Cleanup properly so we get a clean exit
try:
remaining_tasks = asyncio.all_tasks(loop=loop)
except AttributeError:
# asyncio.all_tasks was added in 3. Provides reverse compatability.
remaining_tasks = asyncio.Task.all_tasks(loop=loop)
remaining_tasks = asyncio.all_tasks(loop=loop)
loop.run_until_complete(asyncio.gather(*remaining_tasks))
loop.close()
print("supervisor exiting cleanly")
Loading