Skip to content

Adapters layer

Compiler and uploader adapter implementations for EzCompiler.

The adapters layer defines abstract base classes and concrete implementations for compilation and upload operations.

Internal modules

Concrete compiler/uploader implementations live in private modules (prefixed with _) and are instantiated through the factories rather than imported directly. Public consumers should use CompilerFactory and UploaderFactory, or the high-level EzCompiler facade.


Compiler adapters

BaseCompiler

Abstract base class defining the compiler interface.

BaseCompiler

BaseCompiler(config: CompilerConfig)

Bases: ABC

Abstract base class for project compilers.

Defines the interface that all compiler implementations must follow. Provides common functionality for compilation operations and enforces the contract for concrete compiler classes.

Attributes:

Name Type Description
_config

CompilerConfig instance with project settings

_zip_needed

Whether compilation output needs to be zipped

Example

Cannot instantiate directly, must subclass

class MyCompiler(BaseCompiler): ... def compile(self, console=True) -> None: ... pass ... def get_compiler_name(self) -> str: ... return "MyCompiler"

Initialize the compiler with configuration.

Parameters:

Name Type Description Default
config CompilerConfig

CompilerConfig instance with project settings

required

config property

Get the compiler configuration.

Returns:

Name Type Description
CompilerConfig CompilerConfig

Configuration instance with project settings

zip_needed property

zip_needed: bool

Whether the compiled project needs to be zipped.

Returns:

Name Type Description
bool bool

True if output should be zipped, False otherwise

Note

Subclasses set this based on their output format. Cx_Freeze sets to True, PyInstaller sets to False.

compile abstractmethod

compile(console: bool = True) -> None

Compile the project.

This method must be implemented by all subclasses to handle the actual compilation using their respective compiler.

Parameters:

Name Type Description Default
console bool

Whether to show console window (default: True)

True

Raises:

Type Description
CompilationError

If compilation fails

Example

compiler = PyInstallerCompiler(config) compiler.compile(console=False)

get_compiler_name abstractmethod

get_compiler_name() -> str

Get the name of this compiler.

Returns:

Name Type Description
str str

Display name of the compiler

Example

compiler = PyInstallerCompiler(config) name = compiler.get_compiler_name() print(name) 'PyInstaller (Empaquetée)'


CxFreezeCompiler

Cx_Freeze compiler implementation for directory-based builds.

CxFreezeCompiler

CxFreezeCompiler(config: CompilerConfig)

Bases: BaseCompiler

Cx_Freeze compiler implementation.

Handles project compilation using Cx_Freeze, which creates a directory structure containing the executable and all dependencies. The output is typically zipped for distribution.

Compilation runs in a separate subprocess to prevent cx_Freeze output from interfering with the main process display (DLP).

Attributes:

Name Type Description
_config

CompilerConfig with project settings

Example

config = CompilerConfig(...) compiler = CxFreezeCompiler(config) compiler.compile(console=True)

Initialize Cx_Freeze compiler.

Parameters:

Name Type Description Default
config CompilerConfig

CompilerConfig instance with project settings

required
Note

Cx_Freeze output requires zipping, so _zip_needed is set to True.

get_compiler_name

get_compiler_name() -> str

Get the name of this compiler.

Returns:

Name Type Description
str str

Display name "Cx_Freeze"

Example

compiler = CxFreezeCompiler(config) print(compiler.get_compiler_name()) 'Cx_Freeze'

compile

compile(console: bool = True) -> None

Compile the project using Cx_Freeze in a subprocess.

Generates a temporary setup script and executes it in a separate process to isolate cx_Freeze stdout/stderr from the main process.

Parameters:

Name Type Description Default
console bool

Whether to show console window (default: True)

True

Raises:

Type Description
CompilationError

If compilation fails

Note

On Windows with console=False, uses Win32GUI base. Runs in subprocess to preserve DLP rendering in main process.

Example

config = CompilerConfig(...) compiler = CxFreezeCompiler(config) compiler.compile(console=False)


PyInstallerCompiler

PyInstaller compiler implementation for single-file executables.

PyInstallerCompiler

PyInstallerCompiler(config: CompilerConfig)

Bases: BaseCompiler

PyInstaller compiler implementation.

Handles project compilation using PyInstaller, which creates a single executable file with all dependencies bundled. Can generate either single-file or directory-based executables.

