Compare commits

..

2 commits

Author SHA1 Message Date
a351639c52
Merge 9810ae402b into 60df4f7e35 2024-12-03 15:18:56 +01:00
Alexis Métaireau
9810ae402b
Add a --debug flag to the CLI to help retrieve more logs.
When the flag is set, the `RUNSC_DEBUG=1` environment variable is added
to the outer container
2024-11-26 17:19:34 +01:00
32 changed files with 818 additions and 1455 deletions

View file

@ -74,8 +74,6 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get current date
id: date
@ -85,7 +83,7 @@ jobs:
id: cache-container-image
uses: actions/cache@v4
with:
key: v3-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
key: v2-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
path: |
share/container.tar.gz
share/image-id.txt
@ -97,7 +95,6 @@ jobs:
python3 ./install/common/build-image.py
echo ${{ github.token }} | podman login ghcr.io -u USERNAME --password-stdin
gunzip -c share/container.tar.gz | podman load
tag=$(cat share/image-id.txt)
podman push \
dangerzone.rocks/dangerzone:$tag \
${{ env.IMAGE_REGISTRY }}/dangerzone/dangerzone:tag
dangerzone.rocks/dangerzone \
${{ env.IMAGE_REGISTRY }}/dangerzone/dangerzone

View file

@ -48,8 +48,6 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get current date
id: date
@ -59,7 +57,7 @@ jobs:
id: cache-container-image
uses: actions/cache@v4
with:
key: v3-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
key: v2-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
path: |-
share/container.tar.gz
share/image-id.txt
@ -121,14 +119,10 @@ jobs:
key: v1-tessdata-${{ hashFiles('./install/common/download-tessdata.py') }}
- name: Run CLI tests
run: poetry run make test
- name: Set up .NET CLI environment
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.x"
- name: Install WiX Toolset
run: dotnet tool install --global wix
- name: Add WiX UI extension
run: wix extension add --global WixToolset.UI.wixext
# Taken from: https://github.com/orgs/community/discussions/27149#discussioncomment-3254829
- name: Set path for candle and light
run: echo "C:\Program Files (x86)\WiX Toolset v3.14\bin" >> $GITHUB_PATH
shell: bash
- name: Build the MSI installer
# NOTE: This also builds the .exe internally.
run: poetry run .\install\windows\build-app.bat
@ -227,7 +221,7 @@ jobs:
- name: Restore container cache
uses: actions/cache/restore@v4
with:
key: v3-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
key: v2-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
path: |-
share/container.tar.gz
share/image-id.txt
@ -334,7 +328,7 @@ jobs:
- name: Restore container image
uses: actions/cache/restore@v4
with:
key: v3-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
key: v2-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
path: |-
share/container.tar.gz
share/image-id.txt
@ -429,7 +423,7 @@ jobs:
- name: Restore container image
uses: actions/cache/restore@v4
with:
key: v3-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
key: v2-${{ steps.date.outputs.date }}-${{ hashFiles('Dockerfile', 'dangerzone/conversion/common.py', 'dangerzone/conversion/doc_to_pixels.py', 'dangerzone/conversion/pixels_to_pdf.py', 'poetry.lock', 'gvisor_wrapper/entrypoint.py') }}
path: |-
share/container.tar.gz
share/image-id.txt
@ -445,7 +439,6 @@ jobs:
- name: Setup xvfb (Linux)
run: |
sudo apt update
# Stuff copied wildly from several stackoverflow posts
sudo apt-get install -y xvfb libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xinput0 libxcb-xfixes0 libxcb-shape0 libglib2.0-0 libgl1-mesa-dev '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev

View file

@ -14,24 +14,17 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install container build dependencies
run: sudo apt install pipx && pipx install poetry
- name: Build container image
run: python3 ./install/common/build-image.py --runtime docker --no-save
- name: Get image tag
id: tag
run: |
tag=$(docker images dangerzone.rocks/dangerzone --format '{{ .Tag }}')
echo "tag=$tag" >> $GITHUB_OUTPUT
# NOTE: Scan first without failing, else we won't be able to read the scan
# report.
- name: Scan container image (no fail)
uses: anchore/scan-action@v6
uses: anchore/scan-action@v5
id: scan_container
with:
image: "dangerzone.rocks/dangerzone:${{ steps.tag.outputs.tag }}"
image: "dangerzone.rocks/dangerzone:latest"
fail-build: false
only-fixed: false
severity-cutoff: critical
@ -43,9 +36,9 @@ jobs:
- name: Inspect container scan report
run: cat ${{ steps.scan_container.outputs.sarif }}
- name: Scan container image
uses: anchore/scan-action@v6
uses: anchore/scan-action@v5
with:
image: "dangerzone.rocks/dangerzone:${{ steps.tag.outputs.tag }}"
image: "dangerzone.rocks/dangerzone:latest"
fail-build: true
only-fixed: false
severity-cutoff: critical
@ -58,7 +51,7 @@ jobs:
# NOTE: Scan first without failing, else we won't be able to read the scan
# report.
- name: Scan application (no fail)
uses: anchore/scan-action@v6
uses: anchore/scan-action@v5
id: scan_app
with:
path: "."
@ -73,7 +66,7 @@ jobs:
- name: Inspect application scan report
run: cat ${{ steps.scan_app.outputs.sarif }}
- name: Scan application
uses: anchore/scan-action@v6
uses: anchore/scan-action@v5
with:
path: "."
fail-build: true

View file

@ -24,18 +24,13 @@ jobs:
CONTAINER_FILENAME=container-${VERSION:1}-${{ matrix.arch }}.tar.gz
wget https://github.com/freedomofpress/dangerzone/releases/download/${VERSION}/${CONTAINER_FILENAME} -O ${CONTAINER_FILENAME}
docker load -i ${CONTAINER_FILENAME}
- name: Get image tag
id: tag
run: |
tag=$(docker images dangerzone.rocks/dangerzone --format '{{ .Tag }}')
echo "tag=$tag" >> $GITHUB_OUTPUT
# NOTE: Scan first without failing, else we won't be able to read the scan
# report.
- name: Scan container image (no fail)
uses: anchore/scan-action@v6
uses: anchore/scan-action@v5
id: scan_container
with:
image: "dangerzone.rocks/dangerzone:${{ steps.tag.outputs.tag }}"
image: "dangerzone.rocks/dangerzone:latest"
fail-build: false
only-fixed: false
severity-cutoff: critical
@ -47,9 +42,9 @@ jobs:
- name: Inspect container scan report
run: cat ${{ steps.scan_container.outputs.sarif }}
- name: Scan container image
uses: anchore/scan-action@v6
uses: anchore/scan-action@v5
with:
image: "dangerzone.rocks/dangerzone:${{ steps.tag.outputs.tag }}"
image: "dangerzone.rocks/dangerzone:latest"
fail-build: true
only-fixed: false
severity-cutoff: critical
@ -68,7 +63,7 @@ jobs:
# NOTE: Scan first without failing, else we won't be able to read the scan
# report.
- name: Scan application (no fail)
uses: anchore/scan-action@v6
uses: anchore/scan-action@v5
id: scan_app
with:
path: "."
@ -83,7 +78,7 @@ jobs:
- name: Inspect application scan report
run: cat ${{ steps.scan_app.outputs.sarif }}
- name: Scan application
uses: anchore/scan-action@v6
uses: anchore/scan-action@v5
with:
path: "."
fail-build: true

1
.gitignore vendored
View file

@ -149,4 +149,3 @@ share/container.tar
share/container.tar.gz
share/image-id.txt
container/container-pip-requirements.txt
.doit.db.db

View file

@ -2,10 +2,47 @@
# latest release of Dangerzone, and offer our analysis.
ignore:
# CVE-2024-11053
- vulnerability: CVE-2024-5535
# CVE-2024-5171
# =============
#
# NVD Entry: https://nvd.nist.gov/vuln/detail/CVE-2024-5171
# Verdict: Dangerzone is not affected. The rationale is the following:
#
# The affected library, `libaom.so`, is linked by GStreamer's `libgstaom.so`
# library. The vulnerable `aom_img_alloc` function is only used when
# **encoding** a video to AV1. LibreOffce uses the **decode** path instead,
# when generating thumbnails.
#
# See also: https://github.com/freedomofpress/dangerzone/issues/895
- vulnerability: CVE-2024-5171
# CVE-2024-45491, CVE-2024-45492
# ===============================
#
# NVD Entries:
# * https://nvd.nist.gov/vuln/detail/CVE-2024-45491
# * https://nvd.nist.gov/vuln/detail/CVE-2024-45492
#
# Verdict: Dangerzone is not affected. The rationale is the following:
#
# The vulnerabilities that have been assigned to these CVEs affect only 32-bit
# architectures. Dangerzone ships only 64-bit images to users.
#
# See also: https://github.com/freedomofpress/dangerzone/issues/913
- vulnerability: CVE-2024-45491
- vulnerability: CVE-2024-45492
# CVE-2024-45490
# ==============
#
# NVD Entry: https://nvd.nist.gov/vuln/detail/CVE-2024-11053
# Verdict: Dangerzone is not affected because libcurl is an HTTP client, and
# the Dangerzone container does not make any network calls.
- vulnerability: CVE-2024-11053
# NVD Entry: https://nvd.nist.gov/vuln/detail/CVE-2024-45490
# Verdict: Dangerzone is not affected. The rationale is the following:
#
# In order to exploit this bug, the caller must pass a negative length to the
# `XML_ParseBuffer` function. This function is not directly used by
# LibreOffice, which instead uses a higher-level wrapper. Therefore, our
# understanding is that this path cannot be exploited by attackers.
#
# See also: https://github.com/freedomofpress/dangerzone/issues/913
- vulnerability: CVE-2024-45490

View file

