nbdevAuto 0.3.67__tar.gz → 0.3.69__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nbdevAuto
3
- Version: 0.3.67
3
+ Version: 0.3.69
4
4
  Summary: automating nbdev
5
5
  Author-email: Benedict Thekkel <bthekkel1@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -70,26 +70,28 @@ from nbdevAuto.pdf import PDFreader
70
70
  | Page | Covers |
71
71
  |----|----|
72
72
  | [Functions](00_Functions.ipynb) | Dataset and image helpers: reading a list out of a text file, downloading single images or whole search-driven datasets, verifying and resizing what came back, building the folder layout a classifier expects, running a fastai learner over one image, Kaggle competition and dataset shortcuts, and [`graph`](https://bthek1.github.io/nbdevAuto/functions.html#graph), a `graphviz.Digraph` subclass preloaded with a rounded, filled style |
73
- | [Automate](01_Automate.ipynb) | The command line surface: the prep/commit/push pipeline, the GitHub and PyPI release halves, and the help output that lists them all |
73
+ | [Automate](01_Automate.ipynb) | The command line surface: the prep/commit/push pipeline, the GitHub and PyPI release halves, the help output that lists them all, and the one-line-per-stage console grid every command reports through |
74
74
  | [PDF Reader](02_PDF_reader.ipynb) | [`PDFreader`](https://bthek1.github.io/nbdevAuto/pdf_reader.html#pdfreader), a class that converts a PDF through `pdf2image` and renders page ranges inline when you slice it (`pdf[0:5]`) |
75
- | [GitHub](03_GitHub.ipynb) | [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus): reading `.gitmodules`, resolving a token, and asking the GitHub API about every submodule at once - CI runs, Pages deploys, Pages builds, remote `main` shas - plus the `--report` and `--audit` views and the exit codes that keep “absent” apart from “cannot ask |
75
+ | [GitHub](03_GitHub.ipynb) | [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus): reading `.gitmodules`, resolving a token, and asking the GitHub API about every submodule at once - CI runs, Pages deploys, Pages builds, remote `main` shas - plus the `--report` and `--audit` views, the exit codes that keep “absent” apart from “cannot ask”, and `newest_run`, which picks a run by `run_number` because the API’s page order is not a contract |
76
+ | [Fleet](04_Fleet.ipynb) | [`fleet`](https://bthek1.github.io/nbdevAuto/fleet.html#fleet): running one command across every submodule of a superproject. A walker that reports only what did work and counts the rest, plus the verbs built on it - upload, push, copy, status, update and sync |
76
77
 
77
78
  ------------------------------------------------------------------------
78
79
 
79
80
  ## The Command Line Surface
80
81
 
81
- Installing the package puts these on your `PATH`. Most are ordinary functions in [Automate](01_Automate.ipynb), exposed as scripts through `[project.scripts]`; [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus) comes from [GitHub](03_GitHub.ipynb).
82
+ Installing the package puts these on your `PATH`. Most are ordinary functions in [Automate](01_Automate.ipynb), exposed as scripts through `[project.scripts]`; [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus) comes from [GitHub](03_GitHub.ipynb) and [`fleet`](https://bthek1.github.io/nbdevAuto/fleet.html#fleet) from [Fleet](04_Fleet.ipynb).
82
83
 
83
84
  | Command | Does |
84
85
  |----|----|
85
86
  | [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) | Bump the version, then export, test and clean the notebooks, refreshing `_quarto.yml` and the README |
86
- | [`gacp`](https://bthek1.github.io/nbdevAuto/automate.html#gacp) | git add, commit, and push. Without `-m` the commit message is the status output |
87
+ | [`gacp`](https://bthek1.github.io/nbdevAuto/automate.html#gacp) | git add, commit, and push. Without `-m` the message is built from the staged paths |
87
88
  | [`status`](https://bthek1.github.io/nbdevAuto/automate.html#status) | Show the working tree state |
88
89
  | [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) | [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) then [`gacp`](https://bthek1.github.io/nbdevAuto/automate.html#gacp), the one you actually type |
89
90
  | `gitrelease` | Bump the **minor** version, then tag and create a GitHub release |
90
91
  | `piprelease` | Build the sdist and wheel, then upload to PyPI with twine |
91
92
  | [`release`](https://bthek1.github.io/nbdevAuto/automate.html#release) | `gitrelease` then `piprelease` |
92
93
  | [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus) | Remote status for every submodule of an nbdev superproject, from the GitHub API. `--report` for the sync view, `--audit` to check `.gitmodules` against the repos the account owns, `--json` for machine output |
94
+ | [`fleet`](https://bthek1.github.io/nbdevAuto/fleet.html#fleet) | Run one command across every submodule: `fleet upload|push|copy|status|update|sync`. Every verb takes `--help`, and the destructive ones take `--dry-run` |
93
95
  | `h` | Print the list of available commands |
94
96
 
95
97
  Both [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) and [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) take `-p` to choose which version part to increment; it defaults to 2, the patch, so every [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) ships a new version.
@@ -100,6 +102,25 @@ Both [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) and [`uploa
100
102
 
101
103
  ------------------------------------------------------------------------
102
104
 
105
+ ## The Output Contract
106
+
107
+ Every command reports through one grid, defined in [Automate](01_Automate.ipynb) and reused by [Fleet](04_Fleet.ipynb). It exists because these commands run 19 times in a row, so anything that costs three lines per stage costs sixty over a fleet.
108
+
109
+ 1/6 bump version (part 2) OK 75ms 0.3.67 -> 0.3.68
110
+ 2/6 nbdev_export OK 129ms
111
+ 3/6 nbdev_test OK 1.5s 6 notebooks, slowest 01_Automate.ipynb 1.0s
112
+
113
+ Four rules hold it together, and a change to any stage has to keep them:
114
+
115
+ - **One stage, one line, inside 88 columns.** The columns are fixed: `n/total` (5), label (22), outcome (4), duration (7), detail (the rest). A unit test renders real stages through a console pinned to that width and asserts every line fits.
116
+ - **A summariser returns short strings, not a transcript.** [`_test_summary`](https://bthek1.github.io/nbdevAuto/automate.html#_test_summary) turns a line per notebook into `6 notebooks, slowest X 1.0s`; [`_push_summary`](https://bthek1.github.io/nbdevAuto/automate.html#_push_summary) turns nine lines of git progress into a sha range and a size. Anything too long for the detail column moves to an indented continuation line, so a verbose summariser degrades the layout rather than breaking it.
117
+ - **A stage that shells out must capture at the file-descriptor level.** `redirect_stdout` only rebinds Python’s `sys.stdout`, so a child process writing to fd 1 walks straight past it - which is how `nbdev_readme` used to spill sixteen lines of pandoc metadata into the middle of a run. [`_quiet`](https://bthek1.github.io/nbdevAuto/automate.html#_quiet) swaps fds 1 and 2 for a temp file as well and merges both captures.
118
+ - **A green OK has to mean something.** [`_run`](https://bthek1.github.io/nbdevAuto/automate.html#_run) raises on a non-zero exit rather than returning a code nobody reads, and the failure prints the command’s own output. Before that, a failed `git push` still reported `OK`.
119
+
120
+ On a terminal the label is written first without a newline, so a slow stage is visible while it runs, then rewritten in place. Piped output skips that entirely, because a carriage return in a log file is just noise.
121
+
122
+ ------------------------------------------------------------------------
123
+
103
124
  ## Where It Is Used
104
125
 
105
126
  The parent Knowledge repo’s `justfile` calls [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) once per submodule. `just upload` runs it only where the working tree is dirty, `just full_upload` runs it everywhere. That is the main consumer, so a change to `automate.py` affects the publish path for every site in the collection.
@@ -116,9 +137,10 @@ Because [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) bump
116
137
 
117
138
  ## Not Covered Yet
118
139
 
119
- - **No test notebook for the helpers.** `03_GitHub.ipynb` carries unit tests for its pure functions, but the dataset and image helpers are exercised only by being used, so a broken one surfaces in a downstream repo rather than here.
140
+ - **No tests for the helpers.** `01_Automate.ipynb`, `03_GitHub.ipynb` and `04_Fleet.ipynb` all carry unit tests for their pure functions - the fleet ones against a throwaway superproject of real git repos - but the dataset and image helpers in `00_Functions.ipynb` are exercised only by being used, so a broken one surfaces in a downstream repo rather than here.
120
141
  - **Undeclared dependencies.** `pyproject.toml` declares `graphviz` and `fastcore`, plus `githubkit` behind the `gh` extra. The helpers additionally import `fastai`, `fastbook`, `fastdownload`, `PIL`, `matplotlib`, `tqdm`, `pdf2image` (with poppler behind it), `nbdev` and `kaggle` (which also needs its credentials). All of those are imported lazily inside the function that needs them, so installing the package and running the console scripts still works - only the specific helper you call fails. Install them yourself.
121
142
  - **`core.py` is an empty nbdev stub** (a single `foo`) left over from `00_core.ipynb`, which no longer exists.
143
+ - **`CHANGELOG.md` is still the nbdev stub** (`Version 1.0.0 - Initial release`) while the package is past 0.3.68. Nothing generates it, and [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) bumps the patch on every push, so it would need writing by hand or dropping.
122
144
  - The docstrings are one-liners, so the rendered API pages are thin. The notebooks carry the real explanation.
123
145
 
124
146
  ------------------------------------------------------------------------
@@ -40,26 +40,28 @@ from nbdevAuto.pdf import PDFreader
40
40
  | Page | Covers |
41
41
  |----|----|
42
42
  | [Functions](00_Functions.ipynb) | Dataset and image helpers: reading a list out of a text file, downloading single images or whole search-driven datasets, verifying and resizing what came back, building the folder layout a classifier expects, running a fastai learner over one image, Kaggle competition and dataset shortcuts, and [`graph`](https://bthek1.github.io/nbdevAuto/functions.html#graph), a `graphviz.Digraph` subclass preloaded with a rounded, filled style |
43
- | [Automate](01_Automate.ipynb) | The command line surface: the prep/commit/push pipeline, the GitHub and PyPI release halves, and the help output that lists them all |
43
+ | [Automate](01_Automate.ipynb) | The command line surface: the prep/commit/push pipeline, the GitHub and PyPI release halves, the help output that lists them all, and the one-line-per-stage console grid every command reports through |
44
44
  | [PDF Reader](02_PDF_reader.ipynb) | [`PDFreader`](https://bthek1.github.io/nbdevAuto/pdf_reader.html#pdfreader), a class that converts a PDF through `pdf2image` and renders page ranges inline when you slice it (`pdf[0:5]`) |
45
- | [GitHub](03_GitHub.ipynb) | [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus): reading `.gitmodules`, resolving a token, and asking the GitHub API about every submodule at once - CI runs, Pages deploys, Pages builds, remote `main` shas - plus the `--report` and `--audit` views and the exit codes that keep “absent” apart from “cannot ask |
45
+ | [GitHub](03_GitHub.ipynb) | [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus): reading `.gitmodules`, resolving a token, and asking the GitHub API about every submodule at once - CI runs, Pages deploys, Pages builds, remote `main` shas - plus the `--report` and `--audit` views, the exit codes that keep “absent” apart from “cannot ask”, and `newest_run`, which picks a run by `run_number` because the API’s page order is not a contract |
46
+ | [Fleet](04_Fleet.ipynb) | [`fleet`](https://bthek1.github.io/nbdevAuto/fleet.html#fleet): running one command across every submodule of a superproject. A walker that reports only what did work and counts the rest, plus the verbs built on it - upload, push, copy, status, update and sync |
46
47
 
47
48
  ------------------------------------------------------------------------
48
49
 
49
50
  ## The Command Line Surface
50
51
 
51
- Installing the package puts these on your `PATH`. Most are ordinary functions in [Automate](01_Automate.ipynb), exposed as scripts through `[project.scripts]`; [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus) comes from [GitHub](03_GitHub.ipynb).
52
+ Installing the package puts these on your `PATH`. Most are ordinary functions in [Automate](01_Automate.ipynb), exposed as scripts through `[project.scripts]`; [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus) comes from [GitHub](03_GitHub.ipynb) and [`fleet`](https://bthek1.github.io/nbdevAuto/fleet.html#fleet) from [Fleet](04_Fleet.ipynb).
52
53
 
53
54
  | Command | Does |
54
55
  |----|----|
55
56
  | [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) | Bump the version, then export, test and clean the notebooks, refreshing `_quarto.yml` and the README |
56
- | [`gacp`](https://bthek1.github.io/nbdevAuto/automate.html#gacp) | git add, commit, and push. Without `-m` the commit message is the status output |
57
+ | [`gacp`](https://bthek1.github.io/nbdevAuto/automate.html#gacp) | git add, commit, and push. Without `-m` the message is built from the staged paths |
57
58
  | [`status`](https://bthek1.github.io/nbdevAuto/automate.html#status) | Show the working tree state |
58
59
  | [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) | [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) then [`gacp`](https://bthek1.github.io/nbdevAuto/automate.html#gacp), the one you actually type |
59
60
  | `gitrelease` | Bump the **minor** version, then tag and create a GitHub release |
60
61
  | `piprelease` | Build the sdist and wheel, then upload to PyPI with twine |
61
62
  | [`release`](https://bthek1.github.io/nbdevAuto/automate.html#release) | `gitrelease` then `piprelease` |
62
63
  | [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus) | Remote status for every submodule of an nbdev superproject, from the GitHub API. `--report` for the sync view, `--audit` to check `.gitmodules` against the repos the account owns, `--json` for machine output |
64
+ | [`fleet`](https://bthek1.github.io/nbdevAuto/fleet.html#fleet) | Run one command across every submodule: `fleet upload|push|copy|status|update|sync`. Every verb takes `--help`, and the destructive ones take `--dry-run` |
63
65
  | `h` | Print the list of available commands |
64
66
 
65
67
  Both [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) and [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) take `-p` to choose which version part to increment; it defaults to 2, the patch, so every [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) ships a new version.
@@ -70,6 +72,25 @@ Both [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) and [`uploa
70
72
 
71
73
  ------------------------------------------------------------------------
72
74
 
75
+ ## The Output Contract
76
+
77
+ Every command reports through one grid, defined in [Automate](01_Automate.ipynb) and reused by [Fleet](04_Fleet.ipynb). It exists because these commands run 19 times in a row, so anything that costs three lines per stage costs sixty over a fleet.
78
+
79
+ 1/6 bump version (part 2) OK 75ms 0.3.67 -> 0.3.68
80
+ 2/6 nbdev_export OK 129ms
81
+ 3/6 nbdev_test OK 1.5s 6 notebooks, slowest 01_Automate.ipynb 1.0s
82
+
83
+ Four rules hold it together, and a change to any stage has to keep them:
84
+
85
+ - **One stage, one line, inside 88 columns.** The columns are fixed: `n/total` (5), label (22), outcome (4), duration (7), detail (the rest). A unit test renders real stages through a console pinned to that width and asserts every line fits.
86
+ - **A summariser returns short strings, not a transcript.** [`_test_summary`](https://bthek1.github.io/nbdevAuto/automate.html#_test_summary) turns a line per notebook into `6 notebooks, slowest X 1.0s`; [`_push_summary`](https://bthek1.github.io/nbdevAuto/automate.html#_push_summary) turns nine lines of git progress into a sha range and a size. Anything too long for the detail column moves to an indented continuation line, so a verbose summariser degrades the layout rather than breaking it.
87
+ - **A stage that shells out must capture at the file-descriptor level.** `redirect_stdout` only rebinds Python’s `sys.stdout`, so a child process writing to fd 1 walks straight past it - which is how `nbdev_readme` used to spill sixteen lines of pandoc metadata into the middle of a run. [`_quiet`](https://bthek1.github.io/nbdevAuto/automate.html#_quiet) swaps fds 1 and 2 for a temp file as well and merges both captures.
88
+ - **A green OK has to mean something.** [`_run`](https://bthek1.github.io/nbdevAuto/automate.html#_run) raises on a non-zero exit rather than returning a code nobody reads, and the failure prints the command’s own output. Before that, a failed `git push` still reported `OK`.
89
+
90
+ On a terminal the label is written first without a newline, so a slow stage is visible while it runs, then rewritten in place. Piped output skips that entirely, because a carriage return in a log file is just noise.
91
+
92
+ ------------------------------------------------------------------------
93
+
73
94
  ## Where It Is Used
74
95
 
75
96
  The parent Knowledge repo’s `justfile` calls [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) once per submodule. `just upload` runs it only where the working tree is dirty, `just full_upload` runs it everywhere. That is the main consumer, so a change to `automate.py` affects the publish path for every site in the collection.
@@ -86,9 +107,10 @@ Because [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) bump
86
107
 
87
108
  ## Not Covered Yet
88
109
 
89
- - **No test notebook for the helpers.** `03_GitHub.ipynb` carries unit tests for its pure functions, but the dataset and image helpers are exercised only by being used, so a broken one surfaces in a downstream repo rather than here.
110
+ - **No tests for the helpers.** `01_Automate.ipynb`, `03_GitHub.ipynb` and `04_Fleet.ipynb` all carry unit tests for their pure functions - the fleet ones against a throwaway superproject of real git repos - but the dataset and image helpers in `00_Functions.ipynb` are exercised only by being used, so a broken one surfaces in a downstream repo rather than here.
90
111
  - **Undeclared dependencies.** `pyproject.toml` declares `graphviz` and `fastcore`, plus `githubkit` behind the `gh` extra. The helpers additionally import `fastai`, `fastbook`, `fastdownload`, `PIL`, `matplotlib`, `tqdm`, `pdf2image` (with poppler behind it), `nbdev` and `kaggle` (which also needs its credentials). All of those are imported lazily inside the function that needs them, so installing the package and running the console scripts still works - only the specific helper you call fails. Install them yourself.
91
112
  - **`core.py` is an empty nbdev stub** (a single `foo`) left over from `00_core.ipynb`, which no longer exists.
113
+ - **`CHANGELOG.md` is still the nbdev stub** (`Version 1.0.0 - Initial release`) while the package is past 0.3.68. Nothing generates it, and [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) bumps the patch on every push, so it would need writing by hand or dropping.
92
114
  - The docstrings are one-liners, so the rendered API pages are thin. The notebooks carry the real explanation.
93
115
 
94
116
  ------------------------------------------------------------------------
@@ -0,0 +1 @@
1
+ __version__ = "0.3.69"
@@ -13,13 +13,16 @@ d = { 'settings': { 'branch': 'main',
13
13
  'nbdevAuto.automate._commit_summary': ('automate.html#_commit_summary', 'nbdevAuto/automate.py'),
14
14
  'nbdevAuto.automate._done': ('automate.html#_done', 'nbdevAuto/automate.py'),
15
15
  'nbdevAuto.automate._dur': ('automate.html#_dur', 'nbdevAuto/automate.py'),
16
+ 'nbdevAuto.automate._head': ('automate.html#_head', 'nbdevAuto/automate.py'),
16
17
  'nbdevAuto.automate._push_summary': ('automate.html#_push_summary', 'nbdevAuto/automate.py'),
17
18
  'nbdevAuto.automate._quiet': ('automate.html#_quiet', 'nbdevAuto/automate.py'),
18
19
  'nbdevAuto.automate._rule': ('automate.html#_rule', 'nbdevAuto/automate.py'),
19
20
  'nbdevAuto.automate._run': ('automate.html#_run', 'nbdevAuto/automate.py'),
20
21
  'nbdevAuto.automate._skip': ('automate.html#_skip', 'nbdevAuto/automate.py'),
22
+ 'nbdevAuto.automate._staged_summary': ('automate.html#_staged_summary', 'nbdevAuto/automate.py'),
21
23
  'nbdevAuto.automate._status_table': ('automate.html#_status_table', 'nbdevAuto/automate.py'),
22
24
  'nbdevAuto.automate._step': ('automate.html#_step', 'nbdevAuto/automate.py'),
25
+ 'nbdevAuto.automate._tail': ('automate.html#_tail', 'nbdevAuto/automate.py'),
23
26
  'nbdevAuto.automate._test_summary': ('automate.html#_test_summary', 'nbdevAuto/automate.py'),
24
27
  'nbdevAuto.automate._version_summary': ('automate.html#_version_summary', 'nbdevAuto/automate.py'),
25
28
  'nbdevAuto.automate.gacp': ('automate.html#gacp', 'nbdevAuto/automate.py'),
@@ -111,6 +114,7 @@ d = { 'settings': { 'branch': 'main',
111
114
  'nbdevAuto.github.has_failure': ('github.html#has_failure', 'nbdevAuto/github.py'),
112
115
  'nbdevAuto.github.local_sha': ('github.html#local_sha', 'nbdevAuto/github.py'),
113
116
  'nbdevAuto.github.local_state': ('github.html#local_state', 'nbdevAuto/github.py'),
117
+ 'nbdevAuto.github.newest_run': ('github.html#newest_run', 'nbdevAuto/github.py'),
114
118
  'nbdevAuto.github.owned_repos': ('github.html#owned_repos', 'nbdevAuto/github.py'),
115
119
  'nbdevAuto.github.parse_gitmodules': ('github.html#parse_gitmodules', 'nbdevAuto/github.py'),
116
120
  'nbdevAuto.github.repo_status': ('github.html#repo_status', 'nbdevAuto/github.py'),
@@ -16,8 +16,11 @@ from rich.rule import Rule
16
16
  from rich.table import Table
17
17
 
18
18
  # %% ../nbs/01_Automate.ipynb #7c1e5a90-3f52-4d21-9b6a-2c0f4e8d1a37
19
+ import os
19
20
  import re
20
21
  import subprocess
22
+ import sys
23
+ import tempfile
21
24
  from contextlib import contextmanager, redirect_stderr, redirect_stdout
22
25
  from io import StringIO
23
26
  from time import perf_counter
@@ -26,8 +29,16 @@ from nbdevAuto import __version__
26
29
 
27
30
  console = Console()
28
31
 
29
- # A rule drawn across a 200-column terminal is a wall of dashes. 88 reads as a header.
30
- _RULE_W = 88
32
+ # One line per stage, inside 88 columns:
33
+ # "1/6 bump version (part 2) OK 75ms 0.3.67 -> 0.3.68"
34
+ # NUM LABEL STAT DUR detail
35
+ _WIDTH = 88
36
+ _W_NUM = 5
37
+ _W_LABEL = 22
38
+ _W_STAT = 4
39
+ _W_DUR = 7
40
+ _W_HEAD = _W_NUM + _W_LABEL + 1 + _W_STAT + 1 + _W_DUR + 2
41
+ _W_DETAIL = _WIDTH - _W_HEAD
31
42
 
32
43
  # `git status -s` codes: colour, the word for the third column, and the verb a commit
33
44
  # subject gets when every staged path carries that same code.
@@ -57,86 +68,117 @@ def _dur(t0):
57
68
  return f"{int(s)//60}m{int(s)%60:02d}s"
58
69
 
59
70
  def _rule(text, style):
60
- "A rule capped at `_RULE_W`; `console.rule` otherwise spans the whole terminal"
61
- console.print(Rule(text, style=style), width=min(console.width, _RULE_W))
71
+ "A rule capped at `_WIDTH`; `console.rule` otherwise spans the whole terminal"
72
+ console.print(Rule(text, style=style), width=min(console.width, _WIDTH))
62
73
 
63
74
  def _banner(cmd):
64
75
  "Command header, with the package version alongside"
65
76
  _rule(f"[bold cyan]{cmd}[/bold cyan] [dim]nbdevAuto {__version__}[/dim]", "cyan")
66
77
 
67
78
  def _done(cmd, t0):
68
- "Closing summary for a multi-stage command"
69
- _rule(f"[green]{cmd} finished[/green] [dim]in {_dur(t0)}[/dim]", "green")
79
+ "Closing line for a multi-stage command. A dim line, not another full-width rule"
80
+ console.print(f"[dim]{cmd} finished in {_dur(t0)}[/dim]")
81
+
82
+ def _head(n, total, label):
83
+ "The fixed-width left side of a stage line"
84
+ return f"{f'{n}/{total}':<{_W_NUM}}{label:<{_W_LABEL}}"
85
+
86
+ def _tail(detail):
87
+ "(text for the stage line, overflow lines). Detail is joined while it still fits"
88
+ if not detail: return "", []
89
+ joined = "; ".join(detail)
90
+ if len(joined) <= _W_DETAIL: return joined, []
91
+ return detail[0], detail[1:]
70
92
 
71
93
  @contextmanager
72
94
  def _step(n, total, label):
73
- """Run one stage, reporting its outcome and duration.
74
-
75
- Yields a list: append short strings and they print under the step, which is how a
76
- stage says what it did without letting the underlying tool print it all. Re-raises
77
- so nothing is swallowed, and a `StepFailed` shows the output it carries."""
78
- console.print(f"[dim]{n}/{total}[/dim] [bold]{label}[/bold]")
95
+ """Run one stage and report it on a SINGLE line.
96
+
97
+ Yields a list; append short strings and they land in the detail column. On a terminal
98
+ the label is written first without a newline so a slow stage is visible while it runs,
99
+ then rewritten in place once the outcome is known. Piped output skips that, because a
100
+ carriage return in a log file is just noise."""
101
+ head = _head(n, total, label)
102
+ live = console.is_terminal
103
+ if live: console.print(head, end="", highlight=False)
79
104
  t0, detail = perf_counter(), []
105
+ def emit(stat, style, extra):
106
+ text, over = _tail(extra)
107
+ # Back to column 0 WITHOUT going through rich's renderer, which would escape the
108
+ # carriage return instead of acting on it and print the label twice.
109
+ if live: console.file.write("\r"); console.file.flush()
110
+ line = f"{head} [{style}]{stat:<{_W_STAT}}[/{style}] [dim]{_dur(t0):>{_W_DUR}}[/dim]"
111
+ if text: line += f" [dim]{escape(text)}[/dim]"
112
+ console.print(line, highlight=False)
113
+ for o in over: console.print(f" [dim]{escape(o)}[/dim]", highlight=False)
80
114
  try:
81
115
  yield detail
82
116
  except StepFailed as e:
83
- console.print(f" [bold red]FAIL[/bold red] [dim]{_dur(t0)}, exit {e.code}[/dim]")
117
+ emit("FAIL", "bold red", [])
84
118
  for line in e.output.splitlines():
85
- if line.strip(): console.print(f" [red]{escape(line.rstrip())}[/red]")
86
- console.print()
119
+ if line.strip(): console.print(f" [red]{escape(line.rstrip())}[/red]", highlight=False)
87
120
  raise
88
121
  except BaseException:
89
- console.print(f" [bold red]FAIL[/bold red] [dim]{_dur(t0)}[/dim]\n")
122
+ emit("FAIL", "bold red", [])
90
123
  raise
91
- for line in detail: console.print(f" [dim]{escape(line)}[/dim]")
92
- console.print(f" [green]OK[/green] [dim]{_dur(t0)}[/dim]\n")
124
+ emit("OK", "green", detail)
93
125
 
94
126
  def _skip(n, total, label, why):
95
- "Report a stage that was deliberately not run"
96
- console.print(f"[dim]{n}/{total}[/dim] [bold]{label}[/bold]")
97
- console.print(f" [yellow]SKIP[/yellow] [dim]{why}[/dim]\n")
127
+ "Report a stage that was deliberately not run, on the same one-line grid"
128
+ console.print(f"{_head(n, total, label)} [yellow]{'SKIP':<{_W_STAT}}[/yellow] "
129
+ f"[dim]{'-':>{_W_DUR}}[/dim] [dim]{escape(why)}[/dim]", highlight=False)
98
130
 
99
131
  def _run(*cmd):
100
132
  """Run `cmd` with output captured, returning stdout+stderr.
101
133
 
102
134
  A bare `subprocess.run` returns an exit code nobody reads, so a failed `git push`
103
135
  still printed a green OK. Raising is what makes that OK mean something."""
104
- r = subprocess.run(cmd, capture_output=True, text=True)
136
+ r = subprocess.run(cmd, capture_output=True, text=True, check=False)
105
137
  out = f"{r.stdout or ''}{r.stderr or ''}"
106
138
  if r.returncode: raise StepFailed(" ".join(cmd), r.returncode, out)
107
139
  return out
108
140
 
109
141
  @contextmanager
110
142
  def _quiet():
111
- "Collect what a wrapped nbdev call prints, so the step can summarise it instead"
143
+ """Capture what a stage prints, INCLUDING what its subprocesses print.
144
+
145
+ `redirect_stdout` only rebinds Python's `sys.stdout`, so a child process writing
146
+ straight to file descriptor 1 sails past it - which is how `nbdev_readme` used to dump
147
+ sixteen lines of pandoc metadata into the middle of the run. Descriptors 1 and 2 are
148
+ therefore swapped for a temp file as well, and both captures are merged."""
112
149
  buf = StringIO()
113
- try:
114
- with redirect_stdout(buf), redirect_stderr(buf): yield buf
115
- except BaseException:
116
- # A failing stage is the one time every raw line is worth having.
117
- text = buf.getvalue().strip()
118
- if text: console.print(f"[dim]{escape(text)}[/dim]")
119
- raise
150
+ with tempfile.TemporaryFile(mode="w+") as tmp:
151
+ sys.stdout.flush(); sys.stderr.flush()
152
+ saved = os.dup(1), os.dup(2)
153
+ os.dup2(tmp.fileno(), 1); os.dup2(tmp.fileno(), 2)
154
+ try:
155
+ with redirect_stdout(buf), redirect_stderr(buf): yield buf
156
+ finally:
157
+ sys.stdout.flush(); sys.stderr.flush()
158
+ os.dup2(saved[0], 1); os.dup2(saved[1], 2)
159
+ for fd in saved: os.close(fd)
160
+ tmp.seek(0)
161
+ buf.write(tmp.read())
120
162
 
121
163
  def _version_summary(out):
122
164
  "The two lines `nbdev_bump_version` prints, as one arrow"
123
165
  v = dict(_RE_VERSION.findall(out))
124
166
  if "Old" in v and "New" in v: return [f"{v['Old']} -> {v['New']}"]
125
- return [l.strip() for l in out.splitlines() if l.strip()]
167
+ return [l.strip() for l in out.splitlines() if l.strip()][:1]
126
168
 
127
- def _test_summary(out, slow_secs=1.0, keep=3):
128
- "`nbdev_test` prints a line per notebook; keep the count and only the genuinely slow ones"
169
+ def _test_summary(out, slow_secs=1.0):
170
+ "`nbdev_test` prints a line per notebook; keep the count and the slowest one"
129
171
  times = []
130
172
  for line in out.splitlines():
131
173
  m = _RE_TIMING.match(line.strip())
132
174
  if m:
133
175
  try: times.append((m["nb"], float(m["secs"])))
134
176
  except ValueError: pass
135
- if not times: return [l.strip() for l in out.splitlines() if l.strip()][-3:]
177
+ if not times: return [l.strip() for l in out.splitlines() if l.strip()][-1:]
136
178
  head = f"{len(times)} notebook{'' if len(times) == 1 else 's'}"
137
- slow = [t for t in sorted(times, key=lambda t: -t[1]) if t[1] >= slow_secs][:keep]
138
- if not slow: return [f"{head}, none slower than {slow_secs:g}s"]
139
- return [f"{head}, slowest:"] + [f" {nb} {s:.1f}s" for nb, s in slow]
179
+ nb, s = max(times, key=lambda t: t[1])
180
+ if s < slow_secs: return [f"{head}, all under {slow_secs:g}s"]
181
+ return [f"{head}, slowest {nb} {s:.1f}s"]
140
182
 
141
183
  def _commit_summary(out):
142
184
  "Where the commit landed and how big it was, without echoing the subject back"
@@ -146,20 +188,23 @@ def _commit_summary(out):
146
188
  head = f"{m['branch']} {m['sha']}" if m else lines[0]
147
189
  if not c: return [head]
148
190
  files, ins, dels = c.group(1), c.group(2) or "0", c.group(3) or "0"
149
- return [f"{head} {files} file{'' if files == '1' else 's'}, +{ins} -{dels}"]
191
+ return [f"{head} {files} file{'' if files == '1' else 's'} +{ins} -{dels}"]
150
192
 
151
193
  def _push_summary(out):
152
- "What moved and how much went over the wire; the progress meters are noise"
194
+ """What moved, and how much went over the wire.
195
+
196
+ The remote is deliberately left out: the progress meters are noise, and which repo
197
+ this is has already been said by the prompt or by the fleet's `==> path` heading."""
153
198
  lines = [l.rstrip() for l in out.splitlines() if l.strip()]
154
199
  if not lines: return ["nothing to push"]
155
200
  if any("Everything up-to-date" in l for l in lines): return ["everything up-to-date"]
156
201
  detail = []
157
202
  for i, l in enumerate(lines):
158
203
  if l.startswith("To ") and i + 1 < len(lines):
159
- detail += [lines[i + 1].strip(), f"to {l[3:].strip()}"]
204
+ detail.append(lines[i + 1].strip())
160
205
  break
161
206
  m = _RE_WROTE.search(out)
162
- if m: detail.append(f"{m['size'].strip()} written")
207
+ if m: detail.append(m["size"].strip())
163
208
  return detail or lines[-1:]
164
209
 
165
210
  def _auto_msg(porcelain, limit=3):
@@ -172,9 +217,22 @@ def _auto_msg(porcelain, limit=3):
172
217
  if len(names) > limit: head += f" and {len(names) - limit} more"
173
218
  return f"{verb} {head}"
174
219
 
220
+ def _staged_summary(porcelain, width=_W_DETAIL):
221
+ "One line naming what is staged, trimmed to the detail column"
222
+ rows = [l[3:].strip().split(" -> ")[-1] for l in porcelain.splitlines() if l.strip()]
223
+ n = len(rows)
224
+ if not n: return []
225
+ shown, used = [], 0
226
+ for r in rows:
227
+ nxt = used + len(r) + 2
228
+ if shown and nxt > width - 12: break
229
+ shown.append(r); used = nxt
230
+ tail = f", +{n - len(shown)}" if len(shown) < n else ""
231
+ return [f"{n} staged: {', '.join(shown)}{tail}"]
232
+
175
233
  def _status_table(porcelain):
176
234
  "Render `git status -s` output as a table, one row per path"
177
- t = Table(box=box.SIMPLE, show_header=False, pad_edge=False, expand=False)
235
+ t = Table(box=None, show_header=False, pad_edge=False, expand=False)
178
236
  t.add_column("code", no_wrap=True, justify="right")
179
237
  t.add_column("path", overflow="fold")
180
238
  t.add_column("what", style="dim", no_wrap=True)
@@ -225,19 +283,13 @@ def gacp(
225
283
 
226
284
  _banner("gacp")
227
285
  t0 = perf_counter()
228
- with _step(1, 3, "git add"):
229
- _run("git", "add", ".")
230
286
  # NOT via `_run`: `git status -s` pads the code to two columns, and the capture has
231
287
  # to keep that leading space or the first path shifts by one.
232
- status = subprocess.check_output(["git", "status", "-s"]).decode('utf-8')
288
+ with _step(1, 3, "git add") as d:
289
+ _run("git", "add", ".")
290
+ status = subprocess.check_output(["git", "status", "-s"]).decode('utf-8')
291
+ d += _staged_summary(status)
233
292
  staged = bool(status.strip())
234
- if staged:
235
- table, n = _status_table(status)
236
- console.print(table)
237
- console.print(f"[dim]{n} path{'' if n == 1 else 's'} staged[/dim]\n")
238
- else:
239
- console.print("[dim]nothing staged, the tree is clean[/dim]\n")
240
-
241
293
  if staged:
242
294
  with _step(2, 3, "git commit") as d:
243
295
  d += _commit_summary(_run("git", "commit", "-m", m if m != '' else _auto_msg(status)))
@@ -6,8 +6,8 @@ Docs: https://bthek1.github.io/nbdevAuto/github.html.md"""
6
6
 
7
7
  # %% auto #0
8
8
  __all__ = ['Submodule', 'CI_WORKFLOWS', 'PAGES_WORKFLOWS', 'FAILED', 'RUNNING', 'BOLD', 'RED', 'YELLOW', 'CYAN', 'DIM', 'OFF',
9
- 'find_root', 'parse_gitmodules', 'resolve_token', 'local_sha', 'api_error', 'repo_status', 'client',
10
- 'gather_status', 'format_table', 'failures', 'has_failure', 'git', 'local_state', 'ahead_behind',
9
+ 'find_root', 'parse_gitmodules', 'resolve_token', 'local_sha', 'api_error', 'newest_run', 'repo_status',
10
+ 'client', 'gather_status', 'format_table', 'failures', 'has_failure', 'git', 'local_state', 'ahead_behind',
11
11
  'add_local', 'format_report', 'stale_sections', 'owned_repos', 'self_repo', 'audit_result', 'gather_audit',
12
12
  'format_audit', 'ghstatus']
13
13
 
@@ -93,19 +93,30 @@ def api_error(e):
93
93
  CI_WORKFLOWS = ("test.yaml", "test.yml") # fastai/workflows/nbdev3-ci
94
94
  PAGES_WORKFLOWS = ("deploy.yaml", "deploy.yml") # fastai/workflows/quarto-ghp3
95
95
 
96
+ def newest_run(runs):
97
+ """The newest run in `runs`, by `run_number`, which is monotonic per workflow.
98
+
99
+ Do NOT just take `runs[0]`. This endpoint is eventually consistent and its ordering is
100
+ not a contract: on 2026-08-26 a `per_page=1` query for ML_methods returned run number
101
+ 96 (2026-02-17, failure) when 107 (2026-08-16, success) was the newest, and `just
102
+ report` called a healthy repo FAILURE. A single-item page gives the caller no way to
103
+ notice. Asking for several and taking the maximum costs the same one request."""
104
+ numbered = [r for r in runs if getattr(r, "run_number", None) is not None]
105
+ return max(numbered, key=lambda r: r.run_number) if numbered else runs[0]
106
+
96
107
  async def _latest_run(gh, sub, candidates, branch="main"):
97
108
  "Newest run of the first workflow in `candidates` that exists, or None"
98
109
  from githubkit.exception import RequestFailed
99
110
  for wf in candidates:
100
111
  try:
101
112
  r = await gh.rest.actions.async_list_workflow_runs(
102
- sub.owner, sub.repo, wf, branch=branch, per_page=1)
113
+ sub.owner, sub.repo, wf, branch=branch, per_page=10)
103
114
  except RequestFailed as e:
104
115
  if _missing(e): continue
105
116
  raise
106
117
  runs = r.parsed_data.workflow_runs
107
118
  if runs:
108
- run = runs[0]
119
+ run = newest_run(runs)
109
120
  # conclusion is None while a run is still going, status covers that case
110
121
  return {"state": run.conclusion or run.status, "sha": run.head_sha, "url": run.html_url}
111
122
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nbdevAuto
3
- Version: 0.3.67
3
+ Version: 0.3.69
4
4
  Summary: automating nbdev
5
5
  Author-email: Benedict Thekkel <bthekkel1@gmail.com>
6
6
  License-Expression: Apache-2.0
@@ -70,26 +70,28 @@ from nbdevAuto.pdf import PDFreader
70
70
  | Page | Covers |
71
71
  |----|----|
72
72
  | [Functions](00_Functions.ipynb) | Dataset and image helpers: reading a list out of a text file, downloading single images or whole search-driven datasets, verifying and resizing what came back, building the folder layout a classifier expects, running a fastai learner over one image, Kaggle competition and dataset shortcuts, and [`graph`](https://bthek1.github.io/nbdevAuto/functions.html#graph), a `graphviz.Digraph` subclass preloaded with a rounded, filled style |
73
- | [Automate](01_Automate.ipynb) | The command line surface: the prep/commit/push pipeline, the GitHub and PyPI release halves, and the help output that lists them all |
73
+ | [Automate](01_Automate.ipynb) | The command line surface: the prep/commit/push pipeline, the GitHub and PyPI release halves, the help output that lists them all, and the one-line-per-stage console grid every command reports through |
74
74
  | [PDF Reader](02_PDF_reader.ipynb) | [`PDFreader`](https://bthek1.github.io/nbdevAuto/pdf_reader.html#pdfreader), a class that converts a PDF through `pdf2image` and renders page ranges inline when you slice it (`pdf[0:5]`) |
75
- | [GitHub](03_GitHub.ipynb) | [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus): reading `.gitmodules`, resolving a token, and asking the GitHub API about every submodule at once - CI runs, Pages deploys, Pages builds, remote `main` shas - plus the `--report` and `--audit` views and the exit codes that keep “absent” apart from “cannot ask |
75
+ | [GitHub](03_GitHub.ipynb) | [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus): reading `.gitmodules`, resolving a token, and asking the GitHub API about every submodule at once - CI runs, Pages deploys, Pages builds, remote `main` shas - plus the `--report` and `--audit` views, the exit codes that keep “absent” apart from “cannot ask”, and `newest_run`, which picks a run by `run_number` because the API’s page order is not a contract |
76
+ | [Fleet](04_Fleet.ipynb) | [`fleet`](https://bthek1.github.io/nbdevAuto/fleet.html#fleet): running one command across every submodule of a superproject. A walker that reports only what did work and counts the rest, plus the verbs built on it - upload, push, copy, status, update and sync |
76
77
 
77
78
  ------------------------------------------------------------------------
78
79
 
79
80
  ## The Command Line Surface
80
81
 
81
- Installing the package puts these on your `PATH`. Most are ordinary functions in [Automate](01_Automate.ipynb), exposed as scripts through `[project.scripts]`; [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus) comes from [GitHub](03_GitHub.ipynb).
82
+ Installing the package puts these on your `PATH`. Most are ordinary functions in [Automate](01_Automate.ipynb), exposed as scripts through `[project.scripts]`; [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus) comes from [GitHub](03_GitHub.ipynb) and [`fleet`](https://bthek1.github.io/nbdevAuto/fleet.html#fleet) from [Fleet](04_Fleet.ipynb).
82
83
 
83
84
  | Command | Does |
84
85
  |----|----|
85
86
  | [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) | Bump the version, then export, test and clean the notebooks, refreshing `_quarto.yml` and the README |
86
- | [`gacp`](https://bthek1.github.io/nbdevAuto/automate.html#gacp) | git add, commit, and push. Without `-m` the commit message is the status output |
87
+ | [`gacp`](https://bthek1.github.io/nbdevAuto/automate.html#gacp) | git add, commit, and push. Without `-m` the message is built from the staged paths |
87
88
  | [`status`](https://bthek1.github.io/nbdevAuto/automate.html#status) | Show the working tree state |
88
89
  | [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) | [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) then [`gacp`](https://bthek1.github.io/nbdevAuto/automate.html#gacp), the one you actually type |
89
90
  | `gitrelease` | Bump the **minor** version, then tag and create a GitHub release |
90
91
  | `piprelease` | Build the sdist and wheel, then upload to PyPI with twine |
91
92
  | [`release`](https://bthek1.github.io/nbdevAuto/automate.html#release) | `gitrelease` then `piprelease` |
92
93
  | [`ghstatus`](https://bthek1.github.io/nbdevAuto/github.html#ghstatus) | Remote status for every submodule of an nbdev superproject, from the GitHub API. `--report` for the sync view, `--audit` to check `.gitmodules` against the repos the account owns, `--json` for machine output |
94
+ | [`fleet`](https://bthek1.github.io/nbdevAuto/fleet.html#fleet) | Run one command across every submodule: `fleet upload|push|copy|status|update|sync`. Every verb takes `--help`, and the destructive ones take `--dry-run` |
93
95
  | `h` | Print the list of available commands |
94
96
 
95
97
  Both [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) and [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) take `-p` to choose which version part to increment; it defaults to 2, the patch, so every [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) ships a new version.
@@ -100,6 +102,25 @@ Both [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) and [`uploa
100
102
 
101
103
  ------------------------------------------------------------------------
102
104
 
105
+ ## The Output Contract
106
+
107
+ Every command reports through one grid, defined in [Automate](01_Automate.ipynb) and reused by [Fleet](04_Fleet.ipynb). It exists because these commands run 19 times in a row, so anything that costs three lines per stage costs sixty over a fleet.
108
+
109
+ 1/6 bump version (part 2) OK 75ms 0.3.67 -> 0.3.68
110
+ 2/6 nbdev_export OK 129ms
111
+ 3/6 nbdev_test OK 1.5s 6 notebooks, slowest 01_Automate.ipynb 1.0s
112
+
113
+ Four rules hold it together, and a change to any stage has to keep them:
114
+
115
+ - **One stage, one line, inside 88 columns.** The columns are fixed: `n/total` (5), label (22), outcome (4), duration (7), detail (the rest). A unit test renders real stages through a console pinned to that width and asserts every line fits.
116
+ - **A summariser returns short strings, not a transcript.** [`_test_summary`](https://bthek1.github.io/nbdevAuto/automate.html#_test_summary) turns a line per notebook into `6 notebooks, slowest X 1.0s`; [`_push_summary`](https://bthek1.github.io/nbdevAuto/automate.html#_push_summary) turns nine lines of git progress into a sha range and a size. Anything too long for the detail column moves to an indented continuation line, so a verbose summariser degrades the layout rather than breaking it.
117
+ - **A stage that shells out must capture at the file-descriptor level.** `redirect_stdout` only rebinds Python’s `sys.stdout`, so a child process writing to fd 1 walks straight past it - which is how `nbdev_readme` used to spill sixteen lines of pandoc metadata into the middle of a run. [`_quiet`](https://bthek1.github.io/nbdevAuto/automate.html#_quiet) swaps fds 1 and 2 for a temp file as well and merges both captures.
118
+ - **A green OK has to mean something.** [`_run`](https://bthek1.github.io/nbdevAuto/automate.html#_run) raises on a non-zero exit rather than returning a code nobody reads, and the failure prints the command’s own output. Before that, a failed `git push` still reported `OK`.
119
+
120
+ On a terminal the label is written first without a newline, so a slow stage is visible while it runs, then rewritten in place. Piped output skips that entirely, because a carriage return in a log file is just noise.
121
+
122
+ ------------------------------------------------------------------------
123
+
103
124
  ## Where It Is Used
104
125
 
105
126
  The parent Knowledge repo’s `justfile` calls [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) once per submodule. `just upload` runs it only where the working tree is dirty, `just full_upload` runs it everywhere. That is the main consumer, so a change to `automate.py` affects the publish path for every site in the collection.
@@ -116,9 +137,10 @@ Because [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) bump
116
137
 
117
138
  ## Not Covered Yet
118
139
 
119
- - **No test notebook for the helpers.** `03_GitHub.ipynb` carries unit tests for its pure functions, but the dataset and image helpers are exercised only by being used, so a broken one surfaces in a downstream repo rather than here.
140
+ - **No tests for the helpers.** `01_Automate.ipynb`, `03_GitHub.ipynb` and `04_Fleet.ipynb` all carry unit tests for their pure functions - the fleet ones against a throwaway superproject of real git repos - but the dataset and image helpers in `00_Functions.ipynb` are exercised only by being used, so a broken one surfaces in a downstream repo rather than here.
120
141
  - **Undeclared dependencies.** `pyproject.toml` declares `graphviz` and `fastcore`, plus `githubkit` behind the `gh` extra. The helpers additionally import `fastai`, `fastbook`, `fastdownload`, `PIL`, `matplotlib`, `tqdm`, `pdf2image` (with poppler behind it), `nbdev` and `kaggle` (which also needs its credentials). All of those are imported lazily inside the function that needs them, so installing the package and running the console scripts still works - only the specific helper you call fails. Install them yourself.
121
142
  - **`core.py` is an empty nbdev stub** (a single `foo`) left over from `00_core.ipynb`, which no longer exists.
143
+ - **`CHANGELOG.md` is still the nbdev stub** (`Version 1.0.0 - Initial release`) while the package is past 0.3.68. Nothing generates it, and [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) bumps the patch on every push, so it would need writing by hand or dropping.
122
144
  - The docstrings are one-liners, so the rendered API pages are thin. The notebooks carry the real explanation.
123
145
 
124
146
  ------------------------------------------------------------------------
@@ -1 +0,0 @@
1
- __version__ = "0.3.67"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes