Services layer¶
Business logic services and orchestration for EzCompiler.
The services layer implements the core business logic, handling compilation, configuration, template processing, and upload operations.
CompilerService¶
Service for managing compiler selection and orchestrating the compilation process.
CompilerService
¶
CompilerService(config: CompilerConfig)
Compilation orchestration service.
Orchestrates project compilation using different compiler backends. Handles compiler selection, validation, and execution.
Attributes:
| Name | Type | Description |
|---|---|---|
_config |
CompilerConfig instance with project settings |
Example
config = CompilerConfig(...) service = CompilerService(config) result = service.compile(console=True, compiler="PyInstaller") print(result.zip_needed) False
Initialize the compiler service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
CompilerConfig instance with project settings |
required |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If config is None or invalid |
compiler_instance
property
¶
compiler_instance: CompilerPort | None
Get the current compiler instance.
Returns:
| Type | Description |
|---|---|
CompilerPort | None
|
CompilerPort | None: Current compiler instance or None if not compiled yet |
compile
¶
compile(console: bool = True, compiler: _CompilerName | None = None) -> CompilationResult
Compile the project using specified or auto-selected compiler.
Validates configuration, selects compiler if not specified, and executes compilation. Returns result with zip_needed flag.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
console
|
bool
|
Whether to show console window (default: True) |
True
|
compiler
|
_CompilerName | None
|
Compiler to use or None for auto-selection - "Cx_Freeze": Creates directory with dependencies - "PyInstaller": Creates single executable - "Nuitka": Creates standalone folder or single executable - None: Use config compiler, or prompt user if none set |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
CompilationResult |
CompilationResult
|
Result with zip_needed flag and compiler instance |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If project not initialized |
CompilationError
|
If compilation fails |
Example
service = CompilerService(config) result = service.compile(console=False, compiler="PyInstaller") if result.zip_needed: ... # Create ZIP archive
ConfigService¶
Service for loading, validating, and managing configuration.
ConfigService
¶
Configuration orchestration service.
Orchestrates configuration loading from multiple sources with a cascade merge system. Sources are merged in priority order (later wins):
- pyproject.toml [project] + [tool.ezcompiler] (base)
- ezcompiler.yaml or ezcompiler.json (override)
- CLI arguments (final override)
Example
config = ConfigService.build_compiler_config() config = ConfigService.build_compiler_config( ... config_path=Path("ezcompiler.yaml"), ... cli_overrides={"compiler": "PyInstaller"}, ... )
load_config
staticmethod
¶
load_config(config_path: Path | None = None, pyproject_path: Path | None = None, cli_overrides: dict[str, Any] | None = None, search_dir: Path | None = None) -> dict[str, Any]
Load configuration with full cascade.
Merges configuration from multiple sources in priority order: 1. pyproject.toml [project] + [tool.ezcompiler] (base layer) 2. ezcompiler.yaml or .json - explicit or auto-discovered (override) 3. CLI overrides (final override)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config_path
|
Path | None
|
Explicit config file path for YAML/JSON (skips auto-discovery) |
None
|
pyproject_path
|
Path | None
|
Explicit pyproject.toml path (default: search_dir/pyproject.toml) |
None
|
cli_overrides
|
dict[str, Any] | None
|
Dict of CLI-provided overrides (only non-default values) |
None
|
search_dir
|
Path | None
|
Directory to search for config files (default: cwd) |
None
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: Merged configuration dictionary |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If no configuration source found or loading fails |
build_compiler_config
staticmethod
¶
build_compiler_config(config_path: Path | None = None, pyproject_path: Path | None = None, cli_overrides: dict[str, Any] | None = None, search_dir: Path | None = None) -> CompilerConfig
Load configuration and create a CompilerConfig instance.
Convenience method that combines load_config() with CompilerConfig.from_dict().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config_path
|
Path | None
|
Explicit config file path for YAML/JSON |
None
|
pyproject_path
|
Path | None
|
Explicit pyproject.toml path |
None
|
cli_overrides
|
dict[str, Any] | None
|
Dict of CLI-provided overrides |
None
|
search_dir
|
Path | None
|
Directory to search for config files (default: cwd) |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
CompilerConfig |
CompilerConfig
|
Validated configuration instance |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If configuration is invalid or not found |
load_pyproject_as_dict
staticmethod
¶
Load and extract configuration from a pyproject.toml file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pyproject_path
|
Path
|
Path to the pyproject.toml file |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: Extracted configuration dictionary |
Raises:
| Type | Description |
|---|---|
ConfigurationError
|
If the file cannot be loaded or parsed |
merge_configs
staticmethod
¶
Deep-merge two configuration dictionaries (override wins on conflicts).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
base
|
dict[str, Any]
|
Base configuration dictionary |
required |
override
|
dict[str, Any]
|
Override dictionary (values win over base) |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
dict[str, Any]: Merged configuration dictionary |
TemplateService¶
Service for processing templates and generating dynamic files.
TemplateService
¶
TemplateService(file_writer: BaseFileWriter | None = None)
Template processing and file generation service.
Orchestrates template loading, processing, and file generation for configuration files, setup.py, and version information files.
Attributes:
| Name | Type | Description |
|---|---|---|
_template_loader |
TemplateLoader instance for template operations |
|
_processor |
TemplateProcessor instance for variable substitution |
Example
service = TemplateService() config = {"version": "1.0.0", "project_name": "MyApp"} service.generate_setup_file(config, Path("setup.py")) service.generate_version_file(config, Path("version_info.txt"))
Initialize the template service.
Creates TemplateLoader and TemplateProcessor instances for template operations.
generate_config_file
¶
Generate a configuration file (YAML or JSON) from template.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
dict[str, Any]
|
Project configuration dictionary |
required |
output_path
|
Path
|
Path where to save the config file |
required |
format_type
|
str
|
Format type ("yaml" or "json") |
'yaml'
|
Raises:
| Type | Description |
|---|---|
TemplateError
|
If generation fails |
Example
config = {"version": "1.0.0", "project_name": "MyApp"} service.generate_config_file(config, Path("ezcompiler.yaml"), "yaml")
generate_setup_file
¶
generate_setup_file(config: dict[str, Any], output_path: Path | None = None, output_dir: Path | None = None) -> Path
Generate a setup.py file from template.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
dict[str, Any]
|
Project configuration dictionary |
required |
output_path
|
Path | None
|
Direct path to setup.py file (optional) |
None
|
output_dir
|
Path | None
|
Directory where to save setup.py (optional, defaults to current dir) |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
Path to the generated setup.py file |
Raises:
| Type | Description |
|---|---|
TemplateError
|
If generation fails |
Example
config = {"version": "1.0.0", "project_name": "MyApp"} setup_path = service.generate_setup_file(config, output_dir=Path("build"))
generate_version_file
¶
generate_version_file(config: dict[str, Any], output_path: Path | None = None, format_type: str = 'txt') -> Path
Generate a version information file from template.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
dict[str, Any]
|
Project configuration dictionary |
required |
output_path
|
Path | None
|
Path where to save the version file (optional) |
None
|
format_type
|
str
|
Template format type (default: "txt") |
'txt'
|
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
Path to the generated version file |
Raises:
| Type | Description |
|---|---|
VersionError
|
If generation fails |
Note
If output_path is None, uses version_filename and output_folder from config.
Example
config = { ... "version": "1.0.0", ... "project_name": "MyApp", ... "version_filename": "version_info.txt", ... "output_folder": "dist" ... } version_path = service.generate_version_file(config)
process_config_template
¶
Process a configuration template with values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
format_type
|
str
|
Format type ("yaml" or "json") |
required |
config
|
dict[str, Any]
|
Configuration dictionary |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Processed template content |
Example
config = {"version": "1.0.0", "project_name": "MyApp"} content = service.process_config_template("yaml", config)
process_setup_template
¶
Process a setup template with values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
dict[str, Any]
|
Configuration dictionary |
required |
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Processed template content |
Example
config = {"version": "1.0.0", "project_name": "MyApp"} content = service.process_setup_template(config)
process_version_template
¶
process_version_template(version: str, company_name: str, project_description: str, project_name: str, format_type: str = 'txt') -> str
Process a version template with values.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
version
|
str
|
Project version |
required |
company_name
|
str
|
Company name |
required |
project_description
|
str
|
Project description |
required |
project_name
|
str
|
Project name |
required |
format_type
|
str
|
Format type (default: "txt") |
'txt'
|
Returns:
| Name | Type | Description |
|---|---|---|
str |
str
|
Processed template content |
Example
content = service.process_version_template( ... "1.0.0", "MyCompany", "Description", "MyApp" ... )
list_available_templates
¶
List all available templates.
Returns:
| Type | Description |
|---|---|
dict[str, list[str]]
|
dict[str, list[str]]: Dictionary mapping template types to available formats |
Example
templates = service.list_available_templates() print(templates)
validate_template
¶
Validate a template file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
template_type
|
str
|
Type of template (config, version, setup) |
required |
format_type
|
str
|
Format of the template |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if template is valid, False otherwise |
Example
is_valid = service.validate_template("config", "yaml")
generate_mockup_template
¶
Generate a template file with mockup values instead of placeholders.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
template_type
|
str
|
Type of template (config, version, setup) |
required |
format_type
|
str
|
Format of the template (yaml, json, py, txt) |
required |
output_path
|
Path
|
Path where to save the generated file |
required |
Raises:
| Type | Description |
|---|---|
TemplateError
|
If generation fails |
Example
service.generate_mockup_template("config", "yaml", Path("ezcompiler.yaml"))
generate_raw_template
¶
Generate a raw template file with placeholders.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
template_type
|
str
|
Type of template (config, version, setup) |
required |
format_type
|
str
|
Format of the template (yaml, json, py, txt) |
required |
output_path
|
Path
|
Path where to save the template file |
required |
Raises:
| Type | Description |
|---|---|
TemplateError
|
If generation fails |
Example
service.generate_raw_template("config", "yaml", Path("template.yaml"))
PipelineService¶
Service for orchestrating the full compile → zip → upload pipeline with injectable factory.
PipelineService
¶
PipelineService(compiler_service_factory: Callable[[CompilerConfig], CompilerService] | None = None)
Service that coordinates compile, zip and upload stages.
Initialise the pipeline service.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
compiler_service_factory
|
Callable[[CompilerConfig], CompilerService] | None
|
Optional factory to create a CompilerService
from a CompilerConfig. Defaults to |
None
|
compile_project
¶
compile_project(config: CompilerConfig, console: bool = True, compiler: str | None = None) -> tuple[CompilerService, CompilationResult]
Compile a project and return service + result.
zip_artifact
¶
zip_artifact(config: CompilerConfig, compiler_service: CompilerService, compilation_result: CompilationResult | None, progress_callback: Callable[[str, int], None] | None = None) -> bool
Create ZIP artifact when required and return True when created.
build_stages
staticmethod
¶
build_stages(config: CompilerConfig, should_zip: bool = False, should_upload: bool = False, should_release: bool = False, should_installer: bool = False) -> list[dict[str, Any]]
Build the stage list for dynamic_layered_progress.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
Compiler configuration (used for display labels) |
required |
should_zip
|
bool
|
Whether a ZIP stage should be included |
False
|
should_upload
|
bool
|
Whether an upload stage should be included |
False
|
Returns:
| Type | Description |
|---|---|
list[dict[str, Any]]
|
list[dict]: Stage configuration list ready for dynamic_layered_progress |
upload_artifact
¶
upload_artifact(config: CompilerConfig, structure: str, destination: str, compilation_result: CompilationResult | None, upload_config: dict[str, Any] | None = None) -> None
Upload project artifact to a destination.
assemble_release_dir
staticmethod
¶
assemble_release_dir(config: CompilerConfig) -> Path
Assemble le dossier release contenant le zip et l'installeur.
Layout (nettoyé à chaque run)::
release/
├── <App>.zip (si le fichier existe)
└── <App>-<version>-setup.exe (si installer_enabled=True)
L'arbre TUF (metadata/ + targets/) reste dans tufup_repo_dir et est poussé directement vers le backend d'update par upload().
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
Configuration (fournit output_folder et zip_file_path). |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
Le dossier |
release_artifact
staticmethod
¶
release_artifact(config: CompilerConfig, compilation_result: CompilationResult | None) -> Path
Build le repo TUF local depuis output_folder. Ne publie jamais.
build_installer
staticmethod
¶
build_installer(config: CompilerConfig, compilation_result: CompilationResult | None) -> Path | None
Build the Inno Setup installer when installer_enabled=True.
UploaderService¶
Service for orchestrating upload operations to various backends.
UploaderService
¶
Upload orchestration service.
Orchestrates file and directory uploads using different upload backends. Handles upload type selection, validation, and execution.
Example
service = UploaderService() service.upload( ... source_path=Path("dist.zip"), ... upload_type="disk", ... destination="releases/", ... upload_config={"overwrite": True} ... )
upload
staticmethod
¶
upload(source_path: Path, upload_type: UploadType, destination: str, upload_config: dict[str, Any] | None = None) -> None
Upload a file or directory to the specified destination.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
source_path
|
Path
|
Path to the source file or directory |
required |
upload_type
|
UploadType
|
Type of upload ("disk" or "server") |
required |
destination
|
str
|
Destination path or URL |
required |
upload_config
|
dict[str, Any] | None
|
Additional uploader configuration options |
None
|
Raises:
| Type | Description |
|---|---|
UploadError
|
If upload fails or upload type is invalid |
Example
UploaderService.upload( ... Path("dist.zip"), ... "disk", ... "releases/", ... {"overwrite": True} ... )
download
staticmethod
¶
download(remote_source: str, upload_type: UploadType, destination_local: Path, upload_config: dict[str, Any] | None = None) -> None
Download a remote tree into destination_local via the backend.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
remote_source
|
str
|
Remote source (path, URL or prefix) to fetch. |
required |
upload_type
|
UploadType
|
Backend type ("disk", "server" or "r2"). |
required |
destination_local
|
Path
|
Local directory to populate. |
required |
upload_config
|
dict[str, Any] | None
|
Additional uploader configuration options. |
None
|
Raises:
| Type | Description |
|---|---|
UploadError
|
If the download fails or the type is invalid. |
upload_release
staticmethod
¶
upload_release(config: CompilerConfig, repo_dir: Path, release_root: Path | None, destination: str | None = None, repo_destination: str | None = None, release_destination: str | None = None, upload_config: dict[str, Any] | None = None) -> None
Double upload séquentiel : arbre TUF puis zip installeur.
Étape 1 — upload arbre TUF vers <dest>/update/ (ou préfixe R2).
Étape 2 — upload zip installeur vers <dest>/release/ (ignoré si R2).
Note: Le double-upload n'est pas atomique. Si l'upload du zip échoue, le repo TUF est déjà en ligne. En cas d'échec, ré-exécuter upload() pour reprendre.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
CompilerConfig contenant les destinations et options R2. |
required |
repo_dir
|
Path
|
Répertoire local du repo TUF. |
required |
release_root
|
Path | None
|
Répertoire local du zip installeur (None si R2). |
required |
destination
|
str | None
|
Override commun pour les deux destinations. |
None
|
repo_destination
|
str | None
|
Override de |
None
|
release_destination
|
str | None
|
Override de |
None
|
upload_config
|
dict[str, Any] | None
|
Options supplémentaires passées aux uploaders. |
None
|
Raises:
| Type | Description |
|---|---|
UploadError
|
Si un upload échoue. |
get_supported_types
staticmethod
¶
Get list of supported upload types.
Returns:
| Type | Description |
|---|---|
list[str]
|
list[str]: List of supported upload type names |
Example
types = UploaderService.get_supported_types() print(types) ['disk', 'server']
validate_upload_config
staticmethod
¶
Validate configuration for a specific upload type.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
upload_type
|
UploadType
|
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 |
Example
is_valid = UploaderService.validate_upload_config( ... "disk", {"overwrite": True} ... )
UpdaterService¶
Service for generating client updater files (update.py, settings.py, root.json) for tufup-based auto-update workflows.
UpdaterService
¶
Service that generates the tufup client update files.
generate
staticmethod
¶
generate(config: CompilerConfig, output_dir: Path) -> list[Path]
Generate update.py, settings.py, and copy root.json.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
CompilerConfig
|
Compiler configuration (must have tuf_enabled=True). |
required |
output_dir
|
Path
|
Directory where files are written (created if absent). |
required |
Returns:
| Type | Description |
|---|---|
list[Path]
|
List of generated file paths [settings.py, update.py, root.json]. |
Raises:
| Type | Description |
|---|---|
UpdaterConfigError
|
If config is invalid (tuf_enabled=False, root.json absent, repo_public_url missing for non-disk). |
UpdaterGenerationError
|
If writing files fails. |
InstallerService¶
Service orchestrating first-deployment installer packaging via an installer adapter.
InstallerService
¶
Service orchestrating first-deployment installer packaging.
build_installer
staticmethod
¶
build_installer(bundle_dir: Path, app_name: str, version: str, output_dir: Path, *, installer_type: str = 'innosetup', installer_config: dict[str, Any] | None = None) -> Path
Build the installer executable for a compiled bundle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bundle_dir
|
Path
|
Directory containing the compiled application. |
required |
app_name
|
str
|
Application name. |
required |
version
|
str
|
Application version string. |
required |
output_dir
|
Path
|
Directory where the setup.exe is produced. |
required |
installer_type
|
str
|
Installer backend to use (default: "innosetup"). |
'innosetup'
|
installer_config
|
dict[str, Any] | None
|
Extra config forwarded to the installer adapter (e.g. "icon", "company_name", "iss_path", "iscc_path"). |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
The produced setup.exe path. |
Raises:
| Type | Description |
|---|---|
InstallerError
|
When installer packaging fails. |
ReleaseService¶
Service orchestrating secure-release packaging (TUF) and, optionally, publication.
ReleaseService
¶
Service orchestrating secure-release packaging and publication.
release_and_publish
staticmethod
¶
release_and_publish(bundle_dir: Path, app_name: str, version: str, repo_dir: Path, *, release_type: str = 'tufup', publish: bool = False, pull_before: bool = False, upload_type: str | None = None, destination: str | None = None, releaser_config: dict[str, Any] | None = None, upload_config: dict[str, Any] | None = None) -> Path
Build the local TUF repo, then optionally publish it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bundle_dir
|
Path
|
Directory containing the compiled application. |
required |
app_name
|
str
|
Application name (used by tufup to name bundles). |
required |
version
|
str
|
Application version string. |
required |
repo_dir
|
Path
|
Root directory for the local TUF repository tree. |
required |
release_type
|
str
|
Release backend to use (default: "tufup"). |
'tufup'
|
publish
|
bool
|
When True, transfer the repository/ tree via an uploader. |
False
|
pull_before
|
bool
|
When True, download the current remote tree into
|
False
|
upload_type
|
str | None
|
Upload backend ("disk" or "server"). Required when publish=True. |
None
|
destination
|
str | None
|
Upload destination path or URL. Required when publish=True. |
None
|
releaser_config
|
dict[str, Any] | None
|
Extra config forwarded to the releaser adapter. |
None
|
upload_config
|
dict[str, Any] | None
|
Extra config forwarded to the uploader adapter. |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
Path |
Path
|
The local |
Raises:
| Type | Description |
|---|---|
ValueError
|
When publish=True but upload_type or destination is missing. |
ReleaseError
|
When release packaging or publishing fails. |
init_release
staticmethod
¶
init_release(app_name: str, repo_dir: Path, keys_dir: Path, *, release_type: str = 'tufup', releaser_config: dict[str, Any] | None = None) -> bool
Crée le releaser via la factory et délègue à init_keys.
Returns True si init effectuée, False si déjà présente (skip).
refresh_expiration
staticmethod
¶
refresh_expiration(app_name: str, repo_dir: Path, keys_dir: Path, *, roles: tuple[str, ...] = ('targets', 'snapshot', 'timestamp'), days: int | None = None, release_type: str = 'tufup', releaser_config: dict[str, Any] | None = None) -> Path
Re-signe les metadata pour repousser l'expiration sans nouvelle release.
Keep-alive natif tufup pour les projets mis à jour irrégulièrement.
Returns le dossier du repo TUF local.