@ -471,24 +471,11 @@ poetry shell
.\dev_scripts\dangerzone.bat
```
### If you want to build the Windows installer
### If you want to build the installer
Install [.NET SDK](https://dotnet.microsoft.com/en-us/download) version 6 or later. Then, open a terminal and install the latest version of [WiX Toolset .NET tool](https://wixtoolset.org/) **v5** with:
```sh
dotnet tool install --global wix --version 5.*
```
Install the WiX UI extension. You may need to open a new terminal in order to use the newly installed `wix` .NET tool:
```sh
wix extension add --global WixToolset.UI.wixext/5.x.y
```
> [!IMPORTANT]
> To avoid compatibility issues, ensure the WiX UI extension version matches the version of the WiX Toolset.
>
> Run `wix --version` to check the version of WiX Toolset you have installed and replace `5.x.y` with the full version number without the Git revision.
* Go to https://dotnet.microsoft.com/download/dotnet-framework and download and install .NET Framework 3.5 SP1 Runtime. I downloaded `dotnetfx35.exe`.
* Go to https://wixtoolset.org/releases/ and download and install WiX toolset. I downloaded `wix314.exe`.
* Add `C:\Program Files (x86)\WiX Toolset v3.14\bin` to the path ([instructions](https://web.archive.org/web/20230221104142/https://windowsloop.com/how-to-add-to-windows-path/)).
### If you want to sign binaries with Authenticode

View file

@ -16,11 +16,6 @@ since 0.4.1, and this project adheres to [Semantic Versioning](https://semver.or
- Platform support: Drop support for Fedora 39, since it's end-of-life ([#999](https://github.com/freedomofpress/dangerzone/pull/999))
### Development changes
Thanks [@jkarasti](https://github.com/jkarasti) for the contribution.
- Automate a large portion of our release tasks with `doit` ([#1016](https://github.com/freedomofpress/dangerzone/issues/1016))
## [0.8.0](https://github.com/freedomofpress/dangerzone/compare/v0.8.0...0.7.1)
### Added

View file

@ -1,6 +1,23 @@
LARGE_TEST_REPO_DIR:=tests/test_docs_large
GIT_DESC=$$(git describe)
JUNIT_FLAGS := --capture=sys -o junit_logging=all
.PHONY: lint-black
lint-black: ## check python source code formatting issues, with black
black --check --diff ./
.PHONY: lint-black-apply
lint-black-apply: ## apply black's source code formatting suggestions
black ./
.PHONY: lint-isort
lint-isort: ## check imports are organized, with isort
isort --check --diff ./
.PHONY: lint-isort-apply
lint-isort-apply: ## apply isort's imports organization suggestions
isort ./
MYPY_ARGS := --ignore-missing-imports \
--disallow-incomplete-defs \
--disallow-untyped-defs \
@ -9,17 +26,19 @@ MYPY_ARGS := --ignore-missing-imports \
--warn-unused-ignores \
--exclude $(LARGE_TEST_REPO_DIR)/*.py
.PHONY: lint
lint: ## Check the code for linting, formatting, and typing issues with ruff and mypy
ruff check
ruff format --check
mypy-host:
mypy $(MYPY_ARGS) dangerzone
mypy-tests:
mypy $(MYPY_ARGS) tests
.PHONY: fix
fix: ## apply all the suggestions from ruff
ruff check --fix
ruff format
mypy: mypy-host mypy-tests ## check type hints with mypy
.PHONY: lint
lint: lint-black lint-isort mypy ## check the code with various linters
.PHONY: lint-apply
format: lint-black-apply lint-isort-apply ## apply all the linter's suggestions
.PHONY: test
test:
@ -47,22 +66,6 @@ test-large: test-large-init ## Run large test set
python -m pytest --tb=no tests/test_large_set.py::TestLargeSet -v $(JUNIT_FLAGS) --junitxml=$(TEST_LARGE_RESULTS)
python $(TEST_LARGE_RESULTS)/report.py $(TEST_LARGE_RESULTS)
.PHONY: build-clean
build-clean:
doit clean
.PHONY: build-macos-intel
build-macos-intel: build-clean
doit -n 8
.PHONY: build-macos-arm
build-macos-arm: build-clean
doit -n 8 macos_build_dmg
.PHONY: build-linux
build-linux: build-clean
doit -n 8 fedora_rpm debian_deb
# Makefile self-help borrowed from the securedrop-client project
# Explaination of the below shell command should it ever break.
# 1. Set the field separator to ": ##" and any make targets that might appear between : and ##

8
QA.md
View file

@ -107,9 +107,9 @@ Close the Dangerzone application and get the container image for that
version. For example:
```
$ docker images dangerzone.rocks/dangerzone
$ docker images dangerzone.rocks/dangerzone:latest
REPOSITORY TAG IMAGE ID CREATED SIZE
dangerzone.rocks/dangerzone <tag> <image ID> <date> <size>
dangerzone.rocks/dangerzone latest <image ID> <date> <size>
```
Then run the version under QA and ensure that the settings remain changed.
@ -118,9 +118,9 @@ Afterwards check that new docker image was installed by running the same command
and seeing the following differences:
```
$ docker images dangerzone.rocks/dangerzone
$ docker images dangerzone.rocks/dangerzone:latest
REPOSITORY TAG IMAGE ID CREATED SIZE
dangerzone.rocks/dangerzone <other tag> <different ID> <newer date> <different size>
dangerzone.rocks/dangerzone latest <different ID> <newer date> <different size>
```
#### 4. Dangerzone successfully installs the container image

View file

@ -76,16 +76,7 @@ Once we are confident that the release will be out shortly, and doesn't need any
### macOS Release
> [!TIP]
> You can automate these steps from your macOS terminal app with:
>
> ```
> export APPLE_ID=<email>
> make build-macos-intel # for Intel macOS
> make build-macos-arm # for Apple Silicon macOS
> ```
The following needs to happen for both Silicon and Intel chipsets.
This needs to happen for both Silicon and Intel chipsets.
#### Initial Setup
@ -151,6 +142,8 @@ Here is what you need to do:
poetry run ./install/macos/build-app.py
```
- [ ] Make sure that the build application works with the containerd graph
driver (see [#933](https://github.com/freedomofpress/dangerzone/issues/933))
- [ ] Sign the application bundle, and notarize it
You need to run this command as the account that has access to the code signing certificate
@ -219,6 +212,9 @@ The Windows release is performed in a Windows 11 virtual machine (as opposed to
- [ ] Copy the container image into the VM
> [!IMPORTANT]
> Instead of running `python .\install\windows\build-image.py` in the VM, run the build image script on the host (making sure to build for `linux/amd64`). Copy `share/container.tar.gz` and `share/image-id.txt` from the host into the `share` folder in the VM.
> Also, don't forget to add the supplementary image ID (see
> [#933](https://github.com/freedomofpress/dangerzone/issues/933)) in
> `share/image-id.txt`)
- [ ] Run `poetry run .\install\windows\build-app.bat`
- [ ] When you're done you will have `dist\Dangerzone.msi`
@ -226,16 +222,12 @@ Rename `Dangerzone.msi` to `Dangerzone-$VERSION.msi`.
### Linux release
> [!TIP]
> You can automate these steps from any Linux distribution with:
> [!INFO]
> Below we explain how we build packages for each Linux distribution we support.
>
> ```
> make build-linux
> ```
>
> You can then add the created artifacts to the appropriate APT/YUM repo.
> There is also a `release.sh` script available which creates all
> the `.rpm` and `.deb` files with a single command.
Below we explain how we build packages for each Linux distribution we support.
#### Debian/Ubuntu

View file

@ -1,149 +0,0 @@
import gzip
import logging
import platform
import shutil
import subprocess
from typing import List, Tuple
from . import errors
from .util import get_resource_path, get_subprocess_startupinfo
CONTAINER_NAME = "dangerzone.rocks/dangerzone"
log = logging.getLogger(__name__)
def get_runtime_name() -> str:
if platform.system() == "Linux":
runtime_name = "podman"
else:
# Windows, Darwin, and unknown use docker for now, dangerzone-vm eventually
runtime_name = "docker"
return runtime_name
def get_runtime_version() -> Tuple[int, int]:
"""Get the major/minor parts of the Docker/Podman version.
Some of the operations we perform in this module rely on some Podman features
that are not available across all of our platforms. In order to have a proper
fallback, we need to know the Podman version. More specifically, we're fine with
just knowing the major and minor version, since writing/installing a full-blown
semver parser is an overkill.
"""
# Get the Docker/Podman version, using a Go template.
runtime = get_runtime_name()
if runtime == "podman":
query = "{{.Client.Version}}"
else:
query = "{{.Server.Version}}"
cmd = [runtime, "version", "-f", query]
try:
version = subprocess.run(
cmd,
startupinfo=get_subprocess_startupinfo(),
capture_output=True,
check=True,
).stdout.decode()
except Exception as e:
msg = f"Could not get the version of the {runtime.capitalize()} tool: {e}"
raise RuntimeError(msg) from e
# Parse this version and return the major/minor parts, since we don't need the
# rest.
try:
major, minor, _ = version.split(".", 3)
return (int(major), int(minor))
except Exception as e:
msg = (
f"Could not parse the version of the {runtime.capitalize()} tool"
f" (found: '{version}') due to the following error: {e}"
)
raise RuntimeError(msg)
def get_runtime() -> str:
container_tech = get_runtime_name()
runtime = shutil.which(container_tech)
if runtime is None:
raise errors.NoContainerTechException(container_tech)
return runtime
def list_image_tags() -> List[str]:
"""Get the tags of all loaded Dangerzone images.
This method returns a mapping of image tags to image IDs, for all Dangerzone
images. This can be useful when we want to find which are the local image tags,
and which image ID does the "latest" tag point to.
"""
return (
subprocess.check_output(
[
get_runtime(),
"image",
"list",
"--format",
"{{ .Tag }}",
CONTAINER_NAME,
],
text=True,
startupinfo=get_subprocess_startupinfo(),
)
.strip()
.split()
)
def delete_image_tag(tag: str) -> None:
"""Delete a Dangerzone image tag."""
name = CONTAINER_NAME + ":" + tag
log.warning(f"Deleting old container image: {name}")
try:
subprocess.check_output(
[get_runtime(), "rmi", "--force", name],
startupinfo=get_subprocess_startupinfo(),
)
except Exception as e:
log.warning(
f"Couldn't delete old container image '{name}', so leaving it there."
f" Original error: {e}"
)
def get_expected_tag() -> str:
"""Get the tag of the Dangerzone image tarball from the image-id.txt file."""
with open(get_resource_path("image-id.txt")) as f:
return f.read().strip()
def load_image_tarball() -> None:
log.info("Installing Dangerzone container image...")
p = subprocess.Popen(
[get_runtime(), "load"],
stdin=subprocess.PIPE,
startupinfo=get_subprocess_startupinfo(),
)
chunk_size = 4 << 20
compressed_container_path = get_resource_path("container.tar.gz")
with gzip.open(compressed_container_path) as f:
while True:
chunk = f.read(chunk_size)
if len(chunk) > 0:
if p.stdin:
p.stdin.write(chunk)
else:
break
_, err = p.communicate()
if p.returncode < 0:
if err:
error = err.decode()
else:
error = "No output"
raise errors.ImageInstallationException(
f"Could not install container image: {error}"
)
log.info("Successfully installed container image from")

View file

@ -117,26 +117,3 @@ def handle_document_errors(func: F) -> F:
sys.exit(1)
return cast(F, wrapper)
#### Container-related errors
class ImageNotPresentException(Exception):
pass
class ImageInstallationException(Exception):
pass
class NoContainerTechException(Exception):
def __init__(self, container_tech: str) -> None:
super().__init__(f"{container_tech} is not installed")
class NotAvailableContainerTechException(Exception):
def __init__(self, container_tech: str, error: str) -> None:
self.error = error
self.container_tech = container_tech
super().__init__(f"{container_tech} is not available")

View file

@ -25,7 +25,13 @@ else:
from .. import errors
from ..document import SAFE_EXTENSION, Document
from ..isolation_provider.qubes import is_qubes_native_conversion
from ..isolation_provider.container import (
Container,
NoContainerTechException,
NotAvailableContainerTechException,
)
from ..isolation_provider.dummy import Dummy
from ..isolation_provider.qubes import Qubes, is_qubes_native_conversion
from ..util import format_exception, get_resource_path, get_version
from .logic import Alert, CollapsibleBox, DangerzoneGui, UpdateDialog
from .updater import UpdateReport
@ -191,11 +197,14 @@ class MainWindow(QtWidgets.QMainWindow):
header_layout.addWidget(self.hamburger_button)
header_layout.addSpacing(15)
if self.dangerzone.isolation_provider.should_wait_install():
if isinstance(self.dangerzone.isolation_provider, Container):
# Waiting widget replaces content widget while container runtime isn't available
self.waiting_widget: WaitingWidget = WaitingWidgetContainer(self.dangerzone)
self.waiting_widget.finished.connect(self.waiting_finished)
else:
elif isinstance(self.dangerzone.isolation_provider, Dummy) or isinstance(
self.dangerzone.isolation_provider, Qubes
):
# Don't wait with dummy converter and on Qubes.
self.waiting_widget = WaitingWidget()
self.dangerzone.is_waiting_finished = True
@ -491,11 +500,11 @@ class WaitingWidgetContainer(WaitingWidget):
error: Optional[str] = None
try:
self.dangerzone.isolation_provider.is_available()
except errors.NoContainerTechException as e:
self.dangerzone.isolation_provider.is_runtime_available()
except NoContainerTechException as e:
log.error(str(e))
state = "not_installed"
except errors.NotAvailableContainerTechException as e:
except NotAvailableContainerTechException as e:
log.error(str(e))
state = "not_running"
error = e.error

View file

@ -261,16 +261,6 @@ class IsolationProvider(ABC):
)
return errors.exception_from_error_code(error_code)
@abstractmethod
def should_wait_install(self) -> bool:
"""Whether this isolation provider takes a lot of time to install."""
pass
@abstractmethod
def is_available(self) -> bool:
"""Whether the backing implementation of the isolation provider is available."""
pass
@abstractmethod
def get_max_parallel_conversions(self) -> int:
pass

View file

@ -1,11 +1,12 @@
import gzip
import logging
import os
import platform
import shlex
import shutil
import subprocess
from typing import List
from typing import List, Tuple
from .. import container_utils, errors
from ..document import Document
from ..util import get_resource_path, get_subprocess_startupinfo
from .base import IsolationProvider, terminate_process_group
@ -24,8 +25,88 @@ else:
log = logging.getLogger(__name__)
class NoContainerTechException(Exception):
def __init__(self, container_tech: str) -> None:
super().__init__(f"{container_tech} is not installed")
class NotAvailableContainerTechException(Exception):
def __init__(self, container_tech: str, error: str) -> None:
self.error = error
self.container_tech = container_tech
super().__init__(f"{container_tech} is not available")
class ImageNotPresentException(Exception):
pass
class ImageInstallationException(Exception):
pass
class Container(IsolationProvider):
# Name of the dangerzone container
CONTAINER_NAME = "dangerzone.rocks/dangerzone"
@staticmethod
def get_runtime_name() -> str:
if platform.system() == "Linux":
runtime_name = "podman"
else:
# Windows, Darwin, and unknown use docker for now, dangerzone-vm eventually
runtime_name = "docker"
return runtime_name
@staticmethod
def get_runtime_version() -> Tuple[int, int]:
"""Get the major/minor parts of the Docker/Podman version.
Some of the operations we perform in this module rely on some Podman features
that are not available across all of our platforms. In order to have a proper
fallback, we need to know the Podman version. More specifically, we're fine with
just knowing the major and minor version, since writing/installing a full-blown
semver parser is an overkill.
"""
# Get the Docker/Podman version, using a Go template.
runtime = Container.get_runtime_name()
if runtime == "podman":
query = "{{.Client.Version}}"
else:
query = "{{.Server.Version}}"
cmd = [runtime, "version", "-f", query]
try:
version = subprocess.run(
cmd,
startupinfo=get_subprocess_startupinfo(),
capture_output=True,
check=True,
).stdout.decode()
except Exception as e:
msg = f"Could not get the version of the {runtime.capitalize()} tool: {e}"
raise RuntimeError(msg) from e
# Parse this version and return the major/minor parts, since we don't need the
# rest.
try:
major, minor, _ = version.split(".", 3)
return (int(major), int(minor))
except Exception as e:
msg = (
f"Could not parse the version of the {runtime.capitalize()} tool"
f" (found: '{version}') due to the following error: {e}"
)
raise RuntimeError(msg)
@staticmethod
def get_runtime() -> str:
container_tech = Container.get_runtime_name()
runtime = shutil.which(container_tech)
if runtime is None:
raise NoContainerTechException(container_tech)
return runtime
@staticmethod
def get_runtime_security_args() -> List[str]:
"""Security options applicable to the outer Dangerzone container.
@ -46,12 +127,12 @@ class Container(IsolationProvider):
* Do not log the container's output.
* Do not map the host user to the container, with `--userns nomap` (available
from Podman 4.1 onwards)
- This particular argument is specified in `start_doc_to_pixels_proc()`, but
should move here once #748 is merged.
"""
if container_utils.get_runtime_name() == "podman":
if Container.get_runtime_name() == "podman":
security_args = ["--log-driver", "none"]
security_args += ["--security-opt", "no-new-privileges"]
if container_utils.get_runtime_version() >= (4, 1):
security_args += ["--userns", "nomap"]
else:
security_args = ["--security-opt=no-new-privileges:true"]
@ -75,52 +156,51 @@ class Container(IsolationProvider):
@staticmethod
def install() -> bool:
"""Install the container image tarball, or verify that it's already installed.
Perform the following actions:
1. Get the tags of any locally available images that match Dangerzone's image
name.
2. Get the expected image tag from the image-id.txt file.
- If this tag is present in the local images, then we can return.
- Else, prune the older container images and continue.
3. Load the image tarball and make sure it matches the expected tag.
"""
old_tags = container_utils.list_image_tags()
expected_tag = container_utils.get_expected_tag()
if expected_tag not in old_tags:
# Prune older container images.
log.info(
f"Could not find a Dangerzone container image with tag '{expected_tag}'"
)
for tag in old_tags:
container_utils.delete_image_tag(tag)
else:
Make sure the podman container is installed. Linux only.
"""
if Container.is_container_installed():
return True
# Load the image tarball into the container runtime.
container_utils.load_image_tarball()
# Load the container into podman
log.info("Installing Dangerzone container image...")
# Check that the container image has the expected image tag.
# See https://github.com/freedomofpress/dangerzone/issues/988 for an example
# where this was not the case.
new_tags = container_utils.list_image_tags()
if expected_tag not in new_tags:
raise errors.ImageNotPresentException(
f"Could not find expected tag '{expected_tag}' after loading the"
" container image tarball"
p = subprocess.Popen(
[Container.get_runtime(), "load"],
stdin=subprocess.PIPE,
startupinfo=get_subprocess_startupinfo(),
)
chunk_size = 4 << 20
compressed_container_path = get_resource_path("container.tar.gz")
with gzip.open(compressed_container_path) as f:
while True:
chunk = f.read(chunk_size)
if len(chunk) > 0:
if p.stdin:
p.stdin.write(chunk)
else:
break
_, err = p.communicate()
if p.returncode < 0:
if err:
error = err.decode()
else:
error = "No output"
raise ImageInstallationException(
f"Could not install container image: {error}"
)
if not Container.is_container_installed(raise_on_error=True):
return False
log.info("Container image installed")
return True
@staticmethod
def should_wait_install() -> bool:
return True
@staticmethod
def is_available() -> bool:
container_runtime = container_utils.get_runtime()
runtime_name = container_utils.get_runtime_name()
def is_runtime_available() -> bool:
container_runtime = Container.get_runtime()
runtime_name = Container.get_runtime_name()
# Can we run `docker/podman image ls` without an error
with subprocess.Popen(
[container_runtime, "image", "ls"],
@ -130,11 +210,61 @@ class Container(IsolationProvider):
) as p:
_, stderr = p.communicate()
if p.returncode != 0:
raise errors.NotAvailableContainerTechException(
runtime_name, stderr.decode()
)
raise NotAvailableContainerTechException(runtime_name, stderr.decode())
return True
@staticmethod
def is_container_installed(raise_on_error: bool = False) -> bool:
"""
See if the container is installed.
"""
# Get the image id
with open(get_resource_path("image-id.txt")) as f:
expected_image_ids = f.read().strip().split()
# See if this image is already installed
installed = False
found_image_id = subprocess.check_output(
[
Container.get_runtime(),
"image",
"list",
"--format",
"{{.ID}}",
Container.CONTAINER_NAME,
],
text=True,
startupinfo=get_subprocess_startupinfo(),
)
found_image_id = found_image_id.strip()
if found_image_id in expected_image_ids:
installed = True
elif found_image_id == "":
if raise_on_error:
raise ImageNotPresentException(
"Image is not listed after installation. Bailing out."
)
else:
msg = (
f"{Container.CONTAINER_NAME} images found, but IDs do not match."
f" Found: {found_image_id}, Expected: {','.join(expected_image_ids)}"
)
if raise_on_error:
raise ImageNotPresentException(msg)
log.info(msg)
log.info("Deleting old dangerzone container image")
try:
subprocess.check_output(
[Container.get_runtime(), "rmi", "--force", found_image_id],
startupinfo=get_subprocess_startupinfo(),
)
except Exception:
log.warning("Couldn't delete old container image, so leaving it there")
return installed
def doc_to_pixels_container_name(self, document: Document) -> str:
"""Unique container name for the doc-to-pixels phase."""
return f"dangerzone-doc-to-pixels-{document.id}"
@ -165,8 +295,9 @@ class Container(IsolationProvider):
self,
command: List[str],
name: str,
extra_args: List[str] = [],
) -> subprocess.Popen:
container_runtime = container_utils.get_runtime()
container_runtime = self.get_runtime()
security_args = self.get_runtime_security_args()
debug_args = []
if self.debug:
@ -175,9 +306,6 @@ class Container(IsolationProvider):
enable_stdin = ["-i"]
set_name = ["--name", name]
prevent_leakage_args = ["--rm"]
image_name = [
container_utils.CONTAINER_NAME + ":" + container_utils.get_expected_tag()
]
args = (
["run"]
+ security_args
@ -185,7 +313,8 @@ class Container(IsolationProvider):
+ prevent_leakage_args
+ enable_stdin
+ set_name
+ image_name
+ extra_args
+ [self.CONTAINER_NAME]
+ command
)
args = [container_runtime] + args
@ -201,7 +330,7 @@ class Container(IsolationProvider):
connected to the Docker daemon, and killing it will just close the associated
standard streams.
"""
container_runtime = container_utils.get_runtime()
container_runtime = self.get_runtime()
cmd = [container_runtime, "kill", name]
try:
# We do not check the exit code of the process here, since the container may
@ -234,8 +363,15 @@ class Container(IsolationProvider):
"-m",
"dangerzone.conversion.doc_to_pixels",
]
# NOTE: Using `--userns nomap` is available only on Podman >= 4.1.0.
# XXX: Move this under `get_runtime_security_args()` once #748 is merged.
extra_args = []
if Container.get_runtime_name() == "podman":
if Container.get_runtime_version() >= (4, 1):
extra_args += ["--userns", "nomap"]
name = self.doc_to_pixels_container_name(document)
return self.exec_container(command, name=name)
return self.exec_container(command, name=name, extra_args=extra_args)
def terminate_doc_to_pixels_proc(
self, document: Document, p: subprocess.Popen
@ -258,7 +394,7 @@ class Container(IsolationProvider):
# after a podman kill / docker kill invocation, this will likely be the case,
# else the container runtime (Docker/Podman) has experienced a problem, and we
# should report it.
container_runtime = container_utils.get_runtime()
container_runtime = self.get_runtime()
name = self.doc_to_pixels_container_name(document)
all_containers = subprocess.run(
[container_runtime, "ps", "-a"],
@ -280,11 +416,11 @@ class Container(IsolationProvider):
if cpu_count is not None:
n_cpu = cpu_count
elif container_utils.get_runtime_name() == "docker":
elif self.get_runtime_name() == "docker":
# For Windows and MacOS containers run in VM
# So we obtain the CPU count for the VM
n_cpu_str = subprocess.check_output(
[container_utils.get_runtime(), "info", "--format", "{{.NCPU}}"],
[self.get_runtime(), "info", "--format", "{{.NCPU}}"],
text=True,
startupinfo=get_subprocess_startupinfo(),
)

View file

@ -39,14 +39,6 @@ class Dummy(IsolationProvider):
def install(self) -> bool:
return True
@staticmethod
def is_available() -> bool:
return True
@staticmethod
def should_wait_install() -> bool:
return False
def start_doc_to_pixels_proc(self, document: Document) -> subprocess.Popen:
cmd = [
sys.executable,

View file

@ -21,14 +21,6 @@ class Qubes(IsolationProvider):
def install(self) -> bool:
return True
@staticmethod
def is_available() -> bool:
return True
@staticmethod
def should_wait_install() -> bool:
return False
def get_max_parallel_conversions(self) -> int:
return 1

View file

@ -71,7 +71,7 @@ class DangerzoneCore(object):
ocr_lang,
stdout_callback,
)
except Exception:
except Exception as e:
log.exception(
f"Unexpected error occurred while converting '{document}'"
)

View file

@ -8,6 +8,7 @@ import platform
import shutil
import subprocess
import sys
import urllib.request
from datetime import date
DEFAULT_GUI = True

View file

@ -127,9 +127,9 @@ Close the Dangerzone application and get the container image for that
version. For example:
```
$ docker images dangerzone.rocks/dangerzone
$ docker images dangerzone.rocks/dangerzone:latest
REPOSITORY TAG IMAGE ID CREATED SIZE
dangerzone.rocks/dangerzone <tag> <image ID> <date> <size>
dangerzone.rocks/dangerzone latest <image ID> <date> <size>
```
Then run the version under QA and ensure that the settings remain changed.
@ -138,9 +138,9 @@ Afterwards check that new docker image was installed by running the same command
and seeing the following differences:
```
$ docker images dangerzone.rocks/dangerzone
$ docker images dangerzone.rocks/dangerzone:latest
REPOSITORY TAG IMAGE ID CREATED SIZE
dangerzone.rocks/dangerzone <other tag> <different ID> <newer date> <different size>
dangerzone.rocks/dangerzone latest <different ID> <newer date> <different size>
```
#### 4. Dangerzone successfully installs the container image

View file

@ -95,11 +95,11 @@ def main():
parser.add_argument(
"--version",
required=True,
help="look for assets with this Dangerzone version",
help=f"look for assets with this Dangerzone version",
)
parser.add_argument(
"dir",
help="look for assets in this directory",
help=f"look for assets in this directory",
)
args = parser.parse_args()
setup_logging()

View file

@ -1,67 +0,0 @@
# Using the Doit Automation Tool
Developers can use the [Doit](https://pydoit.org/) automation tool to create
release artifacts. The purpose of the tool is to automate the manual release
instructions in `RELEASE.md` file. Not everything is automated yet, since we're
still experimenting with this tool. You can find our task definitions in this
repo's `dodo.py` file.
## Why Doit?
We picked Doit out of the various tools out there for the following reasons:
* **Pythonic:** The configuration file and tasks can be written in Python. Where
applicable, it's easy to issue shell commands as well.
* **File targets:** Doit borrows the file target concept from Makefiles. Tasks
can have file dependencies, and targets they build. This makes it easy to
define a dependency graph for tasks.
* **Hash-based caching:** Unlike Makefiles, doit does not look at the
modification timestamp of source/target files, to figure out if it needs to
run them. Instead, it hashes those files, and will run a task only if the
hash of a file dependency has changed.
* **Parallelization:** Tasks can be run in parallel with the `-n` argument,
which is similar to `make`'s `-j` argument.
## How to Doit?
First, enter your Poetry shell. Then, make sure that your environment is clean,
and you have ample disk space. You can run:
```bash
doit clean --dry-run # if you want to see what would happen
doit clean # you'll be asked to cofirm that you want to clean everything
```
Finally, you can build all the release artifacts with `doit`, or a specific task
with:
```
doit <task>
```
## Tips and tricks
* You can run `doit list --all -s` to see the full list of tasks, their
dependencies, and whether they are up to date.
* You can run `doit info <task>` to see which dependencies are missing.
* You can change this line in `pyproject.toml` to `true`, to allow using the
Docker/Podman build cache:
```
use_cache = true
```
> [!WARNING]
> Using caching may speed up image builds, but is not suitable for release
> artifacts. The ID of our base container image (Alpine Linux) does not change
> that often, but its APK package index does. So, if we use caching, we risk
> skipping the `apk upgrade` layer and end up with packages that are days
> behind.
* You can pass the following environment variables to the script, in order to
affect some global parameters:
- `CONTAINER_RUNTIME`: The container runtime to use. Either `podman` (default)
or `docker`.
- `RELEASE_DIR`: Where to store the release artifacts. Default path is
`~/release-assets/<version>`
- `APPLE_ID`: The Apple ID to use when signing/notarizing the macOS DMG.

393
dodo.py
View file

@ -1,393 +0,0 @@
import json
import os
import platform
import shutil
from pathlib import Path
from doit.action import CmdAction
ARCH = "arm64" if platform.machine() == "arm64" else "i686"
VERSION = open("share/version.txt").read().strip()
FEDORA_VERSIONS = ["40", "41"]
DEBIAN_VERSIONS = ["bullseye", "focal", "jammy", "mantic", "noble", "trixie"]
### Global parameters
CONTAINER_RUNTIME = os.environ.get("CONTAINER_RUNTIME", "podman")
DEFAULT_RELEASE_DIR = Path.home() / "release-assets" / VERSION
RELEASE_DIR = Path(os.environ.get("RELEASE_DIR", DEFAULT_RELEASE_DIR))
APPLE_ID = os.environ.get("APPLE_ID", None)
### Task Parameters
PARAM_APPLE_ID = {
"name": "apple_id",
"long": "apple-id",
"default": APPLE_ID,
"help": "The Apple developer ID that will be used to sign the .dmg",
}
PARAM_USE_CACHE = {
"name": "use_cache",
"long": "use-cache",
"help": (
"Whether to use cached results or not. For reproducibility reasons,"
" it's best to leave it to false"
),
"default": False,
}
### File dependencies
#
# Define all the file dependencies for our tasks in a single place, since some file
# dependencies are shared between tasks.
def list_files(path, recursive=False):
"""List files in a directory, and optionally traverse into subdirectories."""
glob_fn = Path(path).rglob if recursive else Path(path).glob
return [f for f in glob_fn("*") if f.is_file() and not f.suffix == ".pyc"]
def list_language_data():
"""List the expected language data that Dangerzone downloads and stores locally."""
tessdata_dir = Path("share") / "tessdata"
langs = json.loads(open(tessdata_dir.parent / "ocr-languages.json").read()).values()
targets = [tessdata_dir / f"{lang}.traineddata" for lang in langs]
targets.append(tessdata_dir)
return targets
TESSDATA_DEPS = ["install/common/download-tessdata.py", "share/ocr-languages.json"]
TESSDATA_TARGETS = list_language_data()
IMAGE_DEPS = [
"Dockerfile",
"poetry.lock",
*list_files("dangerzone/conversion"),
"dangerzone/gvisor_wrapper/entrypoint.py",
"install/common/build-image.py",
]
IMAGE_TARGETS = ["share/container.tar.gz", "share/image-id.txt"]
SOURCE_DEPS = [
*list_files("assets"),
*list_files("share"),
*list_files("dangerzone", recursive=True),
]
PYTHON_DEPS = ["poetry.lock", "pyproject.toml"]
DMG_DEPS = [
*list_files("install/macos"),
*TESSDATA_TARGETS,
*IMAGE_TARGETS,
*PYTHON_DEPS,
*SOURCE_DEPS,
]
LINUX_DEPS = [
*list_files("install/linux"),
*IMAGE_TARGETS,
*PYTHON_DEPS,
*SOURCE_DEPS,
]
DEB_DEPS = [*LINUX_DEPS, *list_files("debian")]
RPM_DEPS = [*LINUX_DEPS, *list_files("qubes")]
def copy_dir(src, dst):
"""Copy a directory to a destination dir, and overwrite it if it exists."""
shutil.rmtree(dst, ignore_errors=True)
shutil.copytree(src, dst)
def create_release_dir():
RELEASE_DIR.mkdir(parents=True, exist_ok=True)
(RELEASE_DIR / "tmp").mkdir(exist_ok=True)
def build_linux_pkg(distro, version, cwd, qubes=False):
"""Generic command for building a .deb/.rpm in a Dangerzone dev environment."""
pkg = "rpm" if distro == "fedora" else "deb"
cmd = [
"python3",
"./dev_scripts/env.py",
"--distro",
distro,
"--version",
version,
"run",
"--no-gui",
"--dev",
f"./dangerzone/install/linux/build-{pkg}.py",
]
if qubes:
cmd += ["--qubes"]
return CmdAction(" ".join(cmd), cwd=cwd)
def build_deb(cwd):
"""Build a .deb package on Debian Bookworm."""
return build_linux_pkg(distro="debian", version="bookworm", cwd=cwd)
def build_rpm(version, cwd, qubes=False):
"""Build an .rpm package on the requested Fedora distro."""
return build_linux_pkg(distro="Fedora", version=version, cwd=cwd, qubes=qubes)
### Tasks
def task_clean_container_runtime():
"""Clean the storage space of the container runtime."""
return {
"actions": None,
"clean": [
[CONTAINER_RUNTIME, "system", "prune", "-a", "-f"],
],
}
def task_check_container_runtime():
"""Test that the container runtime is ready."""
return {
"actions": [
["which", CONTAINER_RUNTIME],
[CONTAINER_RUNTIME, "ps"],
],
}
def task_macos_check_cert():
"""Test that the Apple developer certificate can be used."""
return {
"actions": [
"xcrun notarytool history --apple-id %(apple_id)s --keychain-profile dz-notarytool-release-key"
],
"params": [PARAM_APPLE_ID],
}
def task_macos_check_system():
"""Run macOS specific system checks, as well as the generic ones."""
return {
"actions": None,
"task_dep": ["check_container_runtime", "macos_check_cert"],
}
def task_init_release_dir():
"""Create a directory for release artifacts."""
return {
"actions": [create_release_dir],
"clean": [f"rm -rf {RELEASE_DIR}"],
}
def task_download_tessdata():
"""Download the Tesseract data using ./install/common/download-tessdata.py"""
return {
"actions": ["python install/common/download-tessdata.py"],
"file_dep": TESSDATA_DEPS,
"targets": TESSDATA_TARGETS,
"clean": True,
}
def task_build_image():
"""Build the container image using ./install/common/build-image.py"""
img_src = "share/container.tar.gz"
img_dst = RELEASE_DIR / f"container-{VERSION}-{ARCH}.tar.gz" # FIXME: Add arch
img_id_src = "share/image-id.txt"
img_id_dst = RELEASE_DIR / "image-id.txt" # FIXME: Add arch
return {
"actions": [
f"python install/common/build-image.py --use-cache=%(use_cache)s --runtime={CONTAINER_RUNTIME}",
["cp", img_src, img_dst],
["cp", img_id_src, img_id_dst],
],
"params": [PARAM_USE_CACHE],
"file_dep": IMAGE_DEPS,
"targets": [img_src, img_dst, img_id_src, img_id_dst],
"task_dep": ["init_release_dir", "check_container_runtime"],
"clean": True,
}
def task_poetry_install():
"""Setup the Poetry environment"""
return {"actions": ["poetry install --sync"], "clean": ["poetry env remove --all"]}
def task_macos_build_dmg():
"""Build the macOS .dmg file for Dangerzone."""
dz_dir = RELEASE_DIR / "tmp" / "macos"
dmg_src = dz_dir / "dist" / "Dangerzone.dmg"
dmg_dst = RELEASE_DIR / f"Dangerzone-{VERSION}-{ARCH}.dmg" # FIXME: Add -arch
return {
"actions": [
(copy_dir, [".", dz_dir]),
f"cd {dz_dir} && poetry run install/macos/build-app.py --with-codesign",
(
"xcrun notarytool submit --wait --apple-id %(apple_id)s"
f" --keychain-profile dz-notarytool-release-key {dmg_src}"
),
f"xcrun stapler staple {dmg_src}",
["cp", dmg_src, dmg_dst],
["rm", "-rf", dz_dir],
],
"params": [PARAM_APPLE_ID],
"file_dep": DMG_DEPS,
"task_dep": [
"macos_check_system",
"init_release_dir",
"poetry_install",
"download_tessdata",
],
"targets": [dmg_src, dmg_dst],
"clean": True,
}
def task_debian_env():
"""Build a Debian Bookworm dev environment."""
return {
"actions": [
[
"python3",
"./dev_scripts/env.py",
"--distro",
"debian",
"--version",
"bookworm",
"build-dev",
]
],
"task_dep": ["check_container_runtime"],
}
def task_debian_deb():
"""Build a Debian package for Debian Bookworm."""
dz_dir = RELEASE_DIR / "tmp" / "debian"
deb_name = f"dangerzone_{VERSION}-1_amd64.deb"
deb_src = dz_dir / "deb_dist" / deb_name
deb_dst = RELEASE_DIR / deb_name
return {
"actions": [
(copy_dir, [".", dz_dir]),
build_deb(cwd=dz_dir),
["cp", deb_src, deb_dst],
["rm", "-rf", dz_dir],
],
"file_dep": DEB_DEPS,
"task_dep": ["init_release_dir", "debian_env"],
"targets": [deb_dst],
"clean": True,
}
def task_fedora_env():
"""Build Fedora dev environments."""
for version in FEDORA_VERSIONS:
yield {
"name": version,
"doc": f"Build Fedora {version} dev environments",
"actions": [
[
"python3",
"./dev_scripts/env.py",
"--distro",
"fedora",
"--version",
version,
"build-dev",
],
],
"task_dep": ["check_container_runtime"],
}
def task_fedora_rpm():
"""Build Fedora packages for every supported version."""
for version in FEDORA_VERSIONS:
for qubes in (True, False):
qubes_ident = "-qubes" if qubes else ""
qubes_desc = " for Qubes" if qubes else ""
dz_dir = RELEASE_DIR / "tmp" / f"f{version}{qubes_ident}"
rpm_names = [
f"dangerzone{qubes_ident}-{VERSION}-1.fc{version}.x86_64.rpm",
f"dangerzone{qubes_ident}-{VERSION}-1.fc{version}.src.rpm",
]
rpm_src = [dz_dir / "dist" / rpm_name for rpm_name in rpm_names]
rpm_dst = [RELEASE_DIR / rpm_name for rpm_name in rpm_names]
yield {
"name": version + qubes_ident,
"doc": f"Build a Fedora {version} package{qubes_desc}",
"actions": [
(copy_dir, [".", dz_dir]),
build_rpm(version, cwd=dz_dir, qubes=qubes),
["cp", *rpm_src, RELEASE_DIR],
["rm", "-rf", dz_dir],
],
"file_dep": RPM_DEPS,
"task_dep": ["init_release_dir", f"fedora_env:{version}"],
"targets": rpm_dst,
"clean": True,
}
def task_git_archive():
"""Build a Git archive of the repo."""
target = f"{RELEASE_DIR}/dangerzone-{VERSION}.tar.gz"
return {
"actions": [
f"git archive --format=tar.gz -o {target} --prefix=dangerzone/ v{VERSION}"
],
"targets": [target],
"task_dep": ["init_release_dir"],
}
#######################################################################################
#
# END OF TASKS
#
# The following task should be the LAST one in the dodo file, so that it runs first when
# running `do clean`.
def clean_prompt():
ans = input(
f"""
You have not specified a target to clean.
This means that doit will clean the following targets:
* ALL the containers, images, and build cache in {CONTAINER_RUNTIME.capitalize()}
* ALL the built targets and directories
For a full list of the targets that doit will clean, run: doit clean --dry-run
Are you sure you want to clean everything (y/N): \
"""
)
if ans.lower() in ["yes", "y"]:
return
else:
print("Exiting...")
exit(1)
def task_clean_prompt():
"""Make sure that the user really wants to run the clean tasks."""
return {
"actions": None,
"clean": [clean_prompt],
}

View file

@ -2,13 +2,12 @@ import argparse
import gzip
import os
import platform
import secrets
import subprocess
import sys
from pathlib import Path
BUILD_CONTEXT = "dangerzone/"
IMAGE_NAME = "dangerzone.rocks/dangerzone"
TAG = "dangerzone.rocks/dangerzone:latest"
REQUIREMENTS_TXT = "container-pip-requirements.txt"
if platform.system() in ["Darwin", "Windows"]:
CONTAINER_RUNTIME = "docker"
@ -18,17 +17,6 @@ elif platform.system() == "Linux":
ARCH = platform.machine()
def str2bool(v):
if isinstance(v, bool):
return v
if v.lower() in ("yes", "true", "t", "y", "1"):
return True
elif v.lower() in ("no", "false", "f", "n", "0"):
return False
else:
raise argparse.ArgumentTypeError("Boolean value expected.")
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
@ -51,39 +39,13 @@ def main():
)
parser.add_argument(
"--use-cache",
type=str2bool,
nargs="?",
default=False,
const=True,
action="store_true",
help="Use the builder's cache to speed up the builds (not suitable for release builds)",
)
args = parser.parse_args()
tarball_path = Path("share") / "container.tar.gz"
image_id_path = Path("share") / "image-id.txt"
print(f"Building for architecture '{ARCH}'")
# Designate a unique tag for this image, depending on the Git commit it was created
# from:
# 1. If created from a Git tag (e.g., 0.8.0), the image tag will be `0.8.0`.
# 2. If created from a commit, it will be something like `0.8.0-31-g6bdaa7a`.
# 3. If the contents of the Git repo are dirty, we will append a unique identifier
# for this run, something like `0.8.0-31-g6bdaa7a-fdcb` or `0.8.0-fdcb`.
dirty_ident = secrets.token_hex(2)
tag = (
subprocess.check_output(
["git", "describe", "--long", "--first-parent", f"--dirty=-{dirty_ident}"],
)
.decode()
.strip()[1:] # remove the "v" prefix of the tag.
)
image_name_tagged = IMAGE_NAME + ":" + tag
print(f"Will tag the container image as '{image_name_tagged}'")
with open(image_id_path, "w") as f:
f.write(tag)
print("Exporting container pip dependencies")
with ContainerPipDependencies():
if not args.use_cache:
@ -97,7 +59,6 @@ def main():
check=True,
)
# Build the container image, and tag it with the calculated tag
print("Building container image")
cache_args = [] if args.use_cache else ["--no-cache"]
subprocess.run(
@ -113,7 +74,7 @@ def main():
"-f",
"Dockerfile",
"--tag",
image_name_tagged,
TAG,
],
check=True,
)
@ -124,7 +85,7 @@ def main():
[
CONTAINER_RUNTIME,
"save",
image_name_tagged,
TAG,
],
stdout=subprocess.PIPE,
)
@ -132,7 +93,7 @@ def main():
print("Compressing container image")
chunk_size = 4 << 20
with gzip.open(
tarball_path,
"share/container.tar.gz",
"wb",
compresslevel=args.compress_level,
) as gzip_f:
@ -144,6 +105,21 @@ def main():
break
cmd.wait(5)
print("Looking up the image id")
image_id = subprocess.check_output(
[
args.runtime,
"image",
"list",
"--format",
"{{.ID}}",
TAG,
],
text=True,
)
with open("share/image-id.txt", "w") as f:
f.write(image_id)
class ContainerPipDependencies:
"""Generates PIP dependencies within container"""

