From b616960c72edbe79ced4a2263eb755c58e04c4ef Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 31 Jan 2025 17:29:26 +0100 Subject: [PATCH 1/3] Removed example until package is stable --- README.md | 35 +---------------------------------- 1 file changed, 1 insertion(+), 34 deletions(-) diff --git a/README.md b/README.md index 91f7b25..c3f04c1 100644 --- a/README.md +++ b/README.md @@ -16,37 +16,4 @@ pip install PyPiUpdater ``` ## Usage example -**NOTE:** Example only work for client application, GUI apps such as OptimaLab35 with QT need also to close the window and should not use the restart function. - -**Now the example by ChatGPT:** - -Here's a short example code snippet for your `PyPiUpdater` package to include in your README: - -```python -# Example usage of PyPiUpdater - -from PyPiUpdater import PyPiUpdater - -# Initialize the updater with the package name, current version, and log file path -updater = PyPiUpdater(package_name="OptimaLab35", local_version="0.7.0", log_path="update_log.txt") - -# Check if an update is available (optionally forcing the check, otherwise only checked every 20 hours(default set with update_interval_seconds = int seconds)) -is_newer, latest_version = updater.check_for_update(force=False) - -if is_newer: - print(f"Update available! Latest version: {latest_version}") - # Update the package using pip - success, message = updater.update_package() - print(message) - if success: - # Restart the program after update - updater.restart_program() -else: - print("No update available or checked too recently.") -``` - -### Explanation: -- The example shows how to initialize the `PyPiUpdater` class with the package name, current version, and the path to a log file. -- It checks for an update by calling `check_for_update()`. -- If an update is available, it proceeds with updating the package using `update_package()` and restarts the program with `restart_program()`. -- If no update is available, or it was checked too recently, it prints an appropriate message. +Example has been removed, still very active development. From e7305f6ecfbdc23a3667f60c0118a20e8c144e4b Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 31 Jan 2025 17:29:34 +0100 Subject: [PATCH 2/3] v0.4.0 --- CHANGELOG.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 291ec3b..2e5aa9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,22 @@ # Changelog -## 0.3.0: Rework BREAKING + +## 0.4.0: Rework (BREAKING CHANGE) +- The log file is now a JSON file, allowing it to store multiple package names, versions, and last update timestamps. +- Some return values are now lists. + +--- + +## 0.3.0: Rework (BREAKING CHANGE) - Changed how program behaves +--- + ## 0.2.1: CI/CD pipeline - Added auto tagging and publishing +--- + ## 0.0.1: Project Initiation - First working version - ATM terminal promt to accept or deny update From d08b4b9807babc6ae31701d69b6137c6944849ef Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Fri, 31 Jan 2025 17:30:01 +0100 Subject: [PATCH 3/3] refactor: few more option to save, and some returns are lists. --- src/PyPiUpdater/pypi_updater.py | 84 +++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 25 deletions(-) diff --git a/src/PyPiUpdater/pypi_updater.py b/src/PyPiUpdater/pypi_updater.py index 880df45..c923438 100644 --- a/src/PyPiUpdater/pypi_updater.py +++ b/src/PyPiUpdater/pypi_updater.py @@ -3,23 +3,26 @@ import subprocess import sys import os import time +import json from packaging import version from xml.etree import ElementTree as ET class PyPiUpdater: - def __init__(self, package_name, local_version, log_path, update_interval_seconds=20 * 3600): + def __init__(self, package_name, local_version, log_path, update_interval_seconds = 20 * 3600): """ Initialize PyPiUpdater. :param package_name: Name of the package on PyPI. :param local_version: Currently installed version. - :param log_path: Path to the update log file (txt file). + :param log_path: Path to the update log file (JSON file). :param update_interval_seconds: Seconds to wait before update is allowed again (default: 20 hours). """ self.package_name = package_name self.local_version = version.parse(local_version) self.log_path = log_path self.update_interval = update_interval_seconds + self.latest_version = "" + self.last_update_check = 0.1 def _get_latest_version(self): """Fetch the latest version from PyPI RSS feed.""" @@ -31,6 +34,7 @@ class PyPiUpdater: root = ET.fromstring(response.content) latest_version = root.find(".//item/title").text.strip() + self.latest_version = latest_version return latest_version, None except requests.exceptions.RequestException as e: return None, f"Network error: {str(e)}" @@ -39,7 +43,6 @@ class PyPiUpdater: def check_for_update(self, force = False): """Check if an update is available.""" - if not force and not self.should_check_for_update(): return None, "Checked too recently" @@ -49,7 +52,7 @@ class PyPiUpdater: is_newer = version.parse(latest_version) > self.local_version if is_newer: - self.record_update_check() # Save the check timestamp only if successful + self.record_update_check() # Save check timestamp & latest version return is_newer, latest_version def update_package(self): @@ -68,31 +71,62 @@ class PyPiUpdater: subprocess.run([python] + sys.argv) sys.exit() - def last_update_check(self): - """Retrieve the last update check timestamp from a text file.""" - if not os.path.exists(self.log_path): - return 0 # If no log, assume a long time passed + def get_last_state(self): + """Retrieve last update info for the package.""" + data = self._read_json() + if self.package_name in data: + entry = data[self.package_name] + last_check = self.last_update_date_string(entry["last_checked"]) + return [last_check, entry["last_online_version"], self.package_name] + return [None, None, self.package_name] - try: - with open(self.log_path, "r") as f: - last_check = float(f.readline().strip()) # Read first line as timestamp - return last_check - except Exception: - return 0 # Handle read errors gracefully + def record_update_check(self): + """Save the last update check time and online version in JSON.""" + data = self._read_json() + data[self.package_name] = { + "last_checked": time.time(), + "last_online_version": self.latest_version + } + self._write_json(data) - def last_update_date_string(self): - time_float = self.last_update_check() + def remove_package_entry(self, package_name): + """Remove a specific package entry from the log file.""" + data = self._read_json() + if package_name in data: + del data[package_name] + self._write_json(data) + return True + return False + + def clear_all_entries(self): + """Clear all update history.""" + self._write_json({}) + + def should_check_for_update(self): + """Returns True if enough time has passed since the last check.""" + data = self._read_json() + last_check = data.get(self.package_name, {}).get("last_checked", 0) + elapsed_time = time.time() - last_check + return elapsed_time >= self.update_interval + + def last_update_date_string(self, time_float): local_time = time.localtime(time_float) time_string = f"{local_time.tm_mday:02d}/{local_time.tm_mon:02d} {local_time.tm_hour:02d}:{local_time.tm_min:02d}" return time_string - def record_update_check(self): - """Save the current time as the last update check timestamp.""" - with open(self.log_path, "w") as f: - f.write(f"{time.time()}") - def should_check_for_update(self): - """Returns True if enough time has passed since the last check.""" - last_check = self.last_update_check() - elapsed_time = time.time() - last_check - return elapsed_time >= self.update_interval + def _read_json(self): + """Read JSON log file.""" + if not os.path.exists(self.log_path): + return {} + + try: + with open(self.log_path, "r") as f: + return json.load(f) + except (json.JSONDecodeError, FileNotFoundError): + return {} + + def _write_json(self, data): + """Write data to JSON log file.""" + with open(self.log_path, "w") as f: + json.dump(data, f, indent=4)