create-awesome-python-app 0.2.0__tar.gz → 0.2.2__tar.gz

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.
Files changed (20) hide show
  1. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/PKG-INFO +2 -2
  2. create_awesome_python_app-0.2.2/assets/hero.svg +19 -0
  3. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/pyproject.toml +2 -2
  4. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/src/create_awesome_python_app/__init__.py +1 -1
  5. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/src/create_awesome_python_app/catalog.py +3 -16
  6. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/src/create_awesome_python_app/cli.py +0 -1
  7. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/tests/test_catalog_resolve.py +25 -0
  8. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/tests/test_interactive.py +65 -0
  9. create_awesome_python_app-0.2.0/assets/hero.svg +0 -4
  10. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/.gitignore +0 -0
  11. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/README.md +0 -0
  12. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/src/create_awesome_python_app/cache.py +0 -0
  13. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/tests/test_cache_cmds.py +0 -0
  14. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/tests/test_cache_env.py +0 -0
  15. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/tests/test_catalog.py +0 -0
  16. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/tests/test_catalog_fetch.py +0 -0
  17. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/tests/test_cli.py +0 -0
  18. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/tests/test_cpa_templates_integration.py +0 -0
  19. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/tests/test_flags.py +0 -0
  20. {create_awesome_python_app-0.2.0 → create_awesome_python_app-0.2.2}/tests/test_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: create-awesome-python-app
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Composable scaffolding CLI for production-ready Python apps
5
5
  Project-URL: Homepage, https://github.com/Create-Python-App/create-python-app
6
6
  Project-URL: Repository, https://github.com/Create-Python-App/create-python-app
@@ -8,7 +8,7 @@ Project-URL: Issues, https://github.com/Create-Python-App/create-python-app/issu
8
8
  Project-URL: Changelog, https://github.com/Create-Python-App/create-python-app/blob/main/CHANGELOG.md
9
9
  License-Expression: MIT
10
10
  Requires-Python: >=3.12
11
- Requires-Dist: create-python-app-core>=0.2.0
11
+ Requires-Dist: create-python-app-core>=0.2.2
12
12
  Requires-Dist: questionary>=2.1.1
13
13
  Requires-Dist: rich>=15.0.0
14
14
  Requires-Dist: typer>=0.27.0
@@ -0,0 +1,19 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="800" height="200" role="img" aria-label="Create Awesome Python App">
2
+ <defs>
3
+ <linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
4
+ <stop offset="0%" stop-color="#3b82f6"/>
5
+ <stop offset="55%" stop-color="#2563eb"/>
6
+ <stop offset="100%" stop-color="#16a34a"/>
7
+ </linearGradient>
8
+ </defs>
9
+ <rect width="100%" height="100%" fill="#0f172a"/>
10
+ <circle cx="60" cy="70" r="10" fill="#3b82f6" fill-opacity="0.9"/>
11
+ <circle cx="120" cy="70" r="10" fill="#16a34a" fill-opacity="0.9"/>
12
+ <circle cx="90" cy="110" r="14" fill="url(#g)"/>
13
+ <circle cx="90" cy="155" r="8" fill="#16a34a" fill-opacity="0.75"/>
14
+ <line x1="68" y1="78" x2="82" y2="100" stroke="#3b82f6" stroke-opacity="0.5" stroke-width="3" stroke-linecap="round"/>
15
+ <line x1="112" y1="78" x2="98" y2="100" stroke="#16a34a" stroke-opacity="0.5" stroke-width="3" stroke-linecap="round"/>
16
+ <line x1="90" y1="124" x2="90" y2="147" stroke="#16a34a" stroke-opacity="0.45" stroke-width="3" stroke-linecap="round"/>
17
+ <text x="160" y="105" fill="url(#g)" font-size="36" font-weight="700" font-family="ui-sans-serif, system-ui, sans-serif">Create Awesome Python App</text>
18
+ <text x="160" y="145" fill="#94a3b8" font-size="18" font-family="ui-sans-serif, system-ui, sans-serif">One command. Any Python stack. · Beta</text>
19
+ </svg>
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "create-awesome-python-app"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "Composable scaffolding CLI for production-ready Python apps"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
7
7
  license = "MIT"
