nbdevAuto 0.3.68__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.
- {nbdevauto-0.3.68/nbdevAuto.egg-info → nbdevauto-0.3.69}/PKG-INFO +24 -4
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/README.md +23 -3
- nbdevauto-0.3.69/nbdevAuto/__init__.py +1 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/nbdevAuto/_modidx.py +4 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/nbdevAuto/automate.py +104 -52
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/nbdevAuto/github.py +15 -4
- {nbdevauto-0.3.68 → nbdevauto-0.3.69/nbdevAuto.egg-info}/PKG-INFO +24 -4
- nbdevauto-0.3.68/nbdevAuto/__init__.py +0 -1
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/LICENSE +0 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/MANIFEST.in +0 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/nbdevAuto/core.py +0 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/nbdevAuto/fleet.py +0 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/nbdevAuto/functions.py +0 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/nbdevAuto/pdf.py +0 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/nbdevAuto.egg-info/SOURCES.txt +0 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/nbdevAuto.egg-info/dependency_links.txt +0 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/nbdevAuto.egg-info/entry_points.txt +0 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/nbdevAuto.egg-info/requires.txt +0 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/nbdevAuto.egg-info/top_level.txt +0 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/pyproject.toml +0 -0
- {nbdevauto-0.3.68 → nbdevauto-0.3.69}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nbdevAuto
|
|
3
|
-
Version: 0.3.
|
|
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,9 +70,9 @@ 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,
|
|
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
|
|
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
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 |
|
|
77
77
|
|
|
78
78
|
------------------------------------------------------------------------
|
|
@@ -102,6 +102,25 @@ Both [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) and [`uploa
|
|
|
102
102
|
|
|
103
103
|
------------------------------------------------------------------------
|
|
104
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
|
+
|
|
105
124
|
## Where It Is Used
|
|
106
125
|
|
|
107
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.
|
|
@@ -118,9 +137,10 @@ Because [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) bump
|
|
|
118
137
|
|
|
119
138
|
## Not Covered Yet
|
|
120
139
|
|
|
121
|
-
- **No
|
|
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.
|
|
122
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.
|
|
123
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.
|
|
124
144
|
- The docstrings are one-liners, so the rendered API pages are thin. The notebooks carry the real explanation.
|
|
125
145
|
|
|
126
146
|
------------------------------------------------------------------------
|
|
@@ -40,9 +40,9 @@ 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,
|
|
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
|
|
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
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 |
|
|
47
47
|
|
|
48
48
|
------------------------------------------------------------------------
|
|
@@ -72,6 +72,25 @@ Both [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) and [`uploa
|
|
|
72
72
|
|
|
73
73
|
------------------------------------------------------------------------
|
|
74
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
|
+
|
|
75
94
|
## Where It Is Used
|
|
76
95
|
|
|
77
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.
|
|
@@ -88,9 +107,10 @@ Because [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) bump
|
|
|
88
107
|
|
|
89
108
|
## Not Covered Yet
|
|
90
109
|
|
|
91
|
-
- **No
|
|
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.
|
|
92
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.
|
|
93
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.
|
|
94
114
|
- The docstrings are one-liners, so the rendered API pages are thin. The notebooks carry the real explanation.
|
|
95
115
|
|
|
96
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
|
-
#
|
|
30
|
-
|
|
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 `
|
|
61
|
-
console.print(Rule(text, style=style), width=min(console.width,
|
|
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
|
|
69
|
-
|
|
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
|
|
74
|
-
|
|
75
|
-
Yields a list
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
122
|
+
emit("FAIL", "bold red", [])
|
|
90
123
|
raise
|
|
91
|
-
|
|
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"
|
|
97
|
-
|
|
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
|
-
"
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
128
|
-
"`nbdev_test` prints a line per notebook; keep the count and
|
|
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()][-
|
|
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
|
-
|
|
138
|
-
if
|
|
139
|
-
return [f"{head}, slowest
|
|
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'}
|
|
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
|
|
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
|
|
204
|
+
detail.append(lines[i + 1].strip())
|
|
160
205
|
break
|
|
161
206
|
m = _RE_WROTE.search(out)
|
|
162
|
-
if m: detail.append(
|
|
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=
|
|
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
|
-
|
|
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', '
|
|
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=
|
|
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
|
|
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.
|
|
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,9 +70,9 @@ 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,
|
|
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
|
|
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
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 |
|
|
77
77
|
|
|
78
78
|
------------------------------------------------------------------------
|
|
@@ -102,6 +102,25 @@ Both [`prep`](https://bthek1.github.io/nbdevAuto/automate.html#prep) and [`uploa
|
|
|
102
102
|
|
|
103
103
|
------------------------------------------------------------------------
|
|
104
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
|
+
|
|
105
124
|
## Where It Is Used
|
|
106
125
|
|
|
107
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.
|
|
@@ -118,9 +137,10 @@ Because [`upload`](https://bthek1.github.io/nbdevAuto/automate.html#upload) bump
|
|
|
118
137
|
|
|
119
138
|
## Not Covered Yet
|
|
120
139
|
|
|
121
|
-
- **No
|
|
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.
|
|
122
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.
|
|
123
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.
|
|
124
144
|
- The docstrings are one-liners, so the rendered API pages are thin. The notebooks carry the real explanation.
|
|
125
145
|
|
|
126
146
|
------------------------------------------------------------------------
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.68"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|