Merge pull request #74 from YunoHost-Apps/install-python

Audo update `install_python.py` from `manageprojects` via tests if needed
This commit is contained in:
Jens Diemer 2024-08-30 07:30:07 +02:00 committed by GitHub
commit 2026bdb62a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 306 additions and 182 deletions

View file

@ -1,22 +1,16 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Setup Python Interpreter DocWrite: install_python.md # Install Python Interpreter
~~~~~~~~~~~~~~~~~~~~~~~~
This script downloads, builds and installs a Python interpreter, but: `install_python.py` downloads, builds and installs a Python interpreter, but:
- only if the required version is not already installed - **only** if the system Python is not the required major version
- only if the required version is not already built - **only** once (if the required major version is not already build and installed)
Download Python source code from official Python FTP server. Origin of this script is:
Download only over verified HTTPS connection. * https://github.com/jedie/manageprojects/blob/main/manageprojects/install_python.py
Verify the download with the GPG signature, if gpg is available.
Has a CLI interface e.g.: Licensed under GPL-3.0-or-later (Feel free to copy and use it in your project)
$ python install_python.py --help
Defaults to Python 3.11 and ~/.local/ as prefix.
""" """
from __future__ import annotations from __future__ import annotations
@ -35,26 +29,37 @@ import urllib.request
from pathlib import Path from pathlib import Path
"""DocWrite: install_python.md # Install Python Interpreter
Minimal needed Python version to run the script is: **v3.9**."""
assert sys.version_info >= (3, 9), f'Python version {sys.version_info} is too old!' assert sys.version_info >= (3, 9), f'Python version {sys.version_info} is too old!'
DEFAULT_MAJOR_VERSION = '3.11' DEFAULT_MAJOR_VERSION = '3.11'
"""DocWrite: install_python.md # Install Python Interpreter
Download Python source code from official Python FTP server:
DocWriteMacro: manageprojects.tests.docwrite_macros.ftp_url"""
PY_FTP_INDEX_URL = 'https://www.python.org/ftp/python/' PY_FTP_INDEX_URL = 'https://www.python.org/ftp/python/'
"""DocWrite: install_python.md ## Supported Python Versions
The following major Python versions are supported and verified with GPG keys:
DocWriteMacro: manageprojects.tests.docwrite_macros.supported_python_versions
The GPG keys taken from the official Python download page: https://www.python.org/downloads/"""
GPG_KEY_IDS = { GPG_KEY_IDS = {
# from: https://www.python.org/downloads/ # Thomas Wouters (3.12.x and 3.13.x source files and tags):
'3.13': 'A821E680E5FA6305',
'3.12': 'A821E680E5FA6305',
# #
# Thomas Wouters (3.12.x and 3.13.x source files and tags) (key id: A821E680E5FA6305): # Pablo Galindo Salgado (3.10.x and 3.11.x source files and tags):
'3.13': '7169605F62C751356D054A26A821E680E5FA6305', '3.11': '64E628F8D684696D',
'3.12': '7169605F62C751356D054A26A821E680E5FA6305', '3.10': '64E628F8D684696D',
#
# Pablo Galindo Salgado (3.10.x and 3.11.x source files and tags) (key id: 64E628F8D684696D):
'3.11': 'A035C8C19219BA821ECEA86B64E628F8D684696D',
'3.10': 'A035C8C19219BA821ECEA86B64E628F8D684696D',
} }
GPG_KEY_SERVER = 'hkps://keys.openpgp.org'
# https://docs.python.org/3/using/configure.html#cmdoption-prefix """DocWrite: install_python.md ## Workflow - 3. Check local installed Python
We assume that the `make altinstall` will install local Python interpreter into:
DocWriteMacro: manageprojects.tests.docwrite_macros.default_install_prefix
See: https://docs.python.org/3/using/configure.html#cmdoption-prefix"""
DEFAULT_INSTALL_PREFIX = '/usr/local' DEFAULT_INSTALL_PREFIX = '/usr/local'
TEMP_PREFIX = 'setup_python_' TEMP_PREFIX = 'setup_python_'
@ -81,15 +86,15 @@ class TemporaryDirectory:
def fetch(url: str) -> bytes: def fetch(url: str) -> bytes:
with urllib.request.urlopen( """DocWrite: install_python.md # Install Python Interpreter
url=url, Download only over verified HTTPS connection."""
context=ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH), context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH)
) as response: with urllib.request.urlopen(url=url, context=context) as response:
return response.read() return response.read()
def get_html_page(url) -> str: def get_html_page(url) -> str:
logger.debug(f'Getting HTML page from {url}') logger.debug('Getting HTML page from %s', url)
html = fetch(url).decode('utf-8') html = fetch(url).decode('utf-8')
assert html, 'Failed to get Python FTP index page' assert html, 'Failed to get Python FTP index page'
return html return html
@ -97,62 +102,73 @@ def get_html_page(url) -> str:
def extract_versions(*, html, major_version) -> list[str]: def extract_versions(*, html, major_version) -> list[str]:
pattern = rf'href="({re.escape(major_version)}\.[0-9]+)' pattern = rf'href="({re.escape(major_version)}\.[0-9]+)'
logger.debug(f'Extracting versions with pattern: {pattern}') logger.debug('Extracting versions with pattern: %s', pattern)
versions = re.findall(pattern, html) versions = re.findall(pattern, html)
versions.sort(reverse=True) versions.sort(reverse=True)
logger.debug(f'Extracted versions: {versions}') logger.debug('Extracted versions: %s', versions)
return versions return versions
def get_latest_versions(*, html, major_version) -> str: def get_latest_versions(*, html, major_version) -> str:
latest_versions = extract_versions(html=html, major_version=major_version)[0] latest_versions = extract_versions(html=html, major_version=major_version)[0]
logger.info(f'Latest version of Python {major_version}: {latest_versions}') logger.info('Latest version of Python %s: %s', major_version, latest_versions)
return latest_versions return latest_versions
def run(args, **kwargs): def run(args, **kwargs):
logger.debug(f'Running: {shlex.join(str(arg) for arg in args)} ({kwargs=})') logger.debug('Running: %s (%s)', shlex.join(str(arg) for arg in args), kwargs)
return subprocess.run(args, **kwargs) return subprocess.run(args, **kwargs)
def run_build_step(args, *, step: str, cwd: Path) -> None: def run_build_step(args, *, step: str, cwd: Path) -> None:
with tempfile.NamedTemporaryFile(prefix=f'{TEMP_PREFIX}_{step}_', suffix='.txt', delete=False) as temp_file: with tempfile.NamedTemporaryFile(prefix=f'{TEMP_PREFIX}_{step}_', suffix='.txt', delete=False) as temp_file:
logger.info(f'Running: {shlex.join(str(arg) for arg in args)}... Output in {temp_file.name}') logger.info('Running: %s... Output in %s', shlex.join(str(arg) for arg in args), temp_file.name)
try: try:
subprocess.run(args, stdout=temp_file, stderr=temp_file, check=True, cwd=cwd) subprocess.run(args, stdout=temp_file, stderr=temp_file, check=True, cwd=cwd)
except subprocess.SubprocessError as err: except subprocess.SubprocessError as err:
logger.error(f'Failed to run {step} step: {err}') logger.error('Failed to run %s step: %s', step, err)
run(['tail', temp_file.name]) run(['tail', temp_file.name])
raise raise
def get_python_version(python_bin) -> str: def get_python_version(python_bin: str | Path) -> str | None:
logger.debug(f'Check {python_bin} version') logger.debug('Check %s version', python_bin)
full_version = run([python_bin, '--version'], capture_output=True, text=True).stdout.split()[1] if output := run([python_bin, '-V'], capture_output=True, text=True).stdout.split():
logger.info(f'{python_bin} version: {full_version}') full_version = output[-1]
logger.info('Version of "%s" is: %r', python_bin, full_version)
return full_version return full_version
def download2temp(*, temp_path: Path, base_url: str, filename: str) -> Path: def download2temp(*, temp_path: Path, base_url: str, filename: str) -> Path:
url = f'{base_url}/{filename}' url = f'{base_url}/{filename}'
dst_path = temp_path / filename dst_path = temp_path / filename
logger.info(f'Downloading {url} into {dst_path}...') logger.info('Downloading %s into %s...', url, dst_path)
dst_path.write_bytes(fetch(url)) dst_path.write_bytes(fetch(url))
logger.info(f'Downloaded {filename} is {dst_path.stat().st_size} Bytes') logger.info('Downloaded %s is %d Bytes', filename, dst_path.stat().st_size)
return dst_path return dst_path
def verify_download(*, major_version: str, tar_file_path: Path, asc_file_path: Path): def verify_download(*, major_version: str, tar_file_path: Path, asc_file_path: Path, delete_temp: bool):
"""DocWrite: install_python.md ## Workflow - 5. Verify download
The sha256 hash downloaded tar archive will logged.
If `gpg` is available, the signature will be verified.
"""
hash_obj = hashlib.sha256(tar_file_path.read_bytes()) hash_obj = hashlib.sha256(tar_file_path.read_bytes())
logger.info(f'Downloaded sha256: {hash_obj.hexdigest()}') logger.info('Downloaded sha256: %s', hash_obj.hexdigest())
"""DocWrite: install_python.md # Install Python Interpreter
The Downloaded tar archive will be verified with the GPG signature, if `gpg` is available."""
if gpg_bin := shutil.which('gpg'): if gpg_bin := shutil.which('gpg'):
logger.debug(f'Verifying signature with {gpg_bin}...') logger.debug('Verifying signature with %s...', gpg_bin)
assert major_version in GPG_KEY_IDS, f'No GPG key ID for Python {major_version}' assert major_version in GPG_KEY_IDS, f'No GPG key ID for Python {major_version}'
gpg_key_id = GPG_KEY_IDS[major_version] gpg_key_id = GPG_KEY_IDS[major_version]
run([gpg_bin, '--keyserver', 'hkps://keys.openpgp.org', '--recv-keys', gpg_key_id], check=True) with TemporaryDirectory(prefix='install-python-gpg-', delete=delete_temp) as temp_path:
run([gpg_bin, '--verify', asc_file_path, tar_file_path], check=True) """DocWrite: install_python.md ## Workflow - 5. Verify download
run(['gpgconf', '--kill', 'all'], check=True) We set the `GNUPGHOME` environment variable to a temporary directory."""
env = {'GNUPGHOME': str(temp_path)}
run([gpg_bin, '--keyserver', GPG_KEY_SERVER, '--recv-keys', gpg_key_id], check=True, env=env)
run([gpg_bin, '--verify', asc_file_path, tar_file_path], check=True, env=env)
run(['gpgconf', '--kill', 'all'], check=True, env=env)
else: else:
logger.warning('No GPG verification possible! (gpg not found)') logger.warning('No GPG verification possible! (gpg not found)')
@ -163,17 +179,26 @@ def install_python(
write_check: bool = True, write_check: bool = True,
delete_temp: bool = True, delete_temp: bool = True,
) -> Path: ) -> Path:
logger.info(f'Installing Python {major_version} interpreter.') logger.info('Requested major Python version: %s', major_version)
# Check system Python version """DocWrite: install_python.md ## Workflow
The setup process is as follows:"""
"""DocWrite: install_python.md ## Workflow - 1. Check system Python
If the system Python is the same major version as the required Python, we skip the installation."""
for try_version in (major_version, '3'): for try_version in (major_version, '3'):
filename = f'python{try_version}' filename = f'python{try_version}'
logger.debug(f'Checking {filename}...') logger.debug('Checking %s...', filename)
if python3bin := shutil.which(filename): if python3bin := shutil.which(filename):
if get_python_version(python3bin).startswith(major_version): if (full_version := get_python_version(python3bin)) and full_version.startswith(major_version):
logger.info('Python version already installed') logger.info('Python version already installed: Return path %r of it.', python3bin)
"""DocWrite: install_python.md ## Workflow - 1. Check system Python
The script just returns the path to the system Python interpreter."""
return Path(python3bin) return Path(python3bin)
"""DocWrite: install_python.md ## Workflow - 2. Get latest Python release
We fetch the latest Python release from the Python FTP server, from:
DocWriteMacro: manageprojects.tests.docwrite_macros.ftp_url"""
# Get latest full version number of Python from Python FTP: # Get latest full version number of Python from Python FTP:
py_required_version = get_latest_versions( py_required_version = get_latest_versions(
html=get_html_page(PY_FTP_INDEX_URL), html=get_html_page(PY_FTP_INDEX_URL),
@ -182,19 +207,27 @@ def install_python(
local_bin_path = Path(DEFAULT_INSTALL_PREFIX) / 'bin' local_bin_path = Path(DEFAULT_INSTALL_PREFIX) / 'bin'
# Check existing built version of Python in /usr/local/bin """DocWrite: install_python.md ## Workflow - 3. Check local installed Python
The script checks if the latest release already build and installed."""
local_python_path = local_bin_path / f'python{major_version}' local_python_path = local_bin_path / f'python{major_version}'
if local_python_path.exists() and get_python_version(local_python_path) == py_required_version: if local_python_path.exists() and get_python_version(local_python_path) == py_required_version:
logger.info('Local Python is up to date') logger.info('Local Python is up-to-date')
"""DocWrite: install_python.md ## Workflow - 3. Check local installed Python
If the local Python is up-to-date, the script exist and returns the path this local interpreter."""
return local_python_path return local_python_path
# Before we start building Python, check if we have write permissions: """DocWrite: install_python.md ## Workflow - 4. Download Python sources
Before we start building Python, check if we have write permissions.
The check can be skipped via CLI argument."""
if write_check and not os.access(local_bin_path, os.W_OK): if write_check and not os.access(local_bin_path, os.W_OK):
raise PermissionError(f'No write permission to {local_bin_path} (Hint: Call with "sudo" ?!)') raise PermissionError(f'No write permission to {local_bin_path} (Hint: Call with "sudo" ?!)')
# Download, build and Setup Python """DocWrite: install_python.md ## Workflow - 4. Download Python sources
The download will be done in a temporary directory. The directory will be deleted after the installation.
This can be skipped via CLI argument. The directory will be prefixed with:
DocWriteMacro: manageprojects.tests.docwrite_macros.temp_prefix"""
with TemporaryDirectory(prefix=TEMP_PREFIX, delete=delete_temp) as temp_path: with TemporaryDirectory(prefix=TEMP_PREFIX, delete=delete_temp) as temp_path:
base_url = f'https://www.python.org/ftp/python/{py_required_version}' base_url = f'{PY_FTP_INDEX_URL}{py_required_version}'
tar_filename = f'Python-{py_required_version}.tar.xz' tar_filename = f'Python-{py_required_version}.tar.xz'
asc_filename = f'{tar_filename}.asc' asc_filename = f'{tar_filename}.asc'
@ -212,15 +245,18 @@ def install_python(
major_version=major_version, major_version=major_version,
tar_file_path=tar_file_path, tar_file_path=tar_file_path,
asc_file_path=asc_file_path, asc_file_path=asc_file_path,
delete_temp=delete_temp,
) )
tar_bin = shutil.which('tar') tar_bin = shutil.which('tar')
logger.debug(f'Extracting {tar_file_path} with ...') logger.debug('Extracting %s with ...', tar_file_path)
run([tar_bin, 'xf', tar_file_path], check=True, cwd=temp_path) run([tar_bin, 'xf', tar_file_path], check=True, cwd=temp_path)
extracted_dir = temp_path / f'Python-{py_required_version}' extracted_dir = temp_path / f'Python-{py_required_version}'
logger.info(f'Building Python {py_required_version} (may take a while)...') logger.info('Building Python %s (may take a while)...', py_required_version)
"""DocWrite: install_python.md ## Workflow - 6. Build and install Python
If the verify passed, the script will start the build process."""
run_build_step( run_build_step(
['./configure', '--enable-optimizations'], ['./configure', '--enable-optimizations'],
step='configure', step='configure',
@ -231,13 +267,16 @@ def install_python(
step='make', step='make',
cwd=extracted_dir, cwd=extracted_dir,
) )
"""DocWrite: install_python.md ## Workflow - 6. Build and install Python
The installation will be done with `make altinstall`."""
run_build_step( run_build_step(
['make', 'altinstall'], ['make', 'altinstall'],
step='install', step='install',
cwd=extracted_dir, cwd=extracted_dir,
) )
logger.info(f'Python {py_required_version} installed to {local_python_path}') logger.info('Python %s installed to %s', py_required_version, local_python_path)
local_python_version = get_python_version(local_python_path) local_python_version = get_python_version(local_python_path)
assert local_python_version == py_required_version, f'{local_python_version} is not {py_required_version}' assert local_python_version == py_required_version, f'{local_python_version} is not {py_required_version}'
@ -245,11 +284,21 @@ def install_python(
return local_python_path return local_python_path
def main() -> Path: def get_parser() -> argparse.ArgumentParser:
"""
DocWrite: install_python.md ## CLI
The CLI interface looks like e.g.:
```shell
$ python3 install_python.py --help
DocWriteMacro: manageprojects.tests.docwrite_macros.help
```
"""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Setup Python Interpreter', description='Install Python Interpreter',
formatter_class=argparse.ArgumentDefaultsHelpFormatter, formatter_class=argparse.ArgumentDefaultsHelpFormatter,
) )
parser.add_argument( parser.add_argument(
'major_version', 'major_version',
nargs=argparse.OPTIONAL, nargs=argparse.OPTIONAL,
@ -267,13 +316,18 @@ def main() -> Path:
parser.add_argument( parser.add_argument(
'--skip-temp-deletion', '--skip-temp-deletion',
action='store_true', action='store_true',
help='Skip deletion of temporary files created during build steps', help='Skip deletion of temporary files',
) )
parser.add_argument( parser.add_argument(
'--skip-write-check', '--skip-write-check',
action='store_true', action='store_true',
help='Skip the test for write permission to /usr/local/bin', help='Skip the test for write permission to /usr/local/bin',
) )
return parser
def main() -> Path:
parser = get_parser()
args = parser.parse_args() args = parser.parse_args()
verbose2level = {0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG} verbose2level = {0: logging.WARNING, 1: logging.INFO, 2: logging.DEBUG}
logging.basicConfig( logging.basicConfig(
@ -281,7 +335,7 @@ def main() -> Path:
format='%(levelname)9s %(message)s', format='%(levelname)9s %(message)s',
stream=sys.stderr, stream=sys.stderr,
) )
logger.debug(f'Arguments: {args}') logger.debug('Arguments: %s', args)
return install_python( return install_python(
major_version=args.major_version, major_version=args.major_version,
write_check=not args.skip_write_check, write_check=not args.skip_write_check,
@ -291,4 +345,10 @@ def main() -> Path:
if __name__ == '__main__': if __name__ == '__main__':
python_path = main() python_path = main()
"""DocWrite: install_python.md ## Workflow - 7. print the path
If no errors occurred, the path to the Python interpreter will be printed to `stdout`.
So it's usable in shell scripts, like:
DocWriteMacro: manageprojects.tests.docwrite_macros.example_shell_script
"""
print(python_path) print(python_path)

View file

@ -41,9 +41,9 @@ bx-py-utils==98 \
# cli-base-utilities # cli-base-utilities
# django-for-runners # django-for-runners
# django-tools # django-tools
cattrs==23.2.3 \ cattrs==24.1.0 \
--hash=sha256:0341994d94971052e9ee70662542699a3162ea1e0c62f7ce1b4a57f563685108 \ --hash=sha256:043bb8af72596432a7df63abcff0055ac0f198a4d2e95af8db5a936a7074a761 \
--hash=sha256:a934090d95abaa9e911dac357e3a8699e0b4b14f8529bcc7d2b1ad9d51672b9f --hash=sha256:8274f18b253bf7674a43da851e3096370d67088165d23138b04a1c04c8eaf48e
# via requests-cache # via requests-cache
certifi==2024.7.4 \ certifi==2024.7.4 \
--hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \ --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \
@ -158,51 +158,72 @@ colorlog==6.8.2 \
# django-for-runners # django-for-runners
# django-tools # django-tools
# django-yunohost-integration # django-yunohost-integration
contourpy==1.2.1 \ contourpy==1.3.0 \
--hash=sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2 \ --hash=sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0 \
--hash=sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9 \ --hash=sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639 \
--hash=sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9 \ --hash=sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd \
--hash=sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4 \ --hash=sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad \
--hash=sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce \ --hash=sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843 \
--hash=sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7 \ --hash=sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8 \
--hash=sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f \ --hash=sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4 \
--hash=sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922 \ --hash=sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1 \
--hash=sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4 \ --hash=sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294 \
--hash=sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e \ --hash=sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84 \
--hash=sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b \ --hash=sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927 \
--hash=sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619 \ --hash=sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8 \
--hash=sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205 \ --hash=sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09 \
--hash=sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480 \ --hash=sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7 \
--hash=sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965 \ --hash=sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f \
--hash=sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c \ --hash=sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab \
--hash=sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd \ --hash=sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b \
--hash=sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5 \ --hash=sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3 \
--hash=sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f \ --hash=sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223 \
--hash=sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc \ --hash=sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973 \
--hash=sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec \ --hash=sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087 \
--hash=sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd \ --hash=sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081 \
--hash=sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b \ --hash=sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc \
--hash=sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9 \ --hash=sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18 \
--hash=sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe \ --hash=sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f \
--hash=sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce \ --hash=sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d \
--hash=sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609 \ --hash=sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2 \
--hash=sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8 \ --hash=sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41 \
--hash=sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0 \ --hash=sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67 \
--hash=sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f \ --hash=sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6 \
--hash=sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8 \ --hash=sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b \
--hash=sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b \ --hash=sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2 \
--hash=sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364 \ --hash=sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c \
--hash=sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040 \ --hash=sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42 \
--hash=sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f \ --hash=sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d \
--hash=sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083 \ --hash=sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4 \
--hash=sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df \ --hash=sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5 \
--hash=sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba \ --hash=sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49 \
--hash=sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445 \ --hash=sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b \
--hash=sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da \ --hash=sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7 \
--hash=sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3 \ --hash=sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102 \
--hash=sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72 \ --hash=sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb \
--hash=sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02 \ --hash=sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7 \
--hash=sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985 --hash=sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e \
--hash=sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c \
--hash=sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8 \
--hash=sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35 \
--hash=sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b \
--hash=sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14 \
--hash=sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb \
--hash=sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589 \
--hash=sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c \
--hash=sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0 \
--hash=sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da \
--hash=sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800 \
--hash=sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6 \
--hash=sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66 \
--hash=sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca \
--hash=sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb \
--hash=sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c \
--hash=sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06 \
--hash=sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779 \
--hash=sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8 \
--hash=sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f \
--hash=sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c
# via matplotlib # via matplotlib
cycler==0.12.1 \ cycler==0.12.1 \
--hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \ --hash=sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 \
@ -860,9 +881,9 @@ pygments==2.18.0 \
--hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \
--hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a
# via rich # via rich
pyparsing==3.1.3 \ pyparsing==3.1.4 \
--hash=sha256:1e80fdf93e6c1aeaf4702523f1d48f66d52fa6459096a8f812591157270a5896 \ --hash=sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c \
--hash=sha256:5d549d2a1b5e1c3e952bb55ea247bfb5ad427ea307566a350db2b3c34d4ce181 --hash=sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032
# via matplotlib # via matplotlib
python-dateutil==2.9.0.post0 \ python-dateutil==2.9.0.post0 \
--hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \
@ -952,9 +973,9 @@ retry-requests==2.0.0 \
--hash=sha256:38e8e3f55051e7b7915c1768884269097865a5da2ea87d5dcafd6ba9498c363f \ --hash=sha256:38e8e3f55051e7b7915c1768884269097865a5da2ea87d5dcafd6ba9498c363f \
--hash=sha256:3d02135e5aafedf09240414182fc7389c5d2b4de0252daba0054c9d6a27e7639 --hash=sha256:3d02135e5aafedf09240414182fc7389c5d2b4de0252daba0054c9d6a27e7639
# via django-for-runners # via django-for-runners
rich==13.7.1 \ rich==13.8.0 \
--hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \ --hash=sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc \
--hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432 --hash=sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4
# via # via
# cli-base-utilities # cli-base-utilities
# django-rich # django-rich

View file

@ -0,0 +1,17 @@
"""
https://github.com/jedie/manageprojects/blob/main/docs/install_python.md#include-in-own-projects
"""
from django_yunohost_integration.path_utils import get_project_root
from manageprojects.utilities.include_install_python import IncludeInstallPythonBaseTestCase
class IncludeInstallPythonTestCase(IncludeInstallPythonBaseTestCase):
"""
Updates `conf/install_python.py` from `manageprojects` if needed.
"""
DESTINATION_PATH = get_project_root() / 'conf' / 'install_python.py'
def test_install_python_is_up2date(self):
self.auto_update_install_python()

View file

@ -21,7 +21,7 @@ dependencies = [
dev = [ dev = [
"bx_django_utils", # https://github.com/boxine/bx_django_utils "bx_django_utils", # https://github.com/boxine/bx_django_utils
"beautifulsoup4", # https://pypi.org/project/beautifulsoup4/ "beautifulsoup4", # https://pypi.org/project/beautifulsoup4/
"manageprojects", # https://github.com/jedie/manageprojects "manageprojects>=0.18.0", # https://github.com/jedie/manageprojects
"pip-tools", # https://github.com/jazzband/pip-tools/ "pip-tools", # https://github.com/jazzband/pip-tools/
"tblib", # https://github.com/ionelmc/python-tblib "tblib", # https://github.com/ionelmc/python-tblib
"tox", # https://github.com/tox-dev/tox "tox", # https://github.com/tox-dev/tox

View file

@ -115,9 +115,9 @@ cachetools==5.5.0 \
--hash=sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292 \ --hash=sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292 \
--hash=sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a --hash=sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a
# via tox # via tox
cattrs==23.2.3 \ cattrs==24.1.0 \
--hash=sha256:0341994d94971052e9ee70662542699a3162ea1e0c62f7ce1b4a57f563685108 \ --hash=sha256:043bb8af72596432a7df63abcff0055ac0f198a4d2e95af8db5a936a7074a761 \
--hash=sha256:a934090d95abaa9e911dac357e3a8699e0b4b14f8529bcc7d2b1ad9d51672b9f --hash=sha256:8274f18b253bf7674a43da851e3096370d67088165d23138b04a1c04c8eaf48e
# via requests-cache # via requests-cache
certifi==2024.7.4 \ certifi==2024.7.4 \
--hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \ --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \
@ -323,51 +323,72 @@ colorlog==6.8.2 \
# django-for-runners # django-for-runners
# django-tools # django-tools
# django-yunohost-integration # django-yunohost-integration
contourpy==1.2.1 \ contourpy==1.3.0 \
--hash=sha256:00e5388f71c1a0610e6fe56b5c44ab7ba14165cdd6d695429c5cd94021e390b2 \ --hash=sha256:00ccd0dbaad6d804ab259820fa7cb0b8036bda0686ef844d24125d8287178ce0 \
--hash=sha256:10a37ae557aabf2509c79715cd20b62e4c7c28b8cd62dd7d99e5ed3ce28c3fd9 \ --hash=sha256:0be4d8425bfa755e0fd76ee1e019636ccc7c29f77a7c86b4328a9eb6a26d0639 \
--hash=sha256:11959f0ce4a6f7b76ec578576a0b61a28bdc0696194b6347ba3f1c53827178b9 \ --hash=sha256:0dce35502151b6bd35027ac39ba6e5a44be13a68f55735c3612c568cac3805fd \
--hash=sha256:187fa1d4c6acc06adb0fae5544c59898ad781409e61a926ac7e84b8f276dcef4 \ --hash=sha256:0fa4c02abe6c446ba70d96ece336e621efa4aecae43eaa9b030ae5fb92b309ad \
--hash=sha256:1a07fc092a4088ee952ddae19a2b2a85757b923217b7eed584fdf25f53a6e7ce \ --hash=sha256:14e262f67bd7e6eb6880bc564dcda30b15e351a594657e55b7eec94b6ef72843 \
--hash=sha256:1cac0a8f71a041aa587410424ad46dfa6a11f6149ceb219ce7dd48f6b02b87a7 \ --hash=sha256:167d6c890815e1dac9536dca00828b445d5d0df4d6a8c6adb4a7ec3166812fa8 \
--hash=sha256:1d59e739ab0e3520e62a26c60707cc3ab0365d2f8fecea74bfe4de72dc56388f \ --hash=sha256:1ec4dc6bf570f5b22ed0d7efba0dfa9c5b9e0431aeea7581aa217542d9e809a4 \
--hash=sha256:2855c8b0b55958265e8b5888d6a615ba02883b225f2227461aa9127c578a4922 \ --hash=sha256:303c252947ab4b14c08afeb52375b26781ccd6a5ccd81abcdfc1fafd14cf93c1 \
--hash=sha256:2e785e0f2ef0d567099b9ff92cbfb958d71c2d5b9259981cd9bee81bd194c9a4 \ --hash=sha256:31cd3a85dbdf1fc002280c65caa7e2b5f65e4a973fcdf70dd2fdcb9868069294 \
--hash=sha256:309be79c0a354afff9ff7da4aaed7c3257e77edf6c1b448a779329431ee79d7e \ --hash=sha256:32b238b3b3b649e09ce9aaf51f0c261d38644bdfa35cbaf7b263457850957a84 \
--hash=sha256:39f3ecaf76cd98e802f094e0d4fbc6dc9c45a8d0c4d185f0f6c2234e14e5f75b \ --hash=sha256:33c92cdae89ec5135d036e7218e69b0bb2851206077251f04a6c4e0e21f03927 \
--hash=sha256:457499c79fa84593f22454bbd27670227874cd2ff5d6c84e60575c8b50a69619 \ --hash=sha256:345af746d7766821d05d72cb8f3845dfd08dd137101a2cb9b24de277d716def8 \
--hash=sha256:49e70d111fee47284d9dd867c9bb9a7058a3c617274900780c43e38d90fe1205 \ --hash=sha256:3634b5385c6716c258d0419c46d05c8aa7dc8cb70326c9a4fb66b69ad2b52e09 \
--hash=sha256:4c75507d0a55378240f781599c30e7776674dbaf883a46d1c90f37e563453480 \ --hash=sha256:364174c2a76057feef647c802652f00953b575723062560498dc7930fc9b1cb7 \
--hash=sha256:4c863140fafc615c14a4bf4efd0f4425c02230eb8ef02784c9a156461e62c965 \ --hash=sha256:36e0cff201bcb17a0a8ecc7f454fe078437fa6bda730e695a92f2d9932bd507f \
--hash=sha256:4d8908b3bee1c889e547867ca4cdc54e5ab6be6d3e078556814a22457f49423c \ --hash=sha256:36f965570cff02b874773c49bfe85562b47030805d7d8360748f3eca570f4cab \
--hash=sha256:5b9eb0ca724a241683c9685a484da9d35c872fd42756574a7cfbf58af26677fd \ --hash=sha256:3bb3808858a9dc68f6f03d319acd5f1b8a337e6cdda197f02f4b8ff67ad2057b \
--hash=sha256:6022cecf8f44e36af10bd9118ca71f371078b4c168b6e0fab43d4a889985dbb5 \ --hash=sha256:3e1c7fa44aaae40a2247e2e8e0627f4bea3dd257014764aa644f319a5f8600e3 \
--hash=sha256:6150ffa5c767bc6332df27157d95442c379b7dce3a38dff89c0f39b63275696f \ --hash=sha256:3faeb2998e4fcb256542e8a926d08da08977f7f5e62cf733f3c211c2a5586223 \
--hash=sha256:62828cada4a2b850dbef89c81f5a33741898b305db244904de418cc957ff05dc \ --hash=sha256:420d39daa61aab1221567b42eecb01112908b2cab7f1b4106a52caaec8d36973 \
--hash=sha256:7b4182299f251060996af5249c286bae9361fa8c6a9cda5efc29fe8bfd6062ec \ --hash=sha256:4553c421929ec95fb07b3aaca0fae668b2eb5a5203d1217ca7c34c063c53d087 \
--hash=sha256:94b34f32646ca0414237168d68a9157cb3889f06b096612afdd296003fdd32fd \ --hash=sha256:4865cd1d419e0c7a7bf6de1777b185eebdc51470800a9f42b9e9decf17762081 \
--hash=sha256:9ce6889abac9a42afd07a562c2d6d4b2b7134f83f18571d859b25624a331c90b \ --hash=sha256:4cfb5c62ce023dfc410d6059c936dcf96442ba40814aefbfa575425a3a7f19dc \
--hash=sha256:9cffe0f850e89d7c0012a1fb8730f75edd4320a0a731ed0c183904fe6ecfc3a9 \ --hash=sha256:4d63ee447261e963af02642ffcb864e5a2ee4cbfd78080657a9880b8b1868e18 \
--hash=sha256:a12a813949e5066148712a0626895c26b2578874e4cc63160bb007e6df3436fe \ --hash=sha256:570ef7cf892f0afbe5b2ee410c507ce12e15a5fa91017a0009f79f7d93a1268f \
--hash=sha256:a1eea9aecf761c661d096d39ed9026574de8adb2ae1c5bd7b33558af884fb2ce \ --hash=sha256:637f674226be46f6ba372fd29d9523dd977a291f66ab2a74fbeb5530bb3f445d \
--hash=sha256:a31f94983fecbac95e58388210427d68cd30fe8a36927980fab9c20062645609 \ --hash=sha256:68a32389b06b82c2fdd68276148d7b9275b5f5cf13e5417e4252f6d1a34f72a2 \
--hash=sha256:ac58bdee53cbeba2ecad824fa8159493f0bf3b8ea4e93feb06c9a465d6c87da8 \ --hash=sha256:69375194457ad0fad3a839b9e29aa0b0ed53bb54db1bfb6c3ae43d111c31ce41 \
--hash=sha256:af3f4485884750dddd9c25cb7e3915d83c2db92488b38ccb77dd594eac84c4a0 \ --hash=sha256:6cb6cc968059db9c62cb35fbf70248f40994dfcd7aa10444bbf8b3faeb7c2d67 \
--hash=sha256:b33d2bc4f69caedcd0a275329eb2198f560b325605810895627be5d4b876bf7f \ --hash=sha256:710a26b3dc80c0e4febf04555de66f5fd17e9cf7170a7b08000601a10570bda6 \
--hash=sha256:b59c0ffceff8d4d3996a45f2bb6f4c207f94684a96bf3d9728dbb77428dd8cb8 \ --hash=sha256:732896af21716b29ab3e988d4ce14bc5133733b85956316fb0c56355f398099b \
--hash=sha256:bb6834cbd983b19f06908b45bfc2dad6ac9479ae04abe923a275b5f48f1a186b \ --hash=sha256:75ee7cb1a14c617f34a51d11fa7524173e56551646828353c4af859c56b766e2 \
--hash=sha256:bd3db01f59fdcbce5b22afad19e390260d6d0222f35a1023d9adc5690a889364 \ --hash=sha256:76a896b2f195b57db25d6b44e7e03f221d32fe318d03ede41f8b4d9ba1bff53c \
--hash=sha256:bd7c23df857d488f418439686d3b10ae2fbf9bc256cd045b37a8c16575ea1040 \ --hash=sha256:76c905ef940a4474a6289c71d53122a4f77766eef23c03cd57016ce19d0f7b42 \
--hash=sha256:c2528d60e398c7c4c799d56f907664673a807635b857df18f7ae64d3e6ce2d9f \ --hash=sha256:7a52040312b1a858b5e31ef28c2e865376a386c60c0e248370bbea2d3f3b760d \
--hash=sha256:d31a63bc6e6d87f77d71e1abbd7387ab817a66733734883d1fc0021ed9bfa083 \ --hash=sha256:7ffa0db17717a8ffb127efd0c95a4362d996b892c2904db72428d5b52e1938a4 \
--hash=sha256:d4492d82b3bc7fbb7e3610747b159869468079fe149ec5c4d771fa1f614a14df \ --hash=sha256:81cb5ed4952aae6014bc9d0421dec7c5835c9c8c31cdf51910b708f548cf58e5 \
--hash=sha256:ddcb8581510311e13421b1f544403c16e901c4e8f09083c881fab2be80ee31ba \ --hash=sha256:834e0cfe17ba12f79963861e0f908556b2cedd52e1f75e6578801febcc6a9f49 \
--hash=sha256:e1d59258c3c67c865435d8fbeb35f8c59b8bef3d6f46c1f29f6123556af28445 \ --hash=sha256:87ddffef1dbe5e669b5c2440b643d3fdd8622a348fe1983fad7a0f0ccb1cd67b \
--hash=sha256:eb3315a8a236ee19b6df481fc5f997436e8ade24a9f03dfdc6bd490fea20c6da \ --hash=sha256:880ea32e5c774634f9fcd46504bf9f080a41ad855f4fef54f5380f5133d343c7 \
--hash=sha256:ef2b055471c0eb466033760a521efb9d8a32b99ab907fc8358481a1dd29e3bd3 \ --hash=sha256:8ca947601224119117f7c19c9cdf6b3ab54c5726ef1d906aa4a69dfb6dd58102 \
--hash=sha256:ef5adb9a3b1d0c645ff694f9bca7702ec2c70f4d734f9922ea34de02294fdf72 \ --hash=sha256:90f73a5116ad1ba7174341ef3ea5c3150ddf20b024b98fb0c3b29034752c8aeb \
--hash=sha256:f32c38afb74bd98ce26de7cc74a67b40afb7b05aae7b42924ea990d51e4dac02 \ --hash=sha256:92f8557cbb07415a4d6fa191f20fd9d2d9eb9c0b61d1b2f52a8926e43c6e9af7 \
--hash=sha256:fe0ccca550bb8e5abc22f530ec0466136379c01321fd94f30a22231e8a48d985 --hash=sha256:94e848a6b83da10898cbf1311a815f770acc9b6a3f2d646f330d57eb4e87592e \
--hash=sha256:9c0da700bf58f6e0b65312d0a5e695179a71d0163957fa381bb3c1f72972537c \
--hash=sha256:a11077e395f67ffc2c44ec2418cfebed032cd6da3022a94fc227b6faf8e2acb8 \
--hash=sha256:aea348f053c645100612b333adc5983d87be69acdc6d77d3169c090d3b01dc35 \
--hash=sha256:b11b39aea6be6764f84360fce6c82211a9db32a7c7de8fa6dd5397cf1d079c3b \
--hash=sha256:c6c7c2408b7048082932cf4e641fa3b8ca848259212f51c8c59c45aa7ac18f14 \
--hash=sha256:c6ec93afeb848a0845a18989da3beca3eec2c0f852322efe21af1931147d12cb \
--hash=sha256:cacd81e2d4b6f89c9f8a5b69b86490152ff39afc58a95af002a398273e5ce589 \
--hash=sha256:d402880b84df3bec6eab53cd0cf802cae6a2ef9537e70cf75e91618a3801c20c \
--hash=sha256:d51fca85f9f7ad0b65b4b9fe800406d0d77017d7270d31ec3fb1cc07358fdea0 \
--hash=sha256:d73f659398a0904e125280836ae6f88ba9b178b2fed6884f3b1f95b989d2c8da \
--hash=sha256:d78ab28a03c854a873787a0a42254a0ccb3cb133c672f645c9f9c8f3ae9d0800 \
--hash=sha256:da84c537cb8b97d153e9fb208c221c45605f73147bd4cadd23bdae915042aad6 \
--hash=sha256:dbc4c3217eee163fa3984fd1567632b48d6dfd29216da3ded3d7b844a8014a66 \
--hash=sha256:e12968fdfd5bb45ffdf6192a590bd8ddd3ba9e58360b29683c6bb71a7b41edca \
--hash=sha256:e1fd23e9d01591bab45546c089ae89d926917a66dceb3abcf01f6105d927e2cb \
--hash=sha256:e8134301d7e204c88ed7ab50028ba06c683000040ede1d617298611f9dc6240c \
--hash=sha256:eb8b141bb00fa977d9122636b16aa67d37fd40a3d8b52dd837e536d64b9a4d06 \
--hash=sha256:eca7e17a65f72a5133bdbec9ecf22401c62bcf4821361ef7811faee695799779 \
--hash=sha256:f317576606de89da6b7e0861cf6061f6146ead3528acabff9236458a6ba467f8 \
--hash=sha256:fd2a0fc506eccaaa7595b7e1418951f213cf8255be2600f1ea1b61e46a60c55f \
--hash=sha256:fe41b41505a5a33aeaed2a613dccaeaa74e0e3ead6dd6fd3a118fb471644fd6c
# via matplotlib # via matplotlib
cookiecutter==2.6.0 \ cookiecutter==2.6.0 \
--hash=sha256:a54a8e37995e4ed963b3e82831072d1ad4b005af736bb17b99c2cbd9d41b6e2d \ --hash=sha256:a54a8e37995e4ed963b3e82831072d1ad4b005af736bb17b99c2cbd9d41b6e2d \
@ -966,9 +987,9 @@ lxml==5.3.0 \
--hash=sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945 \ --hash=sha256:f914c03e6a31deb632e2daa881fe198461f4d06e57ac3d0e05bbcab8eae01945 \
--hash=sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8 --hash=sha256:fb66442c2546446944437df74379e9cf9e9db353e61301d1a0e26482f43f0dd8
# via django-for-runners # via django-for-runners
manageprojects==0.17.1 \ manageprojects==0.18.0 \
--hash=sha256:355d970261f14b53b574d102e7e82462fe6769baa06c479f00f07a0bcfcb8e4d \ --hash=sha256:37b452c8eb464cf79d29f4e30e8e9c9bce0c1e525a3112d11ff66f99bd48dda4 \
--hash=sha256:4662ff7f0e64ea9b420b67c270594c88542858a1434ebe8b5f93b7bf2ae2e706 --hash=sha256:548ca16ba2c1d58c8f532d35cfbfb16fee236695b090b0e2d5303b1e9f60ce99
# via for_runners_ynh (pyproject.toml) # via for_runners_ynh (pyproject.toml)
markdown-it-py==3.0.0 \ markdown-it-py==3.0.0 \
--hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \
@ -1181,6 +1202,7 @@ mypy==1.11.2 \
# via # via
# for_runners_ynh (pyproject.toml) # for_runners_ynh (pyproject.toml)
# manageprojects # manageprojects
# refurb
mypy-extensions==1.0.0 \ mypy-extensions==1.0.0 \
--hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \ --hash=sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d \
--hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782 --hash=sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782
@ -1489,9 +1511,9 @@ pygments==2.18.0 \
# darker # darker
# readme-renderer # readme-renderer
# rich # rich
pyparsing==3.1.3 \ pyparsing==3.1.4 \
--hash=sha256:1e80fdf93e6c1aeaf4702523f1d48f66d52fa6459096a8f812591157270a5896 \ --hash=sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c \
--hash=sha256:5d549d2a1b5e1c3e952bb55ea247bfb5ad427ea307566a350db2b3c34d4ce181 --hash=sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032
# via # via
# matplotlib # matplotlib
# pip-requirements-parser # pip-requirements-parser
@ -1595,6 +1617,10 @@ redis==5.0.8 \
--hash=sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870 \ --hash=sha256:0c5b10d387568dfe0698c6fad6615750c24170e548ca2deac10c649d463e9870 \
--hash=sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4 --hash=sha256:56134ee08ea909106090934adc36f65c9bcbbaecea5b21ba704ba6fb561f8eb4
# via django-redis # via django-redis
refurb==2.0.0 \
--hash=sha256:8a8f1e7c131ef7dc460cbecbeaf536f5eb0ecb657c099d7823941f0e65b1cfe1 \
--hash=sha256:fa9e950dc6edd7473642569c118f8714eefd1e6f21a15ee4210a1be853aaaf80
# via manageprojects
requests==2.32.3 \ requests==2.32.3 \
--hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \ --hash=sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760 \
--hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6 --hash=sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6
@ -1623,9 +1649,9 @@ rfc3986==2.0.0 \
--hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \
--hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c
# via twine # via twine
rich==13.7.1 \ rich==13.8.0 \
--hash=sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222 \ --hash=sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc \
--hash=sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432 --hash=sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4
# via # via
# cli-base-utilities # cli-base-utilities
# cookiecutter # cookiecutter
@ -1753,9 +1779,9 @@ wheel==0.44.0 \
--hash=sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f \ --hash=sha256:2376a90c98cc337d18623527a97c31797bd02bad0033d41547043a1cbfbe448f \
--hash=sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49 --hash=sha256:a29c3f2817e95ab89aa4660681ad547c0e9547f20e75b0562fe7723c9a2a9d49
# via pip-tools # via pip-tools
zipp==3.20.0 \ zipp==3.20.1 \
--hash=sha256:0145e43d89664cfe1a2e533adc75adafed82fe2da404b4bbb6b026c0157bdb31 \ --hash=sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064 \
--hash=sha256:58da6168be89f0be59beb194da1250516fdaa062ccebd30127ac65d30045e10d --hash=sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b
# via importlib-metadata # via importlib-metadata
# The following packages are considered to be unsafe in a requirements file: # The following packages are considered to be unsafe in a requirements file:
@ -1765,7 +1791,7 @@ pip==24.2 \
# via # via
# pip-api # pip-api
# pip-tools # pip-tools
setuptools==73.0.1 \ setuptools==74.0.0 \
--hash=sha256:b208925fcb9f7af924ed2dc04708ea89791e24bde0d3020b27df0e116088b34e \ --hash=sha256:0274581a0037b638b9fc1c6883cc71c0210865aaa76073f7882376b641b84e8f \
--hash=sha256:d59a3e788ab7e012ab2c4baed1b376da6366883ee20d7a5fc426816e3d7b1193 --hash=sha256:a85e96b8be2b906f3e3e789adec6a9323abf79758ecfa3065bd740d81158b11e
# via pip-tools # via pip-tools