Skip to content

Commit 328bec0

Browse files
authored
Merge pull request #10 from davidlatwe/auto-upgrade
Auto upgrade
2 parents 37ccd34 + f9a25c7 commit 328bec0

File tree

15 files changed

+488
-7
lines changed

15 files changed

+488
-7
lines changed

.github/workflows/pypi-cli.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# This workflow will upload a Python Package using Twine when a release is created
2+
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3+
4+
name: Upload Python Package (cli)
5+
6+
on:
7+
release:
8+
types: [created]
9+
10+
jobs:
11+
deploy:
12+
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- uses: actions/checkout@v2
17+
- name: Set up Python
18+
uses: actions/setup-python@v2
19+
with:
20+
python-version: '3.x'
21+
- name: Install dependencies
22+
run: |
23+
python -m pip install --upgrade pip
24+
pip install setuptools wheel twine
25+
- name: Build and publish
26+
env:
27+
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
28+
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
29+
run: |
30+
cd cli
31+
python setup.py sdist bdist_wheel
32+
twine upload dist/*

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,27 @@ list containers
117117
```
118118
$ rezup list
119119
```
120+
121+
122+
### Auto Upgrade
123+
124+
Updating package that has running console scripts can be error prone. We need to separate API package and CLI scripts as two different PyPI project (`rezup-api` & `rezup`), so we can safely upgrade only the package part while the console script is running.
125+
126+
Both projects will be released together under same version, so that one could still upgrading the API part via upgrading CLI. As long as console script is not in use.
127+
128+
```
129+
pip install --upgrade rezup
130+
```
131+
132+
The upgrade checking process will be triggered immediately when calling `rezup` in console, except version querying or last upgrade check is done not long before `REZUP_UPGRADE_PAUSE` period ends. If upgrade is needed and completed, session will be restarted with previous command.
133+
134+
135+
### Environment Variables
136+
137+
|Name|Description|
138+
| --- | --- |
139+
|REZUP_ROOT_LOCAL|Local root of all containers and metadata, default is `~/.rezup`|
140+
|REZUP_ROOT_REMOTE|Remote root of all containers but only recipe file|
141+
|REZUP_NO_UPGRADE|Disable auto upgrade if set (any value is valid except empty string)|
142+
|REZUP_UPGRADE_PAUSE|Pause version check after last upgrade, default 86400 (1 day, in second)|
143+
|REZUP_UPGRADE_SOURCE|Local source repository for upgrade, check from PyPI if not set.|

cli/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# rezup (cli)
2+
3+
This project is for distributing console entry points of [rezup-api](https://pypi.org/project/rezup-api), please visit there for documentation.
4+
5+
Why not use `extras` specification ?
6+
See [pypa/pip#9726](https://github.com/pypa/pip/issues/9726)

cli/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
rezup-api>=1.4

cli/setup.cfg

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[metadata]
2+
name = rezup
3+
version = attr: version.__version__
4+
description = Rez launching environment manager
5+
long_description = file: README.md
6+
long_description_content_type = text/markdown
7+
url = https://github.com/davidlatwe/rezup
8+
author = davidlatwe
9+
author_email = davidlatwe@gmail.com
10+
maintainer = davidlatwe
11+
maintainer_email = davidlatwe@gmail.com
12+
license = GPLv3
13+
license_file = LICENSE
14+
platforms = any
15+
project_urls =
16+
Source=https://github.com/davidlatwe/rezup
17+
Tracker=https://github.com/davidlatwe/rezup/issues
18+
19+
[options]
20+
zip_safe = true
21+
python_requires = >=3, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
22+
install_requires =
23+
rezup-api>=1.4
24+
packages = find:
25+
26+
[options.entry_points]
27+
console_scripts =
28+
rezup=rezup.cli:run
29+
30+
[sdist]
31+
formats = gztar
32+
33+
[bdist_wheel]
34+
universal = true

cli/setup.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
2+
from setuptools import setup
3+
setup()

cli/version.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
g = {"__version__": None}
3+
with open("../src/rezup/_version.py") as f:
4+
exec(f.read(), g)
5+
6+
__version__ = g["__version__"]

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ distlib>=0.3.1,<1
22
virtualenv>=20.4.4,<21
33
shellingham>=1.4.0,<2
44
python-dotenv>=0.17,<1
5+
requests==2.24.0

setup.cfg

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[metadata]
2-
name = rezup
2+
name = rezup-api
33
version = attr: rezup._version.__version__
44
description = Rez launching environment manager
55
long_description = file: README.md
@@ -41,6 +41,7 @@ install_requires =
4141
virtualenv>=20.4.4,<21
4242
shellingham>=1.4.0,<2
4343
python-dotenv>=0.17,<1
44+
requests==2.24.0
4445
packages = find:
4546
package_dir =
4647
= src
@@ -51,10 +52,6 @@ where = src
5152
exclude =
5253
tests
5354

54-
[options.entry_points]
55-
console_scripts =
56-
rezup=rezup.cli:run
57-
5855
[options.package_data]
5956
rezup = rezup.toml
6057
rezup.launch = up.*

src/rezup/_vendor/version.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
from __future__ import print_function
2+
import sys
3+
import re
4+
5+
if sys.version_info >= (3, 0):
6+
from itertools import zip_longest as izip_longest
7+
else:
8+
from itertools import izip_longest
9+
10+
11+
class _Comparable(object):
12+
13+
"""Rich comparison operators based on __lt__ and __eq__."""
14+
15+
__gt__ = lambda self, other: not self < other and not self == other
16+
__le__ = lambda self, other: self < other or self == other
17+
__ne__ = lambda self, other: not self == other
18+
__ge__ = lambda self, other: self > other or self == other
19+
20+
21+
class VersionError(Exception):
22+
pass
23+
24+
25+
_re = re.compile('^'
26+
'(\\d+)\\.(\\d+)\\.(\\d+)' # minor, major, patch
27+
'(-[0-9A-Za-z-.]+)?' # pre-release
28+
'(\\+[0-9A-Za-z-.]+)?' # build
29+
'$')
30+
31+
32+
class _Seq(_Comparable):
33+
34+
"""Sequence of identifies that could be compared according to semver."""
35+
36+
def __init__(self, seq):
37+
self.seq = seq
38+
39+
def __lt__(self, other):
40+
assert set([int, str]) >= set(map(type, self.seq))
41+
for s, o in izip_longest(self.seq, other.seq):
42+
assert not (s is None and o is None)
43+
if s is None or o is None:
44+
return bool(s is None)
45+
if type(s) is int and type(o) is int:
46+
if s < o:
47+
return True
48+
elif type(s) is int or type(o) is int:
49+
return type(s) is int
50+
elif s != o:
51+
return s < o
52+
53+
def __eq__(self, other):
54+
return self.seq == other.seq
55+
56+
57+
def _try_int(s):
58+
assert type(s) is str
59+
try:
60+
return int(s)
61+
except ValueError:
62+
return s
63+
64+
65+
def _make_group(g):
66+
return [] if g is None else list(map(_try_int, g[1:].split('.')))
67+
68+
69+
class Version(_Comparable):
70+
71+
def __init__(self, version):
72+
match = _re.match(version)
73+
if not match:
74+
raise VersionError('invalid version %r' % version)
75+
self.major, self.minor, self.patch = map(int, match.groups()[:3])
76+
self.pre_release = _make_group(match.group(4))
77+
self.build = _make_group(match.group(5))
78+
79+
def __str__(self):
80+
s = '.'.join(str(s) for s in self._mmp())
81+
if self.pre_release:
82+
s += '-%s' % '.'.join(str(s) for s in self.pre_release)
83+
if self.build:
84+
s += '+%s' % '.'.join(str(s) for s in self.build)
85+
return s
86+
87+
def __repr__(self):
88+
return '%s(%r)' % (self.__class__.__name__, self.__str__())
89+
90+
def _mmp(self):
91+
return [self.major, self.minor, self.patch]
92+
93+
def __lt__(self, other):
94+
self._assume_to_be_comparable(other)
95+
if self._mmp() == other._mmp():
96+
if self.pre_release == other.pre_release:
97+
if self.build == other.build:
98+
return False
99+
elif self.build and other.build:
100+
return _Seq(self.build) < _Seq(other.build)
101+
elif self.build or other.build:
102+
return bool(other.build)
103+
assert not 'reachable'
104+
elif self.pre_release and other.pre_release:
105+
return _Seq(self.pre_release) < _Seq(other.pre_release)
106+
elif self.pre_release or other.pre_release:
107+
return bool(self.pre_release)
108+
assert not 'reachable'
109+
return self._mmp() < other._mmp()
110+
111+
def __eq__(self, other):
112+
self._assume_to_be_comparable(other)
113+
return all([self._mmp() == other._mmp(),
114+
self.build == other.build,
115+
self.pre_release == other.pre_release])
116+
117+
def _assume_to_be_comparable(self, other):
118+
if not isinstance(other, Version):
119+
raise TypeError('cannot compare `%r` with `%r`' % (self, other))
120+
121+
122+
__version__ = str(Version('0.1.2'))
123+
124+
125+
# Copyright (c) 2014 Vladimir Keleshev, <vladimir@keleshev.com>
126+
#
127+
# Permission is hereby granted, free of charge, to any person
128+
# obtaining a copy of this software and associated documentation
129+
# files (the "Software"), to deal in the Software without
130+
# restriction, including without limitation the rights to use,
131+
# copy, modify, merge, publish, distribute, sublicense, and/or sell
132+
# copies of the Software, and to permit persons to whom the
133+
# Software is furnished to do so, subject to the following
134+
# conditions:
135+
#
136+
# The above copyright notice and this permission notice shall be
137+
# included in all copies or substantial portions of the Software.
138+
#
139+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
140+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
141+
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
142+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
143+
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
144+
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
145+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
146+
# OTHER DEALINGS IN THE SOFTWARE.

src/rezup/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "1.3.1"
1+
__version__ = "1.4.0"
22

33

44
def package_info():

src/rezup/cli.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ def run():
99
parser = argparse.ArgumentParser("rezup")
1010
parser.add_argument("-V", "--version", action="store_true",
1111
help="show version and exit.")
12+
parser.add_argument("--check-latest", action="store_true",
13+
help="show version of latest rezup(api) and exit.")
14+
parser.add_argument("--no-upgrade", action="store_true",
15+
help="disable auto upgrade check.")
1216

1317
subparsers = parser.add_subparsers(dest="cmd", metavar="COMMAND")
1418

@@ -48,6 +52,14 @@ def run():
4852
from rezup._version import print_info
4953
sys.exit(print_info())
5054

55+
if opts.check_latest:
56+
from .upgrade import show_latest
57+
sys.exit(show_latest())
58+
59+
if not (opts.no_upgrade or os.getenv("REZUP_NO_UPGRADE")):
60+
from .upgrade import auto_upgrade
61+
auto_upgrade()
62+
5163
if opts.cmd == "use":
5264
cmd_use(opts.name, make=opts.make, job=opts.do)
5365

0 commit comments

Comments
 (0)