View file

@ -17,23 +17,22 @@ signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd
REM verify the signature of dangerzone-cli.exe
signtool.exe verify /pa build\exe.win-amd64-3.12\dangerzone-cli.exe
REM build the wxs file
python install\windows\build-wxs.py
REM build the wix file
python install\windows\build-wxs.py > build\Dangerzone.wxs
REM build the msi package
cd build
wix build -arch x64 -ext WixToolset.UI.wixext .\Dangerzone.wxs -out Dangerzone.msi
REM validate Dangerzone.msi
wix msi validate Dangerzone.msi
candle.exe Dangerzone.wxs
light.exe -ext WixUIExtension Dangerzone.wixobj
REM code sign Dangerzone.msi
insignia.exe -im Dangerzone.msi
signtool.exe sign /v /d "Dangerzone" /a /n "Freedom of the Press Foundation" /fd sha256 /t http://time.certum.pl/ Dangerzone.msi
REM verify the signature of Dangerzone.msi
signtool.exe verify /pa Dangerzone.msi
REM move Dangerzone.msi to dist
REM moving Dangerzone.msi to dist
cd ..
mkdir dist
move build\Dangerzone.msi dist

View file

@ -4,75 +4,114 @@ import uuid
import xml.etree.ElementTree as ET
def build_data(base_path, path_prefix, dir_id, dir_name):
def build_data(dirname, dir_prefix, id_, name):
data = {
"directory_name": dir_name,
"directory_id": dir_id,
"id": id_,
"name": name,
"files": [],
"dirs": [],
}
if dir_id == "INSTALLFOLDER":
data["component_id"] = "ApplicationFiles"
else:
data["component_id"] = "Component" + dir_id
data["component_guid"] = str(uuid.uuid4()).upper()
for entry in os.listdir(base_path):
entry_path = os.path.join(base_path, entry)
if os.path.isfile(entry_path):
data["files"].append(os.path.join(path_prefix, entry))
elif os.path.isdir(entry_path):
if dir_id == "INSTALLFOLDER":
next_dir_prefix = "Folder"
for basename in os.listdir(dirname):
filename = os.path.join(dirname, basename)
if os.path.isfile(filename):
data["files"].append(os.path.join(dir_prefix, basename))
elif os.path.isdir(filename):
if id_ == "INSTALLDIR":
id_prefix = "Folder"
else:
next_dir_prefix = dir_id
id_prefix = id_
# Skip lib/PySide6/examples folder due to ilegal file names
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\examples" in base_path:
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\examples" in dirname:
continue
# Skip lib/PySide6/qml/QtQuick folder due to ilegal file names
# XXX Since we're not using Qml it should be no problem
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\qml\\QtQuick" in base_path:
if "\\build\\exe.win-amd64-3.12\\lib\\PySide6\\qml\\QtQuick" in dirname:
continue
next_dir_id = next_dir_prefix + entry.capitalize().replace("-", "_")
subdata = build_data(
os.path.join(base_path, entry),
os.path.join(path_prefix, entry),
next_dir_id,
entry,
id_value = f"{id_prefix}{basename.capitalize().replace('-', '_')}"
data["dirs"].append(
build_data(
os.path.join(dirname, basename),
os.path.join(dir_prefix, basename),
id_value,
basename,
)
)
# Add the subdirectory only if it contains files or subdirectories
if subdata["files"] or subdata["dirs"]:
data["dirs"].append(subdata)
if len(data["files"]) > 0:
if id_ == "INSTALLDIR":
data["component_id"] = "ApplicationFiles"
else:
data["component_id"] = "FolderComponent" + id_[len("Folder") :]
data["component_guid"] = str(uuid.uuid4())
return data
def build_directory_xml(root, data):
def build_dir_xml(root, data):
attrs = {}
attrs["Id"] = data["directory_id"]
attrs["Name"] = data["directory_name"]
directory_el = ET.SubElement(root, "Directory", attrs)
if "id" in data:
attrs["Id"] = data["id"]
if "name" in data:
attrs["Name"] = data["name"]
el = ET.SubElement(root, "Directory", attrs)
for subdata in data["dirs"]:
build_directory_xml(directory_el, subdata)
build_dir_xml(el, subdata)
# If this is the ProgramMenuFolder, add the menu component
if "id" in data and data["id"] == "ProgramMenuFolder":
component_el = ET.SubElement(
el,
"Component",
Id="ApplicationShortcuts",
Guid="539e7de8-a124-4c09-aa55-0dd516aad7bc",
)
ET.SubElement(
component_el,
"Shortcut",
Id="ApplicationShortcut1",
Name="Dangerzone",
Description="Dangerzone",
Target="[INSTALLDIR]dangerzone.exe",
WorkingDirectory="INSTALLDIR",
)
ET.SubElement(
component_el,
"RegistryValue",
Root="HKCU",
Key="Software\Freedom of the Press Foundation\Dangerzone",
Name="installed",
Type="integer",
Value="1",
KeyPath="yes",
)
def build_components_xml(root, data):
component_el = ET.SubElement(
root,
"Component",
Id=data["component_id"],
Guid=data["component_guid"],
Directory=data["directory_id"],
)
for filename in data["files"]:
ET.SubElement(component_el, "File", Source=filename)
component_ids = []
if "component_id" in data:
component_ids.append(data["component_id"])
for subdata in data["dirs"]:
build_components_xml(root, subdata)
if "component_guid" in subdata:
dir_ref_el = ET.SubElement(root, "DirectoryRef", Id=subdata["id"])
component_el = ET.SubElement(
dir_ref_el,
"Component",
Id=subdata["component_id"],
Guid=subdata["component_guid"],
)
for filename in subdata["files"]:
file_el = ET.SubElement(
component_el, "File", Source=filename, Id="file_" + uuid.uuid4().hex
)
component_ids += build_components_xml(root, subdata)
return component_ids
def main():
@ -86,188 +125,120 @@ def main():
# -rc markers.
version = f.read().strip().split("-")[0]
build_dir = os.path.join(
dist_dir = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
"build",
"exe.win-amd64-3.12",
)
cx_freeze_dir = "exe.win-amd64-3.12"
dist_dir = os.path.join(build_dir, cx_freeze_dir)
if not os.path.exists(dist_dir):
print("You must build the dangerzone binary before running this")
return
# Prepare data for WiX file harvesting from the output of cx_Freeze
data = build_data(
dist_dir,
cx_freeze_dir,
"INSTALLFOLDER",
"Dangerzone",
data = {
"id": "TARGETDIR",
"name": "SourceDir",
"dirs": [
{
"id": "ProgramFilesFolder",
"dirs": [],
},
{
"id": "ProgramMenuFolder",
"dirs": [],
},
],
}
data["dirs"][0]["dirs"].append(
build_data(
dist_dir,
"exe.win-amd64-3.12",
"INSTALLDIR",
"Dangerzone",
)
)
# Add the Wix root element
wix_el = ET.Element(
"Wix",
{
"xmlns": "http://wixtoolset.org/schemas/v4/wxs",
"xmlns:ui": "http://wixtoolset.org/schemas/v4/wxs/ui",
},
)
# Add the Package element
package_el = ET.SubElement(
wix_el,
"Package",
root_el = ET.Element("Wix", xmlns="http://schemas.microsoft.com/wix/2006/wi")
product_el = ET.SubElement(
root_el,
"Product",
Name="Dangerzone",
Manufacturer="Freedom of the Press Foundation",
UpgradeCode="12B9695C-965B-4BE0-BC33-21274E809576",
Id="*",
UpgradeCode="$(var.ProductUpgradeCode)",
Language="1033",
Compressed="yes",
Codepage="1252",
Version=version,
Version="$(var.ProductVersion)",
)
ET.SubElement(
package_el,
"SummaryInformation",
product_el,
"Package",
Id="*",
Keywords="Installer",
Description="Dangerzone " + version + " Installer",
Codepage="1252",
Description="Dangerzone $(var.ProductVersion) Installer",
Manufacturer="Freedom of the Press Foundation",
InstallerVersion="100",
Languages="1033",
Compressed="yes",
SummaryCodepage="1252",
)
ET.SubElement(package_el, "MediaTemplate", EmbedCab="yes")
ET.SubElement(product_el, "Media", Id="1", Cabinet="product.cab", EmbedCab="yes")
ET.SubElement(
package_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico"
product_el, "Icon", Id="ProductIcon", SourceFile="..\\share\\dangerzone.ico"
)
ET.SubElement(package_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon")
ET.SubElement(product_el, "Property", Id="ARPPRODUCTICON", Value="ProductIcon")
ET.SubElement(
package_el,
product_el,
"Property",
Id="ARPHELPLINK",
Value="https://dangerzone.rocks",
)
ET.SubElement(
package_el,
product_el,
"Property",
Id="ARPURLINFOABOUT",
Value="https://freedom.press",
)
ET.SubElement(
package_el, "ui:WixUI", Id="WixUI_InstallDir", InstallDirectory="INSTALLFOLDER"
product_el,
"Property",
Id="WIXUI_INSTALLDIR",
Value="INSTALLDIR",
)
ET.SubElement(package_el, "UIRef", Id="WixUI_ErrorProgressText")
ET.SubElement(product_el, "UIRef", Id="WixUI_InstallDir")
ET.SubElement(product_el, "UIRef", Id="WixUI_ErrorProgressText")
ET.SubElement(
package_el,
product_el,
"WixVariable",
Id="WixUILicenseRtf",
Value="..\\install\\windows\\license.rtf",
)
ET.SubElement(
package_el,
product_el,
"WixVariable",
Id="WixUIDialogBmp",
Value="..\\install\\windows\\dialog.bmp",
)
ET.SubElement(
package_el,
product_el,
"MajorUpgrade",
AllowSameVersionUpgrades="yes",
DowngradeErrorMessage="A newer version of [ProductName] is already installed. If you are sure you want to downgrade, remove the existing installation via Programs and Features.",
)
# Workaround for an issue after upgrading from WiX Toolset v3 to v5 where the previous
# version of Dangerzone is not uninstalled during the upgrade by checking if the older installation
# exists in "C:\Program Files (x86)\Dangerzone".
#
# Also handle a special case for Dangerzone 0.8.0 which allows choosing the install location
# during install by checking if the registry key for it exists.
#
# Note that this seems to allow installing Dangerzone 0.8.0 after installing Dangerzone from this branch.
# In this case the installer errors until Dangerzone 0.8.0 is uninstalled again
#
# TODO: Revert this once we are reasonably certain there aren't too many affected Dangerzone installations.
find_old_el = ET.SubElement(package_el, "Property", Id="OLDDANGERZONEFOUND")
directory_search_el = ET.SubElement(
find_old_el,
"DirectorySearch",
Id="dangerzone_install_folder",
Path="C:\\Program Files (x86)\\Dangerzone",
)
ET.SubElement(directory_search_el, "FileSearch", Name="dangerzone.exe")
registry_search_el = ET.SubElement(package_el, "Property", Id="DANGERZONE080FOUND")
ET.SubElement(
registry_search_el,
"RegistrySearch",
Root="HKLM",
Key="SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{03C2D2B2-9955-4AED-831F-DA4E67FC0FDB}",
Name="DisplayName",
Type="raw",
)
ET.SubElement(
package_el,
"Launch",
Condition="NOT OLDDANGERZONEFOUND AND NOT DANGERZONE080FOUND",
Message="A previous version of [ProductName] is already installed. Please uninstall it from Programs and Features before proceeding with the installation.",
)
build_dir_xml(product_el, data)
component_ids = build_components_xml(product_el, data)
# Add the ProgramMenuFolder StandardDirectory
programmenufolder_el = ET.SubElement(
package_el,
"StandardDirectory",
Id="ProgramMenuFolder",
)
# Add a shortcut for Dangerzone in the Start menu
shortcut_el = ET.SubElement(
programmenufolder_el,
"Component",
Id="ApplicationShortcuts",
Guid="539E7DE8-A124-4C09-AA55-0DD516AAD7BC",
)
ET.SubElement(
shortcut_el,
"Shortcut",
Id="DangerzoneStartMenuShortcut",
Name="Dangerzone",
Description="Dangerzone",
Target="[INSTALLFOLDER]dangerzone.exe",
WorkingDirectory="INSTALLFOLDER",
)
ET.SubElement(
shortcut_el,
"RegistryValue",
Root="HKCU",
Key="Software\\Freedom of the Press Foundation\\Dangerzone",
Name="installed",
Type="integer",
Value="1",
KeyPath="yes",
)
# Add the ProgramFilesFolder StandardDirectory
programfilesfolder_el = ET.SubElement(
package_el,
"StandardDirectory",
Id="ProgramFiles64Folder",
)
# Create the directory structure for the installed product
build_directory_xml(programfilesfolder_el, data)
# Create a component group for application components
applicationcomponents_el = ET.SubElement(
package_el, "ComponentGroup", Id="ApplicationComponents"
)
# Populate the application components group with components for the installed package
build_components_xml(applicationcomponents_el, data)
# Add the Feature element
feature_el = ET.SubElement(package_el, "Feature", Id="DefaultFeature", Level="1")
ET.SubElement(feature_el, "ComponentGroupRef", Id="ApplicationComponents")
feature_el = ET.SubElement(product_el, "Feature", Id="DefaultFeature", Level="1")
for component_id in component_ids:
ET.SubElement(feature_el, "ComponentRef", Id=component_id)
ET.SubElement(feature_el, "ComponentRef", Id="ApplicationShortcuts")
ET.indent(wix_el, space=" ")
with open(os.path.join(build_dir, "Dangerzone.wxs"), "w") as wxs_file:
wxs_file.write(ET.tostring(wix_el).decode())
print('<?xml version="1.0" encoding="windows-1252"?>')
print(f'<?define ProductVersion = "{version}"?>')
print('<?define ProductUpgradeCode = "12b9695c-965b-4be0-bc33-21274e809576"?>')
ET.indent(root_el)
print(ET.tostring(root_el).decode())
if __name__ == "__main__":

537
poetry.lock generated
View file

@ -13,24 +13,24 @@ files = [
[[package]]
name = "anyio"
version = "4.7.0"
version = "4.6.2.post1"
description = "High level compatibility layer for multiple asynchronous event loop implementations"
optional = false
python-versions = ">=3.9"
files = [
{file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"},
{file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"},
{file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"},
{file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"},
]
[package.dependencies]
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
idna = ">=2.8"
sniffio = ">=1.1"
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""}
[package.extras]
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"]
doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"]
trio = ["trio (>=0.26.1)"]
[[package]]
@ -44,15 +44,61 @@ files = [
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
[[package]]
name = "black"
version = "24.10.0"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.9"
files = [
{file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"},
{file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"},
{file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"},
{file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"},
{file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"},
{file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"},
{file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"},
{file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"},
{file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"},
{file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"},
{file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"},
{file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"},
{file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"},
{file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"},
{file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"},
{file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"},
{file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"},
{file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"},
{file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"},
{file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"},
{file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"},
{file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"},
]
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.10)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "certifi"
version = "2024.12.14"
version = "2024.8.30"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.6"
files = [
{file = "certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56"},
{file = "certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db"},
{file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"},
{file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"},
]
[[package]]
@ -183,17 +229,6 @@ files = [
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "cloudpickle"
version = "3.1.0"
description = "Pickler class to extend the standard pickle.Pickler functionality"
optional = false
python-versions = ">=3.8"
files = [
{file = "cloudpickle-3.1.0-py3-none-any.whl", hash = "sha256:fe11acda67f61aaaec473e3afe030feb131d78a43461b718185363384f1ba12e"},
{file = "cloudpickle-3.1.0.tar.gz", hash = "sha256:81a929b6e3c7335c863c771d673d105f02efdb89dfaba0c90495d1c64796601b"},
]
[[package]]
name = "colorama"
version = "0.4.6"
@ -207,73 +242,73 @@ files = [
[[package]]
name = "coverage"
version = "7.6.9"
version = "7.6.7"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.9"
files = [
{file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"},
{file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"},
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"},
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"},
{file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"},
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"},
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"},
{file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"},
{file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"},
{file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"},
{file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"},
{file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"},
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"},
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"},
{file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"},
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"},
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"},
{file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"},
{file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"},
{file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"},
{file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"},
{file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"},
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"},
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"},
{file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"},
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"},
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"},
{file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"},
{file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"},
{file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"},
{file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"},
{file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"},
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"},
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"},
{file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"},
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"},
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"},
{file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"},
{file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"},
{file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"},
{file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"},
{file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"},
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"},
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"},
{file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"},
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"},
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"},
{file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"},
{file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"},
{file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"},
{file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"},
{file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"},
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"},
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"},
{file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"},
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"},
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"},
{file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"},
{file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"},
{file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"},
{file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"},
{file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"},
{file = "coverage-7.6.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:108bb458827765d538abcbf8288599fee07d2743357bdd9b9dad456c287e121e"},
{file = "coverage-7.6.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c973b2fe4dc445cb865ab369df7521df9c27bf40715c837a113edaa2aa9faf45"},
{file = "coverage-7.6.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c6b24007c4bcd0b19fac25763a7cac5035c735ae017e9a349b927cfc88f31c1"},
{file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acbb8af78f8f91b3b51f58f288c0994ba63c646bc1a8a22ad072e4e7e0a49f1c"},
{file = "coverage-7.6.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad32a981bcdedb8d2ace03b05e4fd8dace8901eec64a532b00b15217d3677dd2"},
{file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:34d23e28ccb26236718a3a78ba72744212aa383141961dd6825f6595005c8b06"},
{file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e25bacb53a8c7325e34d45dddd2f2fbae0dbc230d0e2642e264a64e17322a777"},
{file = "coverage-7.6.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:af05bbba896c4472a29408455fe31b3797b4d8648ed0a2ccac03e074a77e2314"},
{file = "coverage-7.6.7-cp310-cp310-win32.whl", hash = "sha256:796c9b107d11d2d69e1849b2dfe41730134b526a49d3acb98ca02f4985eeff7a"},
{file = "coverage-7.6.7-cp310-cp310-win_amd64.whl", hash = "sha256:987a8e3da7da4eed10a20491cf790589a8e5e07656b6dc22d3814c4d88faf163"},
{file = "coverage-7.6.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e61b0e77ff4dddebb35a0e8bb5a68bf0f8b872407d8d9f0c726b65dfabe2469"},
{file = "coverage-7.6.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1a5407a75ca4abc20d6252efeb238377a71ce7bda849c26c7a9bece8680a5d99"},
{file = "coverage-7.6.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df002e59f2d29e889c37abd0b9ee0d0e6e38c24f5f55d71ff0e09e3412a340ec"},
{file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:673184b3156cba06154825f25af33baa2671ddae6343f23175764e65a8c4c30b"},
{file = "coverage-7.6.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69ad502f1a2243f739f5bd60565d14a278be58be4c137d90799f2c263e7049a"},
{file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60dcf7605c50ea72a14490d0756daffef77a5be15ed1b9fea468b1c7bda1bc3b"},
{file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9c2eb378bebb2c8f65befcb5147877fc1c9fbc640fc0aad3add759b5df79d55d"},
{file = "coverage-7.6.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c0317288f032221d35fa4cbc35d9f4923ff0dfd176c79c9b356e8ef8ef2dff4"},
{file = "coverage-7.6.7-cp311-cp311-win32.whl", hash = "sha256:951aade8297358f3618a6e0660dc74f6b52233c42089d28525749fc8267dccd2"},
{file = "coverage-7.6.7-cp311-cp311-win_amd64.whl", hash = "sha256:5e444b8e88339a2a67ce07d41faabb1d60d1004820cee5a2c2b54e2d8e429a0f"},
{file = "coverage-7.6.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f07ff574986bc3edb80e2c36391678a271d555f91fd1d332a1e0f4b5ea4b6ea9"},
{file = "coverage-7.6.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:49ed5ee4109258973630c1f9d099c7e72c5c36605029f3a91fe9982c6076c82b"},
{file = "coverage-7.6.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3e8796434a8106b3ac025fd15417315d7a58ee3e600ad4dbcfddc3f4b14342c"},
{file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3b925300484a3294d1c70f6b2b810d6526f2929de954e5b6be2bf8caa1f12c1"},
{file = "coverage-7.6.7-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c42ec2c522e3ddd683dec5cdce8e62817afb648caedad9da725001fa530d354"},
{file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0266b62cbea568bd5e93a4da364d05de422110cbed5056d69339bd5af5685433"},
{file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e5f2a0f161d126ccc7038f1f3029184dbdf8f018230af17ef6fd6a707a5b881f"},
{file = "coverage-7.6.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c132b5a22821f9b143f87446805e13580b67c670a548b96da945a8f6b4f2efbb"},
{file = "coverage-7.6.7-cp312-cp312-win32.whl", hash = "sha256:7c07de0d2a110f02af30883cd7dddbe704887617d5c27cf373362667445a4c76"},
{file = "coverage-7.6.7-cp312-cp312-win_amd64.whl", hash = "sha256:fd49c01e5057a451c30c9b892948976f5d38f2cbd04dc556a82743ba8e27ed8c"},
{file = "coverage-7.6.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:46f21663e358beae6b368429ffadf14ed0a329996248a847a4322fb2e35d64d3"},
{file = "coverage-7.6.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:40cca284c7c310d622a1677f105e8507441d1bb7c226f41978ba7c86979609ab"},
{file = "coverage-7.6.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77256ad2345c29fe59ae861aa11cfc74579c88d4e8dbf121cbe46b8e32aec808"},
{file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87ea64b9fa52bf395272e54020537990a28078478167ade6c61da7ac04dc14bc"},
{file = "coverage-7.6.7-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d608a7808793e3615e54e9267519351c3ae204a6d85764d8337bd95993581a8"},
{file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdd94501d65adc5c24f8a1a0eda110452ba62b3f4aeaba01e021c1ed9cb8f34a"},
{file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82c809a62e953867cf57e0548c2b8464207f5f3a6ff0e1e961683e79b89f2c55"},
{file = "coverage-7.6.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb684694e99d0b791a43e9fc0fa58efc15ec357ac48d25b619f207c41f2fd384"},
{file = "coverage-7.6.7-cp313-cp313-win32.whl", hash = "sha256:963e4a08cbb0af6623e61492c0ec4c0ec5c5cf74db5f6564f98248d27ee57d30"},
{file = "coverage-7.6.7-cp313-cp313-win_amd64.whl", hash = "sha256:14045b8bfd5909196a90da145a37f9d335a5d988a83db34e80f41e965fb7cb42"},
{file = "coverage-7.6.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f2c7a045eef561e9544359a0bf5784b44e55cefc7261a20e730baa9220c83413"},
{file = "coverage-7.6.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dd4e4a49d9c72a38d18d641135d2fb0bdf7b726ca60a103836b3d00a1182acd"},
{file = "coverage-7.6.7-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c95e0fa3d1547cb6f021ab72f5c23402da2358beec0a8e6d19a368bd7b0fb37"},
{file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f63e21ed474edd23f7501f89b53280014436e383a14b9bd77a648366c81dce7b"},
{file = "coverage-7.6.7-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead9b9605c54d15be228687552916c89c9683c215370c4a44f1f217d2adcc34d"},
{file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0573f5cbf39114270842d01872952d301027d2d6e2d84013f30966313cadb529"},
{file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:e2c8e3384c12dfa19fa9a52f23eb091a8fad93b5b81a41b14c17c78e23dd1d8b"},
{file = "coverage-7.6.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:70a56a2ec1869e6e9fa69ef6b76b1a8a7ef709972b9cc473f9ce9d26b5997ce3"},
{file = "coverage-7.6.7-cp313-cp313t-win32.whl", hash = "sha256:dbba8210f5067398b2c4d96b4e64d8fb943644d5eb70be0d989067c8ca40c0f8"},
{file = "coverage-7.6.7-cp313-cp313t-win_amd64.whl", hash = "sha256:dfd14bcae0c94004baba5184d1c935ae0d1231b8409eb6c103a5fd75e8ecdc56"},
{file = "coverage-7.6.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37a15573f988b67f7348916077c6d8ad43adb75e478d0910957394df397d2874"},
{file = "coverage-7.6.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6cce5c76985f81da3769c52203ee94722cd5d5889731cd70d31fee939b74bf0"},
{file = "coverage-7.6.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ab9763d291a17b527ac6fd11d1a9a9c358280adb320e9c2672a97af346ac2c"},
{file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6cf96ceaa275f071f1bea3067f8fd43bec184a25a962c754024c973af871e1b7"},
{file = "coverage-7.6.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aee9cf6b0134d6f932d219ce253ef0e624f4fa588ee64830fcba193269e4daa3"},
{file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2bc3e45c16564cc72de09e37413262b9f99167803e5e48c6156bccdfb22c8327"},
{file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:623e6965dcf4e28a3debaa6fcf4b99ee06d27218f46d43befe4db1c70841551c"},
{file = "coverage-7.6.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:850cfd2d6fc26f8346f422920ac204e1d28814e32e3a58c19c91980fa74d8289"},
{file = "coverage-7.6.7-cp39-cp39-win32.whl", hash = "sha256:c296263093f099da4f51b3dff1eff5d4959b527d4f2f419e16508c5da9e15e8c"},
{file = "coverage-7.6.7-cp39-cp39-win_amd64.whl", hash = "sha256:90746521206c88bdb305a4bf3342b1b7316ab80f804d40c536fc7d329301ee13"},
{file = "coverage-7.6.7-pp39.pp310-none-any.whl", hash = "sha256:0ddcb70b3a3a57581b450571b31cb774f23eb9519c2aaa6176d3a84c9fc57671"},
{file = "coverage-7.6.7.tar.gz", hash = "sha256:d79d4826e41441c9a118ff045e4bccb9fdbdcb1d02413e7ea6eb5c87b5439d24"},
]
[package.dependencies]
@ -284,61 +319,53 @@ toml = ["tomli"]
[[package]]
name = "cx-freeze"
version = "7.2.7"
version = "7.2.5"
description = "Create standalone executables from Python scripts"
optional = false
python-versions = ">=3.8"
files = [
{file = "cx_Freeze-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5fd56b5bfc37d38cc64700b8f17ff76632dcb03f739ca5b0f6038f8ab39e136d"},
{file = "cx_Freeze-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e065227b26477bfe8e0a2adc1e7b6bd318c7e0690b60bf1b2370598e5696c598"},
{file = "cx_Freeze-7.2.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da55ecd56e5e05f9485151d9af0abf18150f86ce4c2734d9a71fabba3f3cd524"},
{file = "cx_Freeze-7.2.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77866e408abe64869361590dc6aba8d57d7e7ab4693c06f1334eabc5d7155361"},
{file = "cx_Freeze-7.2.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9b1a27ce838d4c262264e3aa6a5e7c5d4468a870ee873e3a49269a8752ab61fa"},
{file = "cx_Freeze-7.2.7-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:97a227c567e86b7d3b83ab5f96f96070b2396943c47d7c64d643770944acd5c5"},
{file = "cx_Freeze-7.2.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:839140debb42792be2ff6f0e8b261f3e230d0dfe71164c06ee8fa1bbaf2ff1a9"},
{file = "cx_Freeze-7.2.7-cp310-cp310-win32.whl", hash = "sha256:c34a6e85897f5cb1be84a204feef564adb6f1c753626bf0cf7713a8c4809ed27"},
{file = "cx_Freeze-7.2.7-cp310-cp310-win_amd64.whl", hash = "sha256:5ea3f05d31a7432b0516a58e4b7301277c0a5ac693597d531d9ca913d79169ce"},
{file = "cx_Freeze-7.2.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:65e900cec673dc9bf6b8e21a760b3964b5b20b2586f274184c4c474c78e343f3"},
{file = "cx_Freeze-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c9119b8dbe93b362d4658ed817e28d9d2fd9c29d5ade565ca23cd0e049440a8"},
{file = "cx_Freeze-7.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3a26b65bd4e2414bbab84307a906ea207de3c1f3198bc9f6286a81597ddebe38"},
{file = "cx_Freeze-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa7e75e8dd4ffce44465141c2857fcf5fb5a31dfbcabbbee9929b55e79f051e2"},
{file = "cx_Freeze-7.2.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b1fe00f689a0c06197bef07ba3dce985d768c405e0042dfa796bbafa53d6b4"},
{file = "cx_Freeze-7.2.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:34d0ec5a41d59c55878711cf3812f46444d71f911c8ed1de467591c36aed9d6d"},
{file = "cx_Freeze-7.2.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3131a443473c21e019317419a9b3a82b2ae1cec5c76bce6c9e2f36f41803720a"},
{file = "cx_Freeze-7.2.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:93179c236c3e1e64b6b8101d9012a8084df74736ed5cf10047721b4c7c2076ef"},
{file = "cx_Freeze-7.2.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:05dac0676586eed96fbde187fd63ace05ec0c4462ec64367a7d47af0d266008e"},
{file = "cx_Freeze-7.2.7-cp311-cp311-win32.whl", hash = "sha256:909784281dad31c92c0f402894d99a6188bb81f22b593d9d55fc437d54ab35e0"},
{file = "cx_Freeze-7.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:8fff35321128f680a825d28074edd14b81bdd68b98128d4fa655c6c8901de3ce"},
{file = "cx_Freeze-7.2.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6e22b449cf401a7d152e97a5feec71b492083067ca0336b74eb988fbfebfb3a6"},
{file = "cx_Freeze-7.2.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8611df6e740a7980145f2d6a3d9f9161fe4d6a12c0055da52ccccffe7d77d470"},
{file = "cx_Freeze-7.2.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66fa38df2423a3ded9c59a6a089293db3b7a6d3493a701b31d24f97cd29f2143"},
{file = "cx_Freeze-7.2.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41c6fd1e77a733fc1a0f4e3678e136726cc45a11b37ff5722ac18afdad703934"},
{file = "cx_Freeze-7.2.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d00dc0bf7ed9a30491ec34bfb8f4347446afae506febc2afc2531ecbf9db94bf"},
{file = "cx_Freeze-7.2.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5a27b2a1925138e122435ef3d52c281d185a57d1f67e08232ea61be83174cd0"},
{file = "cx_Freeze-7.2.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4ef57f4102a658e10704619294180b17a2d6c6595ff3753c9557b05e6a928c7f"},
{file = "cx_Freeze-7.2.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:caa77cbbcb0665c6e5a5b2e31a13b63e99201a8f258244bcfc1aed737484070f"},
{file = "cx_Freeze-7.2.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:58280540d54fd248120fa9acb1ac9d229fa9b508297f855da05e3f4ed9255a2e"},
{file = "cx_Freeze-7.2.7-cp312-cp312-win32.whl", hash = "sha256:344276a8354e4688c20dd3cf70c143d7666f1b93f56532ba9549718c2fac4e56"},
{file = "cx_Freeze-7.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:9a727914e702d98648def98626b13864413a4a5d52b8bd7f6f0d8ffaf7b9207f"},
{file = "cx_Freeze-7.2.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca1e82671db7ba48993a52bc351e34115eb2c5c91d8549d3ab60d717b941e6a3"},
{file = "cx_Freeze-7.2.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:58dbe6c30ab6583cf52f1b38a2f0901a57c80dee84f01271c9e6cfe3b7476ed0"},
{file = "cx_Freeze-7.2.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdb6725bc74e6e501b3289f6b58604bec94e0790f7bd5112e6f35c3e2638c6eb"},
{file = "cx_Freeze-7.2.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dd3da95443bf0bc7eb1df23f55a35f42449f1d211c5edde59a2bd811030141c6"},
{file = "cx_Freeze-7.2.7-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0064f3de0dea0ee3a89cc864b13b1bd0382d163e28e23518e7cce5efd94a87a6"},
{file = "cx_Freeze-7.2.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca1ae3b12abc6472f9e9ab6e66265941e044770f29bc64db7c545eff5611af68"},
{file = "cx_Freeze-7.2.7-cp313-cp313-win32.whl", hash = "sha256:546192ab868b9a4123c252d2fdd0af349b71242ae9706fff201b1c3dabe928c0"},
{file = "cx_Freeze-7.2.7-cp313-cp313-win_amd64.whl", hash = "sha256:a1de14dc9960ae4065d053bcd5a801ef51761c7f92998f5a42810098eb337102"},
{file = "cx_Freeze-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e36fe7ad2974db158293672c9f1ae750e9e08fda56b6281760a23b77770bd240"},
{file = "cx_Freeze-7.2.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6523d10c7066de2d17f60daac8256169e330f207b786ee1d071273b4a1e7c1fc"},
{file = "cx_Freeze-7.2.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ff3587628a73d46e1d9d7c8d7ed6e6fe1b4c7aebbeb034b923b6fabdbbe5149f"},
{file = "cx_Freeze-7.2.7-cp38-cp38-win32.whl", hash = "sha256:9859ba95a103f3b684553e4728fece150cf42b91c9f28873c661a6a3cbc3e226"},
{file = "cx_Freeze-7.2.7-cp38-cp38-win_amd64.whl", hash = "sha256:c359c8333ea798373428f87fc6a56c6d89bdba6f974a55b7c3d6259342ff7036"},
{file = "cx_Freeze-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:638f0107a95c639633cff2e84f9ec55b6687612a460a5f42e1ffc36a8c30e51d"},
{file = "cx_Freeze-7.2.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e324896652b44f06e8fecd0da706bad37311a639a1128bbd41cb19cf3069d4f1"},
{file = "cx_Freeze-7.2.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:fc4ab1bd795dc7c985418fff3431bd6a3712df2e84e2f98d8c65d3a748fb5e14"},
{file = "cx_Freeze-7.2.7-cp39-cp39-win32.whl", hash = "sha256:0c8512d24a7dcf3b35bd1e17f9e2d6f6a75e227aff243cd00c1aa4668b64ec01"},
{file = "cx_Freeze-7.2.7-cp39-cp39-win_amd64.whl", hash = "sha256:3b1cdb80b75e142a3e85e71721edd8c423ff41a974dfe9436a09593129446ebf"},
{file = "cx_freeze-7.2.7.tar.gz", hash = "sha256:c4d43a4e5357352ac5a89203fda3a8cfadf033d43682f73abbefd3860ce50fb4"},
{file = "cx_Freeze-7.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2fee88d083d25ff8e25d8c8b019471d84156579983c09386f858c5993701165"},
{file = "cx_Freeze-7.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e964e72765e29def25c6ecb55d7b12c6f82a65ef3bea6cdfd29ca11e8ad7bf97"},
{file = "cx_Freeze-7.2.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce6812cf281312294de8c6d3f6c477fb7aef3882295c89f0b1a313cbdc460092"},
{file = "cx_Freeze-7.2.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e45fd912d979acf9764672d909a17eec19c43a96044c5b91ba17d812e2e52074"},
{file = "cx_Freeze-7.2.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4f3327800b988bf9d54636ff47423e986b649fa6f0e26e5033dfa4d7c4a86def"},
{file = "cx_Freeze-7.2.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:eacd1b1d8b0428d3a70428a4f5671f1aab61968bee23afc804af8c22ba21c1dd"},
{file = "cx_Freeze-7.2.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1127748fe62a888af8689c005fc8dabad586441d51370c0c49041d66b394c507"},
{file = "cx_Freeze-7.2.5-cp310-cp310-win32.whl", hash = "sha256:bab38d4ea9d79c1c31d8e2d93f47560dd32bf84251d907f0333178c25ae5dff3"},
{file = "cx_Freeze-7.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:2ad91b52cf41e4b097967861b0b1d6698ddd4f3933ae06707477331467510825"},
{file = "cx_Freeze-7.2.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0f69c499b37baed9ca6fe9bb89eed73514d721ae55fe262a3b337bf9322f794c"},
{file = "cx_Freeze-7.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f313b12e1daf5408986906fd5040505b1be75b86a66bea8af3e164c4d3b6a0ba"},
{file = "cx_Freeze-7.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:152c13fe9896d246a68fc32987e60afef46e6c8b2386f13265a8c87d54953575"},
{file = "cx_Freeze-7.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:694887e5439ecbb9d21b96ebb89ee8f4f9ad21e0fb6ee86a60d0f823b1358182"},
{file = "cx_Freeze-7.2.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8e972d72e43142490d3389e843c24af50ddf031150fdf26be8a70e26c7317244"},
{file = "cx_Freeze-7.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3290c6c155fc33c81fa557bae5d648441066fb55a9ff41558b3ea2b085ba3e5b"},
{file = "cx_Freeze-7.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:431771e5a199e8cffaa7a448212a06b04316b3a268a7e6bbbc509ed62339cf4f"},
{file = "cx_Freeze-7.2.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c9933f8346416b23a6d91ec27156aa1d67a0225e69c6602ca42b7d84f65c12c5"},
{file = "cx_Freeze-7.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1843a959e76f407ca86992517236dd11d2fd3568b7ae87396102d4964dc899be"},
{file = "cx_Freeze-7.2.5-cp311-cp311-win32.whl", hash = "sha256:4419722a93a1cf2fef45c1096ea5e1ca402bfc59c0adcb326776561881292cb1"},
{file = "cx_Freeze-7.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:975a25e9cfda1c4c956bc20e8f712416c9b24a8dbe89f56c3f1c20c6e1c77006"},
{file = "cx_Freeze-7.2.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fcd644ac4e8938b4984a1f9af10f29bec9ddc47390f608d1cfd608d0fa3d38"},
{file = "cx_Freeze-7.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de019dd0d537eac05d6bd40452d6e5b83abe3b141d9ae2c757eb68b82760093f"},
{file = "cx_Freeze-7.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fb4b5bb07773fed714e59fc994a40cdc585d9e86bf01a87053906aacbe02aea"},
{file = "cx_Freeze-7.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d0b660d888b18562aa06181f957ae5fcea62767df09b8fbe4613f079343d3fd"},
{file = "cx_Freeze-7.2.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99116e7cb03d1c648c6351e7e0b9c2ed24be614118a4c52f00f8e34afa61d634"},
{file = "cx_Freeze-7.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3b731c9b6c217d1b22d24a71692f481c26ea6ce14dc6d5f7b18cbd89e0f942e"},
{file = "cx_Freeze-7.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a37384ca157c7f4f527d8075853622fab0870fa3481a00394704e1599ad00486"},
{file = "cx_Freeze-7.2.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4cad6a0cdf1e198674c3fc5f84027edd1dd3dc3a9de94a4c9573c9afb7a66e7"},
{file = "cx_Freeze-7.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:389f2ff47a624016e5410bfe4906357554e6f191000e658f06e87c51d649cd3b"},
{file = "cx_Freeze-7.2.5-cp312-cp312-win32.whl", hash = "sha256:c28960966ae87b53a07519d3c1d64735f67d5c2c866d574657f26a97b033df0d"},
{file = "cx_Freeze-7.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:e7271051c32b0afd0f504d16eb79288a4abc6d118eb7939ef38cabab68b22b9a"},
{file = "cx_Freeze-7.2.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c8bbe721146a611c4c37847c6ecb61da3b3404c150a7c19b16a520004090ca1d"},
{file = "cx_Freeze-7.2.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31fc0315ccd082d9c14ffc15efd95e86d3cdfe27906c6d8777a9cca7a19823a4"},
{file = "cx_Freeze-7.2.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:4c3419d957725dddd1b9c5adbbe004f5b0bd84888dfe362997db2fa754241f20"},
{file = "cx_Freeze-7.2.5-cp38-cp38-win32.whl", hash = "sha256:2fbf6cf8198f23da8b85d8bdc3c680402465433f402cd1bedc8a7c8681d0bda0"},
{file = "cx_Freeze-7.2.5-cp38-cp38-win_amd64.whl", hash = "sha256:d3f9c7e81b970f8d9e0d3527232cf8e4d486fc8768f185d787f21c3045c3ba24"},
{file = "cx_Freeze-7.2.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:001a8d61e1b93425f3fee84e2cba0ddad07897da7777e576124b674f7952396c"},
{file = "cx_Freeze-7.2.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eaf9aaa15f472cdb05a10d20fc4e8239c3d02348945b09ad10bec777e35db8a"},
{file = "cx_Freeze-7.2.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:03b6cb977a17134c8470f2e626cc52f026d590e900fed75e205310d61515c00d"},
{file = "cx_Freeze-7.2.5-cp39-cp39-win32.whl", hash = "sha256:0b9a1e349e84ef9edc035451714ccd5fabe829336026d4073c987b15ccf63246"},
{file = "cx_Freeze-7.2.5-cp39-cp39-win_amd64.whl", hash = "sha256:bcb4077f531a3559f89394b5f1ddba4e92033bd79653bd09854b705389ebdc07"},
{file = "cx_freeze-7.2.5.tar.gz", hash = "sha256:63f9b745b8a84a1c3ce986d089f3d750ec65b9ff385ed64308c073a821d81eef"},
]
[package.dependencies]
@ -351,7 +378,7 @@ tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.10\""}
[package.extras]
dev = ["build (>=1.2.0)", "bump-my-version (==0.26.1)", "cibuildwheel (==2.22.0)", "pre-commit (>=3.5.0,<=3.8.0)"]
dev = ["bump-my-version (==0.26.1)", "cibuildwheel (==2.21.1)", "pre-commit (>=3.5.0,<=3.8.0)"]
doc = ["furo (==2024.8.6)", "myst-parser (>=3.0.1,<=4.0.0)", "sphinx (>=7.1.2,<8)", "sphinx-new-tab-link (==0.6.0)", "sphinx-tabs (==3.4.5)"]
test = ["coverage (==7.6.1)", "pluggy (==1.5.0)", "pytest (==8.3.3)", "pytest-cov (==5.0.0)", "pytest-datafiles (==3.0.0)", "pytest-mock (==3.14.0)", "pytest-timeout (==2.3.1)", "pytest-xdist[psutil] (==3.6.1)"]
@ -385,24 +412,6 @@ files = [
{file = "cx_logging-3.2.1.tar.gz", hash = "sha256:812665ae5012680a6fe47095c3772bce638e47cf05b2c3483db3bdbe6b06da44"},
]
[[package]]
name = "doit"
version = "0.36.0"
description = "doit - Automation Tool"
optional = false
python-versions = ">=3.8"
files = [
{file = "doit-0.36.0-py3-none-any.whl", hash = "sha256:ebc285f6666871b5300091c26eafdff3de968a6bd60ea35dd1e3fc6f2e32479a"},
{file = "doit-0.36.0.tar.gz", hash = "sha256:71d07ccc9514cb22fe59d98999577665eaab57e16f644d04336ae0b4bae234bc"},
]
[package.dependencies]
cloudpickle = "*"
importlib-metadata = ">=4.4"
[package.extras]
toml = ["tomli"]
[[package]]
name = "exceptiongroup"
version = "1.2.2"
@ -522,6 +531,20 @@ files = [
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
]
[[package]]
name = "isort"
version = "5.13.2"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.8.0"
files = [
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
]
[package.extras]
colors = ["colorama (>=0.4.6)"]
[[package]]
name = "lief"
version = "0.15.1"
@ -686,6 +709,33 @@ files = [
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
]
[[package]]
name = "pathspec"
version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
]
[[package]]
name = "platformdirs"
version = "4.3.6"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
python-versions = ">=3.8"
files = [
{file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"},
{file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"},
]
[package.extras]
docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"]
type = ["mypy (>=1.11.2)"]
[[package]]
name = "pluggy"
version = "1.5.0"
@ -769,54 +819,54 @@ files = [
[[package]]
name = "pyside6"
version = "6.8.1"
version = "6.8.0.2"
description = "Python bindings for the Qt cross-platform application and UI framework"
optional = false
python-versions = "<3.14,>=3.9"
files = [
{file = "PySide6-6.8.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:6d1fd95651cdbdea741af21e155350986eca31ff015fc4c721ce01c2a110a4cc"},
{file = "PySide6-6.8.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7d6adc5d53313249bbe02edb673877c1d437e215d71e88da78412520653f5c9f"},
{file = "PySide6-6.8.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:ddeeaeca8ebd0ddb1ded30dd33e9240a40f330cc91832de346ba6c9d0cd1253e"},
{file = "PySide6-6.8.1-cp39-abi3-win_amd64.whl", hash = "sha256:866eeaca3ffead6b9d30fa3ed395d5624da0246d7586c8b8207e77ac65d82458"},
{file = "PySide6-6.8.0.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:cecc6ce1da6cb04542ff5a0887734f63e6ecf54258d1786285b9c7904abd9b01"},
{file = "PySide6-6.8.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3258f3c63dc5053b8d5b8d2588caca8bb3a36e2f74413511e4676df0e73b6f1e"},
{file = "PySide6-6.8.0.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:6a25cf784f978fa2a23b4d089970b27ebe14d26adcaf38b2819cb04483de4ce9"},
{file = "PySide6-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:3e8fffca9a934e30c07c3f34bb572f84bfcf02385acbc715e65fbdd9746ecc2b"},
]
[package.dependencies]
PySide6-Addons = "6.8.1"
PySide6-Essentials = "6.8.1"
shiboken6 = "6.8.1"
PySide6-Addons = "6.8.0.2"
PySide6-Essentials = "6.8.0.2"
shiboken6 = "6.8.0.2"
[[package]]
name = "pyside6-addons"
version = "6.8.1"
version = "6.8.0.2"
description = "Python bindings for the Qt cross-platform application and UI framework (Addons)"
optional = false
python-versions = "<3.14,>=3.9"
files = [
{file = "PySide6_Addons-6.8.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:879c12346b4b76f5d5ee6499d8ca53b5666c0c998b8fdf8780f08f69ea95d6f9"},
{file = "PySide6_Addons-6.8.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f80cc03c1ac54132c6f800aa461dced64acd7d1646898db164ccb56fe3c23dd4"},
{file = "PySide6_Addons-6.8.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:570a25016d80046274f454ed0bb06734f478ce6c21be5dec62b624773fc7504e"},
{file = "PySide6_Addons-6.8.1-cp39-abi3-win_amd64.whl", hash = "sha256:d7c8c1e89ee0db84631d5b8fdb9129d9d2a0ffb3b4cb2f5192dc8367dd980db4"},
{file = "PySide6_Addons-6.8.0.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:30c9ca570dd18ffbfd34ee95e0a319c34313a80425c4011d6ccc9f4cca0dc4c8"},
{file = "PySide6_Addons-6.8.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:754a9822ab2dc313f9998edef69d8a12bc9fd61727543f8d30806ed272ae1e52"},
{file = "PySide6_Addons-6.8.0.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:553f3fa412f423929b5cd8b7d43fd5f02161851f10a438174a198b0f1a044df7"},
{file = "PySide6_Addons-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:ae4377a3e10fe720a9119677b31d8de13e2a5221c06b332df045af002f5f4c3d"},
]
[package.dependencies]
PySide6-Essentials = "6.8.1"
shiboken6 = "6.8.1"
PySide6-Essentials = "6.8.0.2"
shiboken6 = "6.8.0.2"
[[package]]
name = "pyside6-essentials"
version = "6.8.1"
version = "6.8.0.2"
description = "Python bindings for the Qt cross-platform application and UI framework (Essentials)"
optional = false
python-versions = "<3.14,>=3.9"
files = [
{file = "PySide6_Essentials-6.8.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:bd05155245e3cd1572e68d72772e78fadfd713575bbfdd2c5e060d5278e390e9"},
{file = "PySide6_Essentials-6.8.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2f600b149e65b57acd6a444edb17615adc42cc2491548ae443ccb574036d86b1"},
{file = "PySide6_Essentials-6.8.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:bf8a3c9ee0b997eb18fb00cb09aacaa28b8a51ce3c295a252cc594c5530aba56"},
{file = "PySide6_Essentials-6.8.1-cp39-abi3-win_amd64.whl", hash = "sha256:d5ed4ddb149f36d65bc49ae4260b2d213ee88b2d9a309012ae27f38158c2d1b6"},
{file = "PySide6_Essentials-6.8.0.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:3df4ed75bbb74d74ac338b330819b1a272e7f5cec206765c7176a197c8bc9c79"},
{file = "PySide6_Essentials-6.8.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:7df6d6c1da4858dbdea77c74d7270d9c68e8d1bbe3362892abd1a5ade3815a50"},
{file = "PySide6_Essentials-6.8.0.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:cf490145d18812a6cff48b0b0afb0bfaf7066744bfbd09eb071c3323f1d6d00d"},
{file = "PySide6_Essentials-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:d2f029b8c9f0106f57b26aa8c435435d7f509c80525075343e07177b283f862e"},
]
[package.dependencies]
shiboken6 = "6.8.1"
shiboken6 = "6.8.0.2"
[[package]]
name = "pytest"
@ -987,64 +1037,37 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "ruff"
version = "0.8.3"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"},
{file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"},
{file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"},
{file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"},
{file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"},
{file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"},
{file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"},
]
[[package]]
name = "setuptools"
version = "75.6.0"
version = "75.5.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.9"
files = [
{file = "setuptools-75.6.0-py3-none-any.whl", hash = "sha256:ce74b49e8f7110f9bf04883b730f4765b774ef3ef28f722cce7c273d253aaf7d"},
{file = "setuptools-75.6.0.tar.gz", hash = "sha256:8199222558df7c86216af4f84c30e9b34a61d8ba19366cc914424cdbd28252f6"},
{file = "setuptools-75.5.0-py3-none-any.whl", hash = "sha256:87cb777c3b96d638ca02031192d40390e0ad97737e27b6b4fa831bea86f2f829"},
{file = "setuptools-75.5.0.tar.gz", hash = "sha256:5c4ccb41111392671f02bb5f8436dfc5a9a7185e80500531b133f5775c4163ef"},
]
[package.extras]
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.7.0)"]
core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
core = ["importlib-metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more-itertools", "more-itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
cover = ["pytest-cov"]
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
enabler = ["pytest-enabler (>=2.2)"]
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"]
type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12,<1.14)", "pytest-mypy"]
[[package]]
name = "shiboken6"
version = "6.8.1"
version = "6.8.0.2"
description = "Python/C++ bindings helper module"
optional = false
python-versions = "<3.14,>=3.9"
files = [
{file = "shiboken6-6.8.1-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:9a2f51d1ddd3b6d193a0f0fdc09f8d41f2092bc664723c9b9efc1056660d0608"},
{file = "shiboken6-6.8.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1dc4c1976809b0e68872bb98474cccd590455bdcd015f0e0639907e94af27b6a"},
{file = "shiboken6-6.8.1-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:ab5b60602ca6227103138aae89c4f5df3b1b8e249cbc8ec9e6e2a57f20ad9a91"},
{file = "shiboken6-6.8.1-cp39-abi3-win_amd64.whl", hash = "sha256:3ea127fd72be113b73cacd70e06687ad6f83c1c888047833c7dcdd5cf8e7f586"},
{file = "shiboken6-6.8.0.2-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:9019e1fcfeed8bb350222e981748ef05a2fec11e31ddf616657be702f0b7a468"},
{file = "shiboken6-6.8.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:fa7d411c3c67b4296847b3f5f572268e219d947d029ff9d8bce72fe6982d92bc"},
{file = "shiboken6-6.8.0.2-cp39-abi3-manylinux_2_31_aarch64.whl", hash = "sha256:1aaa8b7f9138818322ef029b2c487d1c6e00dc3f53084e62e1d11bdea47e47c2"},
{file = "shiboken6-6.8.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:b11e750e696bb565d897e0f5836710edfb86bd355f87b09988bd31b2aad404d3"},
]
[[package]]
@ -1071,93 +1094,26 @@ files = [
[[package]]
name = "tomli"
version = "2.2.1"
version = "2.1.0"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
]
[[package]]
name = "types-colorama"
version = "0.4.15.20240311"
description = "Typing stubs for colorama"
optional = false
python-versions = ">=3.8"
files = [
{file = "types-colorama-0.4.15.20240311.tar.gz", hash = "sha256:a28e7f98d17d2b14fb9565d32388e419f4108f557a7d939a66319969b2b99c7a"},
{file = "types_colorama-0.4.15.20240311-py3-none-any.whl", hash = "sha256:6391de60ddc0db3f147e31ecb230006a6823e81e380862ffca1e4695c13a0b8e"},
]
[[package]]
name = "types-docutils"
version = "0.21.0.20241128"
description = "Typing stubs for docutils"
optional = false
python-versions = ">=3.8"
files = [
{file = "types_docutils-0.21.0.20241128-py3-none-any.whl", hash = "sha256:e0409204009639e9b0bf4521eeabe58b5e574ce9c0db08421c2ac26c32be0039"},
{file = "types_docutils-0.21.0.20241128.tar.gz", hash = "sha256:4dd059805b83ac6ec5a223699195c4e9eeb0446a4f7f2aeff1759a4a7cc17473"},
{file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"},
{file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"},
]
[[package]]
name = "types-markdown"
version = "3.7.0.20241204"
version = "3.7.0.20240822"
description = "Typing stubs for Markdown"
optional = false
python-versions = ">=3.8"
files = [
{file = "types_Markdown-3.7.0.20241204-py3-none-any.whl", hash = "sha256:f96146c367ea9c82bfe9903559d72706555cc2a1a3474c58ebba03b418ab18da"},
{file = "types_markdown-3.7.0.20241204.tar.gz", hash = "sha256:ecca2b25cd23163fd28ed5ba34d183d731da03e8a5ed3a20b60daded304c5410"},
{file = "types-Markdown-3.7.0.20240822.tar.gz", hash = "sha256:183557c9f4f865bdefd8f5f96a38145c31819271cde111d35557c3bd2069e78d"},
{file = "types_Markdown-3.7.0.20240822-py3-none-any.whl", hash = "sha256:bec91c410aaf2470ffdb103e38438fbcc53689b00133f19e64869eb138432ad7"},
]
[[package]]
name = "types-pygments"
version = "2.18.0.20240506"
description = "Typing stubs for Pygments"
optional = false
python-versions = ">=3.8"
files = [
{file = "types-Pygments-2.18.0.20240506.tar.gz", hash = "sha256:4b4c37812c87bbde687dbf27adf5bac593745a321e57f678dbc311571ba2ac9d"},
{file = "types_Pygments-2.18.0.20240506-py3-none-any.whl", hash = "sha256:11c90bc1737c9af55e5569558b88df7c2233e12325cb516215f722271444e91d"},
]
[package.dependencies]
types-docutils = "*"
types-setuptools = "*"
[[package]]
name = "types-pyside2"
version = "5.15.2.1.7"
@ -1183,17 +1139,6 @@ files = [
[package.dependencies]
urllib3 = ">=2"
[[package]]
name = "types-setuptools"
version = "75.6.0.20241126"
description = "Typing stubs for setuptools"
optional = false
python-versions = ">=3.8"
files = [
{file = "types_setuptools-75.6.0.20241126-py3-none-any.whl", hash = "sha256:aaae310a0e27033c1da8457d4d26ac673b0c8a0de7272d6d4708e263f2ea3b9b"},
{file = "types_setuptools-75.6.0.20241126.tar.gz", hash = "sha256:7bf25ad4be39740e469f9268b6beddda6e088891fa5a27e985c6ce68bf62ace0"},
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
@ -1244,4 +1189,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.13"
content-hash = "68663ce40ba8a7c7f7cc7868e5f771472555773ff2ef04dad7e0150218ca3eb0"
content-hash = "5d1ff28aa04c3a814280e55c0b2a307efe5ca953cd4cb281056c35fd2e53fdf0"

View file

@ -1,4 +1,4 @@
[tool.poetry]
[tool.poetry]
name = "dangerzone"
version = "0.8.0"
description = "Take potentially dangerous PDFs, office documents, or images and convert them to safe PDFs"
@ -34,17 +34,14 @@ setuptools = "*"
cx_freeze = {version = "^7.2.5", platform = "win32"}
pywin32 = {version = "*", platform = "win32"}
pyinstaller = {version = "*", platform = "darwin"}
doit = "^0.36.0"
# Dependencies required for linting the code.
[tool.poetry.group.lint.dependencies]
click = "*" # Install click so mypy is able to reason about it.
black = "*"
isort = "*"
mypy = "*"
ruff = "*"
types-colorama = "*"
types-PySide2 = "*"
types-Markdown = "*"
types-pygments = "*"
types-requests = "*"
# Dependencies required for testing the code.
@ -63,19 +60,11 @@ pymupdf = "1.24.11" # Last version to support python 3.8 (needed for Ubuntu Foca
[tool.poetry.group.dev.dependencies]
httpx = "^0.27.2"
[tool.doit]
verbosity = 3
[tool.doit.tasks.build_image]
# DO NOT change this to 'true' for release artifacts, else we risk building
# images that are a few days behind. See also: docs/developer/doit.md
use_cache = false
[tool.ruff.lint]
select = [
# isort
"I",
]
[tool.isort]
profile = "black"
skip_gitignore = true
# This is necessary due to https://github.com/PyCQA/isort/issues/1835
follow_links = false
[build-system]
requires = ["poetry-core>=1.2.0"]

View file

@ -33,19 +33,17 @@ def test_order_mime_handers() -> None:
"LibreOffice",
]
with (
mock.patch(
"subprocess.check_output", return_value=b"libreoffice-draw.desktop"
) as mock_default_mime_hander,
mock.patch(
"os.listdir",
side_effect=[
["org.gnome.Evince.desktop"],
["org.pwmt.zathura-pdf-mupdf.desktop"],
["libreoffice-draw.desktop"],
],
) as mock_list,
mock.patch("dangerzone.gui.logic.DesktopEntry", return_value=mock_desktop),
with mock.patch(
"subprocess.check_output", return_value=b"libreoffice-draw.desktop"
) as mock_default_mime_hander, mock.patch(
"os.listdir",
side_effect=[
["org.gnome.Evince.desktop"],
["org.pwmt.zathura-pdf-mupdf.desktop"],
["libreoffice-draw.desktop"],
],
) as mock_list, mock.patch(
"dangerzone.gui.logic.DesktopEntry", return_value=mock_desktop
):
dz = DangerzoneGui(mock_app, dummy)
@ -79,20 +77,18 @@ def test_mime_handers_succeeds_no_default_found() -> None:
"LibreOffice",
]
with (
mock.patch(
"subprocess.check_output",
side_effect=subprocess.CalledProcessError(1, "Oh no, xdg-mime error!)"),
) as mock_default_mime_hander,
mock.patch(
"os.listdir",
side_effect=[
["org.gnome.Evince.desktop"],
["org.pwmt.zathura-pdf-mupdf.desktop"],
["libreoffice-draw.desktop"],
],
) as mock_list,
mock.patch("dangerzone.gui.logic.DesktopEntry", return_value=mock_desktop),
with mock.patch(
"subprocess.check_output",
side_effect=subprocess.CalledProcessError(1, "Oh no, xdg-mime error!)"),
) as mock_default_mime_hander, mock.patch(
"os.listdir",
side_effect=[
["org.gnome.Evince.desktop"],
["org.pwmt.zathura-pdf-mupdf.desktop"],
["libreoffice-draw.desktop"],
],
) as mock_list, mock.patch(
"dangerzone.gui.logic.DesktopEntry", return_value=mock_desktop
):
dz = DangerzoneGui(mock_app, dummy)
@ -113,16 +109,13 @@ def test_malformed_desktop_entry_is_catched() -> None:
mock_app = mock.MagicMock()
dummy = mock.MagicMock()
with (
mock.patch("dangerzone.gui.logic.DesktopEntry") as mock_desktop,
mock.patch(
"os.listdir",
side_effect=[
["malformed.desktop", "another.desktop"],
[],
[],
],
),
with mock.patch("dangerzone.gui.logic.DesktopEntry") as mock_desktop, mock.patch(
"os.listdir",
side_effect=[
["malformed.desktop", "another.desktop"],
[],
[],
],
):
mock_desktop.side_effect = ParsingError("Oh noes!", "malformed.desktop")
DangerzoneGui(mock_app, dummy)

View file

@ -7,9 +7,9 @@ from typing import List
from pytest import MonkeyPatch, fixture
from pytest_mock import MockerFixture
from pytest_subprocess import FakeProcess
from pytestqt.qtbot import QtBot
from dangerzone import errors
from dangerzone.document import Document
from dangerzone.gui import MainWindow
from dangerzone.gui import main_window as main_window_module
@ -25,8 +25,11 @@ from dangerzone.gui.main_window import (
WaitingWidgetContainer,
)
from dangerzone.gui.updater import UpdateReport, UpdaterThread
from dangerzone.isolation_provider.container import Container
from dangerzone.isolation_provider.dummy import Dummy
from dangerzone.isolation_provider.container import (
Container,
NoContainerTechException,
NotAvailableContainerTechException,
)
from .test_updater import assert_report_equal, default_updater_settings
@ -507,9 +510,9 @@ def test_not_available_container_tech_exception(
) -> None:
# Setup
mock_app = mocker.MagicMock()
dummy = Dummy()
fn = mocker.patch.object(dummy, "is_available")
fn.side_effect = errors.NotAvailableContainerTechException(
dummy = mocker.MagicMock()
dummy.is_runtime_available.side_effect = NotAvailableContainerTechException(
"podman", "podman image ls logs"
)
@ -532,7 +535,7 @@ def test_no_container_tech_exception(qtbot: QtBot, mocker: MockerFixture) -> Non
dummy = mocker.MagicMock()
# Raise
dummy.is_available.side_effect = errors.NoContainerTechException("podman")
dummy.is_runtime_available.side_effect = NoContainerTechException("podman")
dz = DangerzoneGui(mock_app, dummy)
widget = WaitingWidgetContainer(dz)

View file

@ -4,8 +4,12 @@ import pytest
from pytest_mock import MockerFixture
from pytest_subprocess import FakeProcess
from dangerzone import container_utils, errors
from dangerzone.isolation_provider.container import Container
from dangerzone.isolation_provider.container import (
Container,
ImageInstallationException,
ImageNotPresentException,
NotAvailableContainerTechException,
)
from dangerzone.isolation_provider.qubes import is_qubes_native_conversion
from .base import IsolationProviderTermination, IsolationProviderTest
@ -23,27 +27,31 @@ def provider() -> Container:
class TestContainer(IsolationProviderTest):
def test_is_available_raises(self, provider: Container, fp: FakeProcess) -> None:
def test_is_runtime_available_raises(
self, provider: Container, fp: FakeProcess
) -> None:
"""
NotAvailableContainerTechException should be raised when
the "podman image ls" command fails.
"""
fp.register_subprocess(
[container_utils.get_runtime(), "image", "ls"],
[provider.get_runtime(), "image", "ls"],
returncode=-1,
stderr="podman image ls logs",
)
with pytest.raises(errors.NotAvailableContainerTechException):
provider.is_available()
with pytest.raises(NotAvailableContainerTechException):
provider.is_runtime_available()
def test_is_available_works(self, provider: Container, fp: FakeProcess) -> None:
def test_is_runtime_available_works(
self, provider: Container, fp: FakeProcess
) -> None:
"""
No exception should be raised when the "podman image ls" can return properly.
"""
fp.register_subprocess(
[container_utils.get_runtime(), "image", "ls"],
[provider.get_runtime(), "image", "ls"],
)
provider.is_available()
provider.is_runtime_available()
def test_install_raise_if_image_cant_be_installed(
self, mocker: MockerFixture, provider: Container, fp: FakeProcess
@ -51,17 +59,17 @@ class TestContainer(IsolationProviderTest):
"""When an image installation fails, an exception should be raised"""
fp.register_subprocess(
[container_utils.get_runtime(), "image", "ls"],
[provider.get_runtime(), "image", "ls"],
)
# First check should return nothing.
fp.register_subprocess(
[
container_utils.get_runtime(),
provider.get_runtime(),
"image",
"list",
"--format",
"{{ .Tag }}",
"{{.ID}}",
"dangerzone.rocks/dangerzone",
],
occurrences=2,
@ -71,11 +79,11 @@ class TestContainer(IsolationProviderTest):
mocker.patch("gzip.open", mocker.mock_open(read_data=""))
fp.register_subprocess(
[container_utils.get_runtime(), "load"],
[provider.get_runtime(), "load"],
returncode=-1,
)
with pytest.raises(errors.ImageInstallationException):
with pytest.raises(ImageInstallationException):
provider.install()
def test_install_raises_if_still_not_installed(
@ -84,17 +92,17 @@ class TestContainer(IsolationProviderTest):
"""When an image keep being not installed, it should return False"""
fp.register_subprocess(
[container_utils.get_runtime(), "image", "ls"],
[provider.get_runtime(), "image", "ls"],
)
# First check should return nothing.
fp.register_subprocess(
[
container_utils.get_runtime(),
provider.get_runtime(),
"image",
"list",
"--format",
"{{ .Tag }}",
"{{.ID}}",
"dangerzone.rocks/dangerzone",
],
occurrences=2,
@ -103,9 +111,9 @@ class TestContainer(IsolationProviderTest):
# Patch gzip.open and podman load so that it works
mocker.patch("gzip.open", mocker.mock_open(read_data=""))
fp.register_subprocess(
[container_utils.get_runtime(), "load"],
[provider.get_runtime(), "load"],
)
with pytest.raises(errors.ImageNotPresentException):
with pytest.raises(ImageNotPresentException):
provider.install()