From 69dcd6541e07bae7db477c9f4e0749af4edc94c3 Mon Sep 17 00:00:00 2001 From: Francisco Serrano Date: Fri, 23 Jan 2026 13:20:49 +0200 Subject: [PATCH 1/2] chore: drop support for python 3.9 --- .github/workflows/main.yml | 1 - CHANGELOG.md | 4 ++++ README.md | 1 - pyproject.toml | 4 ++-- setup.cfg | 2 +- tox.ini | 2 +- 6 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9cf821d..32e95be 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,6 @@ jobs: strategy: matrix: tox_env: - - py39 - py310 - py311 - py312 diff --git a/CHANGELOG.md b/CHANGELOG.md index 218a777..948dadc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Python versions supported: 3.10, 3.11, 3.12, 3.13, PyPy3. Dropped support for 3.9. + ## [2.9.0] - 2025-09-25 ### Removed diff --git a/README.md b/README.md index c867108..8ed1bcd 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,6 @@ python setup.py install ### Supported Python versions -- Python 3.9 - Python 3.10 - Python 3.11 - Python 3.12 diff --git a/pyproject.toml b/pyproject.toml index 902b06b..5a770c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,10 @@ [tool.black] line-length = 99 -target-version = ['py39'] +target-version = ['py310'] skip-string-normalization = true [tool.ruff] -target-version = "py39" +target-version = "py310" exclude = [ ".git", "ENV", diff --git a/setup.cfg b/setup.cfg index 9b54482..d4a9243 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,7 +12,7 @@ license = MIT license_files = [] [options] -python_requires = >=3.9, <4 +python_requires = >=3.10, <4 setup_requires = setuptools install_requires = diff --git a/tox.ini b/tox.ini index 6368648..3c8fc6f 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py39, py310, py311, py312, py313, pypy311 +envlist = py310, py311, py312, py313, pypy311 skip_missing_interpreters = True [testenv] From 55ce59a9a3fb52ed5859746e951c901675249230 Mon Sep 17 00:00:00 2001 From: Francisco Serrano Date: Fri, 23 Jan 2026 13:31:48 +0200 Subject: [PATCH 2/2] chore: pre-commit changes --- upcloud_api/cloud_manager/storage_mixin.py | 14 ++++++-------- upcloud_api/cloud_manager/tag_mixin.py | 4 +--- upcloud_api/server.py | 4 ++-- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/upcloud_api/cloud_manager/storage_mixin.py b/upcloud_api/cloud_manager/storage_mixin.py index a049a08..431f58d 100644 --- a/upcloud_api/cloud_manager/storage_mixin.py +++ b/upcloud_api/cloud_manager/storage_mixin.py @@ -1,5 +1,5 @@ from os import PathLike -from typing import BinaryIO, Optional, Union +from typing import BinaryIO from upcloud_api.api import API from upcloud_api.storage import BackupDeletionPolicy, Storage @@ -47,7 +47,7 @@ def create_storage( title: str = 'Storage disk', encrypted: bool = False, *, - backup_rule: Optional[dict] = None, + backup_rule: dict | None = None, ) -> Storage: """ Create a Storage object. Returns an object based on the API's response. @@ -70,7 +70,7 @@ def create_storage( res = self.api.post_request('/storage', body) return Storage(cloud_manager=self, **res['storage']) - def _modify_storage(self, storage, size, title, backup_rule: Optional[dict] = None): + def _modify_storage(self, storage, size, title, backup_rule: dict | None = None): body = {'storage': {}} if size: body['storage']['size'] = size @@ -81,7 +81,7 @@ def _modify_storage(self, storage, size, title, backup_rule: Optional[dict] = No return self.api.put_request('/storage/' + str(storage), body) def modify_storage( - self, storage: str, size: int, title: str, backup_rule: Optional[dict] = None + self, storage: str, size: int, title: str, backup_rule: dict | None = None ) -> Storage: """ Modify a Storage object. Returns an object based on the API's response. @@ -95,9 +95,7 @@ def delete_storage(self, uuid: str, backups: BackupDeletionPolicy = BackupDeleti """ return self.api.delete_request(f'/storage/{uuid}?backups={backups.value}') - def clone_storage( - self, storage: Union[Storage, str], title: str, zone: str, tier=None - ) -> Storage: + def clone_storage(self, storage: Storage | str, title: str, zone: str, tier=None) -> Storage: """ Clones a Storage object. Returns an object based on the API's response. """ @@ -203,7 +201,7 @@ def create_storage_import( def upload_file_for_storage_import( self, storage_import: StorageImport, - file: Union[str, PathLike, BinaryIO], + file: str | PathLike | BinaryIO, timeout: int = 30, content_type: str = 'application/octet-stream', ): diff --git a/upcloud_api/cloud_manager/tag_mixin.py b/upcloud_api/cloud_manager/tag_mixin.py index 9c68994..8c52e44 100644 --- a/upcloud_api/cloud_manager/tag_mixin.py +++ b/upcloud_api/cloud_manager/tag_mixin.py @@ -1,5 +1,3 @@ -from typing import Optional - from upcloud_api.api import API from upcloud_api.tag import Tag @@ -24,7 +22,7 @@ def get_tag(self, name: str) -> Tag: return Tag(cloud_manager=self, **res['tag']) def create_tag( - self, name: str, description: Optional[str] = None, servers: Optional[list] = None + self, name: str, description: str | None = None, servers: list | None = None ) -> Tag: """ Create a new Tag. Only name is mandatory. diff --git a/upcloud_api/server.py b/upcloud_api/server.py index 4cf80af..64c4136 100644 --- a/upcloud_api/server.py +++ b/upcloud_api/server.py @@ -1,5 +1,5 @@ from time import sleep -from typing import TYPE_CHECKING, Any, Optional +from typing import TYPE_CHECKING, Any from upcloud_api.firewall import FirewallRule from upcloud_api.ip_address import IPAddress @@ -276,7 +276,7 @@ def remove_ip(self, ip_address: IPAddress) -> None: def add_storage( self, - storage: Optional[Storage] = None, # TODO: this probably shouldn't be optional + storage: Storage | None = None, # TODO: this probably shouldn't be optional type: str = 'disk', address=None, ) -> None: