by Audrey M. Roy Greenfeld | Wed, Feb 5, 2025
Fastcore's call_parse decorator turns Python functions into CLI tools quickly. It's nowhere near as fancy as Typer or Click, but it's super quick to use.
From https://fastcore.fast.ai/script.html#example
#| export from fastcore.script import * from fastcore.utils import * from pathlib import Path import time
from nbdev.export import nb_export
@call_parse def main(msg:str, # The message upper:bool): # Convert to uppercase? "Print `msg`, optionally converting to uppercase" print(msg.upper() if upper else msg)
We can use the function right here, which is nice:
main("hey", upper=True)
main("hey", upper=False)
As for using it as a CLI tool, the docs say "If you copy that info a file and run it, you’ll see:
$ examples/test_fastcore.py --help
usage: test_fastcore.py [-h] [--upper] msg
Print `msg`, optionally converting to uppercase
positional arguments:
msg The message
optional arguments:
-h, --help show this help message and exit
--upper Convert to uppercase? (default: False)
"
I have many notebooks like Docker-Run.ipynb that I'd like prepended with the ISO 8601 date that they were last updated. My script will be prepend_nbs_with_dates.py because of the next line:
#| default_exp prepend_nbs_with_dates
I'll define a function to get all of them:
#| export def get_undated_nbs(): return L(Path().glob('[!0-9]*.ipynb'))
nbs = get_undated_nbs() nbs
nbs[1]
Then I'll define a function to prepend a date to a filename:
#| export def prepend_date(fn): "Prepend ISO 8601 date to filename if not already present" if fn.stem[0].isdigit(): return # Already has date mtime = fn.stat().st_mtime date = time.strftime('%Y-%m-%d', time.localtime(mtime)) new_name = fn.parent/f"{date}-{fn.name}" fn.rename(new_name) return new_name
# prepend_date(nbs[1])
Finally, I'll define a main function that gets all undated notebooks and prepends dates to their filenames:
#| export @call_parse def main(dry_run:bool=True): # Don't actually rename if True "Prepend dates to notebook filenames" fns = get_undated_nbs() print(f"Found {len(fns)} undated notebooks") if dry_run: for f in fns: mtime = f.stat().st_mtime date = time.strftime('%Y-%m-%d', time.localtime(mtime)) print(f"{f} -> {date}-{f.name}") else: for f in fns: prepend_date(f)
By default this does a dry run:
main()
Here we run it for real:
main(dry_run=False)
Now there are no more undated notebooks:
get_undated_nbs()
nb_export("2025-02-05-Create-a-CLI-Tool-With-Fastcore-Script.ipynb", lib_path="../scripts")
!ls ../scripts/
I ran it as a function earlier, so I don't have undated notebooks at the moment!
% python ../scripts/prepend_nbs_with_dates.py
Found 0 undated notebooks
Give me a few hours or days, and I'll update this post...