1
0

Merge pull request #9418 from SomberNight/202501_bump_min_python

bump min python to 3.10
This commit is contained in:
ghost43
2025-01-13 12:01:57 +00:00
committed by GitHub
12 changed files with 47 additions and 71 deletions

View File

@@ -10,8 +10,6 @@ task:
TOXENV: py3 TOXENV: py3
ELECTRUM_PYTHON_NAME: python3 ELECTRUM_PYTHON_NAME: python3
matrix: matrix:
- env:
ELECTRUM_PYTHON_VERSION: 3.9
- env: - env:
ELECTRUM_PYTHON_VERSION: 3.10 ELECTRUM_PYTHON_VERSION: 3.10
- env: - env:
@@ -85,11 +83,11 @@ task:
locale_script: locale_script:
- contrib/push_locale - contrib/push_locale
env: env:
ELECTRUM_IMAGE: python:3.9 ELECTRUM_IMAGE: python:3.10
ELECTRUM_REQUIREMENTS_CI: contrib/requirements/requirements-ci.txt ELECTRUM_REQUIREMENTS_CI: contrib/requirements/requirements-ci.txt
# in addition, crowdin_api_key is set as an "override" in https://cirrus-ci.com/settings/... # in addition, crowdin_api_key is set as an "override" in https://cirrus-ci.com/settings/...
depends_on: depends_on:
- "unittests: Tox Python 3.9" - "unittests: Tox Python 3.10"
only_if: $CIRRUS_BRANCH == 'master' only_if: $CIRRUS_BRANCH == 'master'
task: task:
@@ -156,7 +154,7 @@ task:
flake8_script: flake8_script:
- flake8 . --count --select="$ELECTRUM_LINTERS" --ignore="$ELECTRUM_LINTERS_IGNORE" --show-source --statistics --exclude "*_pb2.py,electrum/_vendor/" - flake8 . --count --select="$ELECTRUM_LINTERS" --ignore="$ELECTRUM_LINTERS_IGNORE" --show-source --statistics --exclude "*_pb2.py,electrum/_vendor/"
env: env:
ELECTRUM_IMAGE: python:3.9 ELECTRUM_IMAGE: python:3.10
ELECTRUM_REQUIREMENTS: contrib/requirements/requirements.txt ELECTRUM_REQUIREMENTS: contrib/requirements/requirements.txt
matrix: matrix:
- name: "linter: Flake8 Mandatory" - name: "linter: Flake8 Mandatory"
@@ -212,7 +210,7 @@ task:
CIRRUS_WORKING_DIR: /opt/wine64/drive_c/electrum CIRRUS_WORKING_DIR: /opt/wine64/drive_c/electrum
CIRRUS_DOCKER_CONTEXT: contrib/build-wine CIRRUS_DOCKER_CONTEXT: contrib/build-wine
depends_on: depends_on:
- "unittests: Tox Python 3.9" - "unittests: Tox Python 3.10"
task: task:
name: "build: Android (QML $APK_ARCH)" name: "build: Android (QML $APK_ARCH)"
@@ -246,7 +244,7 @@ task:
binaries_artifacts: binaries_artifacts:
path: "dist/*" path: "dist/*"
depends_on: depends_on:
- "unittests: Tox Python 3.9" - "unittests: Tox Python 3.10"
## mac build disabled, as Cirrus CI no longer supports Intel-based mac builds ## mac build disabled, as Cirrus CI no longer supports Intel-based mac builds
#task: #task:
@@ -309,7 +307,7 @@ task:
env: env:
CIRRUS_DOCKER_CONTEXT: contrib/build-linux/appimage CIRRUS_DOCKER_CONTEXT: contrib/build-linux/appimage
depends_on: depends_on:
- "unittests: Tox Python 3.9" - "unittests: Tox Python 3.10"
task: task:
container: container:
@@ -332,12 +330,12 @@ task:
env: env:
OMIT_UNCLEAN_FILES: 1 OMIT_UNCLEAN_FILES: 1
depends_on: depends_on:
- "unittests: Tox Python 3.9" - "unittests: Tox Python 3.10"
task: task:
name: "check submodules" name: "check submodules"
container: container:
image: python:3.9 image: python:3.10
cpu: 1 cpu: 1
memory: 1G memory: 1G
fetch_script: fetch_script:

View File

@@ -3,7 +3,7 @@
``` ```
Licence: MIT Licence Licence: MIT Licence
Author: Thomas Voegtlin Author: Thomas Voegtlin
Language: Python (>= 3.8) Language: Python (>= 3.10)
Homepage: https://electrum.org/ Homepage: https://electrum.org/
``` ```

