Skip to content

Commit 4a34229

Browse files
MasloMaslanetonowak
andauthoredJul 6, 2023
Check for sinol-make updates when running (#54)
* Add function for checking for sinol-make updates * Add timeout for update checks * catch all requests' errors while checking for updates * Check for updates asynchronously * Add some comments * Remove unnecessary print * Add tests for version checking * Add more comments * Fix update comment Co-authored-by: Tomasz Nowak <36604952+tonowak@users.noreply.github.com> --------- Co-authored-by: Tomasz Nowak <36604952+tonowak@users.noreply.github.com>
1 parent c981f1b commit 4a34229

File tree

5 files changed

+139
-1
lines changed

5 files changed

+139
-1
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ build
66
.idea
77
__pycache__
88
/tests/packages/**/cache
9+
src/sinol_make/data
910

1011
# pytest-cov
1112
.coverage

‎pyproject.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ dependencies = [
2929
[project.optional-dependencies]
3030
tests = [
3131
"pytest",
32-
"pytest-cov"
32+
"pytest-cov",
33+
"requests-mock",
3334
]
3435

3536
[project.urls]

‎src/sinol_make/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ def main():
3232

3333
for command in commands:
3434
if command.get_name() == args.command:
35+
new_version = util.check_for_updates(__version__)
36+
if new_version is not None:
37+
print(util.warning(f'New version of sinol-make is available (your version: {__version__}, available version: {new_version}).\n'
38+
f' You can update it by running `pip3 install sinol-make --upgrade`.'))
39+
3540
if sys.platform == 'linux' and not util.check_oiejq():
3641
print(util.warning('`oiejq` in `~/.local/bin/` not detected, installing now...'))
3742

‎src/sinol_make/util.py

+71
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import glob, importlib, os, sys, subprocess, requests, tarfile, yaml
2+
import importlib.resources
3+
import threading
4+
25

36
def get_commands():
47
"""
@@ -154,6 +157,74 @@ def save_config(config):
154157
yaml.dump(config, config_file)
155158

156159

160+
def check_for_updates(current_version) -> str | None:
161+
"""
162+
Function to check if there is a new version of sinol-make.
163+
:param current_version: current version of sinol-make
164+
:return: returns new version if there is one, None otherwise
165+
"""
166+
data_dir = importlib.resources.files("sinol_make").joinpath("data")
167+
if not data_dir.is_dir():
168+
os.mkdir(data_dir)
169+
170+
# We check for new version asynchronously, so that it doesn't slow down the program.
171+
thread = threading.Thread(target=check_version)
172+
thread.start()
173+
version_file = data_dir.joinpath("version")
174+
175+
if version_file.is_file():
176+
version = version_file.read_text()
177+
try:
178+
if compare_versions(current_version, version) == -1:
179+
return version
180+
else:
181+
return None
182+
except ValueError: # If the version file is corrupted, we just ignore it.
183+
return None
184+
else:
185+
return None
186+
187+
188+
def check_version():
189+
"""
190+
Function that asynchronously checks for new version of sinol-make.
191+
Writes the newest version to data/version file.
192+
"""
193+
try:
194+
request = requests.get("https://pypi.python.org/pypi/sinol-make/json", timeout=1)
195+
except requests.exceptions.RequestException:
196+
return
197+
198+
if request.status_code != 200:
199+
return
200+
201+
data = request.json()
202+
latest_version = data["info"]["version"]
203+
204+
version_file = importlib.resources.files("sinol_make").joinpath("data/version")
205+
version_file.write_text(latest_version)
206+
207+
208+
def compare_versions(version_a, version_b):
209+
"""
210+
Function to compare two versions.
211+
Returns 1 if version_a > version_b, 0 if version_a == version_b, -1 if version_a < version_b.
212+
"""
213+
214+
def convert(version):
215+
return tuple(map(int, version.split(".")))
216+
217+
version_a = convert(version_a)
218+
version_b = convert(version_b)
219+
220+
if version_a > version_b:
221+
return 1
222+
elif version_a == version_b:
223+
return 0
224+
else:
225+
return -1
226+
227+
157228
def lines_diff(lines1, lines2):
158229
"""
159230
Function to compare two lists of lines.

‎tests/test_util.py

+60
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import os
22
import sys
3+
import time
4+
import json
5+
36
import pytest
7+
import importlib.resources
8+
9+
import requests
10+
import requests_mock
411

512
from sinol_make import util
613

@@ -15,3 +22,56 @@ def test_install_oiejq():
1522
assert not util.check_oiejq()
1623

1724
assert util.install_oiejq()
25+
26+
27+
def test_compare_versions():
28+
"""
29+
Tests for compare_versions function
30+
"""
31+
32+
assert util.compare_versions('1.0.0', '1.0.0') == 0
33+
assert util.compare_versions('1.0.0', '1.0.1') == -1
34+
assert util.compare_versions('1.0.1', '1.0.0') == 1
35+
assert util.compare_versions('1.0.0', '1.1.0') == -1
36+
assert util.compare_versions('1.1.0', '1.0.0') == 1
37+
assert util.compare_versions('1.0.0', '2.0.0') == -1
38+
assert util.compare_versions('2.0.0', '1.0.0') == 1
39+
with pytest.raises(ValueError):
40+
util.compare_versions('1.0.0', '')
41+
with pytest.raises(ValueError):
42+
util.compare_versions('', '1.0.0')
43+
with pytest.raises(ValueError):
44+
util.compare_versions('1.0.0', 'abc')
45+
with pytest.raises(ValueError):
46+
util.compare_versions('abc', '1.0.0')
47+
48+
49+
@requests_mock.Mocker(kw="mocker")
50+
def test_check_version(**kwargs):
51+
"""
52+
Tests for check_version function
53+
Simulates wrong responses and exceptions with requests-mock
54+
"""
55+
mocker = kwargs["mocker"]
56+
57+
data_dir = importlib.resources.files('sinol_make').joinpath("data")
58+
version_file = data_dir.joinpath("version")
59+
if not data_dir.is_dir():
60+
data_dir.mkdir()
61+
62+
# Test correct request
63+
mocker.get("https://pypi.python.org/pypi/sinol-make/json", json={"info": {"version": "1.0.0"}})
64+
util.check_version()
65+
assert version_file.is_file()
66+
assert version_file.read_text() == "1.0.0"
67+
version_file.unlink()
68+
69+
# Test wrong request
70+
mocker.get("https://pypi.python.org/pypi/sinol-make/json", status_code=404)
71+
util.check_version()
72+
assert not version_file.is_file()
73+
74+
# Time out
75+
mocker.get("https://pypi.python.org/pypi/sinol-make/json", exc=requests.exceptions.ConnectTimeout)
76+
util.check_version()
77+
assert not version_file.is_file()

0 commit comments

Comments
 (0)