From 47fe83d1f77603b0c22997fae83e15211160bd5a Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Fri, 31 Jan 2025 16:31:18 +0000 Subject: [PATCH 01/16] feat: using json to store more information local --- CHANGELOG.md | 13 ++++- README.md | 35 +------------- src/PyPiUpdater/pypi_updater.py | 84 +++++++++++++++++++++++---------- 3 files changed, 72 insertions(+), 60 deletions(-) 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 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. 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) From 3587ce13178e2c38f8b7a59b61d5c7e1278917da Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Sat, 1 Feb 2025 17:43:54 +0000 Subject: [PATCH 02/16] refactor: consistent return values --- CHANGELOG.md | 7 +++++ src/PyPiUpdater/pypi_updater.py | 55 ++++++++------------------------- 2 files changed, 20 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e5aa9f..ee74deb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.5.0: Rework (BREAKING CHANGE) +- Improved code consistency: return values are now always **lists** when containing multiple objects. +- **Simplified the package**: Removed the default waiting period for update checks. + - It is now the **developer's responsibility** to decide when to check for updates. A separate independent function may be introduced later. + - The last update check is still saved in the config and returned as a `time.time()` object. + +--- ## 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. diff --git a/src/PyPiUpdater/pypi_updater.py b/src/PyPiUpdater/pypi_updater.py index c923438..5767a34 100644 --- a/src/PyPiUpdater/pypi_updater.py +++ b/src/PyPiUpdater/pypi_updater.py @@ -8,7 +8,7 @@ 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): """ Initialize PyPiUpdater. @@ -20,7 +20,6 @@ class PyPiUpdater: 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 @@ -35,34 +34,30 @@ class PyPiUpdater: latest_version = root.find(".//item/title").text.strip() self.latest_version = latest_version - return latest_version, None + return [latest_version, None] except requests.exceptions.RequestException as e: - return None, f"Network error: {str(e)}" + return [None, f"Network error: {str(e)}"] except Exception as e: - return None, f"Error parsing feed: {str(e)}" + return [None, f"Error parsing feed: {str(e)}"] - def check_for_update(self, force = False): + def check_for_update(self): """Check if an update is available.""" - if not force and not self.should_check_for_update(): - return None, "Checked too recently" - latest_version, error = self._get_latest_version() if latest_version is None: - return None, error + return [None, error] is_newer = version.parse(latest_version) > self.local_version - if is_newer: - self.record_update_check() # Save check timestamp & latest version - return is_newer, latest_version + self._record_update_check() # Save check timestamp & latest version + return [is_newer, latest_version] def update_package(self): """Update the package using pip.""" print(f"Updating {self.package_name}...") try: - subprocess.run([sys.executable, "-m", "pip", "install", "--upgrade", self.package_name], check=True) - return True, f"{self.package_name} updated successfully." + subprocess.run([sys.executable, "-m", "pip", "install", "--upgrade", self.package_name], check = True) + return [True, f"{self.package_name} updated successfully."] except subprocess.CalledProcessError as e: - return False, f"Update failed: {str(e)}" + return [False, f"Update failed: {str(e)}"] def restart_program(self): """Restart the Python program after an update.""" @@ -76,11 +71,10 @@ class PyPiUpdater: 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 [entry["last_checked"], entry["last_online_version"], self.package_name] return [None, None, self.package_name] - def record_update_check(self): + def _record_update_check(self): """Save the last update check time and online version in JSON.""" data = self._read_json() data[self.package_name] = { @@ -89,37 +83,14 @@ class PyPiUpdater: } self._write_json(data) - 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 _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) From cb568730d8bed0ff961dbf9b24958246cb02c77c Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Mon, 3 Feb 2025 16:21:01 +0100 Subject: [PATCH 03/16] feat: Added local update functions --- CHANGELOG.md | 10 ++++++++ src/PyPiUpdater/pypi_updater.py | 45 +++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee74deb..678af18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.6.0: New Local Update Feature +- Added support for updating from a local folder containing package files. + - Scans a specified folder for available updates. + - Installs updates directly from local package files. +- **Note:** Local version handling depends on how dependencies are managed. + - Example: If a package requires **PyPiUpdater 0.6.0**, but the installed version is **0.0.1** (e.g., from a dev environment), **OptimaLab35 v0.9.1** may not install correctly. + - This is due to **pip's dependency checks**, ensuring all required versions are satisfied before installation. + +--- + ## 0.5.0: Rework (BREAKING CHANGE) - Improved code consistency: return values are now always **lists** when containing multiple objects. - **Simplified the package**: Removed the default waiting period for update checks. diff --git a/src/PyPiUpdater/pypi_updater.py b/src/PyPiUpdater/pypi_updater.py index 5767a34..4c5b835 100644 --- a/src/PyPiUpdater/pypi_updater.py +++ b/src/PyPiUpdater/pypi_updater.py @@ -4,6 +4,7 @@ import sys import os import time import json +import re from packaging import version from xml.etree import ElementTree as ET @@ -59,6 +60,50 @@ class PyPiUpdater: except subprocess.CalledProcessError as e: return [False, f"Update failed: {str(e)}"] + def check_update_local(self, folder_path): + """ + Check if a newer version of the package is available in the local folder. + + :param folder_path: Path to the folder containing package files. + :return: (bool, latest_version) - True if newer version found, otherwise False. + """ + if not os.path.exists(folder_path): + return [None, "Folder does not exist"] + + pattern = re.compile(rf"{re.escape(self.package_name.lower())}-(\d+\.\d+\.\d+[a-zA-Z0-9]*)") + + available_versions = [] + for file in os.listdir(folder_path): + match = pattern.search(file) + if match: + found_version = match.group(1) + available_versions.append(version.parse(found_version)) + + if not available_versions: + return [None, "No valid package versions found in the folder"] + + latest_version = max(available_versions) + is_newer = latest_version > self.local_version + + return [is_newer, str(latest_version)] + + def update_from_local(self, folder_path): + """ + Install the latest package version from a local folder. + + :param folder_path: Path to the folder containing package files. + :return: (bool, message) - Success status and message. + """ + print(f"Installing {self.package_name} from {folder_path}...") + try: + subprocess.run( + [sys.executable, "-m", "pip", "install", "--no-index", "--find-links", folder_path, self.package_name, "-U"], + check=True + ) + return [True, f"{self.package_name} updated successfully from local folder."] + except subprocess.CalledProcessError as e: + return [False, f"Update from local folder failed: {str(e)}"] + def restart_program(self): """Restart the Python program after an update.""" print("Restarting the application...") From 09a37ae62886ae47a9acc50117a12ff2cd154622 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Sun, 9 Feb 2025 13:26:21 +0100 Subject: [PATCH 04/16] patch: Added classifiers for pypi --- CHANGELOG.md | 6 +++++- pyproject.toml | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 678af18..60dc87f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Changelog -## 0.6.0: New Local Update Feature +## 0.6.x +### 0.6.1: Classifier +- Added Classifier for pypi + +### 0.6.0: New Local Update Feature - Added support for updating from a local folder containing package files. - Scans a specified folder for available updates. - Installs updates directly from local package files. diff --git a/pyproject.toml b/pyproject.toml index 0456616..e0c386a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,13 +11,17 @@ readme = "README.md" requires-python = ">=3.8" dependencies = ["requests", "packaging"] classifiers = [ + "Development Status :: 4 - Beta", "Programming Language :: Python :: 3", + "Topic :: Software Development :: Libraries :: Python Modules", "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", "Operating System :: OS Independent", ] [project.urls] Source = "https://gitlab.com/CodeByMrFinchum/PyPiUpdater" +Documentation = "https://gitlab.com/CodeByMrFinchum/PyPiUpdater/-/blob/main/README.md" +Changelog = "https://gitlab.com/CodeByMrFinchum/PyPiUpdater/-/blob/main/CHANGELOG.md" [tool.hatch.build.targets.wheel] packages = ["src/PyPiUpdater"] From 9680e33730059c55ee604eeafea640b54a15dfb0 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Sun, 9 Feb 2025 17:48:30 +0100 Subject: [PATCH 05/16] feat: function to install single package. --- CHANGELOG.md | 6 ++++++ pyproject.toml | 2 +- src/PyPiUpdater/pypi_updater.py | 11 +++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60dc87f..f07c7b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 0.7.0: Added Function to Install Packages +- Introduced the `install_package` function, allowing packages to be installed directly through the app. + - Useful for optional dependencies that need to be installed separately. This enables installation via the UI. + +--- + ## 0.6.x ### 0.6.1: Classifier - Added Classifier for pypi diff --git a/pyproject.toml b/pyproject.toml index e0c386a..2aeb735 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ readme = "README.md" requires-python = ">=3.8" dependencies = ["requests", "packaging"] classifiers = [ - "Development Status :: 4 - Beta", + "Development Status :: 3 - Alpha", "Programming Language :: Python :: 3", "Topic :: Software Development :: Libraries :: Python Modules", "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", diff --git a/src/PyPiUpdater/pypi_updater.py b/src/PyPiUpdater/pypi_updater.py index 4c5b835..3203ddb 100644 --- a/src/PyPiUpdater/pypi_updater.py +++ b/src/PyPiUpdater/pypi_updater.py @@ -146,3 +146,14 @@ class PyPiUpdater: """Write data to JSON log file.""" with open(self.log_path, "w") as f: json.dump(data, f, indent=4) + + @staticmethod + def install_package(package_name): + """Attempts to install a package via pip.""" + try: + subprocess.run([sys.executable, "-m", "pip", "install", package_name], check = True) + print("Successfull") + return [True, f"{package_name} installed successfully!"] + except subprocess.CalledProcessError as e: + print("Failed") + return [False, f"Failed to install {package_name}:\n{e.stderr}"] From 712d80a0aa17bb9ea2fc7b9caf98654d0999edce Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Mon, 10 Feb 2025 13:16:21 +0100 Subject: [PATCH 06/16] fix: Fixed that prereleases version were listed --- src/PyPiUpdater/__init__.py | 3 ++- src/PyPiUpdater/multi_updater.py | 4 ++++ .../{pypi_updater.py => single_updater.py} | 24 +++++++++++++++---- 3 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 src/PyPiUpdater/multi_updater.py rename src/PyPiUpdater/{pypi_updater.py => single_updater.py} (86%) diff --git a/src/PyPiUpdater/__init__.py b/src/PyPiUpdater/__init__.py index 136d36c..621ee43 100644 --- a/src/PyPiUpdater/__init__.py +++ b/src/PyPiUpdater/__init__.py @@ -1,4 +1,5 @@ -from .pypi_updater import PyPiUpdater +from .single_updater import PyPiUpdater +#from .multi_updater import MultiPackageUpdater __all__ = ["PyPiUpdater"] diff --git a/src/PyPiUpdater/multi_updater.py b/src/PyPiUpdater/multi_updater.py new file mode 100644 index 0000000..71e55a9 --- /dev/null +++ b/src/PyPiUpdater/multi_updater.py @@ -0,0 +1,4 @@ + +class MultiPackageUpdater: + def __init__(self, log_path): + print("Not ready yet...") diff --git a/src/PyPiUpdater/pypi_updater.py b/src/PyPiUpdater/single_updater.py similarity index 86% rename from src/PyPiUpdater/pypi_updater.py rename to src/PyPiUpdater/single_updater.py index 3203ddb..851f69b 100644 --- a/src/PyPiUpdater/pypi_updater.py +++ b/src/PyPiUpdater/single_updater.py @@ -6,6 +6,7 @@ import time import json import re from packaging import version +from packaging.version import parse, Version from xml.etree import ElementTree as ET class PyPiUpdater: @@ -25,7 +26,7 @@ class PyPiUpdater: self.last_update_check = 0.1 def _get_latest_version(self): - """Fetch the latest version from PyPI RSS feed.""" + """Fetch the latest stable version from PyPI RSS feed.""" rss_url = f"https://pypi.org/rss/project/{self.package_name.lower()}/releases.xml" try: @@ -33,9 +34,24 @@ class PyPiUpdater: response.raise_for_status() root = ET.fromstring(response.content) - latest_version = root.find(".//item/title").text.strip() - self.latest_version = latest_version - return [latest_version, None] + # Extract all versions from the feed + versions = [] + for item in root.findall(".//item/title"): + version_text = item.text.strip() + parsed_version = parse(version_text) + print(version_text,"\n",parsed_version) + # Check if the version is stable (not a pre-release) + if isinstance(parsed_version, Version) and not parsed_version.is_prerelease: + versions.append(parsed_version) + + # Return the latest stable version + if versions: + latest_version = str(max(versions)) + self.latest_version = latest_version + return [latest_version, None] + + return [None, "No stable versions found"] + except requests.exceptions.RequestException as e: return [None, f"Network error: {str(e)}"] except Exception as e: From 23214d7d32b4a81bd637dfb0c0a7cf190f71fb46 Mon Sep 17 00:00:00 2001 From: CodeByMrFinchum Date: Mon, 10 Feb 2025 13:21:38 +0100 Subject: [PATCH 07/16] fix: removed debug leftover --- CHANGELOG.md | 9 ++++++++- src/PyPiUpdater/single_updater.py | 1 - 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f07c7b0..c7a8815 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Changelog -## 0.7.0: Added Function to Install Packages +## 0.7.x +### 0.7.2: Removed Debugging Leftovers +- Cleaned up code used for debugging. + +### 0.7.1: Fixed Prerelease Update Detection +- Prevented prerelease versions from being listed as updates, as they must be installed manually. + +### 0.7.0: Added Function to Install Packages - Introduced the `install_package` function, allowing packages to be installed directly through the app. - Useful for optional dependencies that need to be installed separately. This enables installation via the UI. diff --git a/src/PyPiUpdater/single_updater.py b/src/PyPiUpdater/single_updater.py index 851f69b..3e4e6d6 100644 --- a/src/PyPiUpdater/single_updater.py +++ b/src/PyPiUpdater/single_updater.py @@ -39,7 +39,6 @@ class PyPiUpdater: for item in root.findall(".//item/title"): version_text = item.text.strip() parsed_version = parse(version_text) - print(version_text,"\n",parsed_version) # Check if the version is stable (not a pre-release) if isinstance(parsed_version, Version) and not parsed_version.is_prerelease: versions.append(parsed_version) From 8f91f64d04a5a66145965ca13e2ee869089b0092 Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Thu, 10 Apr 2025 16:30:40 +0200 Subject: [PATCH 08/16] CI: woodpecker CI --- .gitlab-ci/git/create_tag.yml | 15 ------ .gitlab-ci/versioning/gitversion.yml | 31 ------------ .woodpecker/woodpecker_ci.yml | 75 ++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 46 deletions(-) delete mode 100644 .gitlab-ci/git/create_tag.yml delete mode 100644 .gitlab-ci/versioning/gitversion.yml create mode 100644 .woodpecker/woodpecker_ci.yml diff --git a/.gitlab-ci/git/create_tag.yml b/.gitlab-ci/git/create_tag.yml deleted file mode 100644 index 2c1afd7..0000000 --- a/.gitlab-ci/git/create_tag.yml +++ /dev/null @@ -1,15 +0,0 @@ ---- - -.git:create_tag: - image: alpine:3.21 - variables: - GIT_STRATEGY: clone - GIT_DEPTH: 0 - GIT_LFS_SKIP_SMUDGE: 1 - VERSION: '' - TOKEN: '' # Token with push privileges - script: - - apk add git - - git remote set-url origin https://oauth2:$TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH - - git tag $VERSION - - git push origin tag $VERSION diff --git a/.gitlab-ci/versioning/gitversion.yml b/.gitlab-ci/versioning/gitversion.yml deleted file mode 100644 index dbbc149..0000000 --- a/.gitlab-ci/versioning/gitversion.yml +++ /dev/null @@ -1,31 +0,0 @@ ---- -.versioning:gitversion: - image: - name: mcr.microsoft.com/dotnet/sdk:9.0 - variables: - GIT_STRATEGY: clone - GIT_DEPTH: 0 # force a deep/non-shallow fetch need by gitversion - GIT_LFS_SKIP_SMUDGE: 1 - cache: [] # caches and before / after scripts can mess things up - script: - - | - dotnet tool install --global GitVersion.Tool --version 5.* - export PATH="$PATH:/root/.dotnet/tools" - - dotnet-gitversion -output buildserver - - # We could just collect the output file gitversion.properties (with artifacts:report:dotenv: gitversion.properties as it is already in DOTENV format, - # however it contains ~33 variables which unnecessarily consumes many of the 50 max DOTENV variables of the free GitLab version. - # Limits are higher for licensed editions, see https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsdotenv - grep 'GitVersion_LegacySemVer=' gitversion.properties >> gitversion.env - grep 'GitVersion_SemVer=' gitversion.properties >> gitversion.env - grep 'GitVersion_FullSemVer=' gitversion.properties >> gitversion.env - grep 'GitVersion_Major=' gitversion.properties >> gitversion.env - grep 'GitVersion_Minor=' gitversion.properties >> gitversion.env - grep 'GitVersion_Patch=' gitversion.properties >> gitversion.env - grep 'GitVersion_MajorMinorPatch=' gitversion.properties >> gitversion.env - grep 'GitVersion_BuildMetaData=' gitversion.properties >> gitversion.env - artifacts: - reports: - # propagates variables into the pipeline level - dotenv: gitversion.env diff --git a/.woodpecker/woodpecker_ci.yml b/.woodpecker/woodpecker_ci.yml new file mode 100644 index 0000000..01f2460 --- /dev/null +++ b/.woodpecker/woodpecker_ci.yml @@ -0,0 +1,75 @@ +steps: + - name: gitversion + depends_on: [] # nothing start emititly + when: + event: push + branch: main + image: mcr.microsoft.com/dotnet/sdk:9.0 + environment: + CI_TOKEN: + from_secret: CI_TOKEN + commands: + - git remote set-url origin https://CodeByMrFinchum:$CI_TOKEN@code.boxyfoxy.net/$CI_REPO.git + - git fetch --unshallow --tags + - apt-get update && apt-get install -y jq + - dotnet tool install --global GitVersion.Tool --version 5.* + - export PATH="$PATH:/root/.dotnet/tools" + - dotnet-gitversion -output json > version.json + - ls + - cat version.json + - | + echo "GitVersion_SemVer=$(jq -r '.SemVer' version.json)" >> gitversion.env + echo "GitVersion_LegacySemVer=$(jq -r '.LegacySemVer' version.json)" >> gitversion.env + echo "GitVersion_FullSemVer=$(jq -r '.FullSemVer' version.json)" >> gitversion.env + echo "GitVersion_Major=$(jq -r '.Major' version.json)" >> gitversion.env + echo "GitVersion_Minor=$(jq -r '.Minor' version.json)" >> gitversion.env + echo "GitVersion_Patch=$(jq -r '.Patch' version.json)" >> gitversion.env + echo "GitVersion_MajorMinorPatch=$(jq -r '.MajorMinorPatch' version.json)" >> gitversion.env + echo "GitVersion_BuildMetaData=$(jq -r '.BuildMetaData' version.json)" >> gitversion.env + + - name: build + depends_on: [gitversion] + when: + event: push + branch: main + image: python:3.9.21 + commands: + - source gitversion.env + - sed -i "s/^__version__ = .*/__version__ = \"$GitVersion_SemVer\"/" src/PyPiUpdater/__init__.py + - cat src/PyPiUpdater/__init__.py + - python3 -m pip install build + - python3 -m build + + - name: release + depends_on: [gitversion, build] + when: + event: push + branch: main + image: python:3.9.21 + environment: + TWINE_PASSWORD: + from_secret: TWINE_API + TWINE_USERNAME: "__token__" + commands: + - ls + - python3 -m pip install twine + - python3 -m twine upload dist/* + + - name: tag-release + depends_on: [gitversion] + when: + event: push + branch: main + image: alpine/git + environment: + CI_TOKEN: + from_secret: CI_TOKEN + commands: + - ls + - cat gitversion.env + - git config --global user.email "ci@noreply.boxyfoxy.net" + - git config --global user.name "CI Bot" + - git remote set-url origin https://CodeByMrFinchum:$${CI_TOKEN}@code.boxyfoxy.net/$${CI_REPO}.git + - source gitversion.env + - git tag $GitVersion_SemVer + - git push origin tag $GitVersion_SemVer From bf5d68061211b2e9208554378df99706588a1c4f Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Thu, 10 Apr 2025 16:48:17 +0200 Subject: [PATCH 09/16] fix: changed order --- .woodpecker/woodpecker_ci.yml | 59 ++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/.woodpecker/woodpecker_ci.yml b/.woodpecker/woodpecker_ci.yml index 01f2460..e015764 100644 --- a/.woodpecker/woodpecker_ci.yml +++ b/.woodpecker/woodpecker_ci.yml @@ -27,35 +27,7 @@ steps: echo "GitVersion_MajorMinorPatch=$(jq -r '.MajorMinorPatch' version.json)" >> gitversion.env echo "GitVersion_BuildMetaData=$(jq -r '.BuildMetaData' version.json)" >> gitversion.env - - name: build - depends_on: [gitversion] - when: - event: push - branch: main - image: python:3.9.21 - commands: - - source gitversion.env - - sed -i "s/^__version__ = .*/__version__ = \"$GitVersion_SemVer\"/" src/PyPiUpdater/__init__.py - - cat src/PyPiUpdater/__init__.py - - python3 -m pip install build - - python3 -m build - - - name: release - depends_on: [gitversion, build] - when: - event: push - branch: main - image: python:3.9.21 - environment: - TWINE_PASSWORD: - from_secret: TWINE_API - TWINE_USERNAME: "__token__" - commands: - - ls - - python3 -m pip install twine - - python3 -m twine upload dist/* - - - name: tag-release + - name: tagging depends_on: [gitversion] when: event: push @@ -73,3 +45,32 @@ steps: - source gitversion.env - git tag $GitVersion_SemVer - git push origin tag $GitVersion_SemVer + + - name: build + depends_on: [gitversion, tagging] + when: + event: push + branch: main + image: python:3.9.21 + commands: + - ls + - source gitversion.env + - sed -i "s/^__version__ = .*/__version__ = \"$GitVersion_SemVer\"/" src/PyPiUpdater/__init__.py + - cat src/PyPiUpdater/__init__.py + - python3 -m pip install build + - python3 -m build + + - name: release + depends_on: [gitversion, tagging, build] + when: + event: push + branch: main + image: python:3.9.21 + environment: + TWINE_PASSWORD: + from_secret: TWINE_API + TWINE_USERNAME: "__token__" + commands: + - ls + - python3 -m pip install twine + - python3 -m twine upload dist/* From 2e2bba2aa5d5a91dce1724dcde6f8a2403af8937 Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Thu, 10 Apr 2025 16:59:59 +0200 Subject: [PATCH 10/16] fix: change source to dot --- .woodpecker/woodpecker_ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.woodpecker/woodpecker_ci.yml b/.woodpecker/woodpecker_ci.yml index e015764..1b3d924 100644 --- a/.woodpecker/woodpecker_ci.yml +++ b/.woodpecker/woodpecker_ci.yml @@ -42,7 +42,7 @@ steps: - git config --global user.email "ci@noreply.boxyfoxy.net" - git config --global user.name "CI Bot" - git remote set-url origin https://CodeByMrFinchum:$${CI_TOKEN}@code.boxyfoxy.net/$${CI_REPO}.git - - source gitversion.env + - . gitversion.env - git tag $GitVersion_SemVer - git push origin tag $GitVersion_SemVer @@ -54,7 +54,7 @@ steps: image: python:3.9.21 commands: - ls - - source gitversion.env + - . gitversion.env - sed -i "s/^__version__ = .*/__version__ = \"$GitVersion_SemVer\"/" src/PyPiUpdater/__init__.py - cat src/PyPiUpdater/__init__.py - python3 -m pip install build From 03cf68b8731ca76ee52c08696fdd902d18704555 Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Thu, 10 Apr 2025 17:05:49 +0200 Subject: [PATCH 11/16] fix: use export for vars --- .woodpecker/woodpecker_ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.woodpecker/woodpecker_ci.yml b/.woodpecker/woodpecker_ci.yml index 1b3d924..ede607d 100644 --- a/.woodpecker/woodpecker_ci.yml +++ b/.woodpecker/woodpecker_ci.yml @@ -54,7 +54,8 @@ steps: image: python:3.9.21 commands: - ls - - . gitversion.env + - cat gitversion.env + - export $(cat gitversion.env | xargs) - sed -i "s/^__version__ = .*/__version__ = \"$GitVersion_SemVer\"/" src/PyPiUpdater/__init__.py - cat src/PyPiUpdater/__init__.py - python3 -m pip install build From 364af1cb6ec378b62db94b7a0700b4f8d4b96b8f Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Fri, 11 Apr 2025 11:42:43 +0200 Subject: [PATCH 12/16] ci: now also upload to package on forgejo. --- .woodpecker/woodpecker_ci.yml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.woodpecker/woodpecker_ci.yml b/.woodpecker/woodpecker_ci.yml index ede607d..ebb0ec7 100644 --- a/.woodpecker/woodpecker_ci.yml +++ b/.woodpecker/woodpecker_ci.yml @@ -61,7 +61,7 @@ steps: - python3 -m pip install build - python3 -m build - - name: release + - name: publish_pypi depends_on: [gitversion, tagging, build] when: event: push @@ -75,3 +75,27 @@ steps: - ls - python3 -m pip install twine - python3 -m twine upload dist/* + + - name: publish_forgejo + depends_on: [gitversion, tagging, build] + when: + event: push + branch: main + image: python:3.9.21 + environment: + CI_TOKEN: + from_secret: CI_TOKEN + commands: + - ls + - python3 -m pip install twine + - | + cat > ~/.pypirc < Date: Fri, 11 Apr 2025 11:56:12 +0200 Subject: [PATCH 13/16] fix: fixes publish to forgejo package --- .woodpecker/woodpecker_ci.yml | 6 +++--- CHANGELOG.md | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.woodpecker/woodpecker_ci.yml b/.woodpecker/woodpecker_ci.yml index ebb0ec7..7822860 100644 --- a/.woodpecker/woodpecker_ci.yml +++ b/.woodpecker/woodpecker_ci.yml @@ -83,8 +83,8 @@ steps: branch: main image: python:3.9.21 environment: - CI_TOKEN: - from_secret: CI_TOKEN + PKG_TOKEN: + from_secret: PKG_TOKEN commands: - ls - python3 -m pip install twine @@ -96,6 +96,6 @@ steps: [forgejo] repository = https://code.boxyfoxy.net/api/packages/CodeByMrFinchum/pypi username = CodeByMrFinchum - password = ${CI_TOKEN} + password = ${PKG_TOKEN} EOF - python3 -m twine upload --repository forgejo dist/* diff --git a/CHANGELOG.md b/CHANGELOG.md index c7a8815..3e847e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.8-0.9: CI woodpecker (25.04.10-11) +- Changes to the pipeline no + ## 0.7.x ### 0.7.2: Removed Debugging Leftovers - Cleaned up code used for debugging. From dc8b1ca9ed18fb9f6983211beeb4470d273bb531 Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Fri, 11 Apr 2025 12:09:32 +0200 Subject: [PATCH 14/16] fix: skip twine file --- .woodpecker/woodpecker_ci.yml | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/.woodpecker/woodpecker_ci.yml b/.woodpecker/woodpecker_ci.yml index 7822860..c531675 100644 --- a/.woodpecker/woodpecker_ci.yml +++ b/.woodpecker/woodpecker_ci.yml @@ -83,19 +83,10 @@ steps: branch: main image: python:3.9.21 environment: - PKG_TOKEN: + TWINE_PASSWORD: from_secret: PKG_TOKEN + TWINE_USERNAME: "CodeByMrFinchum" commands: - ls - python3 -m pip install twine - - | - cat > ~/.pypirc < Date: Fri, 11 Apr 2025 12:16:59 +0200 Subject: [PATCH 15/16] Adding info about the migration to readme. --- README.md | 2 ++ pip_README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c3f04c1..ceb666a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # PyPiUpdater +Developed on my [forgejo instance](https://code.boxyfoxy.net/CodeByMrFinchum), [GitLab](https://gitlab.com/CodeByMrFinchum) is used as backup. + **UNFINISHED** Still early code, functions might change drasticly **PyPiUpdater** is a Python library for managing updates of packages installed via `pip`. diff --git a/pip_README.md b/pip_README.md index 2fb85ac..81c43b3 100644 --- a/pip_README.md +++ b/pip_README.md @@ -1,3 +1,3 @@ Simple program to update package from PyPi with pip. -For more info see [PyPiUpdater gitlab](https://gitlab.com/CodeByMrFinchum/PyPiUpdater#). +For more info see [PyPiUpdater forgejo](https://code.boxyfoxy.net/CodeByMrFinchum/PyPiUpdater) or backup repo [PyPiUpdater gitlab](https://gitlab.com/CodeByMrFinchum/PyPiUpdater#). From 77d7092a24f1bccae7a3aa8ef1784806f2ef4a72 Mon Sep 17 00:00:00 2001 From: Mr Finchum Date: Fri, 11 Apr 2025 12:23:11 +0200 Subject: [PATCH 16/16] patch: removed old gitlab ci file. --- .gitlab-ci.yml | 69 -------------------------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index d93834d..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,69 +0,0 @@ ---- -include: - - local: .gitlab-ci/versioning/gitversion.yml - - local: .gitlab-ci/git/create_tag.yml - -stages: - - build - - release - -gitversion: - extends: .versioning:gitversion - stage: .pre - tags: - - gitlab-org-docker - rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch - -build: - stage: build - image: python:3.9.21 - tags: - - gitlab-org-docker - rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch - needs: - - job: gitversion - artifacts: true - script: - - sed -i "s/^__version__ = .*/__version__ = \"${GitVersion_MajorMinorPatch}\"/" src/PyPiUpdater/__init__.py - - cat src/PyPiUpdater/__init__.py - - python3 -m pip install build - - python3 -m build - artifacts: - paths: - - dist/* - expire_in: 1 day - -publish: - stage: release - image: python:3.9.21 - tags: - - gitlab-org-docker - rules: - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch - variables: - TWINE_USERNAME: "__token__" - TWINE_PASSWORD: $TWINE_API - needs: - - job: build - artifacts: true - script: - - python3 -m pip install twine - - python3 -m twine upload dist/* - -create_tag: - extends: .git:create_tag - stage: release - tags: - - gitlab-org-docker - variables: - VERSION: $GitVersion_SemVer - TOKEN: $GITLAB_TOKEN - needs: - - job: gitversion - artifacts: true - rules: - - if: $CI_COMMIT_TAG - when: never # Do not run this job when a tag is created manually - - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH # Run this job when commits are pushed or merged to the default branch