Compilation runs in a separate subprocess to prevent PyInstaller output from interfering with the main process display (DLP).

Attributes:

Name Type Description
_config

CompilerConfig with project settings

Example

config = CompilerConfig(...) compiler = PyInstallerCompiler(config) compiler.compile(console=True)

Initialize PyInstaller compiler.

Parameters:

Name Type Description Default
config CompilerConfig

CompilerConfig instance with project settings

required
Note

PyInstaller creates single files, so _zip_needed is set to False.

get_compiler_name

get_compiler_name() -> str

Get the name of this compiler.

Returns:

Name Type Description
str str

Display name "PyInstaller (Empaquetée)"

Example

compiler = PyInstallerCompiler(config) print(compiler.get_compiler_name()) 'PyInstaller (Empaquetée)'

compile

compile(console: bool = True) -> None

Compile the project using PyInstaller in a subprocess.

Validates configuration, prepares output directory, builds PyInstaller command-line arguments, and runs compilation in a separate process to isolate stdout/stderr from the DLP.

Parameters:

Name Type Description Default
console bool

Whether to show console window (default: True)

True

Raises:

Type Description
CompilationError

If compilation fails

Note

Adds version file if it exists. Includes all configured packages, includes, and applies excludes. Adds files and folders with appropriate data paths.

Example

config = CompilerConfig(...) compiler = PyInstallerCompiler(config) compiler.compile(console=False)


NuitkaCompiler

Nuitka compiler implementation for optimized native compilation.

NuitkaCompiler

NuitkaCompiler(config: CompilerConfig)

Bases: BaseCompiler

Nuitka compiler implementation.

Handles project compilation using Nuitka, which can produce a standalone folder or a single-file executable depending on options.

Attributes:

Name Type Description
_config

CompilerConfig with project settings

Initialize Nuitka compiler.

Parameters:

Name Type Description Default
config CompilerConfig

CompilerConfig instance with project settings

required

get_compiler_name

get_compiler_name() -> str

Get the name of this compiler.

Returns:

Name Type Description
str str

Display name "Nuitka"

compile

compile(console: bool = True) -> None

Compile the project using Nuitka.

Validates configuration, prepares output directory, builds Nuitka options from project settings, and runs compilation.

Parameters:

Name Type Description Default
console bool

Whether to show console window (default: True)

True

Raises:

Type Description
CompilationError

If compilation fails


CompilerFactory

Factory class for creating compiler instances based on name or configuration.

CompilerFactory

Factory class for creating compiler instances.

create_compiler staticmethod

create_compiler(config: CompilerConfig, compiler_name: str) -> CompilerPort

Create a compiler instance from its name.

Parameters:

Name Type Description Default
config CompilerConfig

Compiler configuration to inject

required
compiler_name str

Compiler name (Cx_Freeze, PyInstaller, Nuitka)

required

Returns:

Name Type Description
CompilerPort CompilerPort

Concrete compiler instance (satisfies the Port)

Raises:

Type Description
CompilationError

If compiler name is unsupported or package not installed

get_supported_compilers staticmethod

get_supported_compilers() -> list[str]

Return the list of supported compiler names.


Installer adapters

BaseInstaller

Abstract base class defining the first-deployment installer interface.

BaseInstaller

BaseInstaller(config: dict[str, Any] | None = None)

Bases: ABC

Abstract base class for first-deployment installer builders.

build abstractmethod

build(bundle_dir: Path, app_name: str, version: str, output_dir: Path) -> Path

Build the installer executable. Raises InstallerError on failure.

get_installer_name abstractmethod

get_installer_name() -> str

Human-readable installer backend name.


InnoSetupInstaller

Inno Setup installer implementation, building a setup.exe via the external ISCC.exe binary.

InnoSetupInstaller

InnoSetupInstaller(config: dict[str, Any] | None = None)

Bases: BaseInstaller

Installer backed by the Inno Setup compiler (ISCC.exe).

build

build(bundle_dir: Path, app_name: str, version: str, output_dir: Path) -> Path

Render the .iss script and compile it into a setup.exe.


InstallerFactory

Factory class for creating installer instances based on backend name.

InstallerFactory

Factory class for creating installer instances.

create_installer staticmethod

create_installer(installer_type: str, config: dict[str, Any] | None = None) -> InstallerPort

Create an installer instance for the given type.

Parameters:

