create-awesome-python-app 0.2.5__py3-none-any.whl → 0.2.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- create_awesome_python_app/__init__.py +1 -1
- create_awesome_python_app/catalog.py +219 -64
- create_awesome_python_app/cli.py +150 -15
- create_awesome_python_app-0.2.7.dist-info/METADATA +622 -0
- create_awesome_python_app-0.2.7.dist-info/RECORD +9 -0
- create_awesome_python_app-0.2.5.dist-info/METADATA +0 -42
- create_awesome_python_app-0.2.5.dist-info/RECORD +0 -9
- {create_awesome_python_app-0.2.5.dist-info → create_awesome_python_app-0.2.7.dist-info}/WHEEL +0 -0
- {create_awesome_python_app-0.2.5.dist-info → create_awesome_python_app-0.2.7.dist-info}/entry_points.txt +0 -0
|
@@ -13,7 +13,6 @@ from typing import Any
|
|
|
13
13
|
|
|
14
14
|
from create_python_app_core.paths import default_cache_dir, resolve_source
|
|
15
15
|
from rich.console import Console
|
|
16
|
-
from rich.table import Table
|
|
17
16
|
|
|
18
17
|
from create_awesome_python_app import __version__
|
|
19
18
|
from create_awesome_python_app.prompt_style import (
|
|
@@ -159,6 +158,17 @@ def validate_extension_compatibility(
|
|
|
159
158
|
raise IncompatibleExtensionsError(pairs)
|
|
160
159
|
|
|
161
160
|
|
|
161
|
+
@dataclass(frozen=True)
|
|
162
|
+
class CategoryInfo:
|
|
163
|
+
"""Catalog category with optional rich metadata (CNA parity)."""
|
|
164
|
+
|
|
165
|
+
slug: str
|
|
166
|
+
name: str
|
|
167
|
+
description: str = ""
|
|
168
|
+
details: str = ""
|
|
169
|
+
labels: tuple[str, ...] = ()
|
|
170
|
+
|
|
171
|
+
|
|
162
172
|
def short_category_label(category_name: str) -> str:
|
|
163
173
|
"""Derive a compact badge label from a catalog category name."""
|
|
164
174
|
stop_words = {"Applications", "Application", "Boilerplate"}
|
|
@@ -168,11 +178,48 @@ def short_category_label(category_name: str) -> str:
|
|
|
168
178
|
return " ".join(words[:2]) or category_name
|
|
169
179
|
|
|
170
180
|
|
|
181
|
+
def category_index(data: dict[str, Any]) -> dict[str, CategoryInfo]:
|
|
182
|
+
"""Index categories by slug, including description/details/labels."""
|
|
183
|
+
out: dict[str, CategoryInfo] = {}
|
|
184
|
+
for raw in data.get("categories", []):
|
|
185
|
+
if not isinstance(raw, dict):
|
|
186
|
+
continue
|
|
187
|
+
slug = str(raw.get("slug", "")).strip()
|
|
188
|
+
if not slug:
|
|
189
|
+
continue
|
|
190
|
+
labels_raw = raw.get("labels", [])
|
|
191
|
+
labels = (
|
|
192
|
+
tuple(str(label) for label in labels_raw)
|
|
193
|
+
if isinstance(labels_raw, list)
|
|
194
|
+
else ()
|
|
195
|
+
)
|
|
196
|
+
out[slug] = CategoryInfo(
|
|
197
|
+
slug=slug,
|
|
198
|
+
name=str(raw.get("name", slug)),
|
|
199
|
+
description=str(raw.get("description", "")).strip(),
|
|
200
|
+
details=str(raw.get("details", "")).strip(),
|
|
201
|
+
labels=labels,
|
|
202
|
+
)
|
|
203
|
+
return out
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def get_category_data(data: dict[str, Any], slug: str) -> CategoryInfo | None:
|
|
207
|
+
return category_index(data).get(slug)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def format_category_choice_title(info: CategoryInfo, extension_count: int) -> str:
|
|
211
|
+
"""Human title for the interactive extension-category picker."""
|
|
212
|
+
noun = "extension" if extension_count == 1 else "extensions"
|
|
213
|
+
title = f"{info.name} ({extension_count} {noun})"
|
|
214
|
+
if info.description:
|
|
215
|
+
title = f"{title} — {info.description}"
|
|
216
|
+
if info.labels:
|
|
217
|
+
title = f"{title} · {', '.join(info.labels[:3])}"
|
|
218
|
+
return title
|
|
219
|
+
|
|
220
|
+
|
|
171
221
|
def _category_map(data: dict[str, Any]) -> dict[str, str]:
|
|
172
|
-
return {
|
|
173
|
-
str(category.get("slug", "")): str(category.get("name", ""))
|
|
174
|
-
for category in data.get("categories", [])
|
|
175
|
-
}
|
|
222
|
+
return {slug: info.name for slug, info in category_index(data).items()}
|
|
176
223
|
|
|
177
224
|
|
|
178
225
|
def _search_text(template: dict[str, Any], category_name: str) -> str:
|
|
@@ -344,9 +391,9 @@ CACHE_TTL_SECONDS = 3600
|
|
|
344
391
|
FETCH_TIMEOUT_SECONDS = 10
|
|
345
392
|
USER_AGENT = f"create-awesome-python-app/{__version__} (https://github.com/Create-Python-App/create-python-app)"
|
|
346
393
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
394
|
+
_AUTO_FIXTURE_DIR = Path(__file__).resolve().parents[4]
|
|
395
|
+
_SENTINEL = object()
|
|
396
|
+
_fixture_root_override: Path | None | object = _SENTINEL
|
|
350
397
|
|
|
351
398
|
_memory_cache: dict[str, Any] | None = None
|
|
352
399
|
_memory_ts: float = 0.0
|
|
@@ -360,13 +407,61 @@ def catalog_cache_path() -> Path:
|
|
|
360
407
|
return default_cache_dir() / "catalog" / "templates.json"
|
|
361
408
|
|
|
362
409
|
|
|
410
|
+
def resolve_fixture_root() -> Path | None:
|
|
411
|
+
"""Resolve the repo root that contains ``fixtures/catalog/templates.json``.
|
|
412
|
+
|
|
413
|
+
Priority: ``CPA_FIXTURE_DIR`` → walk-up from package → ``cwd``.
|
|
414
|
+
"""
|
|
415
|
+
if _fixture_root_override is not _SENTINEL:
|
|
416
|
+
return _fixture_root_override # type: ignore[return-value]
|
|
417
|
+
|
|
418
|
+
env = os.environ.get("CPA_FIXTURE_DIR", "").strip()
|
|
419
|
+
if env:
|
|
420
|
+
return Path(env).expanduser().resolve()
|
|
421
|
+
|
|
422
|
+
def _has_fixture_catalog(root: Path) -> bool:
|
|
423
|
+
return (root / "fixtures" / "catalog" / "templates.json").is_file()
|
|
424
|
+
|
|
425
|
+
if _has_fixture_catalog(_AUTO_FIXTURE_DIR):
|
|
426
|
+
return _AUTO_FIXTURE_DIR
|
|
427
|
+
|
|
428
|
+
# Editable / site-packages layouts vary; walk up from this file.
|
|
429
|
+
for parent in Path(__file__).resolve().parents:
|
|
430
|
+
if _has_fixture_catalog(parent):
|
|
431
|
+
return parent
|
|
432
|
+
|
|
433
|
+
cwd = Path.cwd()
|
|
434
|
+
if _has_fixture_catalog(cwd):
|
|
435
|
+
return cwd
|
|
436
|
+
return None
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def set_fixture_root_for_tests(root: Path | None) -> None:
|
|
440
|
+
"""Override fixture root (test helper)."""
|
|
441
|
+
global _fixture_root_override
|
|
442
|
+
_fixture_root_override = root
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def reset_fixture_root_for_tests() -> None:
|
|
446
|
+
global _fixture_root_override
|
|
447
|
+
_fixture_root_override = _SENTINEL
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def fixture_catalog_path() -> Path | None:
|
|
451
|
+
root = resolve_fixture_root()
|
|
452
|
+
if root is None:
|
|
453
|
+
return None
|
|
454
|
+
return root / "fixtures" / "catalog" / "templates.json"
|
|
455
|
+
|
|
456
|
+
|
|
363
457
|
def _read_json_file(path: Path) -> dict[str, Any]:
|
|
364
458
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
365
459
|
|
|
366
460
|
|
|
367
461
|
def _read_fixture() -> dict[str, Any]:
|
|
368
|
-
|
|
369
|
-
|
|
462
|
+
path = fixture_catalog_path()
|
|
463
|
+
if path is not None and path.is_file():
|
|
464
|
+
return _read_json_file(path)
|
|
370
465
|
return {"templates": [], "extensions": [], "categories": []}
|
|
371
466
|
|
|
372
467
|
|
|
@@ -417,6 +512,19 @@ def get_catalog_data(*, force_refresh: bool = False) -> dict[str, Any]:
|
|
|
417
512
|
"""Load templates.json from remote URL, disk cache, or local fixture."""
|
|
418
513
|
global _memory_cache, _memory_ts
|
|
419
514
|
|
|
515
|
+
if os.environ.get("CPA_CATALOG_FIXTURE") == "1":
|
|
516
|
+
path = fixture_catalog_path()
|
|
517
|
+
if path is None or not path.is_file():
|
|
518
|
+
raise RuntimeError(
|
|
519
|
+
"Fixture mode is enabled (CPA_CATALOG_FIXTURE=1) but the fixture "
|
|
520
|
+
"root could not be resolved. Set CPA_FIXTURE_DIR to the repo root "
|
|
521
|
+
"containing fixtures/catalog/templates.json."
|
|
522
|
+
)
|
|
523
|
+
data = _read_fixture()
|
|
524
|
+
_memory_cache = data
|
|
525
|
+
_memory_ts = time.time()
|
|
526
|
+
return data
|
|
527
|
+
|
|
420
528
|
if (
|
|
421
529
|
not force_refresh
|
|
422
530
|
and _memory_cache is not None
|
|
@@ -425,38 +533,33 @@ def get_catalog_data(*, force_refresh: bool = False) -> dict[str, Any]:
|
|
|
425
533
|
):
|
|
426
534
|
return _memory_cache
|
|
427
535
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
536
|
+
url = catalog_url()
|
|
537
|
+
try:
|
|
538
|
+
data = _fetch_remote(url)
|
|
539
|
+
_write_disk_cache(data)
|
|
540
|
+
except (
|
|
541
|
+
urllib.error.URLError,
|
|
542
|
+
TimeoutError,
|
|
543
|
+
OSError,
|
|
544
|
+
json.JSONDecodeError,
|
|
545
|
+
) as err:
|
|
546
|
+
disk = _read_disk_cache()
|
|
547
|
+
if disk is not None:
|
|
548
|
+
console.print(
|
|
549
|
+
"[yellow][cpa] Could not refresh catalog "
|
|
550
|
+
f"({err}); using disk cache.[/yellow]"
|
|
551
|
+
)
|
|
552
|
+
data = disk
|
|
553
|
+
else:
|
|
554
|
+
fixture = _read_fixture()
|
|
555
|
+
if fixture.get("templates"):
|
|
443
556
|
console.print(
|
|
444
557
|
"[yellow][cpa] Could not refresh catalog "
|
|
445
|
-
f"({err}); using
|
|
558
|
+
f"({err}); using fixture.[/yellow]"
|
|
446
559
|
)
|
|
447
|
-
data =
|
|
560
|
+
data = fixture
|
|
448
561
|
else:
|
|
449
|
-
|
|
450
|
-
if fixture.get("templates"):
|
|
451
|
-
console.print(
|
|
452
|
-
"[yellow][cpa] Could not refresh catalog "
|
|
453
|
-
f"({err}); using fixture.[/yellow]"
|
|
454
|
-
)
|
|
455
|
-
data = fixture
|
|
456
|
-
else:
|
|
457
|
-
raise RuntimeError(
|
|
458
|
-
f"Failed to load template catalog: {err}"
|
|
459
|
-
) from err
|
|
562
|
+
raise RuntimeError(f"Failed to load template catalog: {err}") from err
|
|
460
563
|
|
|
461
564
|
_memory_cache = data
|
|
462
565
|
_memory_ts = time.time()
|
|
@@ -471,44 +574,96 @@ def reset_catalog_cache_for_tests() -> None:
|
|
|
471
574
|
|
|
472
575
|
def list_templates() -> None:
|
|
473
576
|
data = get_catalog_data()
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
)
|
|
484
|
-
|
|
577
|
+
categories = category_index(data)
|
|
578
|
+
templates = [t for t in data.get("templates", []) if isinstance(t, dict)]
|
|
579
|
+
|
|
580
|
+
console.print("[bold blue]\nAvailable Templates:[/bold blue]")
|
|
581
|
+
# Preserve catalog category order, then any unknown slugs.
|
|
582
|
+
ordered_slugs = list(categories)
|
|
583
|
+
seen: set[str] = set()
|
|
584
|
+
for slug in ordered_slugs:
|
|
585
|
+
seen.add(slug)
|
|
586
|
+
group = [t for t in templates if str(t.get("category", "")) == slug]
|
|
587
|
+
if not group:
|
|
588
|
+
continue
|
|
589
|
+
info = categories[slug]
|
|
590
|
+
console.print(f"[bold green]\n{info.name}:[/bold green]")
|
|
591
|
+
if info.description:
|
|
592
|
+
console.print(f" {info.description}")
|
|
593
|
+
if info.details:
|
|
594
|
+
console.print(f" [dim]{info.details}[/dim]")
|
|
595
|
+
if info.labels:
|
|
596
|
+
console.print(f" [dim]Keywords: {', '.join(info.labels)}[/dim]")
|
|
597
|
+
for template in group:
|
|
598
|
+
name = str(template.get("name", template.get("slug", "")))
|
|
599
|
+
tslug = str(template.get("slug", ""))
|
|
600
|
+
console.print(f" [yellow]{name}[/yellow] ([cyan]{tslug}[/cyan])")
|
|
601
|
+
desc = str(template.get("description", "")).strip()
|
|
602
|
+
if desc:
|
|
603
|
+
console.print(f" {desc}")
|
|
604
|
+
labels = template.get("labels", [])
|
|
605
|
+
if isinstance(labels, list) and labels:
|
|
606
|
+
console.print(f" Keywords: {', '.join(str(x) for x in labels)}")
|
|
607
|
+
|
|
608
|
+
orphans = [t for t in templates if str(t.get("category", "")) not in seen]
|
|
609
|
+
if orphans:
|
|
610
|
+
console.print("[bold green]\nOther:[/bold green]")
|
|
611
|
+
for template in orphans:
|
|
612
|
+
name = str(template.get("name", template.get("slug", "")))
|
|
613
|
+
tslug = str(template.get("slug", ""))
|
|
614
|
+
console.print(f" [yellow]{name}[/yellow] ([cyan]{tslug}[/cyan])")
|
|
485
615
|
|
|
486
616
|
|
|
487
617
|
def list_addons(template_slug: str | None = None) -> None:
|
|
488
618
|
data = get_catalog_data()
|
|
619
|
+
categories = category_index(data)
|
|
489
620
|
template_type: str | None = None
|
|
490
621
|
if template_slug:
|
|
491
622
|
for t in data.get("templates", []):
|
|
492
|
-
if t.get("slug") == template_slug:
|
|
623
|
+
if isinstance(t, dict) and t.get("slug") == template_slug:
|
|
493
624
|
template_type = str(t.get("type", ""))
|
|
494
625
|
break
|
|
495
626
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
627
|
+
console.print("[bold blue]\nAvailable Addons:[/bold blue]")
|
|
628
|
+
if template_slug:
|
|
629
|
+
console.print(
|
|
630
|
+
f"[bold green]\nCompatible with template: {template_slug}[/bold green]"
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
extensions = [
|
|
634
|
+
ext
|
|
635
|
+
for ext in data.get("extensions", data.get("addons", []))
|
|
636
|
+
if isinstance(ext, dict)
|
|
637
|
+
]
|
|
638
|
+
grouped: dict[str, list[dict[str, Any]]] = {}
|
|
639
|
+
for ext in extensions:
|
|
501
640
|
ext_types = ext.get("type", [])
|
|
502
641
|
if isinstance(ext_types, str):
|
|
503
642
|
ext_types = [ext_types]
|
|
504
643
|
if template_type and template_type not in ext_types:
|
|
505
644
|
continue
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
)
|
|
514
|
-
|
|
645
|
+
slug = str(ext.get("category", "custom"))
|
|
646
|
+
grouped.setdefault(slug, []).append(ext)
|
|
647
|
+
|
|
648
|
+
for slug in list(categories) + [s for s in grouped if s not in categories]:
|
|
649
|
+
group = grouped.get(slug)
|
|
650
|
+
if not group:
|
|
651
|
+
continue
|
|
652
|
+
info = categories.get(slug) or CategoryInfo(slug=slug, name=slug)
|
|
653
|
+
console.print(f"[bold green]\n{info.name}:[/bold green]")
|
|
654
|
+
if info.description:
|
|
655
|
+
console.print(f" {info.description}")
|
|
656
|
+
if info.details:
|
|
657
|
+
console.print(f" [dim]{info.details}[/dim]")
|
|
658
|
+
if info.labels:
|
|
659
|
+
console.print(f" [dim]Keywords: {', '.join(info.labels)}[/dim]")
|
|
660
|
+
for ext in group:
|
|
661
|
+
name = str(ext.get("name", ext.get("slug", "")))
|
|
662
|
+
eslug = str(ext.get("slug", ""))
|
|
663
|
+
console.print(f" [yellow]{name}[/yellow] ([cyan]{eslug}[/cyan])")
|
|
664
|
+
desc = str(ext.get("description", "")).strip()
|
|
665
|
+
if desc:
|
|
666
|
+
console.print(f" {desc}")
|
|
667
|
+
labels = ext.get("labels", [])
|
|
668
|
+
if isinstance(labels, list) and labels:
|
|
669
|
+
console.print(f" Keywords: {', '.join(str(x) for x in labels)}")
|
create_awesome_python_app/cli.py
CHANGED
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import os
|
|
7
|
+
import sys
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
@@ -25,17 +26,60 @@ from rich.console import Console
|
|
|
25
26
|
|
|
26
27
|
from create_awesome_python_app import __version__
|
|
27
28
|
|
|
29
|
+
# Sentinel for bare ``--fixture`` (optional DIR rewritten in argv preprocess).
|
|
30
|
+
_FIXTURE_AUTO = "__CPA_FIXTURE_AUTO__"
|
|
31
|
+
|
|
28
32
|
app = typer.Typer(
|
|
29
33
|
name="create-awesome-python-app",
|
|
30
34
|
help="Composable scaffolding CLI for production-ready Python apps.",
|
|
31
35
|
no_args_is_help=False,
|
|
32
36
|
add_completion=True,
|
|
37
|
+
# Keep cache out of this Typer app (routed in main()). A nested command
|
|
38
|
+
# group turns the CLI into Click Group form `[ARGS] COMMAND`, so
|
|
39
|
+
# `cpa my-api --template …` fails with "No such command '--template'".
|
|
40
|
+
epilog="Cache: create-awesome-python-app cache [list|dir|clean|verify|…]",
|
|
33
41
|
)
|
|
34
42
|
cache_app = typer.Typer(help="Inspect and manage the local template cache")
|
|
35
|
-
app.add_typer(cache_app, name="cache")
|
|
36
43
|
console = Console(stderr=True)
|
|
37
44
|
|
|
38
45
|
|
|
46
|
+
def _preprocess_fixture_argv(argv: list[str] | None = None) -> list[str]:
|
|
47
|
+
"""Rewrite bare ``--fixture`` to ``--fixture=__CPA_FIXTURE_AUTO__``.
|
|
48
|
+
|
|
49
|
+
Typer/Click requires an option argument; Commander allows ``--fixture [dir]``.
|
|
50
|
+
This keeps CNA-compatible UX: ``--fixture`` alone enables auto-detect mode.
|
|
51
|
+
"""
|
|
52
|
+
raw = list(sys.argv if argv is None else argv)
|
|
53
|
+
if not raw:
|
|
54
|
+
return raw
|
|
55
|
+
out = [raw[0]]
|
|
56
|
+
i = 1
|
|
57
|
+
while i < len(raw):
|
|
58
|
+
arg = raw[i]
|
|
59
|
+
if arg == "--fixture":
|
|
60
|
+
if i + 1 < len(raw) and not raw[i + 1].startswith("-"):
|
|
61
|
+
out.extend(["--fixture", raw[i + 1]])
|
|
62
|
+
i += 2
|
|
63
|
+
else:
|
|
64
|
+
out.append(f"--fixture={_FIXTURE_AUTO}")
|
|
65
|
+
i += 1
|
|
66
|
+
continue
|
|
67
|
+
out.append(arg)
|
|
68
|
+
i += 1
|
|
69
|
+
if argv is None:
|
|
70
|
+
sys.argv = out
|
|
71
|
+
return out
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def apply_fixture_mode(fixture: str | None) -> None:
|
|
75
|
+
"""Translate ``--fixture`` into ``CPA_CATALOG_FIXTURE`` / ``CPA_FIXTURE_DIR``."""
|
|
76
|
+
if fixture is None and os.environ.get("CPA_CATALOG_FIXTURE") != "1":
|
|
77
|
+
return
|
|
78
|
+
os.environ["CPA_CATALOG_FIXTURE"] = "1"
|
|
79
|
+
if fixture is not None and fixture != _FIXTURE_AUTO and fixture != "":
|
|
80
|
+
os.environ["CPA_FIXTURE_DIR"] = fixture
|
|
81
|
+
|
|
82
|
+
|
|
39
83
|
def _in_ci() -> bool:
|
|
40
84
|
return os.environ.get("CI", "").lower() in {"1", "true", "yes"}
|
|
41
85
|
|
|
@@ -159,9 +203,8 @@ def main() -> None:
|
|
|
159
203
|
`create-awesome-python-app cache dir` works (Typer would otherwise
|
|
160
204
|
treat `cache` as project_directory).
|
|
161
205
|
"""
|
|
162
|
-
import sys
|
|
163
|
-
|
|
164
206
|
check_python_version(">=3.12", "create-awesome-python-app")
|
|
207
|
+
_preprocess_fixture_argv()
|
|
165
208
|
if len(sys.argv) > 1 and sys.argv[1] == "cache":
|
|
166
209
|
sys.argv = [sys.argv[0], *sys.argv[2:]]
|
|
167
210
|
cache_app(prog_name="create-awesome-python-app cache")
|
|
@@ -169,9 +212,8 @@ def main() -> None:
|
|
|
169
212
|
app()
|
|
170
213
|
|
|
171
214
|
|
|
172
|
-
@app.
|
|
215
|
+
@app.command(name="create-awesome-python-app")
|
|
173
216
|
def scaffold(
|
|
174
|
-
ctx: typer.Context,
|
|
175
217
|
project_directory: str | None = typer.Argument("my-project"),
|
|
176
218
|
version: bool = typer.Option(False, "--version"),
|
|
177
219
|
info: bool = typer.Option(False, "--info", "-i"),
|
|
@@ -192,14 +234,25 @@ def scaffold(
|
|
|
192
234
|
refresh: str | None = typer.Option(None, "--refresh"),
|
|
193
235
|
strict_version: bool = typer.Option(False, "--strict-version"),
|
|
194
236
|
keep_on_failure: bool = typer.Option(False, "--keep-on-failure"),
|
|
237
|
+
fixture: str | None = typer.Option(
|
|
238
|
+
None,
|
|
239
|
+
"--fixture",
|
|
240
|
+
help=(
|
|
241
|
+
"Load the template catalog from the local fixtures/ directory "
|
|
242
|
+
"instead of the network (optional DIR = repo root; also "
|
|
243
|
+
"CPA_FIXTURE_DIR / CPA_CATALOG_FIXTURE)"
|
|
244
|
+
),
|
|
245
|
+
),
|
|
195
246
|
) -> None:
|
|
196
247
|
if version:
|
|
197
248
|
console.print(__version__)
|
|
198
249
|
raise typer.Exit(0)
|
|
199
250
|
if info:
|
|
200
251
|
print_env_info()
|
|
201
|
-
|
|
202
|
-
|
|
252
|
+
|
|
253
|
+
# Translate --fixture into env vars before catalog loads
|
|
254
|
+
# (--list-templates / interactive / scaffold).
|
|
255
|
+
apply_fixture_mode(fixture)
|
|
203
256
|
|
|
204
257
|
if list_templates or list_addons:
|
|
205
258
|
from create_awesome_python_app.catalog import list_addons as la
|
|
@@ -315,12 +368,22 @@ def scaffold(
|
|
|
315
368
|
extension_choices = build_extension_choices(interactive_catalog, template)
|
|
316
369
|
grouped_extensions = group_extension_choices(extension_choices)
|
|
317
370
|
if grouped_extensions:
|
|
371
|
+
from create_awesome_python_app.catalog import (
|
|
372
|
+
CategoryInfo,
|
|
373
|
+
category_index,
|
|
374
|
+
format_category_choice_title,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
categories = category_index(interactive_catalog)
|
|
318
378
|
category_choices = [
|
|
319
379
|
Choice(
|
|
320
|
-
title=(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
380
|
+
title=format_category_choice_title(
|
|
381
|
+
categories.get(category_slug)
|
|
382
|
+
or CategoryInfo(
|
|
383
|
+
slug=category_slug,
|
|
384
|
+
name=choices[0].category_name,
|
|
385
|
+
),
|
|
386
|
+
len(choices),
|
|
324
387
|
),
|
|
325
388
|
value=category_slug,
|
|
326
389
|
)
|
|
@@ -444,8 +507,29 @@ def cache_dir_cmd() -> None:
|
|
|
444
507
|
console.print(str(default_cache_dir()))
|
|
445
508
|
|
|
446
509
|
|
|
510
|
+
def _cache_json_print(payload: Any) -> None:
|
|
511
|
+
"""Print machine-readable JSON to stdout (not the stderr console)."""
|
|
512
|
+
import json
|
|
513
|
+
from dataclasses import asdict, is_dataclass
|
|
514
|
+
|
|
515
|
+
def _to_jsonable(value: Any) -> Any:
|
|
516
|
+
if isinstance(value, Path):
|
|
517
|
+
return str(value)
|
|
518
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
519
|
+
return {k: _to_jsonable(v) for k, v in asdict(value).items()}
|
|
520
|
+
if isinstance(value, list):
|
|
521
|
+
return [_to_jsonable(item) for item in value]
|
|
522
|
+
if isinstance(value, dict):
|
|
523
|
+
return {k: _to_jsonable(v) for k, v in value.items()}
|
|
524
|
+
return value
|
|
525
|
+
|
|
526
|
+
typer.echo(json.dumps(_to_jsonable(payload), indent=2))
|
|
527
|
+
|
|
528
|
+
|
|
447
529
|
@cache_app.command("list")
|
|
448
|
-
def cache_list_cmd(
|
|
530
|
+
def cache_list_cmd(
|
|
531
|
+
json_out: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
532
|
+
) -> None:
|
|
449
533
|
from create_awesome_python_app.cache import (
|
|
450
534
|
format_age,
|
|
451
535
|
format_bytes,
|
|
@@ -454,6 +538,9 @@ def cache_list_cmd() -> None:
|
|
|
454
538
|
)
|
|
455
539
|
|
|
456
540
|
entries = list_cache_entries()
|
|
541
|
+
if json_out:
|
|
542
|
+
_cache_json_print(entries)
|
|
543
|
+
return
|
|
457
544
|
if not entries:
|
|
458
545
|
console.print("[dim]No cached templates or extensions.[/dim]")
|
|
459
546
|
console.print(
|
|
@@ -488,10 +575,38 @@ def cache_list_cmd() -> None:
|
|
|
488
575
|
def cache_clean_cmd(
|
|
489
576
|
id: str | None = typer.Argument(None),
|
|
490
577
|
catalog: bool = typer.Option(False, "--catalog"),
|
|
578
|
+
json_out: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
579
|
+
force: bool = typer.Option(
|
|
580
|
+
False, "--force", "-f", help="Skip interactive confirmation"
|
|
581
|
+
),
|
|
491
582
|
) -> None:
|
|
492
583
|
from create_awesome_python_app.cache import clean_cache
|
|
493
584
|
|
|
585
|
+
# Targeted cleans (id / --catalog) never prompt.
|
|
586
|
+
if not catalog and id is None and not json_out and not force:
|
|
587
|
+
if not sys.stdin.isatty():
|
|
588
|
+
console.print(
|
|
589
|
+
"[yellow]Non-interactive shell — use --json or --force to skip "
|
|
590
|
+
"the prompt, or specify an id to target a specific entry.[/yellow]"
|
|
591
|
+
)
|
|
592
|
+
return
|
|
593
|
+
import questionary
|
|
594
|
+
|
|
595
|
+
from create_awesome_python_app.prompt_style import CPA_PROMPT_STYLE
|
|
596
|
+
|
|
597
|
+
confirmed = questionary.confirm(
|
|
598
|
+
"Remove ALL cached templates and extensions?",
|
|
599
|
+
default=False,
|
|
600
|
+
style=CPA_PROMPT_STYLE,
|
|
601
|
+
).ask()
|
|
602
|
+
if not confirmed:
|
|
603
|
+
console.print("[dim]Clean cancelled.[/dim]")
|
|
604
|
+
return
|
|
605
|
+
|
|
494
606
|
result = clean_cache(id, catalog=catalog)
|
|
607
|
+
if json_out:
|
|
608
|
+
_cache_json_print(result)
|
|
609
|
+
return
|
|
495
610
|
if result.not_found:
|
|
496
611
|
console.print(f"[yellow]No cache entry found for id: {id}[/yellow]")
|
|
497
612
|
return
|
|
@@ -503,10 +618,18 @@ def cache_clean_cmd(
|
|
|
503
618
|
|
|
504
619
|
|
|
505
620
|
@cache_app.command("verify")
|
|
506
|
-
def cache_verify_cmd(
|
|
621
|
+
def cache_verify_cmd(
|
|
622
|
+
id: str | None = typer.Argument(None),
|
|
623
|
+
json_out: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
624
|
+
) -> None:
|
|
507
625
|
from create_awesome_python_app.cache import verify_cache
|
|
508
626
|
|
|
509
627
|
results = verify_cache(id)
|
|
628
|
+
if json_out:
|
|
629
|
+
_cache_json_print(results)
|
|
630
|
+
if any(not bool(entry.fsck_ok) for entry in results):
|
|
631
|
+
raise typer.Exit(1)
|
|
632
|
+
raise typer.Exit(0)
|
|
510
633
|
if not results:
|
|
511
634
|
console.print("[dim]No cached entries.[/dim]")
|
|
512
635
|
raise typer.Exit(0)
|
|
@@ -527,10 +650,15 @@ def cache_verify_cmd(id: str | None = typer.Argument(None)) -> None:
|
|
|
527
650
|
|
|
528
651
|
|
|
529
652
|
@cache_app.command("outdated")
|
|
530
|
-
def cache_outdated_cmd(
|
|
653
|
+
def cache_outdated_cmd(
|
|
654
|
+
json_out: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
655
|
+
) -> None:
|
|
531
656
|
from create_awesome_python_app.cache import check_outdated
|
|
532
657
|
|
|
533
658
|
results = check_outdated()
|
|
659
|
+
if json_out:
|
|
660
|
+
_cache_json_print(results)
|
|
661
|
+
return
|
|
534
662
|
if not results:
|
|
535
663
|
console.print("[dim]No cached entries to check.[/dim]")
|
|
536
664
|
return
|
|
@@ -590,10 +718,17 @@ def cache_update_cmd(id: str | None = typer.Argument(None)) -> None:
|
|
|
590
718
|
|
|
591
719
|
|
|
592
720
|
@cache_app.command("doctor")
|
|
593
|
-
def cache_doctor_cmd(
|
|
721
|
+
def cache_doctor_cmd(
|
|
722
|
+
json_out: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
723
|
+
) -> None:
|
|
594
724
|
from create_awesome_python_app.cache import run_doctor
|
|
595
725
|
|
|
596
726
|
results = run_doctor()
|
|
727
|
+
if json_out:
|
|
728
|
+
_cache_json_print(results)
|
|
729
|
+
if any(not row.ok for row in results):
|
|
730
|
+
raise typer.Exit(1)
|
|
731
|
+
raise typer.Exit(0)
|
|
597
732
|
all_ok = True
|
|
598
733
|
for row in results:
|
|
599
734
|
mark = "[green]✓[/green]" if row.ok else "[red]✗[/red]"
|
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: create-awesome-python-app
|
|
3
|
+
Version: 0.2.7
|
|
4
|
+
Summary: Composable scaffolding CLI for production-ready Python apps
|
|
5
|
+
Project-URL: Homepage, https://github.com/Create-Python-App/create-python-app
|
|
6
|
+
Project-URL: Repository, https://github.com/Create-Python-App/create-python-app
|
|
7
|
+
Project-URL: Issues, https://github.com/Create-Python-App/create-python-app/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/Create-Python-App/create-python-app/blob/main/CHANGELOG.md
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Requires-Dist: create-python-app-core>=0.2.6
|
|
12
|
+
Requires-Dist: questionary>=2.1.1
|
|
13
|
+
Requires-Dist: rich>=15.0.0
|
|
14
|
+
Requires-Dist: typer>=0.27.0
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
<!--lint disable double-link awesome-heading awesome-git-repo-age awesome-toc-->
|
|
18
|
+
|
|
19
|
+
<div align="center">
|
|
20
|
+
|
|
21
|
+
<img src="https://raw.githubusercontent.com/Create-Python-App/create-python-app/main/packages/create-awesome-python-app/assets/hero.svg" alt="Create Awesome Python App banner" width="100%" />
|
|
22
|
+
|
|
23
|
+
# Create Awesome Python App
|
|
24
|
+
|
|
25
|
+
**One command. Any stack.** Generate production-ready Python apps by composing templates, addons, and AI-ready conventions.
|
|
26
|
+
|
|
27
|
+
From blank folder to a working FastAPI, Django, Celery, CLI, or uv workspace project with modern tooling and team-friendly automation.
|
|
28
|
+
|
|
29
|
+
[![PyPI][pypiversion]][pypiurl]
|
|
30
|
+
[![Python][pythonbadge]][pythonurl]
|
|
31
|
+
[![Stars][starsbadge]][starsurl]
|
|
32
|
+
[![License: MIT][licensebadge]][licenseurl]
|
|
33
|
+
|
|
34
|
+
[![AUR][aurbadge]][aururl]
|
|
35
|
+
[![Homebrew][homebrewbadge]][homebrewurl]
|
|
36
|
+
[![Docker][dockerbadge]][dockerurl]
|
|
37
|
+
[![Smoke tests][smokebadge]][smokeurl]
|
|
38
|
+
|
|
39
|
+
[![Tests][testsbadge]][testsurl]
|
|
40
|
+
[![Lint][lintbadge]][linturl]
|
|
41
|
+
[![Typecheck][typecheckbadge]][typecheckurl]
|
|
42
|
+
[![MegaLinter][megalinterbadge]][megalinterurl]
|
|
43
|
+
[![Shellcheck][shellcheckbadge]][shellcheckurl]
|
|
44
|
+
[![Commit Activity][commitactivitybadge]][commitactivityurl]
|
|
45
|
+
[](https://discord.gg/dwFTsR7fK2)
|
|
46
|
+
|
|
47
|
+
**[Official Site](https://create-awesome-python-app.vercel.app)** · [Templates](https://create-awesome-python-app.vercel.app/templates) · [Extensions](https://create-awesome-python-app.vercel.app/extensions) · [Docs](https://create-awesome-python-app.vercel.app/docs) · [GitHub](https://github.com/Create-Python-App/create-python-app) · [PyPI](https://pypi.org/project/create-awesome-python-app/)
|
|
48
|
+
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
**uv (recommended):**
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
uvx create-awesome-python-app@latest my-app
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**pipx:**
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pipx run create-awesome-python-app my-app
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Homebrew (macOS / Linux):**
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
brew tap Create-Python-App/tap
|
|
71
|
+
brew install create-awesome-python-app
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**AUR (Arch Linux):**
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
yay -S create-awesome-python-app # or: paru -S create-awesome-python-app
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**Docker:**
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
docker run --rm -it -v "${PWD}:/app" -w /app \
|
|
84
|
+
ulisesjeremias/create-awesome-python-app:latest my-app \
|
|
85
|
+
--template fastapi-starter
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Interactive by default outside CI. For automation, run headless with flags:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
uvx create-awesome-python-app my-api \
|
|
92
|
+
--template fastapi-starter \
|
|
93
|
+
--addons github-setup \
|
|
94
|
+
--addons fastapi-sqlalchemy \
|
|
95
|
+
--no-interactive
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| If you want... | Start here |
|
|
99
|
+
| ----------------------------- | ------------------------------------------------------- |
|
|
100
|
+
| A guided local setup | `uvx create-awesome-python-app@latest my-app` |
|
|
101
|
+
| A repeatable CI/platform flow | `--no-interactive` with explicit flags |
|
|
102
|
+
| Your company starter | `--template <github-url>` or `--template file://` |
|
|
103
|
+
| Private standards layered in | `--extend <url>` |
|
|
104
|
+
|
|
105
|
+
### Shell completion
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
create-awesome-python-app --install-completion # bash / zsh / fish
|
|
109
|
+
create-awesome-python-app --show-completion # print script only
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
After installing, restart the shell (or `source` your profile) and tab-complete
|
|
113
|
+
flags such as `--template`, `--addons`, and `cache` subcommands.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Why CPA?
|
|
118
|
+
|
|
119
|
+
| Capability | Value |
|
|
120
|
+
| -------------------------------- | ---------------------------------------------------------------------------------------------- |
|
|
121
|
+
| **Composable by design** | Start with a template, then layer only the addons your project actually needs. |
|
|
122
|
+
| **Production-ready defaults** | uv, Ruff, Pyright, tests, docs, and practical DX defaults out of the box. |
|
|
123
|
+
| **AI-ready from day one** | Supported templates generate `AGENTS.md` so coding agents understand the project context. |
|
|
124
|
+
| **CI and platform friendly** | Use `--no-interactive`, `--set`, `--template <url>`, and `--extend <url>` for repeatable runs. |
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Composition Model
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
template -> addons -> custom options -> install -> git init -> AI-ready project
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
You can mix catalog templates and addons with your own GitHub or `file://` sources.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## What You Can Generate
|
|
139
|
+
|
|
140
|
+
### Template Families
|
|
141
|
+
|
|
142
|
+
| Category | Example templates |
|
|
143
|
+
| -------- | ------------------------------------------------------ |
|
|
144
|
+
| Backend | `fastapi-starter`, `django-api`, `celery-worker` |
|
|
145
|
+
| CLI | `cli-starter` |
|
|
146
|
+
| Monorepo | `uv-workspace-starter` |
|
|
147
|
+
|
|
148
|
+
### Addon Families
|
|
149
|
+
|
|
150
|
+
| Category | Examples |
|
|
151
|
+
| -------------- | ------------------------------------------------------------- |
|
|
152
|
+
| CI / tooling | `github-setup`, `development-container` |
|
|
153
|
+
| Containers | `fastapi-docker`, `django-docker`, `celery-docker` |
|
|
154
|
+
| Database | `postgres`, `fastapi-sqlalchemy`, `fastapi-redis` |
|
|
155
|
+
| Observability | `fastapi-sentry` |
|
|
156
|
+
| Security | `fastapi-auth-jwt` |
|
|
157
|
+
|
|
158
|
+
Browse the live catalog on the [official site](https://create-awesome-python-app.vercel.app/templates) or in [`cpa-templates`](https://github.com/Create-Python-App/cpa-templates).
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Popular Recipes
|
|
163
|
+
|
|
164
|
+
### FastAPI + GitHub + SQLAlchemy
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
uvx create-awesome-python-app my-api \
|
|
168
|
+
--template fastapi-starter \
|
|
169
|
+
--addons github-setup \
|
|
170
|
+
--addons fastapi-sqlalchemy \
|
|
171
|
+
--no-interactive
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Django API + Postgres + Docker
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
uvx create-awesome-python-app my-django \
|
|
178
|
+
--template django-api \
|
|
179
|
+
--addons github-setup \
|
|
180
|
+
--addons postgres \
|
|
181
|
+
--addons django-docker \
|
|
182
|
+
--no-interactive
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Celery worker + Redis-friendly stack
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
uvx create-awesome-python-app my-worker \
|
|
189
|
+
--template celery-worker \
|
|
190
|
+
--addons github-setup \
|
|
191
|
+
--addons celery-docker \
|
|
192
|
+
--no-interactive
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Typer / Click CLI starter
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
uvx create-awesome-python-app my-cli \
|
|
199
|
+
--template cli-starter \
|
|
200
|
+
--addons github-setup \
|
|
201
|
+
--no-interactive
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### uv workspace monorepo
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
uvx create-awesome-python-app my-workspace \
|
|
208
|
+
--template uv-workspace-starter \
|
|
209
|
+
--addons github-setup \
|
|
210
|
+
--no-interactive
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Internal platform template (GitHub URL)
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
uvx create-awesome-python-app my-internal-app \
|
|
217
|
+
--template "https://github.com/your-org/platform-starters?subdir=templates/internal-app" \
|
|
218
|
+
--no-interactive
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Local template development (`file://`)
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
uvx create-awesome-python-app my-local-app \
|
|
225
|
+
--template "file:///absolute/path/to/cpa-templates?subdir=templates/fastapi-starter" \
|
|
226
|
+
--no-interactive
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
> `file://` template URLs should be absolute paths (for example `file:///Users/...` or `file:///home/...`).
|
|
230
|
+
|
|
231
|
+
### Layer a private extension
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
uvx create-awesome-python-app my-app \
|
|
235
|
+
--template fastapi-starter \
|
|
236
|
+
--addons github-setup \
|
|
237
|
+
--extend "https://github.com/your-org/platform-starters?subdir=extensions/company-ci"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Pass custom template values
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
uvx create-awesome-python-app my-app \
|
|
244
|
+
--template fastapi-starter \
|
|
245
|
+
--set "productName=Acme Cloud" \
|
|
246
|
+
--set "author=Platform Team" \
|
|
247
|
+
--no-interactive
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Built For Modern Teams
|
|
253
|
+
|
|
254
|
+
- Python 3.12+ runtime support.
|
|
255
|
+
- uv-first workflows (with pipx / Homebrew / AUR / Docker install paths).
|
|
256
|
+
- Interactive wizard for local workflows.
|
|
257
|
+
- `--no-interactive` mode for CI, scripts, and platform automation.
|
|
258
|
+
- GitHub URL and local `file://` template inputs.
|
|
259
|
+
- `--extend` support for private addon layering.
|
|
260
|
+
- `--set key=value` overrides for deterministic custom options.
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Explore The Catalog
|
|
265
|
+
|
|
266
|
+
Browse visually at **[create-awesome-python-app.vercel.app](https://create-awesome-python-app.vercel.app)** or discover from the terminal:
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
# List all available templates
|
|
270
|
+
uvx create-awesome-python-app --list-templates
|
|
271
|
+
|
|
272
|
+
# List addons (optionally filtered by template)
|
|
273
|
+
uvx create-awesome-python-app --template fastapi-starter --list-addons
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Full catalog:
|
|
277
|
+
|
|
278
|
+
- **Templates:** [create-awesome-python-app.vercel.app/templates](https://create-awesome-python-app.vercel.app/templates)
|
|
279
|
+
- **Extensions:** [create-awesome-python-app.vercel.app/extensions](https://create-awesome-python-app.vercel.app/extensions)
|
|
280
|
+
- **Source data:** [`cpa-templates/templates.json`](https://github.com/Create-Python-App/cpa-templates/blob/main/templates.json)
|
|
281
|
+
|
|
282
|
+
Override the catalog URL with `CPA_CATALOG_URL` (HTTPS or `file://`) for forks and local testing.
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
## AI-Ready With `AGENTS.md`
|
|
287
|
+
|
|
288
|
+
Supported templates generate an `AGENTS.md` file so coding assistants understand project context before editing:
|
|
289
|
+
|
|
290
|
+
| Context | Why it matters |
|
|
291
|
+
| ---------------------- | ----------------------------------------------------------- |
|
|
292
|
+
| Project purpose | Agents understand what the app is for before changing code. |
|
|
293
|
+
| Directory layout | Suggestions align with the real structure. |
|
|
294
|
+
| Scripts and validation | Agents know how to lint, test, and verify changes. |
|
|
295
|
+
| Team conventions | Output follows naming and workflow expectations. |
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Interactive Wizard
|
|
300
|
+
|
|
301
|
+
Run the CLI without flags and CPA guides you through:
|
|
302
|
+
|
|
303
|
+
| Step | What you choose |
|
|
304
|
+
| ----------------- | ----------------------------------------------------------------------- |
|
|
305
|
+
| Project name | Confirm or set the target directory |
|
|
306
|
+
| Category | Backend, CLI, Monorepo, or custom URL |
|
|
307
|
+
| Template | Pick from curated starters with descriptions and labels |
|
|
308
|
+
| Addons | Multi-select compatible extensions grouped by purpose |
|
|
309
|
+
| Custom options | Answer `cpa.config.json` / registry prompts when present |
|
|
310
|
+
| Custom extensions | Layer extra URLs for internal standards |
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Requirements
|
|
315
|
+
|
|
316
|
+
- **Python >= 3.12**
|
|
317
|
+
- [uv](https://docs.astral.sh/uv/) recommended (or pipx / Homebrew / AUR / Docker)
|
|
318
|
+
- `git` available on `PATH` (required to clone templates)
|
|
319
|
+
|
|
320
|
+
Recommended quick start:
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
uv python install 3.12
|
|
324
|
+
uvx create-awesome-python-app@latest my-app
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## CLI Reference
|
|
330
|
+
|
|
331
|
+
```text
|
|
332
|
+
Usage: create-awesome-python-app [OPTIONS] [project_directory]
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
| Flag | Description |
|
|
336
|
+
| ---------------------------- | ----------------------------------------------------- |
|
|
337
|
+
| `--interactive` | Force interactive wizard (default outside CI) |
|
|
338
|
+
| `--no-interactive` | Disable wizard and use flags only |
|
|
339
|
+
| `-t, --template <slug\|url>` | Template slug from catalog or remote/local URL |
|
|
340
|
+
| `--addons <slug\|url>` | Addon slug or URL (repeat the flag for multiple) |
|
|
341
|
+
| `--extend <url>` | Extra extension URL layered on top (repeatable) |
|
|
342
|
+
| `--set <key=value>` | Set custom template options; quote values with spaces |
|
|
343
|
+
| `--no-install` | Generate files without installing dependencies |
|
|
344
|
+
| `-f, --force` | Allow scaffolding into a non-empty target directory |
|
|
345
|
+
| `--list-templates` | Print all templates |
|
|
346
|
+
| `--list-addons` | Print addons, optionally filtered by `--template` |
|
|
347
|
+
| `--offline` | Use the local cache only; do not refresh templates |
|
|
348
|
+
| `--no-cache` | Disable the catalog cache; force a refresh each run |
|
|
349
|
+
| `--cache-dir <path>` | Override the cache root (default: `~/.cache/cpa`) |
|
|
350
|
+
| `--refresh <mode>` | When to refresh: `always` \| `stale` \| `manual` |
|
|
351
|
+
| `--pin <ref>` | Pin template to a specific commit SHA, tag, or branch |
|
|
352
|
+
| `--strict-version` | Fail if a newer CLI version is available |
|
|
353
|
+
| `--keep-on-failure` | Keep partial output if scaffolding fails |
|
|
354
|
+
| `-v, --verbose` | Output resolved generation config as JSON |
|
|
355
|
+
| `-i, --info` | Print Python, uv, git, and OS diagnostics |
|
|
356
|
+
| `--version` | Print CLI version |
|
|
357
|
+
| `--help` | Show help |
|
|
358
|
+
|
|
359
|
+
### `cache` subcommand
|
|
360
|
+
|
|
361
|
+
```text
|
|
362
|
+
Usage: create-awesome-python-app cache [OPTIONS] COMMAND [ARGS]...
|
|
363
|
+
|
|
364
|
+
Commands:
|
|
365
|
+
dir Print the cache root directory
|
|
366
|
+
list List cached templates and extensions
|
|
367
|
+
clean Remove one or all entries
|
|
368
|
+
verify Run `git fsck` on one or all entries
|
|
369
|
+
outdated List cached entries that are behind their remote tip
|
|
370
|
+
update Refresh one or all cached entries from their remote
|
|
371
|
+
doctor Diagnose cache health (git, network, permissions)
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Inspect and manage the on-disk cache:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
# Where is my cache?
|
|
378
|
+
uvx create-awesome-python-app cache dir
|
|
379
|
+
# /home/<you>/.cache/cpa
|
|
380
|
+
|
|
381
|
+
# What's in it?
|
|
382
|
+
uvx create-awesome-python-app cache list
|
|
383
|
+
|
|
384
|
+
# Verify integrity
|
|
385
|
+
uvx create-awesome-python-app cache verify
|
|
386
|
+
|
|
387
|
+
# Clear everything
|
|
388
|
+
uvx create-awesome-python-app cache clean
|
|
389
|
+
|
|
390
|
+
# Check for outdated entries
|
|
391
|
+
uvx create-awesome-python-app cache outdated
|
|
392
|
+
|
|
393
|
+
# Refresh a specific entry (or all)
|
|
394
|
+
uvx create-awesome-python-app cache update
|
|
395
|
+
|
|
396
|
+
# Diagnose cache health
|
|
397
|
+
uvx create-awesome-python-app cache doctor
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## Cache & Updates
|
|
403
|
+
|
|
404
|
+
CPA caches both the **template catalog** (`templates.json` from
|
|
405
|
+
`raw.githubusercontent.com`) and the **template git repos** themselves. The
|
|
406
|
+
cache lives at `~/.cache/cpa` by default; override with `--cache-dir
|
|
407
|
+
<path>` or `CPA_CACHE_DIR`.
|
|
408
|
+
|
|
409
|
+
| Path | Contents |
|
|
410
|
+
| ----------------------- | ---------------------------------- |
|
|
411
|
+
| `~/.cache/cpa/catalog/` | Cached `templates.json` |
|
|
412
|
+
| `~/.cache/cpa/repos/` | Shallow clones of templates / addons |
|
|
413
|
+
|
|
414
|
+
### Refresh modes (set with `--refresh <mode>` or `CPA_REFRESH=<mode>`)
|
|
415
|
+
|
|
416
|
+
- **`stale`** (default): pull only when the cache is older than
|
|
417
|
+
`CPA_REFRESH_AFTER_HOURS` (default `24`). No network on a warm cache.
|
|
418
|
+
- **`always`**: pull on every run.
|
|
419
|
+
- **`manual`**: never pull unless `--refresh` is passed.
|
|
420
|
+
|
|
421
|
+
### Pinning templates
|
|
422
|
+
|
|
423
|
+
Pin a template to a specific commit SHA, tag, or branch:
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
uvx create-awesome-python-app my-app \
|
|
427
|
+
--template fastapi-starter \
|
|
428
|
+
--pin abc123def456abc123def456abc123def456abc1 \
|
|
429
|
+
--no-interactive
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
The `--pin` flag is equivalent to appending `?ref=<ref>` to the template URL.
|
|
433
|
+
Combine with `CPA_STRICT_REPRO=1` to enforce full 40-character SHAs.
|
|
434
|
+
|
|
435
|
+
### CI and offline usage
|
|
436
|
+
|
|
437
|
+
```bash
|
|
438
|
+
# Fully offline CI: use the local cache only, no network.
|
|
439
|
+
uvx create-awesome-python-app my-app \
|
|
440
|
+
--template fastapi-starter \
|
|
441
|
+
--offline \
|
|
442
|
+
--no-interactive
|
|
443
|
+
|
|
444
|
+
# Pin the cache to a project-local directory (useful in monorepos and CI).
|
|
445
|
+
CPA_CACHE_DIR="$PWD/.cpa-cache" uvx create-awesome-python-app my-app \
|
|
446
|
+
--template fastapi-starter \
|
|
447
|
+
--no-interactive
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Programmatic Usage
|
|
453
|
+
|
|
454
|
+
Need to integrate CPA into your own tooling? The core is importable:
|
|
455
|
+
|
|
456
|
+
```python
|
|
457
|
+
import asyncio
|
|
458
|
+
from create_python_app_core import create_python_app
|
|
459
|
+
|
|
460
|
+
asyncio.run(
|
|
461
|
+
create_python_app(
|
|
462
|
+
"my-app",
|
|
463
|
+
{
|
|
464
|
+
"template": (
|
|
465
|
+
"https://github.com/Create-Python-App/cpa-templates"
|
|
466
|
+
"?subdir=templates/fastapi-starter"
|
|
467
|
+
),
|
|
468
|
+
"addons": [],
|
|
469
|
+
"install": False,
|
|
470
|
+
},
|
|
471
|
+
)
|
|
472
|
+
)
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
> The programmatic API is experimental and subject to change. Prefer the CLI for stable usage.
|
|
476
|
+
|
|
477
|
+
See also: [`create-python-app-core` README](https://github.com/Create-Python-App/create-python-app/blob/main/packages/create-python-app-core/README.md).
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Security
|
|
482
|
+
|
|
483
|
+
CPA downloads templates from remote sources. Prefer catalog slugs or hash-pinned
|
|
484
|
+
refs (`--pin` / `?ref=`), audit custom URLs before use, and report vulnerabilities
|
|
485
|
+
via GitHub Security Advisories on
|
|
486
|
+
[Create-Python-App/create-python-app](https://github.com/Create-Python-App/create-python-app).
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## FAQ
|
|
491
|
+
|
|
492
|
+
<details>
|
|
493
|
+
<summary><strong>Why another scaffolder?</strong></summary>
|
|
494
|
+
|
|
495
|
+
Most scaffolders lock you into one stack. CPA is composable: choose a template, layer focused addons, and plug in your own GitHub/local blueprints — mirroring [Create Awesome Node App](https://github.com/Create-Node-App/create-node-app) for the Python ecosystem.
|
|
496
|
+
|
|
497
|
+
</details>
|
|
498
|
+
|
|
499
|
+
<details>
|
|
500
|
+
<summary><strong>Can I use my own template?</strong></summary>
|
|
501
|
+
|
|
502
|
+
Yes. Pass a GitHub URL or local `file://` URL with `--template` (supports `?subdir=` and `?ref=`).
|
|
503
|
+
|
|
504
|
+
</details>
|
|
505
|
+
|
|
506
|
+
<details>
|
|
507
|
+
<summary><strong>Can I use private/internal extensions?</strong></summary>
|
|
508
|
+
|
|
509
|
+
Yes. Use `--extend <url>` to layer private extensions on top of a template and addon set.
|
|
510
|
+
|
|
511
|
+
</details>
|
|
512
|
+
|
|
513
|
+
<details>
|
|
514
|
+
<summary><strong>Are addons order-sensitive?</strong></summary>
|
|
515
|
+
|
|
516
|
+
Yes. Addons are applied in sequence. If two addons modify the same file, later addons win.
|
|
517
|
+
|
|
518
|
+
</details>
|
|
519
|
+
|
|
520
|
+
<details>
|
|
521
|
+
<summary><strong>Does it support monorepos?</strong></summary>
|
|
522
|
+
|
|
523
|
+
Yes. Use `uv-workspace-starter` to bootstrap a multi-package uv workspace with shared tooling.
|
|
524
|
+
|
|
525
|
+
</details>
|
|
526
|
+
|
|
527
|
+
<details>
|
|
528
|
+
<summary><strong>Can I use it in CI?</strong></summary>
|
|
529
|
+
|
|
530
|
+
Yes. Pass all required flags and use `--no-interactive` for deterministic automation. Set `CI=true` to disable the wizard by default.
|
|
531
|
+
|
|
532
|
+
</details>
|
|
533
|
+
|
|
534
|
+
<details>
|
|
535
|
+
<summary><strong>Is Python 3.12 required?</strong></summary>
|
|
536
|
+
|
|
537
|
+
Yes. CPA targets Python 3.12+ to keep runtime behavior modern and predictable.
|
|
538
|
+
|
|
539
|
+
</details>
|
|
540
|
+
|
|
541
|
+
<details>
|
|
542
|
+
<summary><strong>Does CPA work with AI coding assistants?</strong></summary>
|
|
543
|
+
|
|
544
|
+
Yes. Supported templates generate `AGENTS.md`, helping assistants understand project layout, scripts, and conventions.
|
|
545
|
+
|
|
546
|
+
</details>
|
|
547
|
+
|
|
548
|
+
<details>
|
|
549
|
+
<summary><strong>Docker says `git executable not found`</strong></summary>
|
|
550
|
+
|
|
551
|
+
Images from `0.2.5` onward ship `git`. Pull `ulisesjeremias/create-awesome-python-app:latest` (or `0.2.5+`) and retry.
|
|
552
|
+
|
|
553
|
+
</details>
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
## Roadmap
|
|
558
|
+
|
|
559
|
+
- More framework templates and vertical starters.
|
|
560
|
+
- Additional testing and observability packs.
|
|
561
|
+
- Diff-based upgrade paths for pinned templates.
|
|
562
|
+
- Richer template analytics and usage insights.
|
|
563
|
+
|
|
564
|
+
Track progress in [Issues](https://github.com/Create-Python-App/create-python-app/issues) and [Discussions](https://github.com/Create-Python-App/create-python-app/discussions).
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
## Contributing
|
|
569
|
+
|
|
570
|
+
Templates, addons, bug fixes, docs, recipes, and ideas are all welcome.
|
|
571
|
+
|
|
572
|
+
- **Main repo:** [github.com/Create-Python-App/create-python-app](https://github.com/Create-Python-App/create-python-app)
|
|
573
|
+
- **Template and extension data:** [github.com/Create-Python-App/cpa-templates](https://github.com/Create-Python-App/cpa-templates)
|
|
574
|
+
- **Contributing guide:** [CONTRIBUTING.md](https://github.com/Create-Python-App/create-python-app/blob/main/CONTRIBUTING.md)
|
|
575
|
+
- **Troubleshooting:** [docs/TROUBLESHOOTING.md](https://github.com/Create-Python-App/create-python-app/blob/main/docs/TROUBLESHOOTING.md)
|
|
576
|
+
|
|
577
|
+
---
|
|
578
|
+
|
|
579
|
+
## License
|
|
580
|
+
|
|
581
|
+
MIT © [Create Python App Contributors](https://github.com/Create-Python-App/create-python-app/graphs/contributors)
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
|
|
585
|
+
<div align="center">
|
|
586
|
+
|
|
587
|
+
**[create-awesome-python-app.vercel.app](https://create-awesome-python-app.vercel.app)**
|
|
588
|
+
|
|
589
|
+
_Built for developers who value speed, composability, craft, and AI-ready workflows._
|
|
590
|
+
|
|
591
|
+
</div>
|
|
592
|
+
|
|
593
|
+
<!-- Reference links -->
|
|
594
|
+
|
|
595
|
+
[testsbadge]: https://github.com/Create-Python-App/create-python-app/actions/workflows/test.yml/badge.svg
|
|
596
|
+
[lintbadge]: https://github.com/Create-Python-App/create-python-app/actions/workflows/lint.yml/badge.svg
|
|
597
|
+
[typecheckbadge]: https://github.com/Create-Python-App/create-python-app/actions/workflows/type-check.yml/badge.svg
|
|
598
|
+
[shellcheckbadge]: https://github.com/Create-Python-App/create-python-app/actions/workflows/shellcheck.yml/badge.svg
|
|
599
|
+
[megalinterbadge]: https://github.com/Create-Python-App/create-python-app/actions/workflows/mega-linter.yml/badge.svg
|
|
600
|
+
[pypiversion]: https://img.shields.io/pypi/v/create-awesome-python-app.svg?style=flat-square&color=3775A9
|
|
601
|
+
[pythonbadge]: https://img.shields.io/pypi/pyversions/create-awesome-python-app.svg?style=flat-square
|
|
602
|
+
[starsbadge]: https://img.shields.io/github/stars/Create-Python-App/create-python-app?style=flat-square&color=yellow
|
|
603
|
+
[licensebadge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square
|
|
604
|
+
[testsurl]: https://github.com/Create-Python-App/create-python-app/actions/workflows/test.yml
|
|
605
|
+
[linturl]: https://github.com/Create-Python-App/create-python-app/actions/workflows/lint.yml
|
|
606
|
+
[typecheckurl]: https://github.com/Create-Python-App/create-python-app/actions/workflows/type-check.yml
|
|
607
|
+
[shellcheckurl]: https://github.com/Create-Python-App/create-python-app/actions/workflows/shellcheck.yml
|
|
608
|
+
[megalinterurl]: https://github.com/Create-Python-App/create-python-app/actions/workflows/mega-linter.yml
|
|
609
|
+
[pypiurl]: https://pypi.org/project/create-awesome-python-app/
|
|
610
|
+
[pythonurl]: https://pypi.org/project/create-awesome-python-app/
|
|
611
|
+
[licenseurl]: https://github.com/Create-Python-App/create-python-app/blob/main/LICENSE
|
|
612
|
+
[starsurl]: https://github.com/Create-Python-App/create-python-app/stargazers
|
|
613
|
+
[commitactivitybadge]: https://img.shields.io/github/commit-activity/m/Create-Python-App/create-python-app?style=flat-square&logo=github&label=commits
|
|
614
|
+
[commitactivityurl]: https://github.com/Create-Python-App/create-python-app/pulse
|
|
615
|
+
[aururl]: https://aur.archlinux.org/packages/create-awesome-python-app
|
|
616
|
+
[aurbadge]: https://img.shields.io/aur/version/create-awesome-python-app?style=flat-square&label=AUR&logo=archlinux
|
|
617
|
+
[homebrewurl]: https://github.com/Create-Python-App/homebrew-tap
|
|
618
|
+
[homebrewbadge]: https://img.shields.io/badge/homebrew-Create--Python--App%2Ftap-orange?style=flat-square&logo=homebrew
|
|
619
|
+
[dockerurl]: https://hub.docker.com/r/ulisesjeremias/create-awesome-python-app
|
|
620
|
+
[dockerbadge]: https://img.shields.io/docker/v/ulisesjeremias/create-awesome-python-app?style=flat-square&label=Docker&logo=docker&color=2496ED
|
|
621
|
+
[smokebadge]: https://github.com/Create-Python-App/create-python-app/actions/workflows/smoke-distribution.yml/badge.svg?event=schedule
|
|
622
|
+
[smokeurl]: https://github.com/Create-Python-App/create-python-app/actions/workflows/smoke-distribution.yml
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
create_awesome_python_app/__init__.py,sha256=ePecLVnfNi_mPoF_Pogp9juH5iycJGewOy1U0mxuMvM,60
|
|
2
|
+
create_awesome_python_app/cache.py,sha256=eHKUlunGexZKYJo0HDNoBoOVq0DjLyRYgEe_wUh4Fk0,11812
|
|
3
|
+
create_awesome_python_app/catalog.py,sha256=ZB-ieyEn_TBqVBbCbFkK9h3VJoYtifvvOxs_BM2tDEw,22754
|
|
4
|
+
create_awesome_python_app/cli.py,sha256=JGdXNAXAXqQ-grQbZOf14Vzii3Qh3eZHt5zRJ-tMIBk,26156
|
|
5
|
+
create_awesome_python_app/prompt_style.py,sha256=pERX1qPQSLZXeJvPVFC7z85dvte73ZV4iuENPQduheE,3463
|
|
6
|
+
create_awesome_python_app-0.2.7.dist-info/METADATA,sha256=gpy3o2dcc-Qw_l1uHnipFJ-IkOUe9GT50bKeGtEqkUc,23271
|
|
7
|
+
create_awesome_python_app-0.2.7.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
8
|
+
create_awesome_python_app-0.2.7.dist-info/entry_points.txt,sha256=rszFbUjY-lvMDZ96zX1lALJwgD1mI3CkUuNyF2qqjYo,81
|
|
9
|
+
create_awesome_python_app-0.2.7.dist-info/RECORD,,
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: create-awesome-python-app
|
|
3
|
-
Version: 0.2.5
|
|
4
|
-
Summary: Composable scaffolding CLI for production-ready Python apps
|
|
5
|
-
Project-URL: Homepage, https://github.com/Create-Python-App/create-python-app
|
|
6
|
-
Project-URL: Repository, https://github.com/Create-Python-App/create-python-app
|
|
7
|
-
Project-URL: Issues, https://github.com/Create-Python-App/create-python-app/issues
|
|
8
|
-
Project-URL: Changelog, https://github.com/Create-Python-App/create-python-app/blob/main/CHANGELOG.md
|
|
9
|
-
License-Expression: MIT
|
|
10
|
-
Requires-Python: >=3.12
|
|
11
|
-
Requires-Dist: create-python-app-core>=0.2.5
|
|
12
|
-
Requires-Dist: questionary>=2.1.1
|
|
13
|
-
Requires-Dist: rich>=15.0.0
|
|
14
|
-
Requires-Dist: typer>=0.27.0
|
|
15
|
-
Description-Content-Type: text/markdown
|
|
16
|
-
|
|
17
|
-
# create-awesome-python-app
|
|
18
|
-
|
|
19
|
-

|
|
20
|
-
|
|
21
|
-
CLI package. Framework: **Typer** (chosen over Click for richer typing/help).
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
uv run create-awesome-python-app --help
|
|
25
|
-
uvx create-awesome-python-app@latest my-app # after PyPI publish
|
|
26
|
-
pipx run create-awesome-python-app my-app
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Shell completion
|
|
30
|
-
|
|
31
|
-
Typer installs completion scripts for bash, zsh, and fish:
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
# Interactive install into your shell profile
|
|
35
|
-
create-awesome-python-app --install-completion
|
|
36
|
-
|
|
37
|
-
# Or print the script and source it manually
|
|
38
|
-
create-awesome-python-app --show-completion
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
After installing, restart the shell (or `source` your profile) and tab-complete
|
|
42
|
-
flags such as `--template`, `--addons`, and `cache` subcommands.
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
create_awesome_python_app/__init__.py,sha256=SiOaZbkhOxVxSBSkzI-OorI4YyurD6WMGgl3o9avmw4,60
|
|
2
|
-
create_awesome_python_app/cache.py,sha256=eHKUlunGexZKYJo0HDNoBoOVq0DjLyRYgEe_wUh4Fk0,11812
|
|
3
|
-
create_awesome_python_app/catalog.py,sha256=w5GS_kQ6MM37AilutXbYZ9Hk5JGa_12crzI0LYzKfRg,16821
|
|
4
|
-
create_awesome_python_app/cli.py,sha256=6og82WI-RZxTqHETqrieA8relgppwCqFntOEmOFrIPk,21195
|
|
5
|
-
create_awesome_python_app/prompt_style.py,sha256=pERX1qPQSLZXeJvPVFC7z85dvte73ZV4iuENPQduheE,3463
|
|
6
|
-
create_awesome_python_app-0.2.5.dist-info/METADATA,sha256=K2hrmcatR8whohIFkG79TxS4CSEsRqupdxZeUKNYNtg,1428
|
|
7
|
-
create_awesome_python_app-0.2.5.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
8
|
-
create_awesome_python_app-0.2.5.dist-info/entry_points.txt,sha256=rszFbUjY-lvMDZ96zX1lALJwgD1mI3CkUuNyF2qqjYo,81
|
|
9
|
-
create_awesome_python_app-0.2.5.dist-info/RECORD,,
|
{create_awesome_python_app-0.2.5.dist-info → create_awesome_python_app-0.2.7.dist-info}/WHEEL
RENAMED
|
File without changes
|
|
File without changes
|