Skip to content

Commit 870cc58

Browse files
docs(core): expand README API reference
Closes #172.
1 parent cf0d014 commit 870cc58

1 file changed

Lines changed: 261 additions & 7 deletions

File tree

Lines changed: 261 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,269 @@
11
# create-python-app-core
22

33
Programmatic scaffolding engine behind Create Awesome Python App.
4+
Import the scaffolding pipeline -- composable, headless, and CI-ready.
5+
6+
Requires **Python >= 3.12**.
7+
8+
> This is the _engine_ package. For the interactive CLI, use
9+
> [`create-awesome-python-app`](https://pypi.org/project/create-awesome-python-app/)
10+
> instead.
11+
12+
---
13+
14+
## Installation
15+
16+
```bash
17+
pip install create-python-app-core
18+
```
19+
20+
Or with uv:
21+
22+
```bash
23+
uv add create-python-app-core
24+
```
25+
26+
---
27+
28+
## Usage
29+
30+
### Scaffold a project programmatically
31+
32+
```python
33+
import asyncio
34+
from create_python_app_core import create_python_app
35+
36+
37+
async def main() -> None:
38+
await create_python_app(
39+
"my-app",
40+
{
41+
"projectName": "my-app",
42+
"template": "file:///path/to/template",
43+
"install": True,
44+
},
45+
transform_options=lambda opts: asyncio.sleep(0, result=opts),
46+
)
47+
48+
49+
asyncio.run(main())
50+
```
51+
52+
### Scaffold with the installer API
453

554
```python
6-
from create_python_app_core import (
7-
create_python_app,
8-
check_python_version,
9-
check_for_latest_version,
10-
print_env_info,
11-
CPA_USER_AGENT,
55+
from create_python_app_core import scaffold_project
56+
57+
scaffold_project(
58+
"my-app",
59+
template="file:///path/to/template",
60+
addons=[],
61+
extend=[],
62+
install=True,
63+
force=False,
64+
offline=False,
1265
)
1366
```
1467

15-
Requires **Python >= 3.12**.
68+
### Resolve a template source
69+
70+
```python
71+
from create_python_app_core import resolve_source, get_template_dir_path
72+
73+
source = resolve_source(
74+
"https://github.com/Create-Python-App/cpa-templates?ref=main&subdir=fastapi"
75+
)
76+
print(source.kind) # github
77+
print(source.ref) # main
78+
print(source.subdir) # fastapi
79+
```
80+
81+
### Download a repository into the cache
82+
83+
```python
84+
from create_python_app_core import resolve_source, download_repository
85+
86+
source = resolve_source("https://github.com/org/my-template")
87+
root = download_repository(source, refresh="stale", offline=False)
88+
template_dir = get_template_dir_path(source, root)
89+
```
90+
91+
### Load template configuration
92+
93+
```python
94+
from pathlib import Path
95+
96+
from create_python_app_core import load_cpa_config
97+
98+
cfg = load_cpa_config(Path("/path/to/template/cpa.config.json"))
99+
for opt in cfg.custom_options:
100+
print(opt.key, opt.default)
101+
```
102+
103+
### Check environment info
104+
105+
```python
106+
from create_python_app_core import print_env_info
107+
108+
print_env_info()
109+
# Prints Python, platform, uv, and git info. Then exits.
110+
```
111+
112+
### Validate the Python version
113+
114+
```python
115+
from create_python_app_core import check_python_version
116+
117+
check_python_version(">=3.12", "my-tool")
118+
# Exits with code 1 if the interpreter does not match.
119+
```
120+
121+
---
122+
123+
## API Reference
124+
125+
All public exports from `create_python_app_core`:
126+
127+
### Functions
128+
129+
| Signature | Description |
130+
| --------- | ----------- |
131+
| `create_python_app(project_directory, options, transform_options=None)` | Async orchestrator. Applies `transform_options`, then delegates to `scaffold_project`. |
132+
| `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. |
133+
| `resolve_source(spec, *, cache_dir=None)` | Parses a template/extension specifier (GitHub URL, `file://`, slug) into a `ResolvedSource`. |
134+
| `get_template_dir_path(source, root)` | Returns the `template/` subdirectory when present, otherwise the resolved root. |
135+
| `default_cache_dir()` | Returns `CPA_CACHE_DIR` or `~/.cache/cpa`. |
136+
| `download_repository(source, *, offline=False, refresh=None, cache_root=None)` | Clones or refreshes a Git repo into the cache. Returns the entry directory. |
137+
| `read_cache_meta(entry)` | Reads `.cpa-cache.json` metadata from a cache entry. |
138+
| `write_cache_meta(entry, meta)` | Writes `.cpa-cache.json` metadata for a cache entry. |
139+
| `load_cpa_config(path)` | Loads optional `cpa.config.json` (custom CLI prompts). Returns empty `CpaConfig` when missing. |
140+
| `assert_directory_is_empty(path, *, force=False)` | Raises `NonEmptyTargetDirectoryError` when the target exists and is non-empty. |
141+
| `load_layer(source, root, dest, *, overwrite=True, context=None)` | Copies one template/extension layer into `dest`. |
142+
| `merge_layers(layers, dest, *, context=None)` | Applies layers in order (template, addons, extend). Later layers win. |
143+
| `merge_pyproject_text(base_text, overlay_text)` | Deep-merges two `pyproject.toml` documents as TOML. |
144+
| `check_python_version(required, package_name)` | Compares `sys.version_info` against a PEP 440 specifier. Exits with code 1 if too old. |
145+
| `check_for_latest_version(package_name)` | Async. Fetches the latest version from PyPI. Returns `None` on failure. |
146+
| `print_env_info()` | Prints OS, Python, uv, and git info to stdout, then exits. |
147+
148+
### Constants
149+
150+
| Name | Description |
151+
| ---- | ----------- |
152+
| `__version__` | Installed package version string. |
153+
| `CPA_USER_AGENT` | HTTP User-Agent sent to PyPI (`create-python-app-core/<version>`). |
154+
| `NON_EMPTY_DIR_ERROR_CODE` | Stable code for `NonEmptyTargetDirectoryError` (`CPA_NON_EMPTY_TARGET_DIR`). |
155+
156+
### Types
157+
158+
| Type | Shape |
159+
| ---- | ----- |
160+
| `ResolvedSource` | `kind` (github \| file \| slug \| git), `url`, `ref`, `subdir`, `local_path` |
161+
| `CacheMeta` | `url`, `ref`, `fetched_at`, `commit` |
162+
| `CpaConfig` | `name`, `custom_options`, `raw` |
163+
| `CpaCustomOption` | `key`, `type`, `message`, `default` |
164+
| `CpaError` | Base exception with `.code` attribute |
165+
| `ConfigParseError` | Invalid `cpa.config.json` (code: `CPA_CONFIG_PARSE`) |
166+
| `ManifestLoadError` | Missing template directory (code: `CPA_MANIFEST_LOAD`) |
167+
| `PackageManagerFallbackError` | Package manager fallback failure (code: `CPA_PM_FALLBACK`) |
168+
| `ScaffoldAbortedError` | Scaffold failed mid-run (code: `CPA_ABORTED`) |
169+
| `NonEmptyTargetDirectoryError` | Target directory not empty (code: `CPA_NON_EMPTY_TARGET_DIR`) |
170+
171+
### `create_python_app` options dict
172+
173+
| Key | Type | Default | Description |
174+
| --- | ---- | ------- | ----------- |
175+
| `template` | `str` | `""` | Primary template specifier (URL, `file://`, or slug). |
176+
| `addons` | `list[str]` | `[]` | Additional template layers applied after the base template. |
177+
| `extend` | `list[str]` | `[]` | Extension layers applied last (later wins on conflicts). |
178+
| `force` | `bool` | `False` | Allow scaffolding into a non-empty directory. |
179+
| `install` | `bool` | `True` | Run `uv sync` when `pyproject.toml` is present. |
180+
| `offline` | `bool` | `False` | Use cached repos only; raise on cache miss. |
181+
| `refresh` | `str` | env / `"stale"` | Cache refresh mode: `always`, `stale`, or `manual`. |
182+
| `keep_on_failure` | `bool` | `False` | Keep the partial project directory when scaffolding fails. |
183+
| `cache_dir` | `str \| Path` | `None` | Override the default cache root. |
184+
| `set` | `dict` | `{}` | Jinja context overrides (merged into `projectName` and custom option defaults). |
185+
186+
---
187+
188+
## Environment Variables
189+
190+
All `CPA_*` variables read by the core engine:
191+
192+
| Variable | Default | Description |
193+
| -------- | ------- | ----------- |
194+
| `CPA_CACHE_DIR` | `~/.cache/cpa` | Root directory for cloned repository cache entries. |
195+
| `CPA_REFRESH` | `stale` | Default cache refresh mode: `always`, `stale`, or `manual`. |
196+
| `CPA_REFRESH_AFTER_HOURS` | `24` | Hours before a `stale` cache entry is refreshed. |
197+
| `CPA_SKIP_GIT` | unset | Set to `1` to skip `git init` and block all git subprocess calls. |
198+
| `CPA_STRICT_REPRO` | unset | Set to `1` to require a full 40-character commit SHA in `?ref=` query params. |
199+
200+
---
201+
202+
## Error Codes
203+
204+
Stable machine-readable codes on `CpaError.code`:
205+
206+
| Code | Exception class | When raised |
207+
| ---- | --------------- | ----------- |
208+
| `CPA_ERROR` | `CpaError` | Generic base error (default). |
209+
| `CPA_CONFIG_PARSE` | `ConfigParseError` | Malformed or invalid `cpa.config.json`. |
210+
| `CPA_MANIFEST_LOAD` | `ManifestLoadError` | Template directory not found on disk. |
211+
| `CPA_PM_FALLBACK` | `PackageManagerFallbackError` | Package manager fallback failure. |
212+
| `CPA_ABORTED` | `ScaffoldAbortedError` | Scaffold failed (template render, unexpected error, etc.). |
213+
| `CPA_NON_EMPTY_TARGET_DIR` | `NonEmptyTargetDirectoryError` | Target directory exists and is not empty. |
214+
| `CPA_GIT` | `CpaError` | Git subprocess failed or `git` not found. |
215+
| `CPA_SKIP_GIT` | `CpaError` | Git operation attempted while `CPA_SKIP_GIT=1`. |
216+
| `CPA_FILE` | `CpaError` | `file://` source path does not exist. |
217+
| `CPA_OFFLINE` | `CpaError` | Offline mode with no cached copy of the repository. |
218+
| `CPA_STRICT_REPRO` | `CpaError` | `?ref=` is not a full SHA while `CPA_STRICT_REPRO=1`. |
219+
220+
---
221+
222+
## How It Works
223+
224+
```text
225+
create_python_app()
226+
|-- transform_options() (optional)
227+
|-- scaffold_project()
228+
|-- assert_directory_is_empty()
229+
|-- resolve_source() for each template / addon / extend
230+
|-- download_repository() (git clone or file://)
231+
|-- load_cpa_config() from cpa.config.json
232+
|-- build_scaffold_context() (projectName + custom options + --set)
233+
|-- merge_layers() (Jinja .template, .append, pyproject merge)
234+
|-- uv sync (when install=True and pyproject.toml exists)
235+
|-- git init (unless CPA_SKIP_GIT=1)
236+
+-- cleanup partial directory on failure (unless keep_on_failure)
237+
```
238+
239+
---
240+
241+
## Architecture
242+
243+
The package is organized into these modules:
244+
245+
| Module | Responsibility |
246+
| ------ | -------------- |
247+
| `__init__.py` | Barrel export and public API surface |
248+
| `api.py` | `create_python_app`, version checks, env info, PyPI lookup |
249+
| `installer.py` | `scaffold_project` orchestration, `uv sync`, git init |
250+
| `loaders.py` | File discovery, `.template` / `.append` processing, layer merge |
251+
| `pyproject_merge.py` | Deep-merge `pyproject.toml` across template layers |
252+
| `paths.py` | URL resolution (GitHub, `file://`, slugs, `?ref=`, `?subdir=`) |
253+
| `git_cache.py` | Clone/pull with cache, refresh modes, offline support |
254+
| `config.py` | Reads optional `cpa.config.json` for custom CLI prompts |
255+
| `errors.py` | Typed `CpaError` hierarchy with stable codes |
256+
257+
---
258+
259+
## Related
260+
261+
- [`create-awesome-python-app`](https://pypi.org/project/create-awesome-python-app/) -- Interactive CLI built on this core
262+
- [Create Python App](https://github.com/Create-Python-App/create-python-app) -- Monorepo
263+
- [Templates catalog](https://github.com/Create-Python-App/cpa-templates)
264+
265+
---
266+
267+
## License
268+
269+
MIT (c) [Create Python App Contributors](https://github.com/Create-Python-App/create-python-app/graphs/contributors)

0 commit comments

Comments
 (0)