diff --git a/changelog.d/20241108_215625_ncoghlan_enable_docstring_style_checks.rst b/changelog.d/20241108_215625_ncoghlan_enable_docstring_style_checks.rst new file mode 100644 index 0000000..5d795d0 --- /dev/null +++ b/changelog.d/20241108_215625_ncoghlan_enable_docstring_style_checks.rst @@ -0,0 +1,11 @@ +Changed +------- + +- Renamed :class:`!EnvironmentExportRequest` to :class:`LayerExportRequest` (part of :issue:`33`). +- Exposed :class:`LayerSpecBase`, :class:`LayeredSpecBase` as public classes (part of :issue:`33`). +- Exposed :class:`LayerEnvBase`, :class:`LayeredEnvBase` as public classes (part of :issue:`33`). +- Added leading underscores to several private functions and methods (part of :issue:`33`). +- Added docstrings to all remaining public functions and methods (part of :issue:`33`). +- Enabled rendered previews for documentation PRs (requested in :issue:`43`). +- Enabled link validity checks when rendering documentation (requested in :issue:`62`). + diff --git a/docs/api/stacks/index.rst b/docs/api/stacks/index.rst index 4f83160..9cc8cef 100644 --- a/docs/api/stacks/index.rst +++ b/docs/api/stacks/index.rst @@ -50,7 +50,7 @@ :toctree: :nosignatures: - EnvironmentExportRequest + LayerExportRequest ExportMetadata ExportedEnvironmentPaths StackExportRequest @@ -61,7 +61,9 @@ :toctree: :nosignatures: + LayerSpecBase RuntimeSpec + LayeredSpecBase FrameworkSpec ApplicationSpec @@ -71,9 +73,11 @@ :toctree: :nosignatures: - ApplicationEnv - FrameworkEnv + LayerEnvBase RuntimeEnv + LayeredEnvBase + FrameworkEnv + ApplicationEnv EnvironmentLock EnvironmentLockMetadata diff --git a/docs/api/stacks/venvstacks.stacks.ApplicationEnv.rst b/docs/api/stacks/venvstacks.stacks.ApplicationEnv.rst index dca1c1e..ec9a1d0 100644 --- a/docs/api/stacks/venvstacks.stacks.ApplicationEnv.rst +++ b/docs/api/stacks/venvstacks.stacks.ApplicationEnv.rst @@ -5,23 +5,6 @@ venvstacks.stacks.ApplicationEnv .. autoclass:: ApplicationEnv - .. rubric:: Methods - - .. autosummary:: - - ~ApplicationEnv.create_archive - ~ApplicationEnv.create_environment - ~ApplicationEnv.define_archive_build - ~ApplicationEnv.export_environment - ~ApplicationEnv.get_constraint_paths - ~ApplicationEnv.install_requirements - ~ApplicationEnv.link_base_runtime - ~ApplicationEnv.link_layered_environments - ~ApplicationEnv.lock_requirements - ~ApplicationEnv.report_python_site_details - ~ApplicationEnv.request_export - ~ApplicationEnv.select_operations - .. rubric:: Attributes .. autosummary:: diff --git a/docs/api/stacks/venvstacks.stacks.ApplicationSpec.rst b/docs/api/stacks/venvstacks.stacks.ApplicationSpec.rst index 8168faf..230ba06 100644 --- a/docs/api/stacks/venvstacks.stacks.ApplicationSpec.rst +++ b/docs/api/stacks/venvstacks.stacks.ApplicationSpec.rst @@ -5,14 +5,6 @@ venvstacks.stacks.ApplicationSpec .. autoclass:: ApplicationSpec - - .. rubric:: Methods - - .. autosummary:: - - ~ApplicationSpec.get_requirements_fname - ~ApplicationSpec.get_requirements_path - .. rubric:: Attributes .. autosummary:: diff --git a/docs/api/stacks/venvstacks.stacks.ArchiveBuildRequest.rst b/docs/api/stacks/venvstacks.stacks.ArchiveBuildRequest.rst index f248b44..c7ac991 100644 --- a/docs/api/stacks/venvstacks.stacks.ArchiveBuildRequest.rst +++ b/docs/api/stacks/venvstacks.stacks.ArchiveBuildRequest.rst @@ -12,7 +12,6 @@ venvstacks.stacks.ArchiveBuildRequest ~ArchiveBuildRequest.create_archive ~ArchiveBuildRequest.define_build - ~ArchiveBuildRequest.needs_archive_build .. rubric:: Attributes diff --git a/docs/api/stacks/venvstacks.stacks.EnvironmentExportRequest.rst b/docs/api/stacks/venvstacks.stacks.EnvironmentExportRequest.rst deleted file mode 100644 index 75e4758..0000000 --- a/docs/api/stacks/venvstacks.stacks.EnvironmentExportRequest.rst +++ /dev/null @@ -1,26 +0,0 @@ -venvstacks.stacks.EnvironmentExportRequest -========================================== - -.. currentmodule:: venvstacks.stacks - -.. autoclass:: EnvironmentExportRequest - - - .. rubric:: Methods - - .. autosummary:: - - ~EnvironmentExportRequest.define_export - ~EnvironmentExportRequest.export_environment - ~EnvironmentExportRequest.needs_new_export - - .. rubric:: Attributes - - .. autosummary:: - - ~EnvironmentExportRequest.env_name - ~EnvironmentExportRequest.env_lock - ~EnvironmentExportRequest.export_path - ~EnvironmentExportRequest.export_metadata - ~EnvironmentExportRequest.needs_export - diff --git a/docs/api/stacks/venvstacks.stacks.FrameworkSpec.rst b/docs/api/stacks/venvstacks.stacks.FrameworkSpec.rst index a871f09..7d9c221 100644 --- a/docs/api/stacks/venvstacks.stacks.FrameworkSpec.rst +++ b/docs/api/stacks/venvstacks.stacks.FrameworkSpec.rst @@ -5,14 +5,6 @@ venvstacks.stacks.FrameworkSpec .. autoclass:: FrameworkSpec - - .. rubric:: Methods - - .. autosummary:: - - ~FrameworkSpec.get_requirements_fname - ~FrameworkSpec.get_requirements_path - .. rubric:: Attributes .. autosummary:: diff --git a/docs/api/stacks/venvstacks.stacks.LayerEnvBase.rst b/docs/api/stacks/venvstacks.stacks.LayerEnvBase.rst new file mode 100644 index 0000000..b1c30a8 --- /dev/null +++ b/docs/api/stacks/venvstacks.stacks.LayerEnvBase.rst @@ -0,0 +1,50 @@ +venvstacks.stacks.LayerEnvBase +============================== + +.. currentmodule:: venvstacks.stacks + +.. autoclass:: LayerEnvBase + + + .. rubric:: Methods + + .. autosummary:: + + ~LayerEnvBase.create_archive + ~LayerEnvBase.create_environment + ~LayerEnvBase.define_archive_build + ~LayerEnvBase.export_environment + ~LayerEnvBase.get_constraint_paths + ~LayerEnvBase.install_requirements + ~LayerEnvBase.lock_requirements + ~LayerEnvBase.report_python_site_details + ~LayerEnvBase.request_export + ~LayerEnvBase.select_operations + + .. rubric:: Attributes + + .. autosummary:: + + ~LayerEnvBase.category + ~LayerEnvBase.env_name + ~LayerEnvBase.env_spec + ~LayerEnvBase.install_target + ~LayerEnvBase.kind + ~LayerEnvBase.want_build + ~LayerEnvBase.want_lock + ~LayerEnvBase.want_publish + ~LayerEnvBase.was_built + ~LayerEnvBase.was_created + ~LayerEnvBase.build_path + ~LayerEnvBase.requirements_path + ~LayerEnvBase.index_config + ~LayerEnvBase.env_path + ~LayerEnvBase.pylib_path + ~LayerEnvBase.dynlib_path + ~LayerEnvBase.executables_path + ~LayerEnvBase.python_path + ~LayerEnvBase.env_lock + ~LayerEnvBase.base_python_path + ~LayerEnvBase.tools_python_path + ~LayerEnvBase.py_version + diff --git a/docs/api/stacks/venvstacks.stacks.LayerExportRequest.rst b/docs/api/stacks/venvstacks.stacks.LayerExportRequest.rst new file mode 100644 index 0000000..a9ec932 --- /dev/null +++ b/docs/api/stacks/venvstacks.stacks.LayerExportRequest.rst @@ -0,0 +1,25 @@ +venvstacks.stacks.LayerExportRequest +==================================== + +.. currentmodule:: venvstacks.stacks + +.. autoclass:: LayerExportRequest + + + .. rubric:: Methods + + .. autosummary:: + + ~LayerExportRequest.define_export + ~LayerExportRequest.export_environment + + .. rubric:: Attributes + + .. autosummary:: + + ~LayerExportRequest.env_name + ~LayerExportRequest.env_lock + ~LayerExportRequest.export_path + ~LayerExportRequest.export_metadata + ~LayerExportRequest.needs_export + diff --git a/docs/api/stacks/venvstacks.stacks.LayerSpecBase.rst b/docs/api/stacks/venvstacks.stacks.LayerSpecBase.rst new file mode 100644 index 0000000..42ea4f7 --- /dev/null +++ b/docs/api/stacks/venvstacks.stacks.LayerSpecBase.rst @@ -0,0 +1,29 @@ +venvstacks.stacks.LayerSpecBase +=============================== + +.. currentmodule:: venvstacks.stacks + +.. autoclass:: LayerSpecBase + + + .. rubric:: Methods + + .. autosummary:: + + ~LayerSpecBase.get_requirements_fname + ~LayerSpecBase.get_requirements_path + + .. rubric:: Attributes + + .. autosummary:: + + ~LayerSpecBase.ENV_PREFIX + ~LayerSpecBase.category + ~LayerSpecBase.env_name + ~LayerSpecBase.kind + ~LayerSpecBase.name + ~LayerSpecBase.versioned + ~LayerSpecBase.requirements + ~LayerSpecBase.build_requirements + ~LayerSpecBase.platforms + diff --git a/docs/api/stacks/venvstacks.stacks.LayeredEnvBase.rst b/docs/api/stacks/venvstacks.stacks.LayeredEnvBase.rst new file mode 100644 index 0000000..9be5be2 --- /dev/null +++ b/docs/api/stacks/venvstacks.stacks.LayeredEnvBase.rst @@ -0,0 +1,36 @@ +venvstacks.stacks.LayeredEnvBase +================================ + +.. currentmodule:: venvstacks.stacks + +.. autoclass:: LayeredEnvBase + + .. rubric:: Attributes + + .. autosummary:: + + ~LayeredEnvBase.base_runtime + ~LayeredEnvBase.category + ~LayeredEnvBase.env_name + ~LayeredEnvBase.env_spec + ~LayeredEnvBase.install_target + ~LayeredEnvBase.kind + ~LayeredEnvBase.linked_constraints_paths + ~LayeredEnvBase.want_build + ~LayeredEnvBase.want_lock + ~LayeredEnvBase.want_publish + ~LayeredEnvBase.was_built + ~LayeredEnvBase.was_created + ~LayeredEnvBase.build_path + ~LayeredEnvBase.requirements_path + ~LayeredEnvBase.index_config + ~LayeredEnvBase.env_path + ~LayeredEnvBase.pylib_path + ~LayeredEnvBase.dynlib_path + ~LayeredEnvBase.executables_path + ~LayeredEnvBase.python_path + ~LayeredEnvBase.env_lock + ~LayeredEnvBase.base_python_path + ~LayeredEnvBase.tools_python_path + ~LayeredEnvBase.py_version + diff --git a/docs/api/stacks/venvstacks.stacks.LayeredSpecBase.rst b/docs/api/stacks/venvstacks.stacks.LayeredSpecBase.rst new file mode 100644 index 0000000..5f61f5d --- /dev/null +++ b/docs/api/stacks/venvstacks.stacks.LayeredSpecBase.rst @@ -0,0 +1,22 @@ +venvstacks.stacks.LayeredSpecBase +================================= + +.. currentmodule:: venvstacks.stacks + +.. autoclass:: LayeredSpecBase + + .. rubric:: Attributes + + .. autosummary:: + + ~LayeredSpecBase.ENV_PREFIX + ~LayeredSpecBase.category + ~LayeredSpecBase.env_name + ~LayeredSpecBase.kind + ~LayeredSpecBase.runtime + ~LayeredSpecBase.name + ~LayeredSpecBase.versioned + ~LayeredSpecBase.requirements + ~LayeredSpecBase.build_requirements + ~LayeredSpecBase.platforms + diff --git a/docs/api/stacks/venvstacks.stacks.RuntimeEnv.rst b/docs/api/stacks/venvstacks.stacks.RuntimeEnv.rst index 836ec81..f7cafa7 100644 --- a/docs/api/stacks/venvstacks.stacks.RuntimeEnv.rst +++ b/docs/api/stacks/venvstacks.stacks.RuntimeEnv.rst @@ -5,23 +5,6 @@ venvstacks.stacks.RuntimeEnv .. autoclass:: RuntimeEnv - - .. rubric:: Methods - - .. autosummary:: - - ~RuntimeEnv.create_archive - ~RuntimeEnv.create_build_environment - ~RuntimeEnv.create_environment - ~RuntimeEnv.define_archive_build - ~RuntimeEnv.export_environment - ~RuntimeEnv.get_constraint_paths - ~RuntimeEnv.install_requirements - ~RuntimeEnv.lock_requirements - ~RuntimeEnv.report_python_site_details - ~RuntimeEnv.request_export - ~RuntimeEnv.select_operations - .. rubric:: Attributes .. autosummary:: diff --git a/docs/api/stacks/venvstacks.stacks.RuntimeSpec.rst b/docs/api/stacks/venvstacks.stacks.RuntimeSpec.rst index 0298f29..4d6f344 100644 --- a/docs/api/stacks/venvstacks.stacks.RuntimeSpec.rst +++ b/docs/api/stacks/venvstacks.stacks.RuntimeSpec.rst @@ -5,14 +5,6 @@ venvstacks.stacks.RuntimeSpec .. autoclass:: RuntimeSpec - - .. rubric:: Methods - - .. autosummary:: - - ~RuntimeSpec.get_requirements_fname - ~RuntimeSpec.get_requirements_path - .. rubric:: Attributes .. autosummary:: diff --git a/docs/conf.py b/docs/conf.py index 8932a0e..603e4c2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -22,6 +22,7 @@ "sphinx.ext.duration", "sphinx.ext.extlinks", "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", ] templates_path = ["_templates"] @@ -49,6 +50,15 @@ # (still using autodoc, but no generated stub files) autosummary_generate = False + +# -- Options for extlinks ------------------------------------------------------------- + +extlinks = { + "issue": ("https://github.com/lmstudio-ai/venvstacks/issues/%s", "#%s"), + "pr": ("https://github.com/lmstudio-ai/venvstacks/pull/%s", "PR #%s"), + "pypi": ("https://pypi.org/project/%s/", "%s"), +} + # -- Options for intersphinx ---------------------------------------------------------- intersphinx_mapping = { @@ -57,10 +67,7 @@ } -# -- Options for extlinks ------------------------------------------------------------- +# -- Options for napoleon ------------------------------------------------------------ -extlinks = { - "issue": ("https://github.com/lmstudio-ai/venvstacks/issues/%s", "#%s"), - "pr": ("https://github.com/lmstudio-ai/venvstacks/pull/%s", "PR #%s"), - "pypi": ("https://pypi.org/project/%s/", "%s"), -} +napoleon_google_docstring = True +napoleon_numpy_docstring = False diff --git a/misc/find_shared_libs.py b/misc/find_shared_libs.py index 3bd3914..b08dd42 100755 --- a/misc/find_shared_libs.py +++ b/misc/find_shared_libs.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Proof of concept for finding non-Python-module shared modules""" +"""Proof of concept for finding non-Python-module shared modules.""" # Concept demonstrator for the cross-environment shared library loading support # described in https://github.com/lmstudio-ai/venvstacks/issues/1 @@ -26,6 +26,7 @@ def _ext_to_suffixes(extension: str) -> tuple["str", ...]: def main() -> None: + """Find non-extension-module shared libraries in specified folder.""" _dir_to_search = sys.argv[1] _paths_to_link = [] for this_dir, _, files in os.walk(_dir_to_search): diff --git a/pyproject.toml b/pyproject.toml index 4c6c6bf..c1fd7e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "venvstacks" -version = "0.2.0.dev0" +version = "0.2.0.dev1" description = "Use layered Python virtual environment stacks to share large dependencies" authors = [ {name = "Alyssa Coghlan", email = "ncoghlan@gmail.com"}, @@ -123,6 +123,23 @@ source = [ # Assume Python 3.11 target-version = "py311" +[tool.ruff.lint] +# Enable all `pydocstyle` rules, limiting to those that adhere to the +# Google convention via `convention = "google"`, below. +select = ["D"] + +# Disable `D105` (it's OK to skip writing docstrings for every magic method) +# Disable `D417` (not yet requiring documentation for every function parameter) +ignore = ["D105", "D417"] + +[tool.ruff.lint.pydocstyle] +# https://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html#example-google +convention = "google" + +[tool.ruff.lint.per-file-ignores] +# Skip checking docstrings in the test suite +"tests/**" = ["D"] + [tool.scriv] version = "literal: pyproject.toml: project.version" ghrel_template = "file: ghrel_template.md.j2" diff --git a/src/venvstacks/__init__.py b/src/venvstacks/__init__.py index e69de29..533468f 100644 --- a/src/venvstacks/__init__.py +++ b/src/venvstacks/__init__.py @@ -0,0 +1,4 @@ +"""Layered virtual environments stacks. + +Note: Python API is *NOT YET STABLE* +""" diff --git a/src/venvstacks/__main__.py b/src/venvstacks/__main__.py index 0863d3a..f74b073 100644 --- a/src/venvstacks/__main__.py +++ b/src/venvstacks/__main__.py @@ -1,4 +1,4 @@ -"""Allow execution of the package as a script""" +"""Allow execution of the package as a script.""" from .cli import main diff --git a/src/venvstacks/_injected/postinstall.py b/src/venvstacks/_injected/postinstall.py index 9823673..e6730fe 100644 --- a/src/venvstacks/_injected/postinstall.py +++ b/src/venvstacks/_injected/postinstall.py @@ -1,4 +1,4 @@ -"""venvstacks layer post-installation script +"""venvstacks layer post-installation script. * Loads `./share/venv/metadata/venvstacks_layer.json` * Generates `pyvenv.cfg` for layered environments @@ -20,7 +20,7 @@ class LayerConfig(TypedDict): - """Additional details needed to fully configure deployed environments""" + """Additional details needed to fully configure deployed environments.""" # fmt: off python: str # Relative path to this layer's Python executable @@ -38,7 +38,7 @@ class LayerConfig(TypedDict): class ResolvedLayerConfig(TypedDict): - """LayerConfig with relative paths resolved for a specific layer location""" + """LayerConfig with relative paths resolved for a specific layer location.""" # fmt: off layer_path: Path # Absolute path to layer environment @@ -53,10 +53,10 @@ class ResolvedLayerConfig(TypedDict): def load_layer_config(layer_path: Path) -> ResolvedLayerConfig: - """Read and resolve config for the specified layer environment""" + """Read and resolve config for the specified layer environment.""" def deployed_path(relative_path: str) -> Path: - """Normalize path and make it absolute, *without* resolving symlinks""" + """Normalize path and make it absolute, *without* resolving symlinks.""" return Path(abspath(layer_path / relative_path)) config_path = layer_path / DEPLOYED_LAYER_CONFIG @@ -76,7 +76,7 @@ def deployed_path(relative_path: str) -> Path: def generate_pyvenv_cfg(base_python_path: Path, py_version: str) -> str: - """Generate `pyvenv.cfg` contents for given base Python path and version""" + """Generate `pyvenv.cfg` contents for given base Python path and version.""" if not base_python_path.is_absolute(): raise RuntimeError("Post-installation must use absolute environment paths") venv_config_lines = [ @@ -107,7 +107,7 @@ def generate_sitecustomize( *, skip_missing_dynlib_paths: bool = True, ) -> str | None: - """Generate `sitecustomize.py` contents for given linked environment directories""" + """Generate `sitecustomize.py` contents for given linked environment directories.""" sc_contents = [_SITE_CUSTOMIZE_HEADER] if pylib_paths: pylib_contents = [ @@ -148,8 +148,7 @@ def generate_sitecustomize( def _run_postinstall(layer_path: Path) -> None: - """Run the required post-installation steps in a deployed environment""" - + """Run the required post-installation steps in a deployed environment.""" # Read the layer config file config = load_layer_config(layer_path) diff --git a/src/venvstacks/_util.py b/src/venvstacks/_util.py index 0c3b93d..7fd911f 100644 --- a/src/venvstacks/_util.py +++ b/src/venvstacks/_util.py @@ -1,4 +1,4 @@ -"""Common utilities for stack creation and venv publication""" +"""Common utilities for stack creation and venv publication.""" import os import os.path @@ -16,7 +16,7 @@ def as_normalized_path(path: StrPath, /) -> Path: - """Normalize given path and make it absolute, *without* resolving symlinks + """Normalize given path and make it absolute, *without* resolving symlinks. Expands user directory references, but *not* environment variable references. """ @@ -27,7 +27,7 @@ def as_normalized_path(path: StrPath, /) -> Path: @contextmanager def default_tarfile_filter(filter: str) -> Generator[None, None, None]: - """Temporarily set a global tarfile filter (useful for 3rd party API warnings)""" + """Temporarily set a global tarfile filter (useful for 3rd party API warnings).""" if sys.version_info < (3, 12): # Python 3.11 or earlier, can't set a default extraction filter yield @@ -46,7 +46,7 @@ def default_tarfile_filter(filter: str) -> Generator[None, None, None]: def get_env_python(env_path: Path) -> Path: - """Return the main Python binary in the given Python environment""" + """Return the main Python binary in the given Python environment.""" if WINDOWS_BUILD: env_python = env_path / "Scripts" / "python.exe" if not env_python.exists(): diff --git a/src/venvstacks/cli.py b/src/venvstacks/cli.py index cffc177..92237a0 100644 --- a/src/venvstacks/cli.py +++ b/src/venvstacks/cli.py @@ -1,4 +1,4 @@ -"""Command line interface implementation""" +"""Command line interface implementation.""" import os.path import sys @@ -174,7 +174,7 @@ def _define_build_environment( index: bool, local_wheels: list[str] | None, ) -> BuildEnvironment: - """Load given stack specification and define a build environment""" + """Load given stack specification and define a build environment.""" stack_spec = StackSpec.load(spec_path) index_config = PackageIndexConfig( query_default_index=index, @@ -548,6 +548,10 @@ def local_export( def main(args: list[str] | None = None) -> None: + """Run the ``venvstacks`` CLI. + + If *args* is not given, defaults to using ``sys.argv``. + """ # Indirectly calls the relevant click.Command variant's `main` method # See https://click.palletsprojects.com/en/8.1.x/api/#click.BaseCommand.main _cli(args) diff --git a/src/venvstacks/pack_venv.py b/src/venvstacks/pack_venv.py index 4f2e557..618b401 100755 --- a/src/venvstacks/pack_venv.py +++ b/src/venvstacks/pack_venv.py @@ -1,5 +1,5 @@ #!/bin/python3 -"""Utility library to convert Python virtual environments to portable archives""" +"""Utility library to convert Python virtual environments to portable archives.""" # This is conceptually inspired by conda-pack (but structured somewhat differently). # venv-pack and venv-pack2 were considered, and may still be an option in the future, @@ -53,7 +53,7 @@ def convert_symlinks( env_dir: StrPath, containing_dir: StrPath | None = None, ) -> tuple[list[SymlinkInfo], list[SymlinkInfo]]: - """Make env portable by making internal symlinks relative and external links hard + """Make env portable by making internal symlinks relative and external links hard. If set, containing path must be a parent directory of the environment path and is used as the boundary for creating relative symlinks instead of hardlinks. If not set, @@ -107,7 +107,7 @@ def convert_symlinks( def get_archive_path(archive_base_name: StrPath) -> Path: - """Report the name of the archive that will be created for the given base name""" + """Report the name of the archive that will be created for the given base name.""" extension = ".zip" if _WINDOWS_BUILD else ".tar.xz" return Path(os.fspath(archive_base_name) + extension) @@ -148,12 +148,12 @@ def export_venv( target_dir: StrPath, run_postinstall: Callable[[Path, Path], None] | None = None, ) -> Path: - """Export the given build environment, skipping archive creation and unpacking + """Export the given build environment, skipping archive creation and unpacking. - * injects a suitable `postinstall.py` script for the environment being exported - * excludes __pycache__ folders (for consistency with archive publication) + * injects a suitable ``postinstall.py`` script for the environment being exported + * excludes ``__pycache__`` folders (for consistency with archive publication) * excludes package metadata RECORD files (for consistency with archive publication) - * excludes `sitecustomize.py` files (generated by the post-installation script) + * excludes ``sitecustomize.py`` files (generated by the post-installation script) * replaces symlinks with copies on Windows or if the target doesn't support symlinks If supplied, *run_postinstall* is called with the path to the environment's Python @@ -195,13 +195,13 @@ def create_archive( work_dir: StrPath | None = None, show_progress: bool = True, ) -> Path: - """shutil.make_archive replacement, tailored for Python virtual environments + """shutil.make_archive replacement, tailored for Python virtual environments. - * injects a suitable `postinstall.py` script for the environment being archived + * injects a suitable ``postinstall.py`` script for the environment being archived * always creates zipfile archives on Windows and xztar archives elsewhere - * excludes __pycache__ folders (to reduce archive size and improve reproducibility) + * excludes ``__pycache__`` folders (to reduce archive size and improve reproducibility) * excludes package metadata RECORD files (to improve reproducibility) - * excludes `sitecustomize.py` files (generated by the post-installation script) + * excludes ``sitecustomize.py`` files (generated by the post-installation script) * replaces symlinks with copies on Windows and allows external symlinks elsewhere * discards tar entry owner and group information * clears tar entry high mode bits (setuid, setgid, sticky) @@ -209,7 +209,7 @@ def create_archive( * clamps mtime of archived files to the given clamp mtime at the latest * shows progress reporting by default (archiving built ML/AI libs is *slooooow*) - Set `work_dir` if /tmp is too small for archiving tasks + Set *work_dir* if ``/tmp`` is too small for archiving tasks """ archive_path = as_normalized_path(archive_base_name) source_path = Path(source_dir) @@ -271,8 +271,7 @@ def _make_tar_archive( *, compress: str = "xz", ) -> str: - """Create a (possibly compressed) tar file from all the files under - 'base_dir'. + """Create a (possibly compressed) tar file from all the files under 'base_dir'. 'compress' must be "gzip", "bzip2", "xz", or None. @@ -342,7 +341,7 @@ def _process_archive_entry(tarinfo: tarfile.TarInfo) -> tarfile.TarInfo: if mode is not None: # Apply the same mode filtering as tarfile.tar_filter in 3.12+ # https://docs.python.org/3.13/library/tarfile.html#tarfile.tar_filter - # Clears high bits (e.g. setuid/)setgid, and the group/other write bits + # Clears high bits (e.g. setuid/setgid), and the group/other write bits tarinfo.mode = mode & 0o755 # Report progress if requested if progress_callback is not None: @@ -500,7 +499,7 @@ def _add_zip_entry(fspath: str, arcname: str) -> None: class _ProgressBar: - """Display & update a progress bar""" + """Display & update a progress bar.""" TEXT_ABORTING = "Aborting..." TEXT_COMPLETE = "Complete!" @@ -518,12 +517,12 @@ def __init__(self, bar_length: int = 25, stream: TextIO = sys.stdout) -> None: self._last_displayed_summary = None def reset(self) -> None: - """Forget any previously displayed text (affects subsequent call to show())""" + """Forget any previously displayed text (affects subsequent call to show()).""" self._last_displayed_text = None self._last_displayed_summary = None def _format_progress(self, progress: float, aborting: bool) -> _ProgressReport: - """Internal helper that also reports the number of completed increments""" + """Internal helper that also reports the number of completed increments.""" bar_length = self.bar_length progress = float(progress) if progress >= 1: @@ -542,11 +541,11 @@ def _format_progress(self, progress: float, aborting: bool) -> _ProgressReport: return progress_text, (completed_increments, status) def format_progress(self, progress: float, *, aborting: bool = False) -> str: - """Format progress bar, percentage, and status for given fractional progress""" + """Format progress bar, percentage, and status for given fractional progress.""" return self._format_progress(progress, aborting)[0] def show(self, progress: float, *, aborting: bool = False) -> None: - """Display the current progress on the console""" + """Display the current progress on the console.""" progress_text, progress_summary = self._format_progress(progress, aborting) if progress_text == self._last_displayed_text: # No change to display output, so skip writing anything diff --git a/src/venvstacks/stacks.py b/src/venvstacks/stacks.py index 82a68fe..62af817 100755 --- a/src/venvstacks/stacks.py +++ b/src/venvstacks/stacks.py @@ -1,5 +1,5 @@ #!/bin/python3 -"""Portable Python environment stacks +"""Portable Python environment stacks. Creates Python runtime, framework, and app environments based on venvstacks.toml """ @@ -54,15 +54,15 @@ class EnvStackError(ValueError): - """Common base class for all errors specific to managing environment stacks""" + """Common base class for all errors specific to managing environment stacks.""" class LayerSpecError(EnvStackError): - """Raised when an internal inconsistency is found in a layer specification""" + """Raised when an internal inconsistency is found in a layer specification.""" class BuildEnvError(EnvStackError): - """Raised when a build environment doesn't comply with process restrictions""" + """Raised when a build environment doesn't comply with process restrictions.""" ###################################################### @@ -70,21 +70,21 @@ class BuildEnvError(EnvStackError): ###################################################### try: - fs_sync = os.sync # type: ignore[attr-defined,unused-ignore] + _fs_sync = os.sync # type: ignore[attr-defined,unused-ignore] except AttributeError: # No os.sync on Windows - def fs_sync() -> None: + def _fs_sync() -> None: pass -def get_path_mtime(fspath: StrPath) -> datetime | None: +def _get_path_mtime(fspath: StrPath) -> datetime | None: path = Path(fspath) if not path.exists(): return None return datetime.fromtimestamp(path.lstat().st_mtime).astimezone() -def format_as_utc(dt: datetime) -> str: +def _format_as_utc(dt: datetime) -> str: return dt.astimezone(timezone.utc).isoformat() @@ -116,7 +116,7 @@ def _resolve_lexical_path(path: StrPath, base_path: Path, /) -> Path: @dataclass class PackageIndexConfig: - """Python package index access configuration""" + """Python package index access configuration.""" query_default_index: bool = field(default=True) local_wheel_dirs: InitVar[Sequence[StrPath] | None] = None @@ -133,14 +133,14 @@ def __post_init__(self, local_wheel_dirs: Sequence[StrPath] | None) -> None: @classmethod def disabled(cls) -> Self: - """Package index configuration that disallows package installation""" + """Package index configuration that disallows package installation.""" return cls( query_default_index=False, local_wheel_dirs=None, ) def resolve_lexical_paths(self, base_path: StrPath) -> None: - """Lexically resolve paths in config relative to the given base path""" + """Lexically resolve paths in config relative to the given base path.""" base_path = Path(base_path) self.local_wheel_paths[:] = [ _resolve_lexical_path(path, base_path) for path in self.local_wheel_paths @@ -175,7 +175,7 @@ def _get_pip_install_args(self) -> list[str]: class EnvironmentLockMetadata(TypedDict): - """Details of the last time this environment was locked""" + """Details of the last time this environment was locked.""" # fmt: off locked_at: str # ISO formatted date/time value @@ -189,7 +189,7 @@ class EnvironmentLockMetadata(TypedDict): @dataclass class EnvironmentLock: - """Layered environment dependency locking management""" + """Layered environment dependency locking management.""" requirements_path: Path versioned: bool @@ -205,6 +205,10 @@ def __post_init__(self) -> None: self._lock_version = self._get_last_locked_version(requirements_hash) def get_deployed_name(self, env_name: EnvNameBuild) -> EnvNameDeploy: + """Report layer name with lock version (if any) appended. + + Deployed name matches the build name if automatic lock versioning is disabled. + """ if self.versioned: return EnvNameDeploy(f"{env_name}@{self.lock_version}") return EnvNameDeploy(env_name) @@ -221,14 +225,20 @@ def _raise_if_none(self, value: _T | None) -> _T: @property def requirements_hash(self) -> str: + """Hash of the last locked set of layer requirements.""" return self._raise_if_none(self._requirements_hash) @property def last_locked(self) -> datetime: + """Date and time when the layer requirements were last locked.""" return self._raise_if_none(self._last_locked) @property def lock_version(self) -> int: + """Last recorded version of the layer requirements. + + Always reports ``1`` if automatic lock versioning is disabled. + """ if not self.versioned: # Unversioned specs are always considered version 1 return 1 @@ -236,11 +246,13 @@ def lock_version(self) -> int: @property def is_locked(self) -> bool: + """``True`` if layer requirements have been locked.""" return self._last_locked is not None @property def locked_at(self) -> str: - return format_as_utc(self.last_locked) + """ISO-formated UTC string reporting the last locked date/time.""" + return _format_as_utc(self.last_locked) def _hash_requirements(self) -> str | None: requirements_path = self.requirements_path @@ -249,7 +261,7 @@ def _hash_requirements(self) -> str | None: return _hash_file(self.requirements_path) def _load_saved_metadata(self) -> EnvironmentLockMetadata | None: - """Loads last locked metadata from disk (if it exists)""" + """Loads last locked metadata from disk (if it exists).""" lock_metadata_path = self.lock_metadata_path if not lock_metadata_path.exists(): return None @@ -263,7 +275,7 @@ def _load_saved_metadata(self) -> EnvironmentLockMetadata | None: def load_valid_metadata( self, requirements_hash: str ) -> EnvironmentLockMetadata | None: - """Loads last locked metadata only if the requirements hash matches""" + """Loads last locked metadata only if the requirements hash matches.""" lock_metadata = self._load_saved_metadata() if lock_metadata and requirements_hash == lock_metadata["requirements_hash"]: return lock_metadata @@ -276,7 +288,7 @@ def _get_last_locked_metadata(self, requirements_hash: str) -> datetime | None: return None def _get_path_mtime(self) -> datetime | None: - return get_path_mtime(self.requirements_path) + return _get_path_mtime(self.requirements_path) def _get_last_locked_time(self, requirements_hash: str | None) -> datetime | None: # Prefer the lock timestamp from an adjacent (still valid) lock metadata file @@ -325,6 +337,7 @@ def _write_lock_metadata(self) -> None: _write_json(self.lock_metadata_path, lock_metadata) def update_lock_metadata(self) -> bool: + """Update the recorded lock metadata for this environment lock.""" # Calculate current requirements hash requirements_hash = self._hash_requirements() if requirements_hash is None: @@ -348,7 +361,7 @@ def update_lock_metadata(self) -> bool: # https://packaging.python.org/en/latest/specifications/platform-compatibility-tags/#basic-platform-tags # macOS target system API version info is omitted (as that will be set universally for macOS builds) class TargetPlatforms(StrEnum): - """Enum for support target deployment platforms""" + """Enum for support target deployment platforms.""" WINDOWS = "win_amd64" LINUX = "linux_x86_64" @@ -357,10 +370,15 @@ class TargetPlatforms(StrEnum): @classmethod def get_all_target_platforms(cls) -> list[Self]: + """Sorted list of all defined target platforms.""" return sorted(set(cls.__members__.values())) @classmethod def ensure_platform_list(cls, metadata: MutableMapping[str, Any]) -> None: + """Ensure given metadata mapping includes a ``"platforms`` field. + + If the field is not already present, it is set to all defined platforms. + """ platform_list = metadata.get("platforms") if platform_list is not None: platform_list = [cls(target) for target in platform_list] @@ -375,7 +393,7 @@ def ensure_platform_list(cls, metadata: MutableMapping[str, Any]) -> None: class LayerVariants(StrEnum): - """Enum for defined layer variants""" + """Enum for defined layer variants.""" RUNTIME = "runtime" FRAMEWORK = "framework" @@ -383,7 +401,7 @@ class LayerVariants(StrEnum): class LayerCategories(StrEnum): - """Enum for defined layer categories (collections of each variant)""" + """Enum for defined layer categories (collections of each variant).""" RUNTIMES = "runtimes" FRAMEWORKS = "frameworks" @@ -391,15 +409,15 @@ class LayerCategories(StrEnum): def ensure_optional_env_spec_fields(env_metadata: MutableMapping[str, Any]) -> None: - """Populate missing environment spec fields that are optional in the TOML file""" + """Populate missing environment spec fields that are optional in the TOML file.""" TargetPlatforms.ensure_platform_list(env_metadata) env_metadata.setdefault("build_requirements", []) env_metadata.setdefault("versioned", False) @dataclass -class _PythonEnvironmentSpec(ABC): - "Common base class for Python environment specifications" +class LayerSpecBase(ABC): + """Common base class for layer environment specifications.""" # Optionally overridden in concrete subclasses ENV_PREFIX = "" @@ -420,7 +438,7 @@ def __post_init__(self) -> None: # they're not allowed to use prefixes that *are* defined if not self.ENV_PREFIX: spec_name = self.name - for spec_type in _PythonEnvironmentSpec.__subclasses__(): + for spec_type in LayerSpecBase.__subclasses__(): reserved_prefix = spec_type.ENV_PREFIX if not reserved_prefix: continue @@ -430,22 +448,25 @@ def __post_init__(self) -> None: @property def env_name(self) -> EnvNameBuild: + """Build environment name for this layer specification.""" prefix = self.ENV_PREFIX if prefix: return EnvNameBuild(f"{prefix}-{self.name}") return EnvNameBuild(self.name) def get_requirements_fname(self, platform: str) -> str: + """Locked requirements file name for this layer specification.""" return f"requirements-{self.env_name}-{platform}.txt" def get_requirements_path(self, platform: str, requirements_dir: StrPath) -> Path: + """Locked requirements path for this layer specification.""" requirements_fname = self.get_requirements_fname(platform) return Path(requirements_dir) / self.env_name / requirements_fname @dataclass -class RuntimeSpec(_PythonEnvironmentSpec): - """Base runtime layer specification""" +class RuntimeSpec(LayerSpecBase): + """Base runtime layer specification.""" kind = LayerVariants.RUNTIME category = LayerCategories.RUNTIMES @@ -453,20 +474,22 @@ class RuntimeSpec(_PythonEnvironmentSpec): @property def py_version(self) -> str: + """Extract just the Python version string from the base runtime identifier.""" # fully_versioned_name should be of the form "implementation@X.Y.Z" # (this may need adjusting if runtimes other than CPython are ever used...) return self.fully_versioned_name.partition("@")[2] @dataclass -class _VirtualEnvironmentSpec(_PythonEnvironmentSpec): +class LayeredSpecBase(LayerSpecBase): + """Common base class for framework and application layer specifications.""" # Intermediate class for covariant property typing (never instantiated) runtime: RuntimeSpec = field(repr=False) @dataclass -class FrameworkSpec(_VirtualEnvironmentSpec): - """Framework layer specification""" +class FrameworkSpec(LayeredSpecBase): + """Framework layer specification.""" ENV_PREFIX = "framework" kind = LayerVariants.FRAMEWORK @@ -474,8 +497,8 @@ class FrameworkSpec(_VirtualEnvironmentSpec): @dataclass -class ApplicationSpec(_VirtualEnvironmentSpec): - """Application layer specification""" +class ApplicationSpec(LayeredSpecBase): + """Application layer specification.""" ENV_PREFIX = "app" kind = LayerVariants.APPLICATION @@ -485,7 +508,7 @@ class ApplicationSpec(_VirtualEnvironmentSpec): class LayerSpecMetadata(TypedDict): - """Details of a defined environment layer""" + """Details of a defined environment layer.""" # fmt: off # Common fields defined for all layers, whether archived or exported @@ -520,7 +543,7 @@ class LayerSpecMetadata(TypedDict): class ArchiveHashes(TypedDict): - """Hash details of a built archive""" + """Hash details of a built archive.""" sha256: str # Only SHA256 hashes for now. Mark both old and new hash fields with `typing.NotRequired` @@ -528,7 +551,7 @@ class ArchiveHashes(TypedDict): class ArchiveBuildMetadata(LayerSpecMetadata): - """Inputs to an archive build request for a single environment""" + """Inputs to an archive build request for a single environment.""" # fmt: off archive_build: int # Auto-incremented from previous build metadata @@ -538,7 +561,7 @@ class ArchiveBuildMetadata(LayerSpecMetadata): class ArchiveMetadata(ArchiveBuildMetadata): - """Archive details for a single environment (includes build request details)""" + """Archive details for a single environment (includes build request details).""" archive_size: int archive_hashes: ArchiveHashes @@ -546,7 +569,7 @@ class ArchiveMetadata(ArchiveBuildMetadata): @dataclass class ArchiveBuildRequest: - """Structured request to build a named output archive""" + """Structured request to build a named output archive.""" env_name: EnvNameBuild env_lock: EnvironmentLock @@ -556,7 +579,7 @@ class ArchiveBuildRequest: # TODO: Save full previous metadata for use when build is skipped @staticmethod - def needs_archive_build( + def _needs_archive_build( archive_path: Path, metadata: ArchiveBuildMetadata, previous_metadata: ArchiveMetadata | None, @@ -586,6 +609,7 @@ def define_build( previous_metadata: ArchiveMetadata | None = None, force: bool = False, ) -> Self: + """Define a new archive build request for the given environment.""" # Bump or set the archive build version lock_version = env_lock.lock_version if previous_metadata is None: @@ -622,7 +646,7 @@ def update_archive_name() -> tuple[Path, Path]: requirements_hash=env_lock.requirements_hash, target_platform=str(target_platform), # Convert enums to plain strings ) - needs_build = force or cls.needs_archive_build( + needs_build = force or cls._needs_archive_build( built_archive_path, build_metadata, previous_metadata ) if needs_build: @@ -649,6 +673,7 @@ def create_archive( previous_metadata: ArchiveMetadata | None = None, work_path: Path | None = None, ) -> tuple[ArchiveMetadata, Path]: + """Create the layer archive specified in this build request.""" if env_path.name != self.env_name: err_msg = ( f"Build mismatch (expected {self.env_name!r}, got {env_path.name!r})" @@ -688,7 +713,7 @@ def create_archive( class StackPublishingRequest(TypedDict): - """Inputs to an archive build request for a full stack specification""" + """Inputs to an archive build request for a full stack specification.""" layers: Mapping[LayerCategories, Sequence[ArchiveBuildMetadata]] @@ -697,13 +722,13 @@ class StackPublishingRequest(TypedDict): class StackPublishingResult(TypedDict): - """Archive details for built stack specification (includes build request details)""" + """Archive details for built stack specification (includes build request details).""" layers: LayeredArchiveMetadata class PublishedArchivePaths(NamedTuple): - """Locations of published metadata and archive files""" + """Locations of published metadata and archive files.""" metadata_path: Path snippet_paths: list[Path] @@ -716,14 +741,14 @@ class PublishedArchivePaths(NamedTuple): class ExportMetadata(LayerSpecMetadata): - """Metadata for a locally exported environment""" + """Metadata for a locally exported environment.""" # Exports currently include only the common metadata @dataclass -class EnvironmentExportRequest: - """Structured request to locally export an environment""" +class LayerExportRequest: + """Structured request to locally export an environment.""" env_name: EnvNameBuild env_lock: EnvironmentLock @@ -733,7 +758,7 @@ class EnvironmentExportRequest: # TODO: Save full previous metadata for use when export is skipped @staticmethod - def needs_new_export( + def _needs_new_export( export_path: Path, metadata: ExportMetadata, previous_metadata: ExportMetadata | None, @@ -760,6 +785,7 @@ def define_export( previous_metadata: ExportMetadata | None = None, force: bool = False, ) -> Self: + """Define a new layer export request for the given environment.""" # Work out the details of the export request deployed_name = env_lock.get_deployed_name(env_name) export_path = output_path / deployed_name @@ -770,7 +796,7 @@ def define_export( locked_at=env_lock.locked_at, requirements_hash=env_lock.requirements_hash, ) - needs_export = force or cls.needs_new_export( + needs_export = force or cls._needs_new_export( export_path, export_metadata, previous_metadata ) return cls(env_name, env_lock, export_path, export_metadata, needs_export) @@ -787,6 +813,7 @@ def export_environment( env_path: Path, previous_metadata: ExportMetadata | None = None, ) -> tuple[ExportMetadata, Path]: + """Locally export the layer environment specified in this export request.""" if env_path.name != self.env_name: err_msg = ( f"Export mismatch (expected {self.env_name!r}, got {env_path.name!r})" @@ -821,13 +848,13 @@ def _run_postinstall(_export_path: Path, postinstall_path: Path) -> None: class StackExportRequest(TypedDict): - """Inputs to an environment export request for a full stack specification""" + """Inputs to an environment export request for a full stack specification.""" layers: LayeredExportMetadata class ExportedEnvironmentPaths(NamedTuple): - """Locations of exported metadata files and deployed environments""" + """Locations of exported metadata files and deployed environments.""" metadata_path: Path snippet_paths: list[Path] @@ -940,6 +967,7 @@ def _hash_directory( def get_build_platform() -> TargetPlatform: + """Report target platform that matches the currently running system.""" # Currently no need for cross-build support, so always query the running system # Examples: win_amd64, linux_x86_64, macosx_10_12_x86_64, macosx_10_12_arm64 platform_name = sysconfig.get_platform() @@ -955,7 +983,8 @@ def get_build_platform() -> TargetPlatform: @dataclass -class _PythonEnvironment(ABC): +class LayerEnvBase(ABC): + """Common base class for layer build environment implementations.""" # Python environment used to run tools like `uv` and `pip` tools_python_path: ClassVar[Path] = Path(sys.executable) @@ -964,7 +993,7 @@ class _PythonEnvironment(ABC): category: ClassVar[LayerCategories] # Specified on creation - _env_spec: _PythonEnvironmentSpec = field(repr=False) + _env_spec: LayerSpecBase = field(repr=False) build_path: Path = field(repr=False) requirements_path: Path = field(repr=False) index_config: PackageIndexConfig = field(repr=False) @@ -1009,14 +1038,16 @@ def _get_python_dir_path(self) -> Path: @property def env_name(self) -> EnvNameBuild: + """The name of this environment in the build folder.""" return self.env_spec.env_name @property def install_target(self) -> EnvNameDeploy: + """The environment name used for this layer when deployed.""" return self.env_lock.get_deployed_name(self.env_spec.env_name) def get_deployed_path(self, build_path: Path) -> str: - """Get relative deployment location for a build env path""" + """Get relative deployment location for a build env path.""" env_deployed_path = Path(self.install_target) relative_path = build_path.relative_to(self.env_path) return str(env_deployed_path / relative_path) @@ -1044,13 +1075,14 @@ def __post_init__(self) -> None: ) @property - def env_spec(self) -> _PythonEnvironmentSpec: + def env_spec(self) -> LayerSpecBase: + """Layer specification for this environment.""" # Define property to allow covariance of the declared type of `env_spec` return self._env_spec @abstractmethod def get_deployed_config(self) -> postinstall.LayerConfig: - """Layer config to be published in `venvstacks_layer.json`""" + """Layer config to be published in `venvstacks_layer.json`.""" raise NotImplementedError def _get_deployed_config( @@ -1127,7 +1159,7 @@ def select_operations( build: bool | None = True, publish: bool = True, ) -> None: - """Enable the selected operations for this environment""" + """Enable the selected operations for this environment.""" self.want_lock = lock self.want_build = build self.want_publish = publish @@ -1167,6 +1199,7 @@ def create_environment(self, clean: bool = False) -> None: self._ensure_portability() def report_python_site_details(self) -> subprocess.CompletedProcess[str]: + """Print the results of running ``python -m site`` in this environment.""" print(f"Reporting environment details for {str(self.env_path)!r}") command = [ str(self.python_path), @@ -1272,10 +1305,12 @@ def _run_pip_install( return result def get_constraint_paths(self) -> list[Path]: + """Get the lower level layer constraints imposed on this environment.""" # No constraints files by default, subclasses override as necessary return [] def lock_requirements(self) -> EnvironmentLock: + """Transitively lock the requirements for this environment.""" spec = self.env_spec requirements_path = self.requirements_path if not self.want_lock and requirements_path.exists(): @@ -1303,6 +1338,10 @@ def lock_requirements(self) -> EnvironmentLock: return self.env_lock def install_requirements(self) -> subprocess.CompletedProcess[str]: + """Install the locked layer requirements into this environment. + + Note: assumes dependencies have already been installed into linked layers. + """ # Run a pip dependency upgrade inside the target environment if not self.env_lock.is_locked: self._fail_build( @@ -1357,6 +1396,7 @@ def define_archive_build( previous_metadata: ArchiveMetadata | None = None, force: bool = False, ) -> ArchiveBuildRequest: + """Define an archive build request for this environment.""" request = ArchiveBuildRequest.define_build( self.env_name, self.env_lock, @@ -1377,6 +1417,7 @@ def create_archive( previous_metadata: ArchiveMetadata | None = None, force: bool = False, ) -> tuple[ArchiveMetadata, Path]: + """Create a layer archive for this environment.""" env_path = self.env_path if not env_path.exists(): raise RuntimeError( @@ -1395,8 +1436,9 @@ def request_export( output_path: Path, previous_metadata: ExportMetadata | None = None, force: bool = False, - ) -> EnvironmentExportRequest: - request = EnvironmentExportRequest.define_export( + ) -> LayerExportRequest: + """Define a local export request for this environment.""" + request = LayerExportRequest.define_export( self.env_name, self.env_lock, output_path, previous_metadata, force ) self._update_output_metadata(request.export_metadata) @@ -1408,6 +1450,7 @@ def export_environment( previous_metadata: ExportMetadata | None = None, force: bool = False, ) -> tuple[ExportMetadata, Path]: + """Locally export this environment.""" env_path = self.env_path if not env_path.exists(): raise RuntimeError("Must create environment before attempting to export it") @@ -1420,8 +1463,8 @@ def export_environment( ) -class RuntimeEnv(_PythonEnvironment): - """Base runtime layer build environment""" +class RuntimeEnv(LayerEnvBase): + """Base runtime layer build environment.""" kind = LayerVariants.RUNTIME category = LayerCategories.RUNTIMES @@ -1441,12 +1484,13 @@ def __post_init__(self) -> None: @property def env_spec(self) -> RuntimeSpec: + """Layer specification for this runtime build environment.""" # Define property to allow covariance of the declared type of `env_spec` assert isinstance(self._env_spec, RuntimeSpec) return self._env_spec def get_deployed_config(self) -> postinstall.LayerConfig: - """Layer config to be published in `venvstacks_layer.json`""" + """Layer config to be published in `venvstacks_layer.json`.""" return self._get_deployed_config([], [], link_external_base=False) def _remove_pip(self) -> subprocess.CompletedProcess[str] | None: @@ -1470,7 +1514,7 @@ def _create_new_environment(self, *, lock_only: bool = False) -> None: # and we don't want to ship it unless explicitly requested to do so # as a declared dependency of an included component self._remove_pip() - fs_sync() + _fs_sync() if not lock_only: print( f"Using {str(self.python_path)!r} as runtime environment layer in {self}" @@ -1487,7 +1531,8 @@ def create_build_environment(self, *, clean: bool = False) -> None: super()._create_environment(clean=clean, lock_only=True) -class _VirtualEnvironment(_PythonEnvironment): +class LayeredEnvBase(LayerEnvBase): + """Common base class for framework and application layer build environments.""" base_runtime: RuntimeEnv | None = field(init=False, repr=False) linked_constraints_paths: list[Path] = field(init=False, repr=False) @@ -1500,12 +1545,13 @@ def __post_init__(self) -> None: self.linked_constraints_paths = [] @property - def env_spec(self) -> _VirtualEnvironmentSpec: + def env_spec(self) -> LayeredSpecBase: + """Layer specification for this environment.""" # Define property to allow covariance of the declared type of `env_spec` - assert isinstance(self._env_spec, _VirtualEnvironmentSpec) + assert isinstance(self._env_spec, LayeredSpecBase) return self._env_spec - def _linked_environments(self) -> Iterator[_PythonEnvironment]: + def _linked_environments(self) -> Iterator[LayerEnvBase]: runtime_env = self.base_runtime # This is only ever invoked *after* the environment has been linked assert runtime_env is not None @@ -1532,6 +1578,7 @@ def _iter_deployed_dynlib_dirs(self) -> Iterator[str]: yield env.get_deployed_path(dynlib_path) def link_base_runtime(self, runtime: RuntimeEnv) -> None: + """Link this layered environment to its base runtime environment.""" if self.base_runtime is not None: raise BuildEnvError( f"Layered environment base runtime already linked {self}" @@ -1547,12 +1594,13 @@ def link_base_runtime(self, runtime: RuntimeEnv) -> None: print(f"Linked {self}") def get_deployed_config(self) -> postinstall.LayerConfig: - """Layer config to be published in `venvstacks_layer.json`""" + """Layer config to be published in `venvstacks_layer.json`.""" return self._get_deployed_config( self._iter_deployed_pylib_dirs(), self._iter_deployed_dynlib_dirs() ) def get_constraint_paths(self) -> list[Path]: + """Get the lower level layer constraints imposed on this environment.""" return self.linked_constraints_paths def _ensure_virtual_environment(self) -> subprocess.CompletedProcess[str]: @@ -1577,7 +1625,7 @@ def _ensure_virtual_environment(self) -> subprocess.CompletedProcess[str]: ] result = run_python_command(command) self._link_build_environment() - fs_sync() + _fs_sync() print(f"Virtual environment configured in {str(self.env_path)!r}") return result @@ -1619,21 +1667,22 @@ def _update_output_metadata(self, metadata: LayerSpecMetadata) -> None: metadata["runtime_name"] = runtime_update_trigger -class FrameworkEnv(_VirtualEnvironment): - """Framework layer build environment""" +class FrameworkEnv(LayeredEnvBase): + """Framework layer build environment.""" kind = LayerVariants.FRAMEWORK category = LayerCategories.FRAMEWORKS @property def env_spec(self) -> FrameworkSpec: + """Layer specification for this framework build environment.""" # Define property to allow covariance of the declared type of `env_spec` assert isinstance(self._env_spec, FrameworkSpec) return self._env_spec -class ApplicationEnv(_VirtualEnvironment): - """Application layer build environment""" +class ApplicationEnv(LayeredEnvBase): + """Application layer build environment.""" kind = LayerVariants.APPLICATION category = LayerCategories.APPLICATIONS @@ -1643,6 +1692,7 @@ class ApplicationEnv(_VirtualEnvironment): @property def env_spec(self) -> ApplicationSpec: + """Layer specification for this application build environment.""" # Define property to allow covariance of the declared type of `env_spec` assert isinstance(self._env_spec, ApplicationSpec) return self._env_spec @@ -1652,7 +1702,7 @@ def __post_init__(self) -> None: self.launch_module_name = self.env_spec.launch_module_path.stem self.linked_frameworks = [] - def _linked_environments(self) -> Iterator[_PythonEnvironment]: + def _linked_environments(self) -> Iterator[LayerEnvBase]: # Linked frameworks are emitted before the base runtime layer for fw_env in self.linked_frameworks: yield fw_env @@ -1661,6 +1711,7 @@ def _linked_environments(self) -> Iterator[_PythonEnvironment]: def link_layered_environments( self, runtime: RuntimeEnv, frameworks: Mapping[LayerBaseName, FrameworkEnv] ) -> None: + """Link this application build environment with its runtime and framework layers.""" self.link_base_runtime(runtime) constraints_paths = self.linked_constraints_paths if not constraints_paths: @@ -1707,12 +1758,12 @@ def _update_output_metadata(self, metadata: LayerSpecMetadata) -> None: # Building layered environments based on a TOML file ###################################################### -BuildEnv = TypeVar("BuildEnv", bound=_PythonEnvironment) +BuildEnv = TypeVar("BuildEnv", bound=LayerEnvBase) @dataclass class StackSpec: - """Layered environment stack specification""" + """Layered environment stack specification.""" # Specified on creation spec_path: Path @@ -1734,8 +1785,7 @@ def __post_init__(self) -> None: @classmethod def load(cls, fname: StrPath) -> Self: - """Load stack specification from given TOML file""" - + """Load stack specification from given TOML file.""" stack_spec_path = as_normalized_path(fname) with open(stack_spec_path, "rb") as f: data = tomllib.load(f) @@ -1805,7 +1855,7 @@ def load(cls, fname: StrPath) -> Self: stack_spec_path, runtimes, frameworks, applications, requirements_dir_path ) - def all_environment_specs(self) -> Iterable[_PythonEnvironmentSpec]: + def all_environment_specs(self) -> Iterable[LayerSpecBase]: """Iterate over the specifications for all defined environments. All runtimes are produced first, then frameworks, then applications. @@ -1819,7 +1869,7 @@ def _define_envs( build_path: Path, index_config: PackageIndexConfig, env_class: type[BuildEnv], - specs: Mapping[LayerBaseName, _PythonEnvironmentSpec], + specs: Mapping[LayerBaseName, LayerSpecBase], ) -> MutableMapping[LayerBaseName, BuildEnv]: requirements_dir = self.requirements_dir_path build_environments: dict[LayerBaseName, BuildEnv] = {} @@ -1837,7 +1887,7 @@ def _define_envs( return build_environments def resolve_lexical_path(self, related_location: StrPath, /) -> Path: - """Resolve a path relative to the location of the stack specification""" + """Resolve a path relative to the location of the stack specification.""" return _resolve_lexical_path(related_location, self.spec_path.parent) def define_build_environment( @@ -1845,8 +1895,7 @@ def define_build_environment( build_dir: StrPath = "", index_config: PackageIndexConfig | None = None, ) -> "BuildEnvironment": - """Define layer build environments for this specification""" - + """Define layer build environments for this specification.""" build_path = self.resolve_lexical_path(build_dir) if index_config is None: index_config = PackageIndexConfig() @@ -1880,7 +1929,7 @@ def define_build_environment( @dataclass class BuildEnvironment: - """Interface to build specified layered environment stacks""" + """Interface to build specified layered environment stacks.""" METADATA_DIR = "__venvstacks__" # Output subdirectory for the build metadata METADATA_MANIFEST = "venvstacks.json" # File with full metadata for this build @@ -1903,14 +1952,16 @@ def __post_init__(self) -> None: # Provide more convenient access to selected stack_spec attributes @property def requirements_dir_path(self) -> Path: + """Parent path containing the locked layer requirements.""" return self.stack_spec.requirements_dir_path @property def build_platform(self) -> str: + """Target platform for this environment.""" return self.stack_spec.build_platform # Iterators over various categories of included environments - def all_environments(self) -> Iterable[_PythonEnvironment]: + def all_environments(self) -> Iterable[LayerEnvBase]: """Iterate over all defined environments. All runtimes are produced first, then frameworks, then applications. @@ -1919,7 +1970,7 @@ def all_environments(self) -> Iterable[_PythonEnvironment]: self.runtimes.values(), self.frameworks.values(), self.applications.values() ) - def environments_to_lock(self) -> Iterable[_PythonEnvironment]: + def environments_to_lock(self) -> Iterable[LayerEnvBase]: """Iterate over all environments where locking is requested or allowed. Runtimes are produced first, then frameworks, then applications. @@ -1934,7 +1985,7 @@ def runtimes_to_lock(self) -> Iterable[RuntimeEnv]: if env.want_lock is not False: # Accepts `None` as meaning "lock if needed" yield env - def environments_to_build(self) -> Iterable[_PythonEnvironment]: + def environments_to_build(self) -> Iterable[LayerEnvBase]: """Iterate over all environments where building is requested or allowed. Runtimes are produced first, then frameworks, then applications. @@ -1953,7 +2004,7 @@ def runtimes_to_build(self) -> Iterable[RuntimeEnv]: ): # Accepts `None` as meaning "build if needed" yield env - def venvstacks_to_build(self) -> Iterable[_VirtualEnvironment]: + def venvstacks_to_build(self) -> Iterable[LayeredEnvBase]: """Iterate over non-runtime environments where building is requested or allowed. Frameworks are produced first, then applications. @@ -1964,7 +2015,7 @@ def venvstacks_to_build(self) -> Iterable[_VirtualEnvironment]: ): # Accepts `None` as meaning "build if needed" yield env - def built_environments(self) -> Iterable[_PythonEnvironment]: + def built_environments(self) -> Iterable[LayerEnvBase]: """Iterate over all environments that were built by this build process. Runtimes are produced first, then frameworks, then applications. @@ -1973,7 +2024,7 @@ def built_environments(self) -> Iterable[_PythonEnvironment]: if env.was_built: yield env - def environments_to_publish(self) -> Iterable[_PythonEnvironment]: + def environments_to_publish(self) -> Iterable[LayerEnvBase]: """Iterate over all environments where publication is requested or allowed. Runtimes are produced first, then frameworks, then applications. @@ -1989,12 +2040,12 @@ def select_operations( build: bool | None = True, publish: bool = True, ) -> None: - """Configure the selected operations on all defined environments""" + """Configure the selected operations on all defined environments.""" for env in self.all_environments(): env.select_operations(lock=lock, build=build, publish=publish) def get_unmatched_patterns(self, patterns: Iterable[str]) -> list[str]: - """Returns a list of the given patterns which do not match any environments""" + """Returns a list of the given patterns which do not match any environments.""" env_names = [env.env_name for env in self.all_environments()] return [ pattern @@ -2014,7 +2065,7 @@ def select_layers( build_derived: bool = True, publish_derived: bool = True, ) -> None: - """Selectively configure operations only on the specified environments""" + """Selectively configure operations only on the specified environments.""" # Ensure later pipeline stages are skipped when earlier ones are skipped # Also update the related layer handling based on the enabled pipeline stages if lock: @@ -2039,7 +2090,7 @@ def select_layers( # If the included layers aren't being published, don't publish anything else publish_derived = publish_dependencies = False # Identify explicitly included environments - envs_by_name: dict[EnvNameBuild, _PythonEnvironment] = { + envs_by_name: dict[EnvNameBuild, LayerEnvBase] = { env.env_name: env for env in self.all_environments() } inclusion_patterns = list(include) @@ -2126,14 +2177,13 @@ def _needs_lock(self) -> bool: spec_dir = self.requirements_dir_path build_platform = self.build_platform - def lock_exists(spec: _PythonEnvironmentSpec) -> bool: + def lock_exists(spec: LayerSpecBase) -> bool: return spec.get_requirements_path(build_platform, spec_dir).exists() return not all(lock_exists(env.env_spec) for env in self.environments_to_lock()) def lock_environments(self, *, clean: bool = False) -> Sequence[EnvironmentLock]: - """Lock build environments for specified layers""" - + """Lock build environments for specified layers.""" # Lock environments without fully building them # Necessarily creates the runtime environments and # installs any declared build dependencies @@ -2143,8 +2193,7 @@ def lock_environments(self, *, clean: bool = False) -> Sequence[EnvironmentLock] return [env.lock_requirements() for env in self.environments_to_lock()] def create_environments(self, *, clean: bool = False, lock: bool = False) -> None: - """Create build environments for specified layers""" - + """Create build environments for specified layers.""" # Base runtime environments need to exist before dependencies can be locked self.build_path.mkdir(parents=True, exist_ok=True) clean_runtime_envs = clean @@ -2166,7 +2215,7 @@ def _env_metadata_path( return env_metadata_dir / f"{env_name}{platform_tag}.json" def _load_env_metadata( - self, env_metadata_dir: Path, env: _PythonEnvironment, platform_tag: str + self, env_metadata_dir: Path, env: LayerEnvBase, platform_tag: str ) -> Any: metadata_path = self._env_metadata_path( env_metadata_dir, env.env_name, platform_tag @@ -2177,10 +2226,9 @@ def _load_env_metadata( return json.load(f) def load_archive_metadata( - self, env_metadata_dir: Path, env: _PythonEnvironment, platform_tag: str = "" + self, env_metadata_dir: Path, env: LayerEnvBase, platform_tag: str = "" ) -> ArchiveMetadata | None: - """Load previously published archive metadata""" - + """Load previously published archive metadata.""" # mypy is right to complain that the JSON hasn't been validated to conform # to the ArchiveMetadata interface, but we're OK with letting the runtime # errors happen in that scenario. Longer term, explicit JSON schemas should be @@ -2189,10 +2237,9 @@ def load_archive_metadata( return cast(ArchiveMetadata, metadata) def load_export_metadata( - self, env_metadata_dir: Path, env: _PythonEnvironment + self, env_metadata_dir: Path, env: LayerEnvBase ) -> ExportMetadata | None: - """Load previously exported environment metadata""" - + """Load previously exported environment metadata.""" # mypy is right to complain that the JSON hasn't been validated to conform # to the ExportMetadata interface, but we're OK with letting the runtime # errors happen in that scenario. Longer term, explicit JSON schemas should be @@ -2226,8 +2273,7 @@ def publish_artifacts( tag_outputs: bool = False, dry_run: bool = False, ) -> PublishedArchivePaths | tuple[Path, StackPublishingRequest]: - """Publish metadata and archives for specified layers""" - + """Publish metadata and archives for specified layers.""" layer_data: dict[ LayerCategories, list[ArchiveMetadata | ArchiveBuildMetadata] ] = { @@ -2349,8 +2395,7 @@ def export_environments( force: bool = False, dry_run: bool = False, ) -> ExportedEnvironmentPaths | tuple[Path, StackExportRequest]: - """Locally export environments for specified layers""" - + """Locally export environments for specified layers.""" export_data: dict[LayerCategories, list[ExportMetadata]] = { RuntimeEnv.category: [], FrameworkEnv.category: [], diff --git a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/app-scipy-client.json b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/app-scipy-client.json index 351ad8f..6eccab5 100644 --- a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/app-scipy-client.json +++ b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/app-scipy-client.json @@ -1,9 +1,9 @@ { "app_launch_module": "scipy_client", - "app_launch_module_hash": "sha256/bbe4da6de13a8f13a05cdd2bb3b90884861a6636b1450248d03aea799a7fc828", + "app_launch_module_hash": "sha256/76d203dd6d3bb1fdb1117053c81590d42ef3f62b4b92b8d316b31f08334fbca6", "archive_build": 1, "archive_hashes": { - "sha256": "d932e554c35c9ce2cd1d594cb20e8a37c0195a5b82ffcb50627a96fc122e79a2" + "sha256": "e114727c3657844ea74de25009748a0c90b7ccf1279a787a9c8f5dd9cdd4e020" }, "archive_name": "app-scipy-client.tar.xz", "archive_size": 3008, diff --git a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/app-scipy-import.json b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/app-scipy-import.json index 91213b7..9b152ce 100644 --- a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/app-scipy-import.json +++ b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/app-scipy-import.json @@ -1,12 +1,12 @@ { "app_launch_module": "scipy_import", - "app_launch_module_hash": "sha256:d806d778921ad216c1f950886d27b4b77e5561fe3467046fec258805980cc6d1", + "app_launch_module_hash": "sha256:3970f360501594e9603c2799861aba17e5e7b4f6c37a3dc0ba59de42f07ba998", "archive_build": 1, "archive_hashes": { - "sha256": "ec5be5ebc608e24bf32642cf0e071ce975f4650dac69c233c36124e53f4c10f9" + "sha256": "437144258c97373ba74e8eedd6006e02f1a5cc9c8342075a6a03f3dcc53cadee" }, "archive_name": "app-scipy-import@1.tar.xz", - "archive_size": 2912, + "archive_size": 2924, "install_target": "app-scipy-import@1", "layer_name": "app-scipy-import", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/app-sklearn-import.json b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/app-sklearn-import.json index cdff627..ef5d645 100644 --- a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/app-sklearn-import.json +++ b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/app-sklearn-import.json @@ -1,12 +1,12 @@ { "app_launch_module": "sklearn_import", - "app_launch_module_hash": "sha256:f66c01bbcca47cd31d79d2fb5377de0de18631ffc3c904629d46f6cad2918694", + "app_launch_module_hash": "sha256:f48aeede7d02ae34856f0149bb15b3cd4bebd3a82911cfd4e2e1c2639b8cd53c", "archive_build": 1, "archive_hashes": { - "sha256": "1ff13c6ae6146bf0e028b56d68f3a6ea63f0c33b1c1cf891db5580112e0f09ef" + "sha256": "da63d7a0df8ff95422cecb718c080c79c3e3f4c0d9f7edb9f28207b4f926dddb" }, "archive_name": "app-sklearn-import.tar.xz", - "archive_size": 2916, + "archive_size": 2920, "install_target": "app-sklearn-import", "layer_name": "app-sklearn-import", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/cpython@3.11.json b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/cpython@3.11.json index d254417..972fbee 100644 --- a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/cpython@3.11.json +++ b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/cpython@3.11.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "0628d08555e421d3c3b4ae7fe141cd368f6cbe9aac761c2aeb0c0cf956ab1583" + "sha256": "553ae6bde1c217cfdb7d92aa631fc685289bd23c019429c62b74d367bd44cfdb" }, "archive_name": "cpython@3.11.tar.xz", - "archive_size": 29723440, + "archive_size": 29723544, "install_target": "cpython@3.11", "layer_name": "cpython@3.11", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/cpython@3.12.json b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/cpython@3.12.json index bd36aaa..15af513 100644 --- a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/cpython@3.12.json +++ b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/cpython@3.12.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "681c4d6f77de9745af858422ce080e483e6d9dee81114f4758cf8c07f0ca9efb" + "sha256": "94327414d1ef97f5c629c4efeefdbe8773f26dc175a8e5f779bc09b2099c4f3b" }, "archive_name": "cpython@3.12.tar.xz", - "archive_size": 42728260, + "archive_size": 42728188, "install_target": "cpython@3.12", "layer_name": "cpython@3.12", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/framework-http-client.json b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/framework-http-client.json index 571db7e..58ad000 100644 --- a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/framework-http-client.json +++ b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/framework-http-client.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "71870e0a056dff917dd4d97ffb61d184a6cd5a2f1cf98e1c305c12debaf57026" + "sha256": "8b0051237e304c87662ea631a119bece0d053fcc0c307ed28293e37f331bf944" }, "archive_name": "framework-http-client.tar.xz", - "archive_size": 364020, + "archive_size": 364028, "install_target": "framework-http-client", "layer_name": "framework-http-client", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/framework-scipy.json b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/framework-scipy.json index 91978f2..ab772b6 100644 --- a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/framework-scipy.json +++ b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/framework-scipy.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "622e3056ccf54d8723bf983c92f35bf4138d2fee5da3cdd56d25391f18906aab" + "sha256": "b11bc910ee4aedf5495eb9dd5544c93929cbb2f05cba4afd26891ee71f0c5bdc" }, "archive_name": "framework-scipy@1.tar.xz", - "archive_size": 23961760, + "archive_size": 23961768, "install_target": "framework-scipy@1", "layer_name": "framework-scipy", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/framework-sklearn.json b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/framework-sklearn.json index 7372d23..b672a04 100644 --- a/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/framework-sklearn.json +++ b/tests/sample_project/expected_manifests/linux_x86_64/env_metadata/framework-sklearn.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "8087f86b0196ae91b13d78f738bb3d0266ee76e2cfcfb3f20b76dd7f103bdace" + "sha256": "e8087b3809c5d50a7efc8efc183378b737f29d0a8dd0eb6ec30f47a7dd4a6f60" }, "archive_name": "framework-sklearn.tar.xz", - "archive_size": 30372332, + "archive_size": 30372340, "install_target": "framework-sklearn", "layer_name": "framework-sklearn", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/linux_x86_64/venvstacks.json b/tests/sample_project/expected_manifests/linux_x86_64/venvstacks.json index 973d2e5..b8499e2 100644 --- a/tests/sample_project/expected_manifests/linux_x86_64/venvstacks.json +++ b/tests/sample_project/expected_manifests/linux_x86_64/venvstacks.json @@ -3,13 +3,13 @@ "applications": [ { "app_launch_module": "scipy_import", - "app_launch_module_hash": "sha256:d806d778921ad216c1f950886d27b4b77e5561fe3467046fec258805980cc6d1", + "app_launch_module_hash": "sha256:3970f360501594e9603c2799861aba17e5e7b4f6c37a3dc0ba59de42f07ba998", "archive_build": 1, "archive_hashes": { - "sha256": "ec5be5ebc608e24bf32642cf0e071ce975f4650dac69c233c36124e53f4c10f9" + "sha256": "437144258c97373ba74e8eedd6006e02f1a5cc9c8342075a6a03f3dcc53cadee" }, "archive_name": "app-scipy-import@1.tar.xz", - "archive_size": 2912, + "archive_size": 2924, "install_target": "app-scipy-import@1", "layer_name": "app-scipy-import", "lock_version": 1, @@ -23,10 +23,10 @@ }, { "app_launch_module": "scipy_client", - "app_launch_module_hash": "sha256/bbe4da6de13a8f13a05cdd2bb3b90884861a6636b1450248d03aea799a7fc828", + "app_launch_module_hash": "sha256/76d203dd6d3bb1fdb1117053c81590d42ef3f62b4b92b8d316b31f08334fbca6", "archive_build": 1, "archive_hashes": { - "sha256": "d932e554c35c9ce2cd1d594cb20e8a37c0195a5b82ffcb50627a96fc122e79a2" + "sha256": "e114727c3657844ea74de25009748a0c90b7ccf1279a787a9c8f5dd9cdd4e020" }, "archive_name": "app-scipy-client.tar.xz", "archive_size": 3008, @@ -44,13 +44,13 @@ }, { "app_launch_module": "sklearn_import", - "app_launch_module_hash": "sha256:f66c01bbcca47cd31d79d2fb5377de0de18631ffc3c904629d46f6cad2918694", + "app_launch_module_hash": "sha256:f48aeede7d02ae34856f0149bb15b3cd4bebd3a82911cfd4e2e1c2639b8cd53c", "archive_build": 1, "archive_hashes": { - "sha256": "1ff13c6ae6146bf0e028b56d68f3a6ea63f0c33b1c1cf891db5580112e0f09ef" + "sha256": "da63d7a0df8ff95422cecb718c080c79c3e3f4c0d9f7edb9f28207b4f926dddb" }, "archive_name": "app-sklearn-import.tar.xz", - "archive_size": 2916, + "archive_size": 2920, "install_target": "app-sklearn-import", "layer_name": "app-sklearn-import", "lock_version": 1, @@ -67,10 +67,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "622e3056ccf54d8723bf983c92f35bf4138d2fee5da3cdd56d25391f18906aab" + "sha256": "b11bc910ee4aedf5495eb9dd5544c93929cbb2f05cba4afd26891ee71f0c5bdc" }, "archive_name": "framework-scipy@1.tar.xz", - "archive_size": 23961760, + "archive_size": 23961768, "install_target": "framework-scipy@1", "layer_name": "framework-scipy", "lock_version": 1, @@ -82,10 +82,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "8087f86b0196ae91b13d78f738bb3d0266ee76e2cfcfb3f20b76dd7f103bdace" + "sha256": "e8087b3809c5d50a7efc8efc183378b737f29d0a8dd0eb6ec30f47a7dd4a6f60" }, "archive_name": "framework-sklearn.tar.xz", - "archive_size": 30372332, + "archive_size": 30372340, "install_target": "framework-sklearn", "layer_name": "framework-sklearn", "lock_version": 1, @@ -97,10 +97,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "71870e0a056dff917dd4d97ffb61d184a6cd5a2f1cf98e1c305c12debaf57026" + "sha256": "8b0051237e304c87662ea631a119bece0d053fcc0c307ed28293e37f331bf944" }, "archive_name": "framework-http-client.tar.xz", - "archive_size": 364020, + "archive_size": 364028, "install_target": "framework-http-client", "layer_name": "framework-http-client", "lock_version": 1, @@ -114,10 +114,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "0628d08555e421d3c3b4ae7fe141cd368f6cbe9aac761c2aeb0c0cf956ab1583" + "sha256": "553ae6bde1c217cfdb7d92aa631fc685289bd23c019429c62b74d367bd44cfdb" }, "archive_name": "cpython@3.11.tar.xz", - "archive_size": 29723440, + "archive_size": 29723544, "install_target": "cpython@3.11", "layer_name": "cpython@3.11", "lock_version": 1, @@ -129,10 +129,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "681c4d6f77de9745af858422ce080e483e6d9dee81114f4758cf8c07f0ca9efb" + "sha256": "94327414d1ef97f5c629c4efeefdbe8773f26dc175a8e5f779bc09b2099c4f3b" }, "archive_name": "cpython@3.12.tar.xz", - "archive_size": 42728260, + "archive_size": 42728188, "install_target": "cpython@3.12", "layer_name": "cpython@3.12", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/app-scipy-client.json b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/app-scipy-client.json index 290b09b..fb31b79 100644 --- a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/app-scipy-client.json +++ b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/app-scipy-client.json @@ -1,12 +1,12 @@ { "app_launch_module": "scipy_client", - "app_launch_module_hash": "sha256/bbe4da6de13a8f13a05cdd2bb3b90884861a6636b1450248d03aea799a7fc828", + "app_launch_module_hash": "sha256/76d203dd6d3bb1fdb1117053c81590d42ef3f62b4b92b8d316b31f08334fbca6", "archive_build": 1, "archive_hashes": { - "sha256": "f0338c45382c53320d30dc99e4370075de52593adb46a969f0692e772dcfb3ca" + "sha256": "fd1f418d9e8a4cef0922bdf11b46b35ac22fefc250e82b05f81fc9b5aadd14f3" }, "archive_name": "app-scipy-client.tar.xz", - "archive_size": 2984, + "archive_size": 2988, "install_target": "app-scipy-client", "layer_name": "app-scipy-client", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/app-scipy-import.json b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/app-scipy-import.json index cd4588b..ce83633 100644 --- a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/app-scipy-import.json +++ b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/app-scipy-import.json @@ -1,12 +1,12 @@ { "app_launch_module": "scipy_import", - "app_launch_module_hash": "sha256:d806d778921ad216c1f950886d27b4b77e5561fe3467046fec258805980cc6d1", + "app_launch_module_hash": "sha256:3970f360501594e9603c2799861aba17e5e7b4f6c37a3dc0ba59de42f07ba998", "archive_build": 1, "archive_hashes": { - "sha256": "3ef2198496159dca41b2fa5cdea6ec0afb40b6060f95f23dc3da02e99e2d435f" + "sha256": "b5038e4f718e9a1a1f887245388e2565aec4e70efd2a7a0826eb5eb423ab10b7" }, "archive_name": "app-scipy-import@1.tar.xz", - "archive_size": 2896, + "archive_size": 2900, "install_target": "app-scipy-import@1", "layer_name": "app-scipy-import", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/cpython@3.11.json b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/cpython@3.11.json index 38a2217..a1ca4b0 100644 --- a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/cpython@3.11.json +++ b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/cpython@3.11.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "b23a9bda7297579207664e52f657ffb59a378f47cf2ae7c336f1ad3a56549ad1" + "sha256": "f4cd8563d8571ce0a6023082e12cfb0983e5b1b91e62e9c609801b44c848c4b4" }, "archive_name": "cpython@3.11.tar.xz", - "archive_size": 14967236, + "archive_size": 14967244, "install_target": "cpython@3.11", "layer_name": "cpython@3.11", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/cpython@3.12.json b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/cpython@3.12.json index 12e0aa4..d74c85b 100644 --- a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/cpython@3.12.json +++ b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/cpython@3.12.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "2c598215a16b22911bc8ae79fcb79a611d56d9f02076fa9c716f4217e29cbe48" + "sha256": "50c5fa0e90d1d7d66f2c4fa8b7bd242497543f9b80d2a1415fedbbbc80511969" }, "archive_name": "cpython@3.12.tar.xz", - "archive_size": 13600984, + "archive_size": 13600980, "install_target": "cpython@3.12", "layer_name": "cpython@3.12", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/framework-http-client.json b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/framework-http-client.json index 41828bf..5cf03be 100644 --- a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/framework-http-client.json +++ b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/framework-http-client.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "37f1d87e0e1a06f0ff86b288565cc1fc4d8d6b33f53e229a3f87d163b4b2d793" + "sha256": "28bfeffcbf53f990b1242428a555b707112fd0dd3b1aa10738259dc83b500bb5" }, "archive_name": "framework-http-client.tar.xz", - "archive_size": 363928, + "archive_size": 363932, "install_target": "framework-http-client", "layer_name": "framework-http-client", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/framework-scipy.json b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/framework-scipy.json index 0735cfd..83974c6 100644 --- a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/framework-scipy.json +++ b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/framework-scipy.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "32f6135ea3b938e47c8897b0da7a51c6992397edbefe2b27358665b1c83d29aa" + "sha256": "33f88805d589f878f0dbff57a58ba107448f29ff02719ed87fd3ed39f8301acb" }, "archive_name": "framework-scipy@1.tar.xz", - "archive_size": 15076180, + "archive_size": 15076184, "install_target": "framework-scipy@1", "layer_name": "framework-scipy", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/framework-sklearn.json b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/framework-sklearn.json index da1848a..1c88011 100644 --- a/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/framework-sklearn.json +++ b/tests/sample_project/expected_manifests/macosx_arm64/env_metadata/framework-sklearn.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "a51ee3b3a25b0cb03044f2f9d23078f1c8982a1438a5510d655c628f71bd4f03" + "sha256": "f25ec03bd96d2668091f52d4ece6d3834b07552f567277813b1d23fbc523c1d8" }, "archive_name": "framework-sklearn.tar.xz", - "archive_size": 20689024, + "archive_size": 20689032, "install_target": "framework-sklearn", "layer_name": "framework-sklearn", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/macosx_arm64/venvstacks.json b/tests/sample_project/expected_manifests/macosx_arm64/venvstacks.json index 08d663a..5c0df5e 100644 --- a/tests/sample_project/expected_manifests/macosx_arm64/venvstacks.json +++ b/tests/sample_project/expected_manifests/macosx_arm64/venvstacks.json @@ -3,13 +3,13 @@ "applications": [ { "app_launch_module": "scipy_import", - "app_launch_module_hash": "sha256:d806d778921ad216c1f950886d27b4b77e5561fe3467046fec258805980cc6d1", + "app_launch_module_hash": "sha256:3970f360501594e9603c2799861aba17e5e7b4f6c37a3dc0ba59de42f07ba998", "archive_build": 1, "archive_hashes": { - "sha256": "3ef2198496159dca41b2fa5cdea6ec0afb40b6060f95f23dc3da02e99e2d435f" + "sha256": "b5038e4f718e9a1a1f887245388e2565aec4e70efd2a7a0826eb5eb423ab10b7" }, "archive_name": "app-scipy-import@1.tar.xz", - "archive_size": 2896, + "archive_size": 2900, "install_target": "app-scipy-import@1", "layer_name": "app-scipy-import", "lock_version": 1, @@ -23,13 +23,13 @@ }, { "app_launch_module": "scipy_client", - "app_launch_module_hash": "sha256/bbe4da6de13a8f13a05cdd2bb3b90884861a6636b1450248d03aea799a7fc828", + "app_launch_module_hash": "sha256/76d203dd6d3bb1fdb1117053c81590d42ef3f62b4b92b8d316b31f08334fbca6", "archive_build": 1, "archive_hashes": { - "sha256": "f0338c45382c53320d30dc99e4370075de52593adb46a969f0692e772dcfb3ca" + "sha256": "fd1f418d9e8a4cef0922bdf11b46b35ac22fefc250e82b05f81fc9b5aadd14f3" }, "archive_name": "app-scipy-client.tar.xz", - "archive_size": 2984, + "archive_size": 2988, "install_target": "app-scipy-client", "layer_name": "app-scipy-client", "lock_version": 1, @@ -47,10 +47,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "32f6135ea3b938e47c8897b0da7a51c6992397edbefe2b27358665b1c83d29aa" + "sha256": "33f88805d589f878f0dbff57a58ba107448f29ff02719ed87fd3ed39f8301acb" }, "archive_name": "framework-scipy@1.tar.xz", - "archive_size": 15076180, + "archive_size": 15076184, "install_target": "framework-scipy@1", "layer_name": "framework-scipy", "lock_version": 1, @@ -62,10 +62,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "a51ee3b3a25b0cb03044f2f9d23078f1c8982a1438a5510d655c628f71bd4f03" + "sha256": "f25ec03bd96d2668091f52d4ece6d3834b07552f567277813b1d23fbc523c1d8" }, "archive_name": "framework-sklearn.tar.xz", - "archive_size": 20689024, + "archive_size": 20689032, "install_target": "framework-sklearn", "layer_name": "framework-sklearn", "lock_version": 1, @@ -77,10 +77,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "37f1d87e0e1a06f0ff86b288565cc1fc4d8d6b33f53e229a3f87d163b4b2d793" + "sha256": "28bfeffcbf53f990b1242428a555b707112fd0dd3b1aa10738259dc83b500bb5" }, "archive_name": "framework-http-client.tar.xz", - "archive_size": 363928, + "archive_size": 363932, "install_target": "framework-http-client", "layer_name": "framework-http-client", "lock_version": 1, @@ -94,10 +94,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "b23a9bda7297579207664e52f657ffb59a378f47cf2ae7c336f1ad3a56549ad1" + "sha256": "f4cd8563d8571ce0a6023082e12cfb0983e5b1b91e62e9c609801b44c848c4b4" }, "archive_name": "cpython@3.11.tar.xz", - "archive_size": 14967236, + "archive_size": 14967244, "install_target": "cpython@3.11", "layer_name": "cpython@3.11", "lock_version": 1, @@ -109,10 +109,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "2c598215a16b22911bc8ae79fcb79a611d56d9f02076fa9c716f4217e29cbe48" + "sha256": "50c5fa0e90d1d7d66f2c4fa8b7bd242497543f9b80d2a1415fedbbbc80511969" }, "archive_name": "cpython@3.12.tar.xz", - "archive_size": 13600984, + "archive_size": 13600980, "install_target": "cpython@3.12", "layer_name": "cpython@3.12", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/win_amd64/env_metadata/app-scipy-client.json b/tests/sample_project/expected_manifests/win_amd64/env_metadata/app-scipy-client.json index 4bf772b..d4aee3c 100644 --- a/tests/sample_project/expected_manifests/win_amd64/env_metadata/app-scipy-client.json +++ b/tests/sample_project/expected_manifests/win_amd64/env_metadata/app-scipy-client.json @@ -1,12 +1,12 @@ { "app_launch_module": "scipy_client", - "app_launch_module_hash": "sha256/bbe4da6de13a8f13a05cdd2bb3b90884861a6636b1450248d03aea799a7fc828", + "app_launch_module_hash": "sha256/76d203dd6d3bb1fdb1117053c81590d42ef3f62b4b92b8d316b31f08334fbca6", "archive_build": 1, "archive_hashes": { - "sha256": "ca15fb796933f031340de069d53f6d3a821e481b1372eb4ff001368ac92a214c" + "sha256": "f310af8ea3be9879115e82f540f75824c5d6937deb7db67effe2f747653dd0fc" }, "archive_name": "app-scipy-client.zip", - "archive_size": 257117, + "archive_size": 257126, "install_target": "app-scipy-client", "layer_name": "app-scipy-client", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/win_amd64/env_metadata/app-scipy-import.json b/tests/sample_project/expected_manifests/win_amd64/env_metadata/app-scipy-import.json index b68d0a2..c3818b6 100644 --- a/tests/sample_project/expected_manifests/win_amd64/env_metadata/app-scipy-import.json +++ b/tests/sample_project/expected_manifests/win_amd64/env_metadata/app-scipy-import.json @@ -1,12 +1,12 @@ { "app_launch_module": "scipy_import", - "app_launch_module_hash": "sha256:d806d778921ad216c1f950886d27b4b77e5561fe3467046fec258805980cc6d1", + "app_launch_module_hash": "sha256:3970f360501594e9603c2799861aba17e5e7b4f6c37a3dc0ba59de42f07ba998", "archive_build": 1, "archive_hashes": { - "sha256": "9eae1f6e63422d58ccef53ae199ac55884c50f19e1faf5ecd98c2dfc651ce723" + "sha256": "0c2755dc2d4d70db2b88c409227c49bb9a141a1c437bb0252c064757fcad539a" }, "archive_name": "app-scipy-import@1.zip", - "archive_size": 256677, + "archive_size": 256686, "install_target": "app-scipy-import@1", "layer_name": "app-scipy-import", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/win_amd64/env_metadata/cpython@3.11.json b/tests/sample_project/expected_manifests/win_amd64/env_metadata/cpython@3.11.json index 97159aa..8ab8acd 100644 --- a/tests/sample_project/expected_manifests/win_amd64/env_metadata/cpython@3.11.json +++ b/tests/sample_project/expected_manifests/win_amd64/env_metadata/cpython@3.11.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "66095910a186e59d023ca357ccd0fbe7c464722f191786f6d090af6769bb5dd9" + "sha256": "75a349c5d90cede0ad9de3da4f5320974fd6b4b13d9f1f1d033fb76a1877e0b0" }, "archive_name": "cpython@3.11.zip", - "archive_size": 46594787, + "archive_size": 46594794, "install_target": "cpython@3.11", "layer_name": "cpython@3.11", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/win_amd64/env_metadata/cpython@3.12.json b/tests/sample_project/expected_manifests/win_amd64/env_metadata/cpython@3.12.json index fb3e2cd..89b1559 100644 --- a/tests/sample_project/expected_manifests/win_amd64/env_metadata/cpython@3.12.json +++ b/tests/sample_project/expected_manifests/win_amd64/env_metadata/cpython@3.12.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "85017932199a87b59bf17432f21c9ddf08361663a1cdf363c5d014146c3c754c" + "sha256": "c97d552563b64316f496cfbcba0465762a7ec5333b5b1fcb8c7a90bdb84d062a" }, "archive_name": "cpython@3.12.zip", - "archive_size": 45867018, + "archive_size": 45867025, "install_target": "cpython@3.12", "layer_name": "cpython@3.12", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/win_amd64/env_metadata/framework-http-client.json b/tests/sample_project/expected_manifests/win_amd64/env_metadata/framework-http-client.json index 11b6f70..0b310e3 100644 --- a/tests/sample_project/expected_manifests/win_amd64/env_metadata/framework-http-client.json +++ b/tests/sample_project/expected_manifests/win_amd64/env_metadata/framework-http-client.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "8b5f396783d7c583cb2202a7bf791b46257b59473be4f12b1583907f4bf931b2" + "sha256": "ea315a40f5dcf1389ee0b6cf8c4a99f451dd0fb8c6a4618fbaf3a23a73824b99" }, "archive_name": "framework-http-client.zip", - "archive_size": 819935, + "archive_size": 819942, "install_target": "framework-http-client", "layer_name": "framework-http-client", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/win_amd64/env_metadata/framework-scipy.json b/tests/sample_project/expected_manifests/win_amd64/env_metadata/framework-scipy.json index 827d795..3e46ff4 100644 --- a/tests/sample_project/expected_manifests/win_amd64/env_metadata/framework-scipy.json +++ b/tests/sample_project/expected_manifests/win_amd64/env_metadata/framework-scipy.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "ac2c6a48192ac3657c5cbec001aeddf48ddd45881ac86a0cfa9eb5b839d2e3d4" + "sha256": "b81d9e4c473d42b90676c62186d5cd47703acadf2fc0c7052af88c9f280f23ce" }, "archive_name": "framework-scipy@1.zip", - "archive_size": 45087226, + "archive_size": 45087233, "install_target": "framework-scipy@1", "layer_name": "framework-scipy", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/win_amd64/env_metadata/framework-sklearn.json b/tests/sample_project/expected_manifests/win_amd64/env_metadata/framework-sklearn.json index e6dacc5..e8f4ccd 100644 --- a/tests/sample_project/expected_manifests/win_amd64/env_metadata/framework-sklearn.json +++ b/tests/sample_project/expected_manifests/win_amd64/env_metadata/framework-sklearn.json @@ -1,10 +1,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "74549eb9c5252b7f91cf573811b71d8d703b6cd840932f7dbdddb9e3d03ccaee" + "sha256": "0ae9df0a137045963efa6c9d8dd61f2643af59cc3affc06becbad19ff1154401" }, "archive_name": "framework-sklearn.zip", - "archive_size": 56188134, + "archive_size": 56188141, "install_target": "framework-sklearn", "layer_name": "framework-sklearn", "lock_version": 1, diff --git a/tests/sample_project/expected_manifests/win_amd64/venvstacks.json b/tests/sample_project/expected_manifests/win_amd64/venvstacks.json index 73a7469..39af1f2 100644 --- a/tests/sample_project/expected_manifests/win_amd64/venvstacks.json +++ b/tests/sample_project/expected_manifests/win_amd64/venvstacks.json @@ -3,13 +3,13 @@ "applications": [ { "app_launch_module": "scipy_import", - "app_launch_module_hash": "sha256:d806d778921ad216c1f950886d27b4b77e5561fe3467046fec258805980cc6d1", + "app_launch_module_hash": "sha256:3970f360501594e9603c2799861aba17e5e7b4f6c37a3dc0ba59de42f07ba998", "archive_build": 1, "archive_hashes": { - "sha256": "9eae1f6e63422d58ccef53ae199ac55884c50f19e1faf5ecd98c2dfc651ce723" + "sha256": "0c2755dc2d4d70db2b88c409227c49bb9a141a1c437bb0252c064757fcad539a" }, "archive_name": "app-scipy-import@1.zip", - "archive_size": 256677, + "archive_size": 256686, "install_target": "app-scipy-import@1", "layer_name": "app-scipy-import", "lock_version": 1, @@ -23,13 +23,13 @@ }, { "app_launch_module": "scipy_client", - "app_launch_module_hash": "sha256/bbe4da6de13a8f13a05cdd2bb3b90884861a6636b1450248d03aea799a7fc828", + "app_launch_module_hash": "sha256/76d203dd6d3bb1fdb1117053c81590d42ef3f62b4b92b8d316b31f08334fbca6", "archive_build": 1, "archive_hashes": { - "sha256": "ca15fb796933f031340de069d53f6d3a821e481b1372eb4ff001368ac92a214c" + "sha256": "f310af8ea3be9879115e82f540f75824c5d6937deb7db67effe2f747653dd0fc" }, "archive_name": "app-scipy-client.zip", - "archive_size": 257117, + "archive_size": 257126, "install_target": "app-scipy-client", "layer_name": "app-scipy-client", "lock_version": 1, @@ -47,10 +47,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "ac2c6a48192ac3657c5cbec001aeddf48ddd45881ac86a0cfa9eb5b839d2e3d4" + "sha256": "b81d9e4c473d42b90676c62186d5cd47703acadf2fc0c7052af88c9f280f23ce" }, "archive_name": "framework-scipy@1.zip", - "archive_size": 45087226, + "archive_size": 45087233, "install_target": "framework-scipy@1", "layer_name": "framework-scipy", "lock_version": 1, @@ -62,10 +62,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "74549eb9c5252b7f91cf573811b71d8d703b6cd840932f7dbdddb9e3d03ccaee" + "sha256": "0ae9df0a137045963efa6c9d8dd61f2643af59cc3affc06becbad19ff1154401" }, "archive_name": "framework-sklearn.zip", - "archive_size": 56188134, + "archive_size": 56188141, "install_target": "framework-sklearn", "layer_name": "framework-sklearn", "lock_version": 1, @@ -77,10 +77,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "8b5f396783d7c583cb2202a7bf791b46257b59473be4f12b1583907f4bf931b2" + "sha256": "ea315a40f5dcf1389ee0b6cf8c4a99f451dd0fb8c6a4618fbaf3a23a73824b99" }, "archive_name": "framework-http-client.zip", - "archive_size": 819935, + "archive_size": 819942, "install_target": "framework-http-client", "layer_name": "framework-http-client", "lock_version": 1, @@ -94,10 +94,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "66095910a186e59d023ca357ccd0fbe7c464722f191786f6d090af6769bb5dd9" + "sha256": "75a349c5d90cede0ad9de3da4f5320974fd6b4b13d9f1f1d033fb76a1877e0b0" }, "archive_name": "cpython@3.11.zip", - "archive_size": 46594787, + "archive_size": 46594794, "install_target": "cpython@3.11", "layer_name": "cpython@3.11", "lock_version": 1, @@ -109,10 +109,10 @@ { "archive_build": 1, "archive_hashes": { - "sha256": "85017932199a87b59bf17432f21c9ddf08361663a1cdf363c5d014146c3c754c" + "sha256": "c97d552563b64316f496cfbcba0465762a7ec5333b5b1fcb8c7a90bdb84d062a" }, "archive_name": "cpython@3.12.zip", - "archive_size": 45867018, + "archive_size": 45867025, "install_target": "cpython@3.12", "layer_name": "cpython@3.12", "lock_version": 1, diff --git a/tests/sample_project/launch_modules/scipy_client/__main__.py b/tests/sample_project/launch_modules/scipy_client/__main__.py index 419ab58..a3f827a 100644 --- a/tests/sample_project/launch_modules/scipy_client/__main__.py +++ b/tests/sample_project/launch_modules/scipy_client/__main__.py @@ -1,4 +1,4 @@ -"""Sample launch package using a helper module""" +"""Sample launch package using a helper module.""" if __name__ == "__main__": from .cli import main diff --git a/tests/sample_project/launch_modules/scipy_client/cli.py b/tests/sample_project/launch_modules/scipy_client/cli.py index 2274de6..1affb99 100644 --- a/tests/sample_project/launch_modules/scipy_client/cli.py +++ b/tests/sample_project/launch_modules/scipy_client/cli.py @@ -1,4 +1,4 @@ -"""Sample CLI helper module importing scipy and httpx""" +"""Sample CLI helper module importing scipy and httpx.""" import numpy import scipy diff --git a/tests/sample_project/launch_modules/scipy_import.py b/tests/sample_project/launch_modules/scipy_import.py index 0a8c190..98eb740 100644 --- a/tests/sample_project/launch_modules/scipy_import.py +++ b/tests/sample_project/launch_modules/scipy_import.py @@ -1,4 +1,4 @@ -"""Sample launch module importing scipy""" +"""Sample launch module importing scipy.""" import numpy import scipy diff --git a/tests/sample_project/launch_modules/sklearn_import.py b/tests/sample_project/launch_modules/sklearn_import.py index 510919a..37c3d0a 100644 --- a/tests/sample_project/launch_modules/sklearn_import.py +++ b/tests/sample_project/launch_modules/sklearn_import.py @@ -1,4 +1,4 @@ -"""Sample launch module importing sklearn""" +"""Sample launch module importing sklearn.""" import numpy import scipy diff --git a/tests/support.py b/tests/support.py index d26fc25..047aacb 100644 --- a/tests/support.py +++ b/tests/support.py @@ -1,4 +1,4 @@ -"""Test support for venvstacks testing""" +"""Test support for venvstacks testing.""" import json import os @@ -25,7 +25,7 @@ LayerBaseName, LayerVariants, PackageIndexConfig, - _PythonEnvironment, + LayerEnvBase, ) _THIS_DIR = Path(__file__).parent @@ -39,7 +39,7 @@ def requires_venv(description: str) -> pytest.MarkDecorator: - """Skip test case when running tests outside a virtual environment""" + """Skip test case when running tests outside a virtual environment.""" return pytest.mark.skipif( sys.prefix == sys.base_prefix, reason=f"{description} requires test execution in venv", @@ -57,7 +57,7 @@ def requires_venv(description: str) -> pytest.MarkDecorator: def get_artifact_export_path() -> Path | None: - """Location to export notable artifacts generated during test execution""" + """Location to export notable artifacts generated during test execution.""" export_dir = os.environ.get(TEST_EXPORT_ENV_VAR) if not export_dir: return None @@ -68,7 +68,7 @@ def get_artifact_export_path() -> Path | None: def force_artifact_export() -> bool: - """Indicate artifacts should be exported even if a test case passes""" + """Indicate artifacts should be exported even if a test case passes.""" # Export is forced if the environment var is defined and non-empty return bool(os.environ.get(FORCED_EXPORT_ENV_VAR)) @@ -236,7 +236,7 @@ def run_module(env_python: Path, module_name: str) -> subprocess.CompletedProces class DeploymentTestCase(unittest.TestCase): - """Native unittest test case with additional deployment validation checks""" + """Native unittest test case with additional deployment validation checks.""" EXPECTED_APP_OUTPUT = "" @@ -295,7 +295,7 @@ def check_env_sys_path( ) def check_build_environments( - self, build_envs: Iterable[_PythonEnvironment] + self, build_envs: Iterable[LayerEnvBase] ) -> None: for env in build_envs: env_path = env.env_path diff --git a/tests/test_basics.py b/tests/test_basics.py index 19288e5..0015076 100644 --- a/tests/test_basics.py +++ b/tests/test_basics.py @@ -1,4 +1,4 @@ -"""Basic tests for venvstacks package components""" +"""Basic tests for venvstacks package components.""" from importlib.metadata import version as pkg_version diff --git a/tests/test_cli_invocation.py b/tests/test_cli_invocation.py index 3afa0a4..e3f29a4 100644 --- a/tests/test_cli_invocation.py +++ b/tests/test_cli_invocation.py @@ -1,4 +1,4 @@ -"""Test cases for CLI invocation""" +"""Test cases for CLI invocation.""" import subprocess import sys @@ -87,7 +87,7 @@ def __post_init__(self) -> None: @classmethod @contextmanager def cli_patch_installed(cls, cli_module: ModuleType) -> Iterator[Self]: - """Patch the given CLI module to invoke a mocked StackSpec instance""" + """Patch the given CLI module to invoke a mocked StackSpec instance.""" app = cli_module._cli patch_cm = patch.object(cli_module, "StackSpec", autospec=True, spec_set=True) with patch_cm as mocked_stack_spec: @@ -99,7 +99,7 @@ def invoke(self, cli_args: list[str]) -> click.testing.Result: def assert_build_config( self, expected_build_dir: str, expected_index_config: PackageIndexConfig ) -> None: - """Check environment build path and index configuration details""" + """Check environment build path and index configuration details.""" env_definition = self.mocked_stack_spec.define_build_environment env_definition.assert_called_with(expected_build_dir, expected_index_config) @@ -110,7 +110,7 @@ def assert_build_config( } def get_output_method(self, command: str) -> MagicMock: - """Return the Mock expected to be called for the given output command""" + """Return the Mock expected to be called for the given output command.""" output_method_name = self._OUTPUT_METHODS[command] return cast(MagicMock, getattr(self.mocked_build_env, output_method_name)) @@ -121,7 +121,7 @@ def get_output_method(self, command: str) -> MagicMock: } def get_default_output_dir(self, command: str) -> str: - """Return the Mock expected to be called for the given output command""" + """Return the Mock expected to be called for the given output command.""" return self._DEFAULT_OUTPUT_DIRS[command] diff --git a/tests/test_hashing.py b/tests/test_hashing.py index e12f212..9838d69 100644 --- a/tests/test_hashing.py +++ b/tests/test_hashing.py @@ -1,4 +1,4 @@ -"""Test cases for hashing utility functions""" +"""Test cases for hashing utility functions.""" import hashlib import shutil diff --git a/tests/test_index_config.py b/tests/test_index_config.py index 9b53c26..6718155 100644 --- a/tests/test_index_config.py +++ b/tests/test_index_config.py @@ -1,4 +1,4 @@ -"""Test for package index access configuration""" +"""Test for package index access configuration.""" import os diff --git a/tests/test_minimal_project.py b/tests/test_minimal_project.py index 7bd2160..a194803 100644 --- a/tests/test_minimal_project.py +++ b/tests/test_minimal_project.py @@ -1,4 +1,4 @@ -"""Test building the minimal project produces the expected results""" +"""Test building the minimal project produces the expected results.""" import json import shutil @@ -52,7 +52,7 @@ def _define_build_env(working_path: Path) -> BuildEnvironment: - """Define a build environment for the sample project in a temporary folder""" + """Define a build environment for the sample project in a temporary folder.""" # To avoid side effects from lock file creation, copy input files to the working path for src_path in MINIMAL_PROJECT_PATHS: dest_path = working_path / src_path.name @@ -131,7 +131,7 @@ def _define_build_env(working_path: Path) -> BuildEnvironment: def _filter_archive_manifest(archive_manifest: ArchiveBuildMetadata) -> ArchiveSummary: - """Drop archive manifest fields that aren't covered by this set of test cases""" + """Drop archive manifest fields that aren't covered by this set of test cases.""" summary: ArchiveSummary = {} for key in _CHECKED_KEYS: value = archive_manifest.get(key) @@ -143,7 +143,7 @@ def _filter_archive_manifest(archive_manifest: ArchiveBuildMetadata) -> ArchiveS def _filter_manifest( manifest: StackPublishingRequest, ) -> tuple[BuildManifest, LastLockedTimes]: - """Extract manifest fields that are relevant to this set of test cases""" + """Extract manifest fields that are relevant to this set of test cases.""" filtered_summaries: ArchiveSummaries = {} last_locked_times: LastLockedTimes = {} for kind, archive_manifests in manifest["layers"].items(): @@ -157,7 +157,7 @@ def _filter_manifest( def _tag_manifest(manifest: BuildManifest, expected_tag: str) -> BuildManifest: - """Add expected build tag to fields that are expected to include the build tag""" + """Add expected build tag to fields that are expected to include the build tag.""" tagged_summaries: ArchiveSummaries = {} for kind, summaries in manifest["layers"].items(): tagged_summaries[kind] = new_summaries = [] diff --git a/tests/test_postinstall.py b/tests/test_postinstall.py index 2c85b10..5efda88 100644 --- a/tests/test_postinstall.py +++ b/tests/test_postinstall.py @@ -1,4 +1,4 @@ -"""Tests for venvstacks post-install script generation""" +"""Tests for venvstacks post-install script generation.""" import os diff --git a/tests/test_sample_project.py b/tests/test_sample_project.py index 1e3b184..f74927e 100644 --- a/tests/test_sample_project.py +++ b/tests/test_sample_project.py @@ -1,4 +1,4 @@ -"""Test building the sample project produces the expected results""" +"""Test building the sample project produces the expected results.""" import os.path import shutil @@ -48,7 +48,7 @@ def _define_build_env(working_path: Path) -> BuildEnvironment: - """Define a build environment for the sample project in a temporary folder""" + """Define a build environment for the sample project in a temporary folder.""" # To simplify regeneration of committed lockfiles and metadata, # use the spec file directly from its checked out location stack_spec = StackSpec.load(SAMPLE_PROJECT_STACK_SPEC_PATH) @@ -57,7 +57,7 @@ def _define_build_env(working_path: Path) -> BuildEnvironment: def _get_expected_metadata(build_env: BuildEnvironment) -> ManifestData: - """Path to the expected sample project archive metadata for the current platform""" + """Path to the expected sample project archive metadata for the current platform.""" return ManifestData(SAMPLE_PROJECT_MANIFESTS_PATH / build_env.build_platform)