8
8
  dependencies = [
9
- "create-python-app-core>=0.2.0",
9
+ "create-python-app-core>=0.2.2",
10
10
  "questionary>=2.1.1",
11
11
  "rich>=15.0.0",
12
12
  "typer>=0.27.0",
@@ -1,3 +1,3 @@
1
1
  """Create Awesome Python App CLI."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.2"
@@ -20,14 +20,6 @@ from create_awesome_python_app import __version__
20
20
  console = Console(stderr=True)
21
21
 
22
22
  CUSTOM_TEMPLATE_SENTINEL = "__custom_template__"
23
- _ANSI_RESET = "\033[0m"
24
- _CATEGORY_PALETTE = (
25
- "\033[33m", # yellow
26
- "\033[32m", # green
27
- "\033[36m", # cyan
28
- "\033[35m", # magenta
29
- "\033[34m", # blue
30
- )
31
23
 
32
24
 
33
25
  @dataclass(frozen=True)
@@ -172,13 +164,6 @@ def short_category_label(category_name: str) -> str:
172
164
  return " ".join(words[:2]) or category_name
173
165
 
174
166
 
175
- def _color_category(slug: str, label: str) -> str:
176
- if os.environ.get("NO_COLOR"):
177
- return label
178
- idx = sum(ord(char) for char in slug) % len(_CATEGORY_PALETTE)
179
- return f"{_CATEGORY_PALETTE[idx]}{label}{_ANSI_RESET}"
180
-
181
-
182
167
  def _category_map(data: dict[str, Any]) -> dict[str, str]:
183
168
  return {
184
169
  str(category.get("slug", "")): str(category.get("name", ""))
@@ -256,8 +241,10 @@ def build_template_choices(data: dict[str, Any]) -> list[TemplateChoice]:
256
241
  label_suffix = " · " + ", ".join(str(label) for label in labels[:3])
257
242
  description = str(template.get("description", "")).strip()
258
243
  description_suffix = f" — {description}" if description else ""
244
+ # Plain text only: questionary.autocomplete wraps choices in HTML for
245
+ # match highlighting, so ANSI / markup here raises XML parse errors.
259
246
  title = (
260
- f"{_color_category(category_slug, badge)} "
247
+ f"{badge} "
261
248
  f"{template.get('name', slug)} ({slug})"
262
249
  f"{label_suffix}{description_suffix}"
263
250
  )
@@ -247,7 +247,6 @@ def scaffold(
247
247
  choices=list(choice_by_title),
248
248
  match_middle=True,
249
249
  qmark="?",
250
- pointer=">",
251
250
  ).ask()
252
251
  selected_template = choice_by_title.get(str(selected_title), selected_title)
253
252
  if selected_template == CUSTOM_TEMPLATE_SENTINEL:
@@ -102,12 +102,37 @@ def test_build_template_choices_are_searchable() -> None:
102
102
  assert "FastAPI Starter" in first.title
103
103
  assert "OpenAPI" in first.title
104
104
  assert "uv" in first.title
105
+ assert "\033" not in first.title
105
106
  assert "openapi" in first.search
106
107
  assert "backend" in first.search
107
108
  assert "uv" in first.search
108
109
  assert choices[-1].value == CUSTOM_TEMPLATE_SENTINEL
109
110
 
110
111
 
112
+ def test_template_choice_titles_are_html_safe_for_questionary() -> None:
113
+ """questionary.autocomplete formats choice text as HTML (match underline)."""
114
+ from prompt_toolkit.formatted_text import HTML
115
+
116
+ catalog = {
117
+ "categories": [
118
+ {"slug": "backend-applications", "name": "Backend Applications"}
119
+ ],
120
+ "templates": [
121
+ {
122
+ "slug": "fastapi-starter",
123
+ "name": "FastAPI Starter",
124
+ "description": "Async API with OpenAPI docs",
125
+ "url": "file:///templates/fastapi",
126
+ "category": "backend-applications",
127
+ "labels": ["FastAPI"],
128
+ }
129
+ ],
130
+ }
131
+ title = build_template_choices(catalog)[0].title
132
+ # Must not raise "not well-formed (invalid token)" from ANSI escapes.
133
+ HTML("{}<b><u>{}</u></b>{}").format(title[:3], title[3:6], title[6:])
134
+
135
+
111
136
  def test_build_extension_choices_filters_by_template_type() -> None:
112
137
  catalog = {
113
138
  "categories": [
@@ -28,6 +28,71 @@ def test_in_ci_env(monkeypatch) -> None:
28
28
  os.environ.pop("CI", None)
29
29
 
30
30
 
31
+ def test_interactive_template_autocomplete_omits_pointer(
32
+ tmp_path: Path, monkeypatch
33
+ ) -> None:
34
+ """questionary.autocomplete rejects ``pointer`` (PromptSession TypeError)."""
35
+ template_dir = tmp_path / "fastapi"
36
+ template_dir.mkdir()
37
+ (template_dir / "cpa.config.json").write_text(
38
+ json.dumps({"name": "fastapi-starter"}),
39
+ encoding="utf-8",
40
+ )
41
+ template_url = f"file://{template_dir}"
42
+ catalog = {
43
+ "categories": [{"slug": "backend-applications", "name": "Backend"}],
44
+ "templates": [
45
+ {
46
+ "slug": "fastapi-starter",
47
+ "name": "FastAPI Starter",
48
+ "url": template_url,
49
+ "type": "fastapi-backend",
50
+ "category": "backend-applications",
51
+ }
52
+ ],
53
+ "extensions": [],
54
+ }
55
+ catalog_file = tmp_path / "templates.json"
56
+ catalog_file.write_text(json.dumps(catalog), encoding="utf-8")
57
+ monkeypatch.setenv("CPA_CATALOG_URL", f"file://{catalog_file}")
58
+ monkeypatch.setenv("CPA_NO_CATALOG_CACHE", "1")
59
+ monkeypatch.delenv("CI", raising=False)
60
+
61
+ captured_kwargs: dict[str, object] = {}
62
+
63
+ def fake_autocomplete(*_args, **kwargs):
64
+ captured_kwargs.update(kwargs)
65
+ choices = kwargs.get("choices") or []
66
+ # Return a mapped title so choice_by_title resolves to the template URL.
67
+ return FakePrompt(choices[0] if choices else template_url)
68
+
69
+ def fake_checkbox(*_args, **_kwargs):
70
+ return FakePrompt([])
71
+
72
+ captured: dict[str, object] = {}
73
+
74
+ async def fake_create_python_app(project_directory, options, *_args, **_kwargs):
75
+ captured["project_directory"] = project_directory
76
+ captured["options"] = options
77
+
78
+ monkeypatch.setattr("questionary.autocomplete", fake_autocomplete)
79
+ monkeypatch.setattr("questionary.checkbox", fake_checkbox)
80
+ monkeypatch.setattr(
81
+ "create_awesome_python_app.cli.create_python_app",
82
+ fake_create_python_app,
83
+ )
84
+ monkeypatch.setattr(
85
+ "create_awesome_python_app.cli.check_for_latest_version",
86
+ fake_check_for_latest_version,
87
+ )
88
+
89
+ result = runner.invoke(app, ["--interactive", "--no-install", "api"])
90
+
91
+ assert result.exit_code == 0, result.stdout + result.stderr
92
+ assert "pointer" not in captured_kwargs
93
+ assert captured["project_directory"] == "api"
94
+
95
+
31
96
  def test_interactive_extension_selection_passes_addon_urls(
32
97
  tmp_path: Path, monkeypatch
33
98
  ) -> None:
@@ -1,4 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="800" height="200" role="img" aria-label="Create Awesome Python App">
2
- <rect width="100%" height="100%" fill="#0f172a"/>
3
- <text x="40" y="110" fill="#14b8a6" font-size="36" font-family="ui-sans-serif, system-ui, sans-serif">Create Awesome Python App</text>
4
- </svg>