Sub-Package Extension Guide¶
This guide explains how tool-specific sub-packages (aws_lbd_art_builder_uv,
aws_lbd_art_builder_pip, aws_lbd_art_builder_poetry) should extend
aws_lbd_art_builder_core to implement Lambda layer building.
Core’s responsibility: define conventions (path layouts, S3 layouts), provide tool-agnostic infrastructure (zip, upload, publish, credentials).
Sub-package’s responsibility: implement Step 1 (dependency installation) using the specific tool, then wire the 4-step workflow together.
What Core Provides¶
Import |
Purpose |
|---|---|
Local directory conventions: |
|
S3 path conventions: |
|
Private repository auth: |
|
|
Abstract base for local builds (4-step workflow) |
Abstract base for Docker-based builds (4-step workflow) |
|
Relocate |
|
Create |
|
Default packages to exclude (boto3, pytest, etc.) |
|
Upload |
|
Smart publish with change detection |
|
Immutable result of a successful publish |
|
One-stop orchestrator: Build → Package → Upload → Publish in a single |
|
Protocol for builders (requires |
All imports are available from aws_lbd_art_builder_core.layer.api.
What Sub-Packages Must Implement¶
Each sub-package needs to implement Step 1 — Build: the tool-specific logic that installs dependencies into the layer build directory.
The minimum implementation is a subclass of
BaseLambdaLayerLocalBuilder
that overrides three methods:
step_2_prepare_environment — Copy Tool-Specific Files¶
The base class handles clean() + mkdirs() + copy_pyproject_toml().
Sub-packages should call super() and then copy their own lock files:
def step_2_prepare_environment(self):
super().step_2_prepare_environment()
# uv needs uv.lock in the isolated build dir
self.path_layout.copy_file(
p_src=self.path_layout.dir_project_root / "uv.lock",
p_dst=self.path_layout.dir_repo / "uv.lock",
)
step_3_execute_build — Run the Tool¶
This is the core of the sub-package. Run the tool-specific commands to install dependencies:
def step_3_execute_build(self):
super().step_3_execute_build()
dir_repo = self.path_layout.dir_repo
# Create a venv and install deps using uv
subprocess.run(
["uv", "venv", str(dir_repo / ".venv")],
cwd=str(dir_repo), check=True,
)
subprocess.run(
["uv", "pip", "install", "-r", "pyproject.toml",
"--python", str(dir_repo / ".venv" / "bin" / "python")],
cwd=str(dir_repo), check=True,
)
step_4_finalize_artifacts — Relocate Packages¶
If the tool installs packages into site-packages/ (uv, poetry do this),
they need to be moved into the python/ directory that Lambda requires:
def step_4_finalize_artifacts(self):
super().step_4_finalize_artifacts()
move_to_dir_python(
dir_site_packages=self.path_layout.dir_build_lambda_layer_repo_venv_site_packages,
dir_python=self.path_layout.dir_python,
)
Note
pip with --target installs directly into dir_python, so pip-based
builders typically skip this step.
Full Example: UvLambdaLayerLocalBuilder¶
import subprocess
import dataclasses
from pathlib import Path
from aws_lbd_art_builder_core.layer.api import BaseLambdaLayerLocalBuilder
from aws_lbd_art_builder_core.layer.api import move_to_dir_python
@dataclasses.dataclass(frozen=True)
class UvLambdaLayerLocalBuilder(BaseLambdaLayerLocalBuilder):
"""Build a Lambda layer using uv on the local machine."""
path_bin_uv: Path = dataclasses.field(default=None)
def step_2_prepare_environment(self):
super().step_2_prepare_environment()
self.path_layout.copy_pyproject_toml()
self.path_layout.copy_file(
p_src=self.path_layout.dir_project_root / "uv.lock",
p_dst=self.path_layout.dir_repo / "uv.lock",
)
def step_3_execute_build(self):
super().step_3_execute_build()
dir_repo = self.path_layout.dir_repo
uv = str(self.path_bin_uv or "uv")
subprocess.run(
[uv, "venv", str(dir_repo / ".venv")],
cwd=str(dir_repo), check=True,
)
subprocess.run(
[uv, "pip", "install", "-r", "pyproject.toml",
"--python", str(dir_repo / ".venv" / "bin" / "python")],
cwd=str(dir_repo), check=True,
)
def step_4_finalize_artifacts(self):
super().step_4_finalize_artifacts()
move_to_dir_python(
dir_site_packages=self.path_layout.dir_build_lambda_layer_repo_venv_site_packages,
dir_python=self.path_layout.dir_python,
)
End-to-End Workflow¶
With the builder implemented, use
LayerDeploymentWorkflow
to run the full pipeline in one call:
from pathlib import Path
from s3pathlib import S3Path
from aws_lbd_art_builder_core.layer.api import LayerDeploymentWorkflow
# Create a tool-specific builder (satisfies T_BUILDER protocol)
builder = UvLambdaLayerLocalBuilder(
path_pyproject_toml=Path("pyproject.toml"),
skip_prompt=True,
)
# Run the full Build → Package → Upload → Publish pipeline
workflow = LayerDeploymentWorkflow(
builder=builder,
path_manifest=Path("uv.lock"),
s3dir_lambda=S3Path("s3://my-bucket/projects/my_app/lambda/"),
layer_name="my_app",
s3_client=s3_client,
lambda_client=lambda_client,
publish_layer_version_kwargs={
"CompatibleRuntimes": ["python3.12"],
"Description": "my_app dependencies layer",
},
)
deployment = workflow.run()
# deployment.layer_version, deployment.layer_version_arn, ...
You can also call individual steps if you need finer control:
workflow.step_1_build()
workflow.step_2_package()
workflow.step_3_upload()
deployment = workflow.step_4_publish()
Note
path_manifest is always the tool-specific lock/requirements file:
uv.lock for uv, poetry.lock for poetry, requirements.txt for pip.
Core never interprets its contents — it only hashes and stores it for
change detection.
Container Builds¶
For cross-platform compatibility (C extensions, etc.), sub-packages can also
subclass BaseLambdaLayerContainerBuilder.
The container builder runs a Python script inside an official AWS SAM Docker
image. The sub-package provides path_script — the build script that
runs inside the container:
@dataclasses.dataclass(frozen=True)
class UvLambdaLayerContainerBuilder(BaseLambdaLayerContainerBuilder):
"""Build a Lambda layer using uv inside a Docker container."""
pass # base class handles everything; just supply path_script
The build script (a standalone .py file) runs inside Docker with the
project root mounted at /var/task. It should install dependencies into
the layer build directory. See
get_path_in_container
for path translation.