Name Type Description Default
installer_type str

Type of installer builder ("innosetup")

required
config dict[str, Any] | None

Configuration dictionary for the installer adapter

None

Returns:

Name Type Description
InstallerPort InstallerPort

Configured installer instance (satisfies the Port)

Raises:

Type Description
InstallerTypeError

If installer type is not supported

get_supported_types staticmethod

get_supported_types() -> list[str]

Return the list of supported installer backend names.


Releaser adapters

BaseReleaser

Abstract base class defining the secure-release packager interface.

BaseReleaser

BaseReleaser(config: dict[str, Any] | None = None)

Bases: ABC

Abstract base class for secure-release packagers.

release abstractmethod

release(bundle_dir: Path, app_name: str, version: str, repo_dir: Path, *, patch: bool = True) -> Path

Build and sign the local TUF repository. Raises ReleaseError.

init_keys abstractmethod

init_keys(app_name: str, repo_dir: Path, keys_dir: Path) -> bool

Initialise clés + squelette repo TUF. Idempotent.

Returns True si init effectuée, False si déjà présente (skip). Raises ReleaseError / SigningKeyError on failure.

refresh_expiration abstractmethod

refresh_expiration(app_name: str, repo_dir: Path, keys_dir: Path, *, roles: tuple[str, ...] = ('targets', 'snapshot', 'timestamp'), days: int | None = None) -> Path

Re-sign metadata to push out expiration without a new release.

Returns the local repository directory. Raises ReleaseError / SigningKeyError on failure.

get_releaser_name abstractmethod

get_releaser_name() -> str

Human-readable releaser name.


TufupReleaser

tufup (TUF) releaser implementation, packaging a compiled bundle into a signed TUF repository.

TufupReleaser

TufupReleaser(config: dict[str, Any] | None = None)

Bases: BaseReleaser

Releaser backed by the tufup TUF framework.

release

release(bundle_dir: Path, app_name: str, version: str, repo_dir: Path, *, patch: bool = True) -> Path

Build and sign the local TUF repository for the bundle.

init_keys

init_keys(app_name: str, repo_dir: Path, keys_dir: Path) -> bool

Initialise clés + squelette repo TUF. Idempotent.

Returns True si l'init a été effectuée, False si déjà présente (skip).

refresh_expiration

refresh_expiration(app_name: str, repo_dir: Path, keys_dir: Path, *, roles: tuple[str, ...] = ('targets', 'snapshot', 'timestamp'), days: int | None = None) -> Path

Re-sign metadata to push out expiration without a new release.

Native tufup keep-alive for projects updated irregularly: bumps the expiration date of the short-lived roles (targets/snapshot/ timestamp by default) and re-publishes. root is long-lived (365 days) and left untouched unless explicitly requested.

Parameters:

Name Type Description Default
app_name str

Application name (must match the initialized repo).

required
repo_dir Path

Root of the local TUF repository tree.

required
keys_dir Path

Directory holding the private signing keys.

required
roles tuple[str, ...]

Role names whose expiration should be refreshed.

('targets', 'snapshot', 'timestamp')
days int | None

Number of days from now. None → the repo's configured per-role expiration_days (or tufup defaults).

None

Returns:

Name Type Description
Path Path

The local repository directory.

Raises:

Type Description
SigningKeyError

If the keys directory is missing.

ReleaseError

If the repo is not initialized or refresh fails.


ReleaserFactory

Factory class for creating releaser instances based on backend name.

ReleaserFactory

Factory class for creating releaser instances.

create_releaser staticmethod

create_releaser(release_type: str, config: dict[str, Any] | None = None) -> ReleaserPort

Create a releaser instance for the given type.

Parameters:

Name Type Description Default
release_type str

Type of releaser ("tufup")

required
config dict[str, Any] | None

Configuration dictionary for the releaser

None

Returns:

Name Type Description
ReleaserPort ReleaserPort

Configured releaser instance (satisfies the Port)

Raises:

Type Description
ReleaserTypeError

If release type is not supported

get_supported_types staticmethod

get_supported_types() -> list[str]

Return the list of supported release backend names.


File writer adapters

BaseFileWriter

Abstract base class defining the file writer interface.

BaseFileWriter

Bases: ABC

Port for writing text content to a target file path.

write_text abstractmethod

write_text(output_path: Path, content: str, encoding: str = 'utf-8') -> None

