mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-10-07 16:58:13 +08:00
Notify users of new versions of cellxgene (#1078)
* Notify users of new versions of cellxgene Fixes https://github.com/chanzuckerberg/cellxgene/issues/683 * Do not use PyGithub client * Protect against AttributeError * Document that all version tags must follow SemVer * Release tags `should -> MUST` follow semantic versioning
This commit is contained in:
@@ -9,11 +9,13 @@ to PyPi, with a matching tagged release on github.
|
|||||||
|
|
||||||
The release process should result in the following side-effects:
|
The release process should result in the following side-effects:
|
||||||
|
|
||||||
- Version number bump, using semantic versioning
|
- Version number bump, using [semantic versioning](https://semver.org/)
|
||||||
- JS assets built & packaged, committed to the repo
|
- JS assets built & packaged, committed to the repo
|
||||||
- Tagged github release
|
- Tagged github release
|
||||||
- Publication to PyPi
|
- Publication to PyPi
|
||||||
|
|
||||||
|
Note all release tags pushed to GitHub MUST follow semantic versioning.
|
||||||
|
|
||||||
## Recipe
|
## Recipe
|
||||||
|
|
||||||
Follow these steps to create a release.
|
Follow these steps to create a release.
|
||||||
|
|||||||
+9
-3
@@ -1,7 +1,9 @@
|
|||||||
import click
|
import click
|
||||||
|
|
||||||
|
from .. import __version__
|
||||||
from .launch import launch
|
from .launch import launch
|
||||||
from .prepare import prepare
|
from .prepare import prepare
|
||||||
|
from .upgrade import log_upgrade_check
|
||||||
|
|
||||||
|
|
||||||
@click.group(
|
@click.group(
|
||||||
@@ -12,13 +14,17 @@ from .prepare import prepare
|
|||||||
)
|
)
|
||||||
@click.help_option("--help", "-h", help="Show this message and exit.")
|
@click.help_option("--help", "-h", help="Show this message and exit.")
|
||||||
@click.version_option(
|
@click.version_option(
|
||||||
version="0.13.0",
|
version=__version__,
|
||||||
prog_name="cellxgene",
|
prog_name="cellxgene",
|
||||||
message="[%(prog)s] Version %(version)s",
|
message="[%(prog)s] Version %(version)s",
|
||||||
help="Show the software version and exit.",
|
help="Show the software version and exit.",
|
||||||
)
|
)
|
||||||
def cli():
|
@click.option(
|
||||||
pass
|
"--upgrade-check/--no-upgrade-check", default=True, show_default=True, help="Check for release upgrades on start.",
|
||||||
|
)
|
||||||
|
def cli(upgrade_check):
|
||||||
|
if upgrade_check:
|
||||||
|
log_upgrade_check()
|
||||||
|
|
||||||
|
|
||||||
cli.add_command(launch)
|
cli.add_command(launch)
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
import click
|
||||||
|
import re
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from requests.exceptions import ConnectionError
|
||||||
|
from .. import __version__
|
||||||
|
|
||||||
|
# Official SemVer regex: https://semver.org/
|
||||||
|
SEMVER_FORMAT = re.compile(
|
||||||
|
r"^(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)"
|
||||||
|
+ r"(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)"
|
||||||
|
+ r"(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?"
|
||||||
|
+ r"(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def log_upgrade_check():
|
||||||
|
# Sanity-check that the CLI version is a properly-formatted SemVer string
|
||||||
|
assert validate_version_str(__version__, release_only=False)
|
||||||
|
|
||||||
|
# Get the current latest release
|
||||||
|
try:
|
||||||
|
release_tag_generator = (r['tag_name'] for r in _request_cellxgene_releases())
|
||||||
|
latest_release = next(release_tag_generator, lambda tag_name: validate_version_str(tag_name))
|
||||||
|
if version_gt(latest_release, __version__):
|
||||||
|
click.echo(f"There's a new version of cellxgene available ({latest_release})!")
|
||||||
|
click.echo("To upgrade, run the following: pip install --upgrade cellxgene\n")
|
||||||
|
except (ConnectionError, RateLimitException):
|
||||||
|
click.echo("Upgrade check failed.\n")
|
||||||
|
|
||||||
|
|
||||||
|
class RateLimitException(Exception):
|
||||||
|
"""
|
||||||
|
Github API Rate Limit Exception
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _request_cellxgene_releases():
|
||||||
|
def raise_on_rate_limit(response):
|
||||||
|
if response.status_code == 403 and res.headers.get('X-RateLimit-Remaining') == '0':
|
||||||
|
raise RateLimitException
|
||||||
|
url = "https://api.github.com/repos/chanzuckerberg/cellxgene/releases"
|
||||||
|
res = requests.get(url)
|
||||||
|
raise_on_rate_limit(res)
|
||||||
|
for release in res.json():
|
||||||
|
yield release
|
||||||
|
while 'next' in res.links.keys():
|
||||||
|
res = requests.get(res.links['next']['url'])
|
||||||
|
raise_on_rate_limit(res)
|
||||||
|
for release in res.json():
|
||||||
|
yield release
|
||||||
|
|
||||||
|
|
||||||
|
def validate_version_str(version_str, release_only=True):
|
||||||
|
"""
|
||||||
|
Test if a string conforms to SemVer format (https://semver.org/)
|
||||||
|
:param version_str: a string to be validated
|
||||||
|
:param release_only: only declare releases (not prereleases) valid
|
||||||
|
:return: True if the version string is of a valid SemVer format else False
|
||||||
|
"""
|
||||||
|
match = SEMVER_FORMAT.match(version_str)
|
||||||
|
has_match = match is not None
|
||||||
|
if has_match and release_only:
|
||||||
|
return not match.group("prerelease")
|
||||||
|
return has_match
|
||||||
|
|
||||||
|
|
||||||
|
def split_version(version_string):
|
||||||
|
"""
|
||||||
|
Split a SemVer-formatted string into its component integers
|
||||||
|
:param version_string: a SemVer string to be split
|
||||||
|
:return: an array of three integers
|
||||||
|
"""
|
||||||
|
match = SEMVER_FORMAT.match(version_string)
|
||||||
|
return [int(match.group(group)) for group in ["major", "minor", "patch"]]
|
||||||
|
|
||||||
|
|
||||||
|
def version_gt(left_version, right_version):
|
||||||
|
for left, right in zip(split_version(left_version), split_version(right_version)):
|
||||||
|
if left > right:
|
||||||
|
return True
|
||||||
|
elif right > left:
|
||||||
|
return False
|
||||||
|
return False
|
||||||
+11
-1
@@ -19,7 +19,17 @@ class EndPoints(unittest.TestCase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.ps = Popen(["cellxgene", "launch", "../example-dataset/pbmc3k.h5ad", "--verbose", "--port", "5005"])
|
cls.ps = Popen(
|
||||||
|
[
|
||||||
|
"cellxgene",
|
||||||
|
"--no-upgrade-check",
|
||||||
|
"launch",
|
||||||
|
"../example-dataset/pbmc3k.h5ad",
|
||||||
|
"--verbose",
|
||||||
|
"--port",
|
||||||
|
"5005",
|
||||||
|
]
|
||||||
|
)
|
||||||
session = requests.Session()
|
session = requests.Session()
|
||||||
for i in range(90):
|
for i in range(90):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from server.cli.upgrade import validate_version_str, split_version, version_gt
|
||||||
|
|
||||||
|
|
||||||
|
class CLIUpgradeTests(unittest.TestCase):
|
||||||
|
""" Test cases for CLI logic """
|
||||||
|
|
||||||
|
def test_validate_version_str(self):
|
||||||
|
self.assertTrue(validate_version_str("0.1.2"))
|
||||||
|
self.assertTrue(validate_version_str("0.1.2-RC", release_only=False))
|
||||||
|
self.assertFalse(validate_version_str("0.1"))
|
||||||
|
self.assertFalse(validate_version_str("0.1.2.3"))
|
||||||
|
self.assertFalse(validate_version_str("0.1.2-RC"))
|
||||||
|
|
||||||
|
def test_split_version_str(self):
|
||||||
|
self.assertEqual(split_version("0.1.2"), [0, 1, 2])
|
||||||
|
with self.assertRaises(AttributeError):
|
||||||
|
split_version("0.1")
|
||||||
|
|
||||||
|
def test_assert_verstion_gt(self):
|
||||||
|
self.assertTrue(version_gt("1.0.0", "0.1.1"))
|
||||||
|
self.assertTrue(version_gt("0.1.0", "0.0.1"))
|
||||||
|
self.assertTrue(version_gt("0.0.1", "0.0.0"))
|
||||||
|
self.assertFalse(version_gt("0.0.0", "0.0.0"))
|
||||||
|
self.assertFalse(version_gt("0.0.0", "0.0.1"))
|
||||||
|
self.assertFalse(version_gt("0.0.1", "0.1.0"))
|
||||||
|
self.assertFalse(version_gt("0.1.1", "1.0.0"))
|
||||||
Reference in New Issue
Block a user