afterpython 0.4.0__py3-none-any.whl → 0.4.1__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.
@@ -1,4 +1,5 @@
1
1
  from afterpython.builders.build_pdoc import build_pdoc
2
+ from afterpython.builders.comments_json import build_comments_json
2
3
  from afterpython.builders.content_json import build_content_json
3
4
  from afterpython.builders.faq_json import build_faq_json
4
5
  from afterpython.builders.index_md import (
@@ -13,6 +14,7 @@ from afterpython.builders.metadata import build_metadata
13
14
  from afterpython.builders.url_md import build_url_md
14
15
 
15
16
  __all__ = (
17
+ "build_comments_json",
16
18
  "build_content_json",
17
19
  "build_faq_json",
18
20
  "build_jupyter_notebooks",
@@ -19,9 +19,15 @@ def build_pdoc(base_path: str) -> None:
19
19
  project logo appears in the top-left of every API page and links
20
20
  back to the project website root (matches MyST's UX).
21
21
  """
22
+ from afterpython._io.toml import _from_tomlkit
23
+ from afterpython.tools._afterpython import read_afterpython
24
+
22
25
  package_dir = find_package_directory()
23
26
  output_dir = ap.paths.build_path / "api_reference"
24
27
  template_dir = ap.paths.templates_path / "pdoc"
28
+ # e.g. "google"; unset leaves it to pdoc (a module's __docformat__, else restructuredtext)
29
+ website = _from_tomlkit(read_afterpython().get("website", {}))
30
+ docformat = website.get("api_reference_docformat")
25
31
 
26
32
  click.echo("Building api_reference/...")
27
33
 
@@ -45,6 +51,7 @@ def build_pdoc(base_path: str) -> None:
45
51
  f"{base_path}/",
46
52
  "--template-directory",
47
53
  str(template_dir),
54
+ *(["--docformat", str(docformat)] if docformat else []),
48
55
  str(package_dir),
49
56
  ],
50
57
  env=env,
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+
6
+ import click
7
+
8
+ import afterpython as ap
9
+
10
+ GISCUS_API = "https://giscus.app/api/discussions/categories"
11
+ DEFAULT_CATEGORY = "Announcements"
12
+
13
+
14
+ def _read_comments_config() -> dict:
15
+ from afterpython._io.toml import _from_tomlkit
16
+ from afterpython.tools._afterpython import read_afterpython
17
+
18
+ website = _from_tomlkit(read_afterpython().get("website", {}))
19
+ return dict(website.get("comments", {}))
20
+
21
+
22
+ def _get_github_repo() -> str | None:
23
+ """Return "owner/repo" from pyproject.toml's repository URL, or the git remote."""
24
+ from afterpython.tools._git import get_github_url
25
+ from afterpython.tools.pyproject import read_metadata
26
+
27
+ url = str(read_metadata().urls.get("repository", "")) or get_github_url() or ""
28
+ match = re.search(r"github\.com[/:]([^/]+)/([^/]+?)(?:\.git)?/?$", url)
29
+ return f"{match[1]}/{match[2]}" if match else None
30
+
31
+
32
+ def _fetch_giscus_categories(repo: str) -> dict:
33
+ """Fetch the repo and category IDs giscus needs. Raises click.ClickException on failure."""
34
+ import httpx2
35
+
36
+ try:
37
+ response = httpx2.get(GISCUS_API, params={"repo": repo}, timeout=10.0)
38
+ except httpx2.HTTPError as e:
39
+ raise click.ClickException(f"could not reach giscus.app ({e})") from e
40
+ try:
41
+ data = response.json()
42
+ except ValueError:
43
+ data = {}
44
+ if response.status_code != 200 or "repositoryId" not in data:
45
+ error = data.get("error") or f"HTTP {response.status_code}"
46
+ raise click.ClickException(error)
47
+ return data
48
+
49
+
50
+ def build_comments_json():
51
+ """Build comments.json for the giscus comment section on content pages.
52
+
53
+ Reads [website.comments] in afterpython.toml. The website template only
54
+ shows comments when comments.json exists, so a disabled or failed setup
55
+ just leaves it out. Never fails the build.
56
+ """
57
+ stale = ap.paths.website_path / "static" / "comments.json"
58
+ stale.unlink(missing_ok=True)
59
+
60
+ config = _read_comments_config()
61
+ if not config.get("enabled", False):
62
+ click.echo("Comments are disabled, skip building comments.json")
63
+ return
64
+
65
+ def warn(reason: str):
66
+ click.secho(f"Warning: skip comments, {reason}", fg="yellow")
67
+
68
+ repo = _get_github_repo()
69
+ if not repo:
70
+ warn(
71
+ "no GitHub repository found "
72
+ "(set [project.urls] repository in pyproject.toml)"
73
+ )
74
+ return
75
+
76
+ category = str(config.get("category") or DEFAULT_CATEGORY)
77
+ try:
78
+ data = _fetch_giscus_categories(repo)
79
+ except click.ClickException as e:
80
+ warn(
81
+ f"giscus is not ready for {repo}: {e.message}\n"
82
+ f" 1. Enable Discussions: https://github.com/{repo}/settings\n"
83
+ " 2. Install the giscus app: https://github.com/apps/giscus\n"
84
+ " Or set [website.comments] enabled = false in afterpython.toml"
85
+ )
86
+ return
87
+
88
+ category_id = next(
89
+ (c["id"] for c in data.get("categories", []) if c["name"] == category), None
90
+ )
91
+ if not category_id:
92
+ names = ", ".join(c["name"] for c in data.get("categories", []))
93
+ warn(
94
+ f'Discussions category "{category}" not found in {repo} '
95
+ f"(available: {names or 'none'})"
96
+ )
97
+ return
98
+
99
+ click.echo(f"Building comments.json ({repo}, {category})...")
100
+ comments = {
101
+ "repo": repo,
102
+ "repoId": data["repositoryId"],
103
+ "category": category,
104
+ "categoryId": category_id,
105
+ }
106
+ with open(ap.paths.build_path / "comments.json", "w") as f:
107
+ json.dump(comments, f, indent=2)
@@ -17,6 +17,7 @@ from click.exceptions import Exit
17
17
 
18
18
  import afterpython as ap
19
19
  from afterpython.builders import (
20
+ build_comments_json,
20
21
  build_content_json,
21
22
  build_faq_json,
22
23
  build_jupyter_notebooks,
@@ -137,6 +138,7 @@ def prebuild():
137
138
  create_placeholder_index_md_files()
138
139
  build_metadata()
139
140
  build_faq_json()
141
+ build_comments_json()
140
142
  build_marimo_readme(mode=_get_readme_py_mode())
141
143
  build_markdown()
142
144
  build_jupyter_notebooks()
@@ -169,14 +169,62 @@ def init_py_typed():
169
169
  click.echo(f"Created {py_typed_path}")
170
170
 
171
171
 
172
- def _preflight_init(skip_website: bool) -> None:
172
+ def _preflight_init(website: bool) -> None:
173
173
  """Validate external prerequisites before any filesystem writes, so a
174
174
  failure aborts cleanly instead of leaving a half-initialized project."""
175
- if not skip_website:
176
- from afterpython.tools.myst import ensure_pnpm_11
175
+ if website:
176
+ from afterpython.tools.myst import ensure_pnpm_12
177
177
  from afterpython.utils import find_node_env
178
178
 
179
- ensure_pnpm_11(find_node_env())
179
+ ensure_pnpm_12(find_node_env())
180
+
181
+
182
+ def _select_steps(yes: bool, skip_website: bool, ask_commit_types: bool) -> set[str]:
183
+ """Ask which optional setup steps to run, as one checkbox list (all checked by default).
184
+
185
+ Without a terminal (CI, piped input) or with --yes, nothing is asked and the defaults are used.
186
+ """
187
+ import sys
188
+
189
+ import questionary
190
+
191
+ # (step, label, checked by default)
192
+ options = [
193
+ (
194
+ "website",
195
+ "Project website (MyST, template, deploy workflow)",
196
+ not skip_website,
197
+ )
198
+ ]
199
+ # only relevant when afterpython.toml is created, it's ignored otherwise
200
+ if ask_commit_types:
201
+ options.append(
202
+ (
203
+ "commit_types",
204
+ "Commit message check (conventional commit types, e.g. 'feat: add X')",
205
+ True,
206
+ )
207
+ )
208
+ options += [
209
+ ("prek", "Pre-commit hooks (prek)", True),
210
+ ("ruff", "ruff: lint & format", True),
211
+ ("ty", "ty: type checking (`ap check`)", True),
212
+ ("cliff", "git-cliff: changelog", True),
213
+ ("release", "Release workflow (.github/workflows/release.yml)", True),
214
+ ("dependabot", "Dependabot (auto-update GitHub Actions versions)", True),
215
+ ]
216
+
217
+ if yes or not (sys.stdin.isatty() and sys.stdout.isatty()):
218
+ return {step for step, _, checked in options if checked}
219
+
220
+ choices = [
221
+ questionary.Choice(label, value=step, checked=checked)
222
+ for step, label, checked in options
223
+ ]
224
+ selected = questionary.checkbox("Select what to set up", choices=choices).ask()
225
+ if selected is None: # Ctrl-C, before anything was written
226
+ raise click.Abort()
227
+ return set(selected)
180
228
 
181
229
 
182
230
  @click.group(invoke_without_command=True)
@@ -184,7 +232,7 @@ def _preflight_init(skip_website: bool) -> None:
184
232
  "--yes",
185
233
  "-y",
186
234
  is_flag=True,
187
- help="Automatically answer yes to all prompts",
235
+ help="Set up everything without asking",
188
236
  )
189
237
  @click.option(
190
238
  "--skip-website",
@@ -205,32 +253,27 @@ def init(ctx, yes, skip_website: bool):
205
253
  from afterpython.tools.prek import init_prek, sync_hooks
206
254
  from afterpython.tools.pyproject import init_pyproject
207
255
 
208
- _preflight_init(skip_website)
209
-
210
256
  paths = ctx.obj["paths"]
211
- click.echo("Initializing afterpython...")
212
257
  afterpython_path = paths.afterpython_path
213
258
  static_path = paths.static_path
214
259
 
260
+ steps = _select_steps(
261
+ yes,
262
+ skip_website,
263
+ ask_commit_types=not (afterpython_path / "afterpython.toml").exists(),
264
+ )
265
+ _preflight_init(website="website" in steps)
266
+
267
+ click.echo("Initializing afterpython...")
215
268
  try:
216
269
  afterpython_path.mkdir(parents=True, exist_ok=True)
217
270
  static_path.mkdir(parents=True, exist_ok=True)
218
271
 
219
272
  init_pyproject()
220
273
 
221
- # only asked when afterpython.toml is created, it's ignored otherwise
222
- commit_types = (
223
- yes
224
- or (afterpython_path / "afterpython.toml").exists()
225
- or click.confirm(
226
- "\nCheck commit messages against conventional commit types "
227
- "(e.g. 'feat: add X', types listed in afterpython.toml)?",
228
- default=True,
229
- )
230
- )
231
- init_afterpython(commit_types=commit_types)
274
+ init_afterpython(commit_types="commit_types" in steps)
232
275
 
233
- if not skip_website:
276
+ if "website" in steps:
234
277
  # check=True so a failure inside `ap init website` aborts the parent
235
278
  # run instead of silently continuing to write more files.
236
279
  subprocess.run(["ap", "init", "website"], check=True)
@@ -239,42 +282,20 @@ def init(ctx, yes, skip_website: bool):
239
282
 
240
283
  create_workflow("ci")
241
284
 
242
- if yes or click.confirm(
243
- f"\nCreate .pre-commit-config.yaml in {afterpython_path}?", default=True
244
- ):
285
+ if "prek" in steps:
245
286
  init_prek()
246
-
247
- if yes or click.confirm(
248
- f"\nCreate ruff.toml in {afterpython_path} (and point pyproject.toml's [tool.ruff] to it)?",
249
- default=True,
250
- ):
287
+ if "ruff" in steps:
251
288
  init_ruff()
252
-
253
- if yes or click.confirm(
254
- f"\nCreate ty.toml in {afterpython_path} (type checking with `ap check`)?",
255
- default=True,
256
- ):
289
+ if "ty" in steps:
257
290
  init_ty()
258
- # whatever the answers: keep ruff/ty hooks only for the tools that were set up
291
+ # whatever was selected: keep ruff/ty hooks only for the tools that were set up
259
292
  sync_hooks()
260
293
 
261
- if yes or click.confirm(
262
- f"\nCreate cliff.toml in {afterpython_path} (changelog generation with git-cliff)?",
263
- default=True,
264
- ):
294
+ if "cliff" in steps:
265
295
  init_cliff()
266
-
267
- if yes or click.confirm(
268
- "\nCreate release workflow in .github/workflows/release.yml?",
269
- default=True,
270
- ):
296
+ if "release" in steps:
271
297
  create_workflow("release")
272
-
273
- if yes or click.confirm(
274
- "\nCreate Dependabot configuration (.github/dependabot.yml) "
275
- "to auto-update GitHub Actions versions?",
276
- default=True,
277
- ):
298
+ if "dependabot" in steps:
278
299
  create_dependabot()
279
300
  except BaseException:
280
301
  if afterpython_path.exists():
@@ -327,12 +348,12 @@ def init_website_subcommand():
327
348
  the website to an existing AfterPython project.
328
349
  """
329
350
  from afterpython.tools.github_actions import create_workflow
330
- from afterpython.tools.myst import ensure_pnpm_11, init_myst
351
+ from afterpython.tools.myst import ensure_pnpm_12, init_myst
331
352
  from afterpython.utils import find_node_env
332
353
 
333
354
  # Pre-flight: same rationale as `ap init` — guard the standalone entry too,
334
355
  # since this subcommand can be invoked directly via `ap init website`.
335
- ensure_pnpm_11(find_node_env())
356
+ ensure_pnpm_12(find_node_env())
336
357
 
337
358
  init_faq()
338
359
  init_myst()
@@ -342,3 +363,44 @@ def init_website_subcommand():
342
363
  # of a broken website install.
343
364
  subprocess.run(["ap", "update", "website"], check=True)
344
365
  create_workflow("deploy")
366
+
367
+
368
+ @init.command("content")
369
+ @click.option("--all", is_flag=True, help="Initialize all content types")
370
+ @click.option("--doc", is_flag=True, help="Initialize afterpython/doc/")
371
+ @click.option("--blog", is_flag=True, help="Initialize afterpython/blog/")
372
+ @click.option("--tutorial", is_flag=True, help="Initialize afterpython/tutorial/")
373
+ @click.option("--example", is_flag=True, help="Initialize afterpython/example/")
374
+ @click.option("--guide", is_flag=True, help="Initialize afterpython/guide/")
375
+ def init_content_subcommand(
376
+ all: bool, doc: bool, blog: bool, tutorial: bool, example: bool, guide: bool
377
+ ):
378
+ """Initialize content directories (MyST config) without touching the website
379
+
380
+ Content types that already have a myst.yml are skipped.
381
+
382
+ Examples:
383
+ ap init content --blog # afterpython/blog/
384
+ ap init content --blog --tutorial # afterpython/blog/ and afterpython/tutorial/
385
+ ap init content --all # every content type that is missing
386
+ """
387
+ from afterpython._typing import tContentType
388
+ from afterpython.const import CONTENT_TYPES
389
+ from afterpython.tools.myst import init_myst
390
+
391
+ flags: dict[tContentType, bool] = {
392
+ "doc": doc,
393
+ "blog": blog,
394
+ "tutorial": tutorial,
395
+ "example": example,
396
+ "guide": guide,
397
+ }
398
+ if all:
399
+ content_types = CONTENT_TYPES
400
+ else:
401
+ content_types = {content_type for content_type, flag in flags.items() if flag}
402
+ if not content_types:
403
+ raise click.UsageError(
404
+ "Specify one or more of --doc/--blog/--tutorial/--example/--guide, or --all"
405
+ )
406
+ init_myst(content_types)
afterpython/const.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from afterpython._typing import tContentType
2
2
 
3
- NODEENV_VERSION = "24.11.0"
3
+ NODEENV_VERSION = "24.21.0"
4
4
  CONTENT_TYPES: set[tContentType] = {"doc", "blog", "tutorial", "example", "guide"}
5
5
  PLACEHOLDER_INDEX_MARKER = "<!-- afterpython:placeholder-index -->"
@@ -13,6 +13,12 @@ announcement = ""
13
13
  readme_py = "wasm" # marimo html export format: "wasm" or "static"
14
14
  execute_readme_py = false # execute README.py cells at build time and embed outputs as a preview (wasm mode only)
15
15
  api_reference = false # set to true to enable the pdoc-generated API reference
16
+ # api_reference_docformat = "google" # docstring format for pdoc: "google", "numpy", "restructuredtext" or "markdown"
17
+
18
+ [website.comments] # giscus comment section on blog posts, tutorials, examples and guides
19
+ enabled = true
20
+ # Discussions category for comments. Announcement format means only maintainers can start discussions there; visitors can only reply.
21
+ category = "Announcements"
16
22
 
17
23
  # [website.blog]
18
24
  # thumbnail = "blog_default_thumbnail.png" # inside blog/static/ folder, default thumbnail for blog posts
@@ -27,7 +27,7 @@ jobs:
27
27
  uses: actions/checkout@v7
28
28
 
29
29
  - name: Install uv
30
- uses: astral-sh/setup-uv@v10.2.0
30
+ uses: astral-sh/setup-uv@v10.3.3
31
31
 
32
32
  - name: Run ruff
33
33
  # fails if ruff-check finds issues or ruff-format would change a file
@@ -66,7 +66,7 @@ jobs:
66
66
  runs-on: ubuntu-latest
67
67
  strategy:
68
68
  matrix:
69
- python-version: ['3.12', '3.13', '3.14']
69
+ python-version: ['3.13', '3.14', '3.15']
70
70
  fail-fast: false
71
71
 
72
72
  env:
@@ -78,7 +78,7 @@ jobs:
78
78
  uses: actions/checkout@v7
79
79
 
80
80
  - name: Install uv
81
- uses: astral-sh/setup-uv@v10.2.0
81
+ uses: astral-sh/setup-uv@v10.3.3
82
82
 
83
83
  - name: Install dependencies
84
84
  # the package + all [dependency-groups] (e.g. test, dev), at the versions in uv.lock
@@ -106,7 +106,7 @@ jobs:
106
106
  runs-on: ubuntu-latest
107
107
  strategy:
108
108
  matrix:
109
- environment: ['py312', 'py313', 'py314']
109
+ environment: ['py313', 'py314', 'py315']
110
110
  fail-fast: false
111
111
 
112
112
  steps:
@@ -114,9 +114,9 @@ jobs:
114
114
  uses: actions/checkout@v7
115
115
 
116
116
  - name: Set up pixi
117
- uses: prefix-dev/setup-pixi@v0.10.2
117
+ uses: prefix-dev/setup-pixi@v0.11.3
118
118
  with:
119
- pixi-version: v0.81.0
119
+ pixi-version: v0.84.1
120
120
  cache: true
121
121
  # install (and cache) only the environment this matrix run tests
122
122
  environments: ${{ matrix.environment }}
@@ -169,7 +169,7 @@ jobs:
169
169
  uses: actions/checkout@v7
170
170
 
171
171
  - name: Install uv
172
- uses: astral-sh/setup-uv@v10.2.0
172
+ uses: astral-sh/setup-uv@v10.3.3
173
173
 
174
174
  - name: Build package
175
175
  # --no-sources: build like it will be published, ignoring [tool.uv.sources] (local paths, git)
@@ -228,7 +228,7 @@ jobs:
228
228
 
229
229
  - name: Install uv
230
230
  if: steps.ty.outputs.enabled == 'true' && hashFiles('pixi.toml') == ''
231
- uses: astral-sh/setup-uv@v10.2.0
231
+ uses: astral-sh/setup-uv@v10.3.3
232
232
 
233
233
  - name: Run ty (uv)
234
234
  # the package + all [dependency-groups] (incl. afterpython itself), at the versions in uv.lock
@@ -239,9 +239,9 @@ jobs:
239
239
 
240
240
  - name: Set up pixi
241
241
  if: steps.ty.outputs.enabled == 'true' && hashFiles('pixi.toml') != ''
242
- uses: prefix-dev/setup-pixi@v0.10.2
242
+ uses: prefix-dev/setup-pixi@v0.11.3
243
243
  with:
244
- pixi-version: v0.81.0
244
+ pixi-version: v0.84.1
245
245
  cache: true
246
246
  environments: default
247
247
 
@@ -47,7 +47,7 @@ jobs:
47
47
  - name: Install pnpm
48
48
  uses: pnpm/action-setup@v6
49
49
  with:
50
- version: 10
50
+ version: 12
51
51
 
52
52
  - name: Set up Node.js
53
53
  uses: actions/setup-node@v7
@@ -57,7 +57,7 @@ jobs:
57
57
  cache-dependency-path: './afterpython/_website/pnpm-lock.yaml'
58
58
 
59
59
  - name: Install uv
60
- uses: astral-sh/setup-uv@v10.2.0
60
+ uses: astral-sh/setup-uv@v10.3.3
61
61
 
62
62
  - name: Install the project
63
63
  # your package + its dependencies (pdoc imports it for the API reference)
@@ -0,0 +1,37 @@
1
+ /* Dark mode for what theme.css can't reach. Colors from Bootstrap 5.3's dark mode,
2
+ like pdoc's light ones are from its light mode. */
3
+
4
+ /* code blocks match inline code instead of github-dark's own background */
5
+ html.dark .pdoc .pdoc-code {
6
+ background: var(--code);
7
+ }
8
+
9
+ html.dark .pdoc .decorator-deprecated {
10
+ color: #ea868f;
11
+ }
12
+
13
+ {#- the icons have their light color baked in, swap it for the dark text color -#}
14
+ {% macro alert(kind, icon, light, color, background, border) %}
15
+ html.dark .pdoc .alert.{{ kind }} {
16
+ color: {{ color }};
17
+ background-color: {{ background }};
18
+ border-color: {{ border }};
19
+ background-image: url("data:image/svg+xml,{% filter urlencode %}{% filter replace(light, color) %}{% include 'resources/' ~ icon ~ '.svg' %}{% endfilter %}{% endfilter %}");
20
+ }
21
+ {% endmacro %}
22
+ {{ alert("note", "info-circle-fill", "#084298", "#6ea8fe", "#031633", "#084298") }}
23
+ {{ alert("tip", "lightbulb", "#0a3622", "#75b798", "#051b11", "#0f5132") }}
24
+ {{ alert("important", "exclamation-square-fill", "#055160", "#6edff6", "#032830", "#087990") }}
25
+ {{ alert("warning", "exclamation-triangle-fill", "#664d03", "#ffda6a", "#332701", "#997404") }}
26
+ {{ alert("caution", "exclamation-octagon-fill", "#842029", "#ea868f", "#2c0b0e", "#842029") }}
27
+ {{ alert("danger", "lightning-fill", "#842029", "#ea868f", "#2c0b0e", "#842029") }}
28
+
29
+ {#- pdoc indents every non-top-level nav link by one --indent, so a nested class's
30
+ members (e.g. Feed.Requests.download) line up with the outer class's own members
31
+ (Feed.download) and read as duplicates. Indent one more step per nesting level. -#}
32
+ nav.pdoc li li li a {
33
+ padding-left: calc(var(--pad) + 2 * var(--indent));
34
+ }
35
+ nav.pdoc li li li li a {
36
+ padding-left: calc(var(--pad) + 3 * var(--indent));
37
+ }
@@ -1,6 +1,20 @@
1
1
  {% extends "default/frame.html.jinja2" %}
2
- {# Override pdoc's default 35vh logo height — too dominant in the sidebar. #}
3
2
  {% block head %}
3
+ {# Same as the project website's app.html: follow the theme chosen on the website
4
+ (same origin, so its localStorage is shared), else the OS setting.
5
+ Dark colors are in theme.css, syntax-highlighting.css and custom.css. #}
6
+ <script>
7
+ (function () {
8
+ const query = window.matchMedia('(prefers-color-scheme: dark)');
9
+ const apply = (dark) => document.documentElement.classList.toggle('dark', dark);
10
+ const stored = localStorage.getItem('theme');
11
+ apply(stored ? stored === 'dark' : query.matches);
12
+ query.addEventListener('change', (e) => {
13
+ if (!localStorage.getItem('theme')) apply(e.matches);
14
+ });
15
+ })();
16
+ </script>
17
+ {# Override pdoc's default 35vh logo height — too dominant in the sidebar. #}
4
18
  <style>
5
19
  nav.pdoc img.logo { max-height: 5rem; margin-bottom: 1.5rem; }
6
20
  </style>
@@ -0,0 +1,155 @@
1
+ /* auto-generated, see templates/README.md */
2
+ pre { line-height: 125%; }
3
+ span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 20px; }
4
+ .pdoc-code .hll { background-color: #ffffcc }
5
+ .pdoc-code { background: #f8f8f8; }
6
+ .pdoc-code .c { color: #3D7B7B; font-style: italic } /* Comment */
7
+ .pdoc-code .err { border: 1px solid #FF0000 } /* Error */
8
+ .pdoc-code .k { color: #008000; font-weight: bold } /* Keyword */
9
+ .pdoc-code .o { color: #666666 } /* Operator */
10
+ .pdoc-code .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */
11
+ .pdoc-code .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */
12
+ .pdoc-code .cp { color: #9C6500 } /* Comment.Preproc */
13
+ .pdoc-code .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */
14
+ .pdoc-code .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */
15
+ .pdoc-code .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */
16
+ .pdoc-code .gd { color: #A00000 } /* Generic.Deleted */
17
+ .pdoc-code .ge { font-style: italic } /* Generic.Emph */
18
+ .pdoc-code .gr { color: #E40000 } /* Generic.Error */
19
+ .pdoc-code .gh { color: #000080; font-weight: bold } /* Generic.Heading */
20
+ .pdoc-code .gi { color: #008400 } /* Generic.Inserted */
21
+ .pdoc-code .go { color: #717171 } /* Generic.Output */
22
+ .pdoc-code .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
23
+ .pdoc-code .gs { font-weight: bold } /* Generic.Strong */
24
+ .pdoc-code .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
25
+ .pdoc-code .gt { color: #0044DD } /* Generic.Traceback */
26
+ .pdoc-code .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
27
+ .pdoc-code .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
28
+ .pdoc-code .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
29
+ .pdoc-code .kp { color: #008000 } /* Keyword.Pseudo */
30
+ .pdoc-code .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
31
+ .pdoc-code .kt { color: #B00040 } /* Keyword.Type */
32
+ .pdoc-code .m { color: #666666 } /* Literal.Number */
33
+ .pdoc-code .s { color: #BA2121 } /* Literal.String */
34
+ .pdoc-code .na { color: #687822 } /* Name.Attribute */
35
+ .pdoc-code .nb { color: #008000 } /* Name.Builtin */
36
+ .pdoc-code .nc { color: #0000FF; font-weight: bold } /* Name.Class */
37
+ .pdoc-code .no { color: #880000 } /* Name.Constant */
38
+ .pdoc-code .nd { color: #AA22FF } /* Name.Decorator */
39
+ .pdoc-code .ni { color: #717171; font-weight: bold } /* Name.Entity */
40
+ .pdoc-code .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */
41
+ .pdoc-code .nf { color: #0000FF } /* Name.Function */
42
+ .pdoc-code .nl { color: #767600 } /* Name.Label */
43
+ .pdoc-code .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
44
+ .pdoc-code .nt { color: #008000; font-weight: bold } /* Name.Tag */
45
+ .pdoc-code .nv { color: #19177C } /* Name.Variable */
46
+ .pdoc-code .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
47
+ .pdoc-code .w { color: #bbbbbb } /* Text.Whitespace */
48
+ .pdoc-code .mb { color: #666666 } /* Literal.Number.Bin */
49
+ .pdoc-code .mf { color: #666666 } /* Literal.Number.Float */
50
+ .pdoc-code .mh { color: #666666 } /* Literal.Number.Hex */
51
+ .pdoc-code .mi { color: #666666 } /* Literal.Number.Integer */
52
+ .pdoc-code .mo { color: #666666 } /* Literal.Number.Oct */
53
+ .pdoc-code .sa { color: #BA2121 } /* Literal.String.Affix */
54
+ .pdoc-code .sb { color: #BA2121 } /* Literal.String.Backtick */
55
+ .pdoc-code .sc { color: #BA2121 } /* Literal.String.Char */
56
+ .pdoc-code .dl { color: #BA2121 } /* Literal.String.Delimiter */
57
+ .pdoc-code .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
58
+ .pdoc-code .s2 { color: #BA2121 } /* Literal.String.Double */
59
+ .pdoc-code .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */
60
+ .pdoc-code .sh { color: #BA2121 } /* Literal.String.Heredoc */
61
+ .pdoc-code .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */
62
+ .pdoc-code .sx { color: #008000 } /* Literal.String.Other */
63
+ .pdoc-code .sr { color: #A45A77 } /* Literal.String.Regex */
64
+ .pdoc-code .s1 { color: #BA2121 } /* Literal.String.Single */
65
+ .pdoc-code .ss { color: #19177C } /* Literal.String.Symbol */
66
+ .pdoc-code .bp { color: #008000 } /* Name.Builtin.Pseudo */
67
+ .pdoc-code .fm { color: #0000FF } /* Name.Function.Magic */
68
+ .pdoc-code .vc { color: #19177C } /* Name.Variable.Class */
69
+ .pdoc-code .vg { color: #19177C } /* Name.Variable.Global */
70
+ .pdoc-code .vi { color: #19177C } /* Name.Variable.Instance */
71
+ .pdoc-code .vm { color: #19177C } /* Name.Variable.Magic */
72
+ .pdoc-code .il { color: #666666 } /* Literal.Number.Integer.Long */
73
+
74
+ /* dark: pygmentize -f html -a "html.dark .pdoc-code" -S github-dark (pre/span.linenos rules dropped, they are above) */
75
+ html.dark .pdoc-code .hll { background-color: #6e7681 }
76
+ html.dark .pdoc-code { background: #0d1117; color: #E6EDF3 }
77
+ html.dark .pdoc-code .c { color: #8B949E; font-style: italic } /* Comment */
78
+ html.dark .pdoc-code .err { color: #F85149 } /* Error */
79
+ html.dark .pdoc-code .esc { color: #E6EDF3 } /* Escape */
80
+ html.dark .pdoc-code .g { color: #E6EDF3 } /* Generic */
81
+ html.dark .pdoc-code .k { color: #FF7B72 } /* Keyword */
82
+ html.dark .pdoc-code .l { color: #A5D6FF } /* Literal */
83
+ html.dark .pdoc-code .n { color: #E6EDF3 } /* Name */
84
+ html.dark .pdoc-code .o { color: #FF7B72; font-weight: bold } /* Operator */
85
+ html.dark .pdoc-code .x { color: #E6EDF3 } /* Other */
86
+ html.dark .pdoc-code .p { color: #E6EDF3 } /* Punctuation */
87
+ html.dark .pdoc-code .ch { color: #8B949E; font-style: italic } /* Comment.Hashbang */
88
+ html.dark .pdoc-code .cm { color: #8B949E; font-style: italic } /* Comment.Multiline */
89
+ html.dark .pdoc-code .cp { color: #8B949E; font-weight: bold; font-style: italic } /* Comment.Preproc */
90
+ html.dark .pdoc-code .cpf { color: #8B949E; font-style: italic } /* Comment.PreprocFile */
91
+ html.dark .pdoc-code .c1 { color: #8B949E; font-style: italic } /* Comment.Single */
92
+ html.dark .pdoc-code .cs { color: #8B949E; font-weight: bold; font-style: italic } /* Comment.Special */
93
+ html.dark .pdoc-code .gd { color: #FFA198; background-color: #490202 } /* Generic.Deleted */
94
+ html.dark .pdoc-code .ge { color: #E6EDF3; font-style: italic } /* Generic.Emph */
95
+ html.dark .pdoc-code .ges { color: #E6EDF3; font-weight: bold; font-style: italic } /* Generic.EmphStrong */
96
+ html.dark .pdoc-code .gr { color: #FFA198 } /* Generic.Error */
97
+ html.dark .pdoc-code .gh { color: #79C0FF; font-weight: bold } /* Generic.Heading */
98
+ html.dark .pdoc-code .gi { color: #56D364; background-color: #0F5323 } /* Generic.Inserted */
99
+ html.dark .pdoc-code .go { color: #8B949E } /* Generic.Output */
100
+ html.dark .pdoc-code .gp { color: #8B949E } /* Generic.Prompt */
101
+ html.dark .pdoc-code .gs { color: #E6EDF3; font-weight: bold } /* Generic.Strong */
102
+ html.dark .pdoc-code .gu { color: #79C0FF } /* Generic.Subheading */
103
+ html.dark .pdoc-code .gt { color: #FF7B72 } /* Generic.Traceback */
104
+ html.dark .pdoc-code .g-Underline { color: #E6EDF3; text-decoration: underline } /* Generic.Underline */
105
+ html.dark .pdoc-code .kc { color: #79C0FF } /* Keyword.Constant */
106
+ html.dark .pdoc-code .kd { color: #FF7B72 } /* Keyword.Declaration */
107
+ html.dark .pdoc-code .kn { color: #FF7B72 } /* Keyword.Namespace */
108
+ html.dark .pdoc-code .kp { color: #79C0FF } /* Keyword.Pseudo */
109
+ html.dark .pdoc-code .kr { color: #FF7B72 } /* Keyword.Reserved */
110
+ html.dark .pdoc-code .kt { color: #FF7B72 } /* Keyword.Type */
111
+ html.dark .pdoc-code .ld { color: #79C0FF } /* Literal.Date */
112
+ html.dark .pdoc-code .m { color: #A5D6FF } /* Literal.Number */
113
+ html.dark .pdoc-code .s { color: #A5D6FF } /* Literal.String */
114
+ html.dark .pdoc-code .na { color: #E6EDF3 } /* Name.Attribute */
115
+ html.dark .pdoc-code .nb { color: #E6EDF3 } /* Name.Builtin */
116
+ html.dark .pdoc-code .nc { color: #F0883E; font-weight: bold } /* Name.Class */
117
+ html.dark .pdoc-code .no { color: #79C0FF; font-weight: bold } /* Name.Constant */
118
+ html.dark .pdoc-code .nd { color: #D2A8FF; font-weight: bold } /* Name.Decorator */
119
+ html.dark .pdoc-code .ni { color: #FFA657 } /* Name.Entity */
120
+ html.dark .pdoc-code .ne { color: #F0883E; font-weight: bold } /* Name.Exception */
121
+ html.dark .pdoc-code .nf { color: #D2A8FF; font-weight: bold } /* Name.Function */
122
+ html.dark .pdoc-code .nl { color: #79C0FF; font-weight: bold } /* Name.Label */
123
+ html.dark .pdoc-code .nn { color: #FF7B72 } /* Name.Namespace */
124
+ html.dark .pdoc-code .nx { color: #E6EDF3 } /* Name.Other */
125
+ html.dark .pdoc-code .py { color: #79C0FF } /* Name.Property */
126
+ html.dark .pdoc-code .nt { color: #7EE787 } /* Name.Tag */
127
+ html.dark .pdoc-code .nv { color: #79C0FF } /* Name.Variable */
128
+ html.dark .pdoc-code .ow { color: #FF7B72; font-weight: bold } /* Operator.Word */
129
+ html.dark .pdoc-code .pm { color: #E6EDF3 } /* Punctuation.Marker */
130
+ html.dark .pdoc-code .w { color: #6E7681 } /* Text.Whitespace */
131
+ html.dark .pdoc-code .mb { color: #A5D6FF } /* Literal.Number.Bin */
132
+ html.dark .pdoc-code .mf { color: #A5D6FF } /* Literal.Number.Float */
133
+ html.dark .pdoc-code .mh { color: #A5D6FF } /* Literal.Number.Hex */
134
+ html.dark .pdoc-code .mi { color: #A5D6FF } /* Literal.Number.Integer */
135
+ html.dark .pdoc-code .mo { color: #A5D6FF } /* Literal.Number.Oct */
136
+ html.dark .pdoc-code .sa { color: #79C0FF } /* Literal.String.Affix */
137
+ html.dark .pdoc-code .sb { color: #A5D6FF } /* Literal.String.Backtick */
138
+ html.dark .pdoc-code .sc { color: #A5D6FF } /* Literal.String.Char */
139
+ html.dark .pdoc-code .dl { color: #79C0FF } /* Literal.String.Delimiter */
140
+ html.dark .pdoc-code .sd { color: #A5D6FF } /* Literal.String.Doc */
141
+ html.dark .pdoc-code .s2 { color: #A5D6FF } /* Literal.String.Double */
142
+ html.dark .pdoc-code .se { color: #79C0FF } /* Literal.String.Escape */
143
+ html.dark .pdoc-code .sh { color: #79C0FF } /* Literal.String.Heredoc */
144
+ html.dark .pdoc-code .si { color: #A5D6FF } /* Literal.String.Interpol */
145
+ html.dark .pdoc-code .sx { color: #A5D6FF } /* Literal.String.Other */
146
+ html.dark .pdoc-code .sr { color: #79C0FF } /* Literal.String.Regex */
147
+ html.dark .pdoc-code .s1 { color: #A5D6FF } /* Literal.String.Single */
148
+ html.dark .pdoc-code .ss { color: #A5D6FF } /* Literal.String.Symbol */
149
+ html.dark .pdoc-code .bp { color: #E6EDF3 } /* Name.Builtin.Pseudo */
150
+ html.dark .pdoc-code .fm { color: #D2A8FF; font-weight: bold } /* Name.Function.Magic */
151
+ html.dark .pdoc-code .vc { color: #79C0FF } /* Name.Variable.Class */
152
+ html.dark .pdoc-code .vg { color: #79C0FF } /* Name.Variable.Global */
153
+ html.dark .pdoc-code .vi { color: #79C0FF } /* Name.Variable.Instance */
154
+ html.dark .pdoc-code .vm { color: #79C0FF } /* Name.Variable.Magic */
155
+ html.dark .pdoc-code .il { color: #A5D6FF } /* Literal.Number.Integer.Long */
@@ -0,0 +1,44 @@
1
+ /* pdoc's default color scheme, plus a dark one used when <html> has the "dark" class */
2
+ :root {
3
+ --pdoc-background: #fff;
4
+ }
5
+
6
+ .pdoc {
7
+ --text: #212529;
8
+ --muted: #6c757d;
9
+ --link: #3660a5;
10
+ --link-hover: #1659c5;
11
+ --code: #f8f8f8;
12
+ --active: #fff598;
13
+
14
+ --accent: #eee;
15
+ --accent2: #c1c1c1;
16
+
17
+ --nav-hover: rgba(255, 255, 255, 0.5);
18
+ --name: #0066BB;
19
+ --def: #008800;
20
+ --annotation: #007020;
21
+ }
22
+
23
+ /* dark grays match the project website's dark mode */
24
+ html.dark {
25
+ --pdoc-background: #121212;
26
+ color-scheme: dark; /* dark scrollbars and search input */
27
+ }
28
+
29
+ html.dark .pdoc {
30
+ --text: #e5e5e5;
31
+ --muted: #94a3b8;
32
+ --link: #58a6ff;
33
+ --link-hover: #79b8ff;
34
+ --code: #1e1e1e;
35
+ --active: #555;
36
+
37
+ --accent: #1e1e1e;
38
+ --accent2: #383838;
39
+
40
+ --nav-hover: rgba(0, 0, 0, 0.3);
41
+ --name: #77c1ff;
42
+ --def: #0cdd0c;
43
+ --annotation: #00c037;
44
+ }
@@ -27,7 +27,7 @@ jobs:
27
27
  fetch-depth: 0 # full history and tags, for git-cliff's release notes
28
28
 
29
29
  - name: Install uv
30
- uses: astral-sh/setup-uv@v10.2.0
30
+ uses: astral-sh/setup-uv@v10.3.3
31
31
 
32
32
  - name: Check tag matches version
33
33
  # e.g. a hand-made tag v0.4.0 on a commit whose pyproject.toml says 0.3.20
@@ -9,7 +9,7 @@
9
9
  src = ["..", "../src"]
10
10
 
11
11
  line-length = 88
12
- target-version = "py312"
12
+ target-version = "py313"
13
13
 
14
14
  [lint]
15
15
  # ruff default rules:
afterpython/tools/myst.py CHANGED
@@ -1,8 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING
3
+ from typing import TYPE_CHECKING, Any
4
4
 
5
5
  if TYPE_CHECKING:
6
+ from collections.abc import Iterable
6
7
  from pathlib import Path
7
8
 
8
9
  from afterpython._typing import NodeEnv, tContentType
@@ -151,11 +152,12 @@ def _write_index_file(content_type: tContentType):
151
152
  return index_file
152
153
 
153
154
 
154
- def ensure_pnpm_11(node_env: NodeEnv) -> None:
155
- """Ensure pnpm 11.x is available on `node_env`'s PATH. No-op if already satisfied.
155
+ def ensure_pnpm_12(node_env: NodeEnv) -> None:
156
+ """Ensure pnpm 12.x is available on `node_env`'s PATH. No-op if already satisfied.
156
157
 
157
- Pinned to major 11 because an unpinned `npm install -g pnpm` previously did
158
- a silent 9→10 jump that broke `ap init` via ERR_PNPM_IGNORED_BUILDS. We
158
+ Pinned to the website template's pnpm major (devEngines in its package.json)
159
+ because an unpinned `npm install -g pnpm` previously did a silent 9→10 jump
160
+ that broke `ap init` via ERR_PNPM_IGNORED_BUILDS. We
159
161
  pre-check the installed version so we don't redundantly invoke `npm install
160
162
  -g`, which fails with EEXIST when pnpm was installed by a different package
161
163
  manager (e.g. Homebrew) that owns the shim filenames npm wants to write.
@@ -171,13 +173,13 @@ def ensure_pnpm_11(node_env: NodeEnv) -> None:
171
173
  )
172
174
  if result.returncode == 0:
173
175
  current = result.stdout.strip()
174
- if current.startswith("11."):
176
+ if current.startswith("12."):
175
177
  return
176
178
  except FileNotFoundError:
177
179
  pass
178
180
 
179
181
  install = subprocess.run(
180
- ["npm", "install", "-g", "pnpm@11"],
182
+ ["npm", "install", "-g", "pnpm@12"],
181
183
  env=node_env,
182
184
  capture_output=True,
183
185
  text=True,
@@ -186,14 +188,14 @@ def ensure_pnpm_11(node_env: NodeEnv) -> None:
186
188
  if install.returncode == 0:
187
189
  return
188
190
 
189
- msg = ["Failed to install pnpm@11 via npm."]
191
+ msg = ["Failed to install pnpm@12 via npm."]
190
192
  if current:
191
193
  msg.append(f"Detected existing pnpm version: {current}")
192
194
  msg.append(
193
195
  "If pnpm is already installed via another package manager "
194
196
  "(e.g. Homebrew: `brew install pnpm`), npm refuses to overwrite "
195
197
  "its shims. Please either:\n"
196
- " - upgrade your existing pnpm to major version 11, or\n"
198
+ " - upgrade your existing pnpm to major version 12, or\n"
197
199
  " - uninstall it and let afterpython manage pnpm via npm."
198
200
  )
199
201
  if install.stderr:
@@ -201,19 +203,27 @@ def ensure_pnpm_11(node_env: NodeEnv) -> None:
201
203
  raise RuntimeError("\n".join(msg))
202
204
 
203
205
 
204
- def init_myst():
206
+ def init_myst(content_types: Iterable[tContentType] | None = None):
205
207
  """
206
208
  Initialize MyST Markdown (mystmd) and myst.yml files in
207
209
  doc/, blog/, tutorial/, example/, guide/ directories with sensible defaults
210
+
211
+ Args:
212
+ content_types: content types to initialize; defaults to all of them.
213
+ A directory that already has a myst.yml is left untouched.
208
214
  """
209
215
  from afterpython.const import CONTENT_TYPES
210
216
  from afterpython.utils import find_node_env
211
217
 
212
218
  # find any existing node.js version and use it, if no, install the Node.js version specified in NODEENV_VERSION
213
219
  node_env: NodeEnv = find_node_env()
214
- ensure_pnpm_11(node_env)
215
- for content_type in CONTENT_TYPES:
220
+ ensure_pnpm_12(node_env)
221
+ initialized = False
222
+ for content_type in sorted(content_types or CONTENT_TYPES):
216
223
  path = ap.paths.afterpython_path / content_type
224
+ if (path / "myst.yml").exists():
225
+ print(f"Skipping {path.name}/ (myst.yml already exists)")
226
+ continue
217
227
  print(f"Initializing MyST Markdown (mystmd) in {path.name}/ directory ...")
218
228
  path.mkdir(parents=True, exist_ok=True)
219
229
  subprocess.run(
@@ -224,6 +234,16 @@ def init_myst():
224
234
  env=node_env,
225
235
  check=True,
226
236
  )
237
+ site_defaults: dict[str, Any] = {
238
+ "options": {
239
+ "analytics_google": "{{ GOOGLE_ANALYTICS_ID }}",
240
+ # "twitter": "",
241
+ },
242
+ }
243
+ # `myst init` defaults every project to book-theme; blog posts are
244
+ # standalone articles, so article-theme fits them better.
245
+ if content_type == "blog":
246
+ site_defaults["template"] = "article-theme"
227
247
  myst_yml_defaults = {
228
248
  "extends": "../authors.yml",
229
249
  "project": {
@@ -232,12 +252,9 @@ def init_myst():
232
252
  if content_type != "doc"
233
253
  else "Documentation",
234
254
  },
235
- "site": {
236
- "options": {
237
- "analytics_google": "{{ GOOGLE_ANALYTICS_ID }}",
238
- # "twitter": "",
239
- },
240
- },
255
+ "site": site_defaults,
241
256
  }
242
257
  update_myst_yml(myst_yml_defaults, path, add_comments=True)
243
- subprocess.run(["ap", "sync"])
258
+ initialized = True
259
+ if initialized:
260
+ subprocess.run(["ap", "sync"])
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: afterpython
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: CLI tool to streamline Python package maintenance and generate a dedicated project website
5
5
  Keywords: package-maintenance,project-website,static-site-generator,developer-tools,documentation,cli-tool,ci/cd,mystmd,semantic-release,python-packaging
6
6
  Author: Stephen Yau
@@ -11,26 +11,27 @@ Classifier: Intended Audience :: Developers
11
11
  Classifier: Topic :: Software Development :: Build Tools
12
12
  Classifier: Topic :: Software Development :: Documentation
13
13
  Classifier: Typing :: Typed
14
- Classifier: Programming Language :: Python :: 3.12
15
14
  Classifier: Programming Language :: Python :: 3.13
16
15
  Classifier: Programming Language :: Python :: 3.14
17
- Requires-Dist: click>=8.5.0
16
+ Classifier: Programming Language :: Python :: 3.15
17
+ Requires-Dist: click>=8.5.1
18
18
  Requires-Dist: trogon>=0.6.0
19
- Requires-Dist: httpx2>=2.13.1
20
- Requires-Dist: tomlkit>=0.15.1
19
+ Requires-Dist: httpx2>=2.16.0
20
+ Requires-Dist: tomlkit>=0.15.2
21
21
  Requires-Dist: ruamel-yaml>=0.19.1
22
- Requires-Dist: gitpython>=3.2.0
23
- Requires-Dist: python-dotenv>=1.2.3
22
+ Requires-Dist: gitpython>=3.2.1
23
+ Requires-Dist: python-dotenv>=1.2.4
24
24
  Requires-Dist: pyproject-metadata>=0.12.1
25
- Requires-Dist: jupytext>=1.19.5
26
- Requires-Dist: mystmd>=1.11.0
25
+ Requires-Dist: jupytext>=1.19.6
26
+ Requires-Dist: mystmd>=1.12.0
27
27
  Requires-Dist: pdoc>=16.0.0
28
- Requires-Dist: ruff>=0.16.9
29
- Requires-Dist: prek>=0.5.4
30
- Requires-Dist: ty>=0.0.84
28
+ Requires-Dist: ruff>=0.17.4
29
+ Requires-Dist: prek>=0.5.10
30
+ Requires-Dist: ty>=0.0.99
31
31
  Requires-Dist: git-cliff>=2.14.2
32
- Requires-Dist: marimo[recommended,lsp]>=0.25.0 ; extra == 'marimo'
33
- Requires-Python: >=3.12
32
+ Requires-Dist: questionary>=2.1.1
33
+ Requires-Dist: marimo[recommended,lsp]>=0.25.2 ; extra == 'marimo'
34
+ Requires-Python: >=3.13
34
35
  Project-URL: homepage, https://afterpython.afterpython.org
35
36
  Project-URL: repository, https://github.com/AfterPythonOrg/afterpython
36
37
  Project-URL: documentation, https://afterpython.afterpython.org/doc
@@ -44,8 +45,8 @@ Description-Content-Type: text/markdown
44
45
  [![PyPI](https://img.shields.io/pypi/v/afterpython.svg?cacheSeconds=300)](https://pypi.org/project/afterpython)
45
46
  ![PyPI - Support Python Versions](https://img.shields.io/pypi/pyversions/afterpython?cacheSeconds=300)
46
47
  [![Discussions](https://img.shields.io/badge/Discussions-Let's%20Chat-green)](https://github.com/AfterPythonOrg/afterpython/discussions)
47
- [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/AfterPythonOrg/afterpython)
48
48
  [![View Code Wiki](https://assets.codewiki.google/readme-badge/static.svg)](https://codewiki.google/github.com/afterpythonorg/afterpython?utm_source=badge&utm_medium=github&utm_campaign=github.com/afterpythonorg/afterpython)
49
+ <!--[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/AfterPythonOrg/afterpython)-->
49
50
  <!-- ![PyPI downloads](https://img.shields.io/pypi/dm/afterpython) -->
50
51
  <!-- [![Pixi Badge](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/prefix-dev/pixi/main/assets/badge/v0.json)](https://pixi.sh) -->
51
52
 
@@ -3,8 +3,9 @@ afterpython/_io/toml.py,sha256=dvmq6n4MhlJ02u_YjfC3mOnCWatfCn_J1lugavigoJw,3410
3
3
  afterpython/_io/yaml.py,sha256=raksve_gaIFJim8lQrq-kQHkRjTloed7CT2FY0oUJSg,654
4
4
  afterpython/_paths.py,sha256=FTwQ7necKYuRosCyMZPfv1eqaEmGIsyqd3yXpm-WQTA,1584
5
5
  afterpython/_typing.py,sha256=kDO25BbJCdNNkqoP9-JYC1zsOuNvzhqvSOCcam3yuAk,128
6
- afterpython/builders/__init__.py,sha256=H7f9mFKXofWOAC3hkrxiFzvW5NziF8wYyduUBykHa5w,975
7
- afterpython/builders/build_pdoc.py,sha256=rasjCtTah5GsZYokqU--Fp3bvTEf464ievENfOoANOk,2040
6
+ afterpython/builders/__init__.py,sha256=2ee-FggKVZmjDazH5dLOe7irTOkHNZhQK9SK7EX_dEA,1069
7
+ afterpython/builders/build_pdoc.py,sha256=lFWQzw4w_-O1PdWHst3eAbgZKkZuQXWtMBFOmZK5BlQ,2442
8
+ afterpython/builders/comments_json.py,sha256=7gY1wy78wqSscTIFlDhwDKNYN6ioLzpDpACLBmwB9rw,3603
8
9
  afterpython/builders/content_json.py,sha256=yem8qhVjy5-aTnmUIING5ttNZFsuTQ-r5M_dYKGRyTk,5362
9
10
  afterpython/builders/faq_json.py,sha256=mMfFepRBL_1QYO0tQ6orf5ESo2LVntk55nfFzencViM,1193
10
11
  afterpython/builders/index_md.py,sha256=ZpBA1FIHgw9MXD8kRaq2OBaEAKnf94_sJmB2cG68iag,6021
@@ -16,7 +17,7 @@ afterpython/builders/metadata.py,sha256=EueKoYWM__Hb9MwX0KYejxJvouoaAxD1oz9AP-Na
16
17
  afterpython/builders/url_md.py,sha256=MfXSy0f44J72OlHASir-Xscqq1onA10vtqUO_L4b1xM,3949
17
18
  afterpython/cli/__init__.py,sha256=G4AB-rllhmr7tURFc5cXJfkjZZ2bXtMBg3oy4zUGEFw,84
18
19
  afterpython/cli/commands/add.py,sha256=Ppp4msdvOwN9_-fdG5-b0-IV9p0Eef9CiIEU3srUjYk,1792
19
- afterpython/cli/commands/build.py,sha256=rJ5BogEkp7YnZRYpywnvWPAVlJmp765XdpJOjar3C90,11810
20
+ afterpython/cli/commands/build.py,sha256=7LgNZnT0daUB7oSIR-ZziZ_pMc9YPulgsCHGkAeMqEw,11861
20
21
  afterpython/cli/commands/bump.py,sha256=ULrZmQuQ-oJSYXNyEyCg3VxrHDhzwOSMv37Uk1fQlgs,13354
21
22
  afterpython/cli/commands/changelog.py,sha256=R4GdvJlnfd8joVnRRon9y-eM8RfSIxE1GCOspMrxXEw,1597
22
23
  afterpython/cli/commands/check.py,sha256=QBt_KHlD72QJmBordcsoGKvyX4Iuj7ezjFStuNZVaOk,1694
@@ -24,7 +25,7 @@ afterpython/cli/commands/clean.py,sha256=havP43ZC1AKFzvscsRMF_to6TvOwruxGA5qzr6d
24
25
  afterpython/cli/commands/commit_msg.py,sha256=2PYTqsxMeYaGwQSb2MER3a73gmUoSAietuApJW0XKw4,4513
25
26
  afterpython/cli/commands/dev.py,sha256=bbugeF52iQI8xr2h59yRdV-IUxM3RD4d-RqVCdj3JA0,10197
26
27
  afterpython/cli/commands/format.py,sha256=VFwCG9KDTF6j4Y_Ewp4_RpS8uNBQNOAj9HU_G97my5A,710
27
- afterpython/cli/commands/init.py,sha256=qDTWmvh9F-NrFQGaXYKoX5kLwCpwmg5TnPZ1xaS9aqs,12242
28
+ afterpython/cli/commands/init.py,sha256=1tECOi_okfuZLgbXnVHek2-7qwLDXbLLEUrnX8EPrU4,14504
28
29
  afterpython/cli/commands/init_branch_rules.py,sha256=fzAm8D5qbW3u4bEMpqPuRgO-0akuCGG7pu09JlKhYn4,243
29
30
  afterpython/cli/commands/install.py,sha256=B8JuQmtU_4EZpHct2J5j30BGLkGmvwKvU26upbmabvQ,750
30
31
  afterpython/cli/commands/lint.py,sha256=4m_eXX7PC4CKPj3YhkNf79XwAe1qTYluEdKPJf0aCkQ,794
@@ -38,30 +39,33 @@ afterpython/cli/commands/start.py,sha256=edbPWsgr4m5P4w-0z12ObwkdD60HGzV5jFQlbwl
38
39
  afterpython/cli/commands/sync.py,sha256=Rx_Wmf8toys3FjENtcKVCw22ejEvOzyuqblWKA04dl0,6467
39
40
  afterpython/cli/commands/update.py,sha256=q_ZxbPC-4fyUPlX3Ohoz5f3cJrz5j3d5-1AH453_VTQ,9649
40
41
  afterpython/cli/main.py,sha256=XgmBo1rDv4EsX97fCfjd7kxFEu7SWNOJiiQulbzMljw,5007
41
- afterpython/const.py,sha256=pZERFcKA60HsTob9kFCh34M_bZhyphHgLKUHab9pNFg,225
42
+ afterpython/const.py,sha256=R4G-rq_1sZ35aQR0eXxpNgIjbk2Aw5lvhMSKNNjiyA4,225
42
43
  afterpython/main.py,sha256=w0z6mTib3u4p2HxMrk-tIJlZtur7uRaqsbE0ewlYlSs,490
43
44
  afterpython/pcu.py,sha256=nfdY2uKgOxHQ1AqUN9Y-L5BxDLd7NiuZf3tt_ef2wvA,6500
44
45
  afterpython/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- afterpython/templates/afterpython-template.toml,sha256=dooIftpTtzl-CVuFFasXECr9EbzG9e_BEbYep1Ey4TE,1338
46
- afterpython/templates/ci-workflow-template.yml,sha256=YqGY5Y5aZMVF7TJC7Ds-3RD0aT5Eii2J7d9d0jjUWiM,8943
46
+ afterpython/templates/afterpython-template.toml,sha256=c4lEl8B6ObhNHBKqlQNl1nS-47azH2Jg6YUj-494JCc,1725
47
+ afterpython/templates/ci-workflow-template.yml,sha256=gcxaaFW4OztMhtfEyraiGAJ0FBFg7A0J_3LiG7Lpa6U,8943
47
48
  afterpython/templates/cliff-template.toml,sha256=HVemOBXIAvxz_8IMMAUcSRqYVmC0etfHleVeTG6woHg,5248
48
49
  afterpython/templates/dependabot-template.yml,sha256=CBQEgh8EsXw2X7IqzGtFI7lVT9liF6PW4DvGLB0X2LY,1136
49
- afterpython/templates/deploy-workflow-template.yml,sha256=47g9jnqOfH0ac9RAgMkNCNwEFlRPODowic0URNpWi8g,2858
50
- afterpython/templates/pdoc/frame.html.jinja2,sha256=GX4pNSuYC4Fq9hvZ01WyaBQes4FyNDOx5Ra4MNLr0eg,250
50
+ afterpython/templates/deploy-workflow-template.yml,sha256=n98miM10UUG1vkPATTGSP2QZQoJxvH5LqXnYhbF5Sbw,2858
51
+ afterpython/templates/pdoc/custom.css,sha256=eAkXzF8jYInX88kHec2MoArxunTWnxSsCzytRtFWriI,1764
52
+ afterpython/templates/pdoc/frame.html.jinja2,sha256=byBlXAQBCcTDgj8vCiDjFjjo46DG6uaLzzRf2x9pMHI,989
53
+ afterpython/templates/pdoc/syntax-highlighting.css,sha256=RqxTpVlTYuevLeGAUQbOJzK5UiVsQBhZfNSExvgmHag,10552
54
+ afterpython/templates/pdoc/theme.css,sha256=Qf8GHLKJryY8EBJBxreAGjSoUTG73PDkYTwlGFMO7eI,904
51
55
  afterpython/templates/pre-commit-config-template.yaml,sha256=MXk79KxRFDaJynuCPtqgVbqJllaKIKI5Fdmwe08uiDU,2016
52
- afterpython/templates/release-workflow-template.yml,sha256=sNlcU4Ec28FE4sJDA9zfPUZIIomprboBorw45s1auxU,4150
53
- afterpython/templates/ruff-template.toml,sha256=kZfjUIFwC5u_pyEKvRSI1m8889LOIeASA7jBgs-4vDk,1316
56
+ afterpython/templates/release-workflow-template.yml,sha256=9vd9C3-pWOHhA5WWgSG4PuH4bCBZs7EWmCc1kDIIVJM,4150
57
+ afterpython/templates/ruff-template.toml,sha256=IU33i84L7IhcLFYZ7Cfdg8EVlpq6YVAQJ3vLQGLXOxk,1316
54
58
  afterpython/templates/ty-template.toml,sha256=8Z5jTnedzrNBgPaWYHY0RSVdXfg_SeNt1HO1OLnSA7E,666
55
59
  afterpython/tools/_afterpython.py,sha256=y0-3IbKe1DV3JVJlKoIztoSyQs2K73LxNCgEyf1vitc,4946
56
60
  afterpython/tools/_git.py,sha256=qUrunBNFwkbn8J1fJDXgiIaA2xNDuRCNryhbBPX2ReQ,6063
57
61
  afterpython/tools/branch_rules.py,sha256=zrlN1FFl8gv9sKMbV7Ri5EuPhiUK3dTMOtMJRpZoRUE,3244
58
62
  afterpython/tools/github_actions.py,sha256=0xzOc41QF1PKagD1CW7BAt_Rugy0KCEx_KfcrPKVl3c,3168
59
- afterpython/tools/myst.py,sha256=zlZcrofF8OAtg6UymF3b8xirV0KG1MXoMnpNaat5MAQ,9374
63
+ afterpython/tools/myst.py,sha256=-bdasdZC6ElTTgPfbsHsZSskotNtlSv1ldev0L2GMJ0,10190
60
64
  afterpython/tools/prek.py,sha256=NEEGf8alTt9MIelAJ8yxWD-V4mnM_pJ7oHgVQG8pbzo,3543
61
65
  afterpython/tools/pyproject.py,sha256=Slhxo-Z9M7Gn9ZGL2BKmbPONpDgAJ1g33TdiulxwEyQ,3700
62
66
  afterpython/utils.py,sha256=rbCOmRCHif2qzefw9rS-jFA05dz57hZKBr2mxVRaGR0,12406
63
67
  afterpython/version.py,sha256=hihRV1S-gc1m46OaXD32ikkEkqzs-Gtq7AcAqOQV-wY,1603
64
- afterpython-0.4.0.dist-info/WHEEL,sha256=VOHKyukui3UMeyUkkBf5F7N5xN_6X6W7zHVvalNVVvI,81
65
- afterpython-0.4.0.dist-info/entry_points.txt,sha256=roa0PDgBueM1JPbRG0G2OSxzaN394OUL_oS9iEwVLBc,80
66
- afterpython-0.4.0.dist-info/METADATA,sha256=8BiWOLlDcQZoq_Sf3XDCOKsGY-Q8Yc7psW9BgsWRHiA,6288
67
- afterpython-0.4.0.dist-info/RECORD,,
68
+ afterpython-0.4.1.dist-info/WHEEL,sha256=MWx7psoF7KNST9G72pGtiBYuaXRXQXBk9pw5R6FzYko,80
69
+ afterpython-0.4.1.dist-info/entry_points.txt,sha256=roa0PDgBueM1JPbRG0G2OSxzaN394OUL_oS9iEwVLBc,80
70
+ afterpython-0.4.1.dist-info/METADATA,sha256=umUYEVFSbdJOdTNhrlnzbZyUHyIjx_ONyIobB4nJbkI,6330
71
+ afterpython-0.4.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.12.21
2
+ Generator: uv 0.13.7
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any