Write text content to output_path.


DiskFileWriter

Concrete file writer implementation for local disk operations.

DiskFileWriter

Bases: BaseFileWriter

Write text files to local disk using pathlib.

write_text

write_text(output_path: Path, content: str, encoding: str = 'utf-8') -> None

Write text content to disk.


Uploader adapters

BaseUploader

Abstract base class defining the uploader interface.

BaseUploader

BaseUploader(config: dict[str, Any] | None = None)

Bases: ABC

Abstract base class for uploaders.

Defines the interface that all uploaders must implement and provides common functionality for upload operations and validation.

Attributes:

Name Type Description
_config

Configuration dictionary for the uploader

Example

class MyUploader(BaseUploader): ... def upload(self, source_path: Path, destination: str) -> None: ... # Implementation ... pass ... def get_uploader_name(self) -> str: ... return "My Uploader"

Initialize the uploader with configuration.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Configuration dictionary (default: None)

None
Note

Subclasses should call super().init(config) to initialize configuration and validation.

upload abstractmethod

upload(source_path: Path, destination: str) -> None

Upload a file or directory to the destination.

Parameters:

Name Type Description Default
source_path Path

Path to the source file or directory

required
destination str

Destination path or URL

required

Raises:

Type Description
UploadError

If upload fails

Note

Subclasses must implement this method to define upload behavior.

get_uploader_name abstractmethod

get_uploader_name() -> str

Get the name of this uploader.

Returns:

Name Type Description
str str

Human-readable name of the uploader

Note

Used for identification purposes.

download

download(remote_source: str, local_dir: Path) -> None

Download a remote tree into local_dir.

Default implementation: backends that host an update channel override this. Others reject it.

Parameters:

Name Type Description Default
remote_source str

Remote source (path, URL or prefix) to fetch.

required
local_dir Path

Local directory to populate with the downloaded tree.

required

Raises:

Type Description
UploadError

Always, unless overridden by a subclass.


DiskUploader

Local disk uploader for saving compiled projects to the file system.

DiskUploader

DiskUploader(config: dict[str, Any] | None = None)

Bases: BaseUploader

Uploader for local disk operations.

Handles copying files and directories to local disk locations with configurable behavior for permissions, overwrites, and backups.

Configuration keys

preserve_permissions (bool): Preserve file permissions (default: True) overwrite (bool): Allow overwriting existing files (default: True) create_backup (bool): Create backup before overwrite (default: False)

Example

config = {"preserve_permissions": True, "overwrite": True} uploader = DiskUploader(config) uploader.upload(Path("source.zip"), "/path/to/destination.zip")

Initialize the disk uploader.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Optional configuration dictionary with keys: - preserve_permissions (bool): Preserve file permissions - overwrite (bool): Allow overwriting existing files - create_backup (bool): Create backup before overwrite

None

get_uploader_name

get_uploader_name() -> str

Get the name of this uploader.

Returns:

Name Type Description
str str

Name of the uploader

upload

upload(source_path: Path, destination: str) -> None

Upload a file or directory to a local disk location.

Parameters:

Name Type Description Default
source_path Path

Path to the source file or directory

required
destination str

Destination path on local disk

required

Raises:

Type Description
UploadError

If upload fails

Note

Creates parent directories automatically if they don't exist.

download

download(remote_source: str, local_dir: Path) -> None

Copy a remote disk tree into local_dir (recursive).

Parameters:

Name Type Description Default
remote_source str

Filesystem path of the remote tree.

required
local_dir Path

Local directory to populate.

required

Raises:

Type Description
UploadError

If the copy fails.

Note

Missing remote source is a no-op (first run: empty channel).


ServerUploader

HTTP/HTTPS server uploader for remote distribution.

ServerUploader

ServerUploader(config: dict[str, Any] | None = None)

Bases: BaseUploader

Uploader for server operations via HTTP/HTTPS.

Handles uploading files to remote servers using HTTP POST requests with support for authentication, SSL verification, and automatic retry logic.

Configuration keys

