core


TmpDir

 TmpDir ()

Create temporary workspaces.

The main way to use TmpDir is through the exported object:

tmpdir = TmpDir()

We start out in our project dir:

d0 = Path.cwd(); d0
Path('/Users/seem/code/sketch-tmpdir')

Switch to path foo/bar under a temp dir:

tmpdir.new('foo/bar')
d1 = tmpdir.dir
Path.cwd()
Path('/private/var/folders/ft/0gnvc3ts5jz4ddqtttp6tjvm0000gn/T/tmp19y8l0h4/foo/bar')

If we switch again, the previous dir is removed:

tmpdir.new('foo/bar')
assert not d1.exists()
d2 = tmpdir.dir
Path.cwd()
Path('/private/var/folders/ft/0gnvc3ts5jz4ddqtttp6tjvm0000gn/T/tmpnjqprlft/foo/bar')

Finally, revert to the original working directory, which also removes the remaining temporary directory:

tmpdir.close()
assert not d2.exists()
test_eq(Path.cwd(), d0)

You can also use it as a context manager to automatically revert to the original working directory at the end:

with tmpdir() as p:
    d3 = tmpdir.dir
    test_eq(Path.cwd().name, p.name)
assert not d3.exists()
test_eq(Path.cwd(), d0)

The primary use-case is to write executable documentation for code that interacts with its workspace, as described in the examples below.

Example 1: Python git interface

def git_repo():
    "Remote repo from git config."
    cmd = 'git config --get remote.origin.url'
    proc = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    if proc.returncode: return
    return proc.stdout.strip().split('/', 1)[1].split('.')[0]

Let’s initialise a minimal repo to demonstrate:

tmpdir.new()
git init -q
git remote add origin git@github.com:my-user/my-repo.git

Get the repo name:

test_eq(git_repo(), 'my-repo')

Returns None if you’re not in a git repo:

with tmpdir(): test_is(git_repo(), None)

And you’re back to your original working directory:

Path.cwd()
Path('/Users/seem/code/sketch-tmpdir')

Example 2: nbdev hooks

This section shows what the end-to-end nbdev hooks test could look like with tmpdir. The test checks that nbdev’s notebook-aware merge driver works. In order to do that we need to simulate a merge conflict, which involves some back-and-forth with git.

from copy import deepcopy
from execnb.nbio import dict2nb, mk_cell, read_nb, write_nb
from fastcore.foundation import Config
from nbdev.read import create_output, show_src

Start a new workspace:

tmpdir.new()

Init a git repo and checkout our main branch – this is where we’ll run our tests:

git init
Initialized empty Git repository in /private/var/folders/ft/0gnvc3ts5jz4ddqtttp6tjvm0000gn/T/tmpggvpu_sy/.git/
git checkout -b main
Switched to a new branch 'main'

Install nbdev hooks:

nbdev_install_hooks
Hooks are installed.

Next, we’ll simulate a merge conflict. First we add random.ipynb notebook to main. Here’s what it looks like to start with:

fn = 'random.ipynb'
meta = {'nbformat': 4,'metadata':{'kernelspec':{'display_name':'Python 3','language': 'python','name': 'python3'}}}
base = dict2nb({'cells':[mk_cell('import random'), mk_cell('random.random()')], **meta})
base.cells[-1].output = create_output('0.3314001088639852\n0.20280244713400464', 'plain')
write_nb(base, fn)
show_nb(fn)
# random.ipynb

# %%
import random

# %%
random.random()
# 0.3314001088639852
# 0.20280244713400464

Commit it:

git add .
git commit -q -m 'add random.ipynb'

Next, checkout a new branch add-heading:

git checkout -b add-heading
Switched to a new branch 'add-heading'

Make a change. We added a new markdown cell Calculate a random number:, imported os, and got different random.random() outputs – the perfect recipe for a merge conflict:

ours = deepcopy(base)
ours.cells[0].source+=',os' # Change first cell
ours.cells.insert(1, mk_cell('Calculate a random number:', cell_type='markdown')) # New cell
ours.cells[-1].output = create_output('0.3379097372590093\n0.7379492349993123', 'plain') # Change outputs
write_nb(ours, fn)
show_nb(fn)
# random.ipynb

# %%
import random,os

# %%
Calculate a random number:

# %%
random.random()
# 0.3379097372590093
# 0.7379492349993123

Commit it:

git commit -am heading
[add-heading 5b71b21] heading
 1 file changed, 10 insertions(+), 3 deletions(-)

Go back to main:

git checkout main
Switched to branch 'main'

Make a different change:

show_nb(fn)
# random.ipynb

# %%
# Random numbers

# %%
import random,sys

# %%
random.random()
# 0.6587181429602441
# 0.5962200692415515

Commit it:

git commit -am docs
[main d3b0252] docs
 1 file changed, 10 insertions(+), 3 deletions(-)

And finally try to merge:

git merge add-heading
One or more conflict remains in the notebook, please inspect manually.
Auto-merging random.ipynb
CONFLICT (content): Merge conflict in random.ipynb
Automatic merge failed; fix conflicts and then commit the result.

We have a merge conflict! But thanks to nbdev:

  1. Conflicting outputs are automatically resolved
  2. The notebook is left in a readable state.
show_nb(fn)
# random.ipynb

# %%
# Random numbers

# %%
`<<<<<<< HEAD`

# %%
import random,sys

# %%
`=======`

# %%
import random,os

# %%
Calculate a random number:

# %%
`>>>>>>> add-heading`

# %%
random.random()
# 0.6587181429602441
# 0.5962200692415515

Close tmpdir, and you’re back to your original working directory:

tmpdir.close()
Path.cwd()