View File

@@ -1,4 +1,4 @@
FROM debian:bullseye@sha256:43ef0c6c3585d5b406caa7a0f232ff5a19c1402aeb415f68bcd1cf9d10180af8 FROM debian:bookworm@sha256:b877a1a3fdf02469440f1768cf69c9771338a875b7add5e80c45b756c92ac20a
ENV LC_ALL=C.UTF-8 LANG=C.UTF-8 ENV LC_ALL=C.UTF-8 LANG=C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive ENV DEBIAN_FRONTEND=noninteractive
@@ -11,7 +11,6 @@ RUN apt-get update -q && \
python3-pip \ python3-pip \
python3-setuptools \ python3-setuptools \
python3-venv \ python3-venv \
faketime \
&& \ && \
rm -rf /var/lib/apt/lists/* && \ rm -rf /var/lib/apt/lists/* && \
apt-get autoremove -y && \ apt-get autoremove -y && \

View File

@@ -6,21 +6,20 @@ PROJECT_ROOT="$(dirname "$(readlink -e "$0")")/../../.."
CONTRIB="$PROJECT_ROOT/contrib" CONTRIB="$PROJECT_ROOT/contrib"
CONTRIB_SDIST="$CONTRIB/build-linux/sdist" CONTRIB_SDIST="$CONTRIB/build-linux/sdist"
DISTDIR="$PROJECT_ROOT/dist" DISTDIR="$PROJECT_ROOT/dist"
BUILDDIR="$CONTRIB_SDIST/build"
LOCALE="$PROJECT_ROOT/electrum/locale" LOCALE="$PROJECT_ROOT/electrum/locale"
. "$CONTRIB"/build_tools_util.sh . "$CONTRIB"/build_tools_util.sh
git -C "$PROJECT_ROOT" rev-parse 2>/dev/null || fail "Building outside a git clone is not supported." git -C "$PROJECT_ROOT" rev-parse 2>/dev/null || fail "Building outside a git clone is not supported."
# note that at least py3.7 is needed, to have https://bugs.python.org/issue30693 rm -rf "$BUILDDIR"
mkdir -p "$BUILDDIR" "$DISTDIR"
python3 --version || fail "python interpreter not found" python3 --version || fail "python interpreter not found"
break_legacy_easy_install break_legacy_easy_install
# upgrade to modern pip so that it knows the flags we need.
# (make_packages.sh will later install a pinned version of pip in a venv)
python3 -m pip install --upgrade pip
rm -rf "$PROJECT_ROOT/packages/" rm -rf "$PROJECT_ROOT/packages/"
if ([ "$OMIT_UNCLEAN_FILES" != 1 ]); then if ([ "$OMIT_UNCLEAN_FILES" != 1 ]); then
"$CONTRIB"/make_packages.sh || fail "make_packages failed" "$CONTRIB"/make_packages.sh || fail "make_packages failed"
@@ -51,16 +50,32 @@ fi
# note: .zip sdists would not be reproducible due to https://bugs.python.org/issue40963 # note: .zip sdists would not be reproducible due to https://bugs.python.org/issue40963
if ([ "$OMIT_UNCLEAN_FILES" = 1 ]); then if ([ "$OMIT_UNCLEAN_FILES" = 1 ]); then
PY_DISTDIR="dist/_sourceonly" # The DISTDIR variable of this script is only used to find where the output is *finally* placed. PY_DISTDIR="$BUILDDIR/dist1/_sourceonly" # The DISTDIR variable of this script is only used to find where the output is *finally* placed.
else else
PY_DISTDIR="dist" PY_DISTDIR="$BUILDDIR/dist1"
fi fi
TZ=UTC faketime -f '2000-11-11 11:11:11' python3 setup.py --quiet sdist --format=gztar --dist-dir="$PY_DISTDIR" # build initial tar.gz
if ([ "$OMIT_UNCLEAN_FILES" = 1 ]); then python3 setup.py --quiet sdist --format=gztar --dist-dir="$PY_DISTDIR"
VERSION=$("$CONTRIB"/print_electrum_version.py) VERSION=$("$CONTRIB"/print_electrum_version.py)
mv "dist/_sourceonly/Electrum-$VERSION.tar.gz" "dist/Electrum-sourceonly-$VERSION.tar.gz" if ([ "$OMIT_UNCLEAN_FILES" = 1 ]); then
FINAL_DISTNAME="Electrum-sourceonly-$VERSION.tar.gz"
else
FINAL_DISTNAME="Electrum-$VERSION.tar.gz"
fi
if ([ "$OMIT_UNCLEAN_FILES" = 1 ]); then
mv "$PY_DISTDIR/Electrum-$VERSION.tar.gz" "$PY_DISTDIR/../$FINAL_DISTNAME"
rmdir "$PY_DISTDIR" rmdir "$PY_DISTDIR"
fi fi
# the initial tar.gz is not reproducible, see https://github.com/pypa/setuptools/issues/2133
# so we untar, fix timestamps, and then re-tar
mkdir -p "$BUILDDIR/dist2"
cd "$BUILDDIR/dist2"
tar -xzf "$BUILDDIR/dist1/$FINAL_DISTNAME"
find -exec touch -h -d '2000-11-11T11:11:11+00:00' {} +
GZIP=-n tar --sort=name -czf "$FINAL_DISTNAME" "Electrum-$VERSION/"
mv "$FINAL_DISTNAME" "$DISTDIR/$FINAL_DISTNAME"
) )

View File

@@ -8,7 +8,7 @@ contrib=$(dirname "$0")
# note: we should not use a higher version of python than what the binaries bundle # note: we should not use a higher version of python than what the binaries bundle
if [[ ! "$SYSTEM_PYTHON" ]] ; then if [[ ! "$SYSTEM_PYTHON" ]] ; then
SYSTEM_PYTHON=$(which python3.8) || printf "" SYSTEM_PYTHON=$(which python3.10) || printf ""
else else
SYSTEM_PYTHON=$(which $SYSTEM_PYTHON) || printf "" SYSTEM_PYTHON=$(which $SYSTEM_PYTHON) || printf ""
fi fi

View File

@@ -46,7 +46,7 @@ from .network import Network
from .util import (json_decode, to_bytes, to_string, profiler, standardize_path, constant_time_compare, InvalidPassword) from .util import (json_decode, to_bytes, to_string, profiler, standardize_path, constant_time_compare, InvalidPassword)
from .invoices import PR_PAID, PR_EXPIRED from .invoices import PR_PAID, PR_EXPIRED
from .util import log_exceptions, ignore_exceptions, randrange, OldTaskGroup, UserFacingException, JsonRPCError from .util import log_exceptions, ignore_exceptions, randrange, OldTaskGroup, UserFacingException, JsonRPCError
from .util import EventListener, event_listener, traceback_format_exception from .util import EventListener, event_listener
from .wallet import Wallet, Abstract_Wallet from .wallet import Wallet, Abstract_Wallet
from .storage import WalletStorage from .storage import WalletStorage
from .wallet_db import WalletDB, WalletRequiresSplit, WalletRequiresUpgrade, WalletUnfinished from .wallet_db import WalletDB, WalletRequiresSplit, WalletRequiresUpgrade, WalletUnfinished
@@ -264,7 +264,7 @@ class AuthenticatedServer(Logger):
'message': "internal error while executing RPC", 'message': "internal error while executing RPC",
'data': { 'data': {
"exception": repr(e), "exception": repr(e),
"traceback": "".join(traceback_format_exception(e)), "traceback": "".join(traceback.format_exception(e)),
}, },
} }
return web.json_response(response) return web.json_response(response)

View File

@@ -38,6 +38,7 @@ from concurrent import futures
import copy import copy
import functools import functools
from enum import IntEnum from enum import IntEnum
from contextlib import nullcontext
import aiorpcx import aiorpcx
from aiorpcx import ignore_after, NetAddress from aiorpcx import ignore_after, NetAddress
@@ -47,7 +48,7 @@ from . import util
from .util import (log_exceptions, ignore_exceptions, OldTaskGroup, from .util import (log_exceptions, ignore_exceptions, OldTaskGroup,
bfh, make_aiohttp_session, send_exception_to_crash_reporter, bfh, make_aiohttp_session, send_exception_to_crash_reporter,
is_hash256_str, is_non_negative_integer, MyEncoder, NetworkRetryManager, is_hash256_str, is_non_negative_integer, MyEncoder, NetworkRetryManager,
nullcontext, error_text_str_to_safe_str) error_text_str_to_safe_str)
from .bitcoin import COIN, DummyAddress, DummyAddressUsedInTxException from .bitcoin import COIN, DummyAddress, DummyAddressUsedInTxException
from . import constants from . import constants
from . import blockchain from . import blockchain

View File

@@ -127,10 +127,7 @@ class Plugins(DaemonThread):
# sys.modules needs to be modified for relative imports to work # sys.modules needs to be modified for relative imports to work
# see https://stackoverflow.com/a/50395128 # see https://stackoverflow.com/a/50395128
sys.modules[path] = module sys.modules[path] = module
if sys.version_info >= (3, 10):
spec.loader.exec_module(module) spec.loader.exec_module(module)
else:
module = spec.loader.load_module(path)
except Exception as e: except Exception as e:
raise Exception(f"Error pre-loading {path}: {repr(e)}") from e raise Exception(f"Error pre-loading {path}: {repr(e)}") from e
return module return module
@@ -209,12 +206,8 @@ class Plugins(DaemonThread):
if name in self.external_plugin_metadata: if name in self.external_plugin_metadata:
raise Exception(f"duplicate plugins for name={name}") raise Exception(f"duplicate plugins for name={name}")
module_path = f'electrum_external_plugins.{name}' module_path = f'electrum_external_plugins.{name}'
if sys.version_info >= (3, 10):
spec = zipfile.find_spec(name) spec = zipfile.find_spec(name)
module = self.exec_module_from_spec(spec, module_path) module = self.exec_module_from_spec(spec, module_path)
else:
module = zipfile.load_module(name)
sys.modules[module_path] = module
d = module.__dict__ d = module.__dict__
gui_good = self.gui_name in d.get('available_for', []) gui_good = self.gui_name in d.get('available_for', [])
if not gui_good: if not gui_good:

View File

@@ -52,6 +52,7 @@ from functools import partial
from abc import abstractmethod, ABC from abc import abstractmethod, ABC
import socket import socket
import enum import enum
from contextlib import nullcontext
import attr import attr
import aiohttp import aiohttp
@@ -2067,35 +2068,6 @@ def test_read_write_permissions(path) -> None:
raise IOError('echo sanity-check failed') raise IOError('echo sanity-check failed')
class nullcontext:
"""Context manager that does no additional processing.
This is a ~backport of contextlib.nullcontext from Python 3.10
"""
def __init__(self, enter_result=None):
self.enter_result = enter_result
def __enter__(self):
return self.enter_result
def __exit__(self, *excinfo):
pass
async def __aenter__(self):
return self.enter_result
async def __aexit__(self, *excinfo):
pass
def traceback_format_exception(exc: BaseException) -> Sequence[str]:
"""Compatibility wrapper for stdlib traceback.format_exception using python 3.10+ API."""
if sys.version_info[:3] >= (3, 10):
return traceback.format_exception(exc)
else:
return traceback.format_exception(type(exc), value=exc, tb=exc.__traceback__)
class classproperty(property): class classproperty(property):
"""~read-only class-level @property """~read-only class-level @property
from https://stackoverflow.com/a/13624858 by denis-ryzhkov from https://stackoverflow.com/a/13624858 by denis-ryzhkov

View File

@@ -27,7 +27,7 @@ import os
import sys import sys
MIN_PYTHON_VERSION = "3.8.0" # FIXME duplicated from setup.py MIN_PYTHON_VERSION = "3.10.0" # FIXME duplicated from setup.py
_min_python_version_tuple = tuple(map(int, (MIN_PYTHON_VERSION.split(".")))) _min_python_version_tuple = tuple(map(int, (MIN_PYTHON_VERSION.split("."))))

View File

@@ -12,7 +12,7 @@ import subprocess
from setuptools import setup, find_packages from setuptools import setup, find_packages
from setuptools.command.install import install from setuptools.command.install import install
MIN_PYTHON_VERSION = "3.8.0" MIN_PYTHON_VERSION = "3.10.0"
_min_python_version_tuple = tuple(map(int, (MIN_PYTHON_VERSION.split(".")))) _min_python_version_tuple = tuple(map(int, (MIN_PYTHON_VERSION.split("."))))

View File

@@ -6,8 +6,6 @@ from unittest import SkipTest
from PyQt6.QtCore import QCoreApplication, QMetaObject, Qt, pyqtSlot, QObject from PyQt6.QtCore import QCoreApplication, QMetaObject, Qt, pyqtSlot, QObject
from electrum.util import traceback_format_exception
class TestQCoreApplication(QCoreApplication): class TestQCoreApplication(QCoreApplication):
@pyqtSlot() @pyqtSlot()
@@ -78,7 +76,7 @@ def qt_test(func):
if not res: if not res:
self._e = Exception('testcase timed out') self._e = Exception('testcase timed out')
if self._e: if self._e:
print("".join(traceback_format_exception(self._e))) print("".join(traceback.format_exception(self._e)))
# deallocate stored exception from qt thread otherwise we SEGV garbage collector # deallocate stored exception from qt thread otherwise we SEGV garbage collector
# instead, re-create using the exception message, special casing AssertionError and SkipTest # instead, re-create using the exception message, special casing AssertionError and SkipTest
e = None e = None