Programmatic scaffolding engine behind Create Awesome Python App. Import the scaffolding pipeline -- composable, headless, and CI-ready.
Requires Python >= 3.12.
This is the engine package. For the interactive CLI, use
create-awesome-python-appinstead.
pip install create-python-app-coreOr with uv:
uv add create-python-app-coreimport asyncio
from create_python_app_core import create_python_app
async def main() -> None:
await create_python_app(
"my-app",
{
"projectName": "my-app",
"template": "file:///path/to/template",
"install": True,
},
transform_options=lambda opts: asyncio.sleep(0, result=opts),
)
asyncio.run(main())from create_python_app_core import scaffold_project
scaffold_project(
"my-app",
template="file:///path/to/template",
addons=[],
extend=[],
install=True,
force=False,
offline=False,
)from create_python_app_core import resolve_source, get_template_dir_path
source = resolve_source(
"https://github.com/Create-Python-App/cpa-templates?ref=main&subdir=fastapi"
)
print(source.kind) # github
print(source.ref) # main
print(source.subdir) # fastapifrom create_python_app_core import resolve_source, download_repository
source = resolve_source("https://github.com/org/my-template")
root = download_repository(source, refresh="stale", offline=False)
template_dir = get_template_dir_path(source, root)from pathlib import Path
from create_python_app_core import load_cpa_config
cfg = load_cpa_config(Path("/path/to/template/cpa.config.json"))
for opt in cfg.custom_options:
print(opt.key, opt.default)from create_python_app_core import print_env_info
print_env_info()
# Prints Python, platform, uv, and git info. Then exits.from create_python_app_core import check_python_version
check_python_version(">=3.12", "my-tool")
# Exits with code 1 if the interpreter does not match.All public exports from create_python_app_core:
| Signature | Description |
|---|---|
create_python_app(project_directory, options, transform_options=None) |
Async orchestrator. Applies transform_options, then delegates to scaffold_project. |
scaffold_project(project_directory, *, template, addons=None, extend=None, force=False, install=True, offline=False, refresh=None, keep_on_failure=False, cache_dir=None, options=None) |
Main scaffolding pipeline. Resolves sources, downloads layers, merges files, runs uv sync, and initializes git. |
resolve_source(spec, *, cache_dir=None) |
Parses a template/extension specifier (GitHub URL, file://, slug) into a ResolvedSource. |
get_template_dir_path(source, root) |
Returns the template/ subdirectory when present, otherwise the resolved root. |
default_cache_dir() |
Returns CPA_CACHE_DIR or ~/.cache/cpa. |
download_repository(source, *, offline=False, refresh=None, cache_root=None) |
Clones or refreshes a Git repo into the cache. Returns the entry directory. |
read_cache_meta(entry) |
Reads .cpa-cache.json metadata from a cache entry. |
write_cache_meta(entry, meta) |
Writes .cpa-cache.json metadata for a cache entry. |
load_cpa_config(path) |
Loads optional cpa.config.json (custom CLI prompts). Returns empty CpaConfig when missing. |
assert_directory_is_empty(path, *, force=False) |
Raises NonEmptyTargetDirectoryError when the target exists and is non-empty. |
load_layer(source, root, dest, *, overwrite=True, context=None) |
Copies one template/extension layer into dest. |
merge_layers(layers, dest, *, context=None) |
Applies layers in order (template, addons, extend). Later layers win. |
merge_pyproject_text(base_text, overlay_text) |
Deep-merges two pyproject.toml documents as TOML. |
check_python_version(required, package_name) |
Compares sys.version_info against a PEP 440 specifier. Exits with code 1 if too old. |
check_for_latest_version(package_name) |
Async. Fetches the latest version from PyPI. Returns None on failure. |
print_env_info() |
Prints OS, Python, uv, and git info to stdout, then exits. |
| Name | Description |
|---|---|
__version__ |
Installed package version string. |
CPA_USER_AGENT |
HTTP User-Agent sent to PyPI (create-python-app-core/<version>). |
NON_EMPTY_DIR_ERROR_CODE |
Stable code for NonEmptyTargetDirectoryError (CPA_NON_EMPTY_TARGET_DIR). |
| Type | Shape |
|---|---|
ResolvedSource |
kind (github | file | slug | git), url, ref, subdir, local_path |
CacheMeta |
url, ref, fetched_at, commit |
CpaConfig |
name, custom_options, raw |
CpaCustomOption |
key, type, message, default |
CpaError |
Base exception with .code attribute |
ConfigParseError |
Invalid cpa.config.json (code: CPA_CONFIG_PARSE) |
ManifestLoadError |
Missing template directory (code: CPA_MANIFEST_LOAD) |
PackageManagerFallbackError |
Package manager fallback failure (code: CPA_PM_FALLBACK) |
ScaffoldAbortedError |
Scaffold failed mid-run (code: CPA_ABORTED) |
NonEmptyTargetDirectoryError |
Target directory not empty (code: CPA_NON_EMPTY_TARGET_DIR) |
| Key | Type | Default | Description |
|---|---|---|---|
template |
str |
"" |
Primary template specifier (URL, file://, or slug). |
addons |
list[str] |
[] |
Additional template layers applied after the base template. |
extend |
list[str] |
[] |
Extension layers applied last (later wins on conflicts). |
force |
bool |
False |
Allow scaffolding into a non-empty directory. |
install |
bool |
True |
Run uv sync when pyproject.toml is present. |
offline |
bool |
False |
Use cached repos only; raise on cache miss. |
refresh |
str |
env / "stale" |
Cache refresh mode: always, stale, or manual. |
keep_on_failure |
bool |
False |
Keep the partial project directory when scaffolding fails. |
cache_dir |
str | Path |
None |
Override the default cache root. |
set |
dict |
{} |
Jinja context overrides (merged into projectName and custom option defaults). |
All CPA_* variables read by the core engine:
| Variable | Default | Description |
|---|---|---|
CPA_CACHE_DIR |
~/.cache/cpa |
Root directory for cloned repository cache entries. |
CPA_REFRESH |
stale |
Default cache refresh mode: always, stale, or manual. |
CPA_REFRESH_AFTER_HOURS |
24 |
Hours before a stale cache entry is refreshed. |
CPA_SKIP_GIT |
unset | Set to 1 to skip git init and block all git subprocess calls. |
CPA_STRICT_REPRO |
unset | Set to 1 to require a full 40-character commit SHA in ?ref= query params. |
Stable machine-readable codes on CpaError.code:
| Code | Exception class | When raised |
|---|---|---|
CPA_ERROR |
CpaError |
Generic base error (default). |
CPA_CONFIG_PARSE |
ConfigParseError |
Malformed or invalid cpa.config.json. |
CPA_MANIFEST_LOAD |
ManifestLoadError |
Template directory not found on disk. |
CPA_PM_FALLBACK |
PackageManagerFallbackError |
Package manager fallback failure. |
CPA_ABORTED |
ScaffoldAbortedError |
Scaffold failed (template render, unexpected error, etc.). |
CPA_NON_EMPTY_TARGET_DIR |
NonEmptyTargetDirectoryError |
Target directory exists and is not empty. |
CPA_GIT |
CpaError |
Git subprocess failed or git not found. |
CPA_SKIP_GIT |
CpaError |
Git operation attempted while CPA_SKIP_GIT=1. |
CPA_FILE |
CpaError |
file:// source path does not exist. |
CPA_OFFLINE |
CpaError |
Offline mode with no cached copy of the repository. |
CPA_STRICT_REPRO |
CpaError |
?ref= is not a full SHA while CPA_STRICT_REPRO=1. |
create_python_app()
|-- transform_options() (optional)
|-- scaffold_project()
|-- assert_directory_is_empty()
|-- resolve_source() for each template / addon / extend
|-- download_repository() (git clone or file://)
|-- load_cpa_config() from cpa.config.json
|-- build_scaffold_context() (projectName + custom options + --set)
|-- merge_layers() (Jinja .template, .append, pyproject merge)
|-- uv sync (when install=True and pyproject.toml exists)
|-- git init (unless CPA_SKIP_GIT=1)
+-- cleanup partial directory on failure (unless keep_on_failure)
The package is organized into these modules:
| Module | Responsibility |
|---|---|
__init__.py |
Barrel export and public API surface |
api.py |
create_python_app, version checks, env info, PyPI lookup |
installer.py |
scaffold_project orchestration, uv sync, git init |
loaders.py |
File discovery, .template / .append processing, layer merge |
pyproject_merge.py |
Deep-merge pyproject.toml across template layers |
paths.py |
URL resolution (GitHub, file://, slugs, ?ref=, ?subdir=) |
git_cache.py |
Clone/pull with cache, refresh modes, offline support |
config.py |
Reads optional cpa.config.json for custom CLI prompts |
errors.py |
Typed CpaError hierarchy with stable codes |
create-awesome-python-app-- Interactive CLI built on this core- Create Python App -- Monorepo
- Templates catalog
MIT (c) Create Python App Contributors