server_url (str): Base URL of the upload server username (str): Username for basic authentication (default: "") password (str): Password for basic authentication (default: "") api_key (str): API key for bearer token authentication (default: "") timeout (int|float): Request timeout in seconds (default: 30) verify_ssl (bool): Verify SSL certificates (default: True) chunk_size (int): Chunk size for uploads (default: 8192) retry_attempts (int): Number of retry attempts (default: 3) proxies (dict): Proxy URLs keyed by scheme, e.g. {"http": "http://proxy:3128", "https": "http://proxy:3128"} (default: {}) extra_headers (dict): Additional HTTP headers merged into every request (default: {}) cert (str | tuple | None): Client certificate for mTLS — path to a .pem file, or a (certfile, keyfile) tuple (default: None)

Example

config = {"server_url": "https://example.com", "api_key": "abc123"} uploader = ServerUploader(config) uploader.upload(Path("file.zip"), "/uploads/file.zip")

Initialize the server uploader.

Parameters:

Name Type Description Default
config dict[str, Any] | None

Optional configuration dictionary with server settings

None

get_uploader_name

get_uploader_name() -> str

Get the name of this uploader.

Returns:

Name Type Description
str str

Name of the uploader

upload

upload(source_path: Path, destination: str) -> None

Upload a file or a directory tree to a remote server.

For directories, walks recursively and POSTs each file using its POSIX path relative to source_path as the server destination.

Parameters:

Name Type Description Default
source_path Path

Path to the source file or directory

required
destination str

Destination path on the server (single-file uploads)

required

Raises:

Type Description
UploadError

If any file fails after all retry attempts.

Note

Automatically retries on failure based on retry_attempts config.

download

download(remote_source: str, local_dir: Path) -> None

Fetch the current TUF tree from remote_source (metadata-driven).

Reads the role metadata by known names, then the targets listed in targets.json. A 404 on timestamp.json means the channel is empty (first run) -> no-op.

Parameters:

Name Type Description Default
remote_source str

Base read URL of the update channel.

required
local_dir Path

Local directory to populate with the TUF tree.

required

Raises:

Type Description
UploadError

On any non-404 transport failure.


Factory

UploaderFactory

Factory class for creating uploader instances based on configuration.

UploaderFactory

Factory class for creating uploader instances.

Provides a centralized way to create and configure uploader instances based on type specification, with support for validation and type discovery.

Example

uploader = UploaderFactory.create_uploader("disk", {"overwrite": True}) types = UploaderFactory.get_supported_types() print(types) ['disk', 'server']

create_uploader staticmethod

create_uploader(upload_type: str, config: dict[str, Any] | None = None) -> UploaderPort

Create an uploader instance based on the specified type.

Parameters:

Name Type Description Default
upload_type str

Type of uploader ("disk" or "server")

required
config dict[str, Any] | None

Configuration dictionary for the uploader (default: None)

None

Returns:

Name Type Description
UploaderPort UploaderPort

Configured uploader instance (satisfies the Port)

Raises:

Type Description
UploadError

If upload type is not supported

Example

disk_uploader = UploaderFactory.create_uploader("disk") server_uploader = UploaderFactory.create_uploader( ... "server", {"server_url": "https://example.com"} ... )

create_from_config staticmethod

create_from_config(config: dict[str, Any]) -> UploaderPort

Create an uploader instance from a configuration dictionary.

Parameters:

Name Type Description Default
config dict[str, Any]

Configuration dictionary containing: - type: Upload type ("disk" or "server") - config: Uploader-specific configuration (optional)

required

Returns:

Name Type Description
UploaderPort UploaderPort

Configured uploader instance (satisfies the Port)

Raises:

Type Description
UploadError

If configuration is invalid or type is missing

Example

config = { ... "type": "disk", ... "config": {"overwrite": True} ... } uploader = UploaderFactory.create_from_config(config)

get_supported_types staticmethod

get_supported_types() -> list[str]

Get list of supported upload types.

Returns:

Type Description
list[str]

list[str]: List of supported upload type names

Example

types = UploaderFactory.get_supported_types() print(types) ['disk', 'server', 'r2']

validate_config staticmethod

validate_config(upload_type: str, config: dict[str, Any] | None = None) -> bool

Validate configuration for a specific upload type.

Parameters:

Name Type Description Default
upload_type str

Type of uploader to validate

required
config dict[str, Any] | None

Configuration to validate (default: None)

None

Returns:

Name Type Description
bool bool

True if configuration is valid, False otherwise

Note

Creates a temporary uploader instance to test configuration validity.

Example

is_valid = UploaderFactory.validate_config("disk", {"overwrite": True}) print(is_valid) True