dirlay

Directory layout object for testing and documentation

license pypi python versions tests coverage tested with multipython uses docsub mypy uv Ruff

Features

  • Create directory tree and files from Python dict

  • Chdir to tree subdirectories

  • Display as rich tree for documentation

  • Developer friendly syntax:

    • reference nodes by paths: tree['a/b.md']

    • get sub-paths: tree / 'a/b.md' (relative), tree // 'a/b.md' (absolute)

    • add, update, delete nodes: tree |= {'d': {}}, del tree['a']

    • create tree under given or temporary directory

    • contextmanager interface to unlink tree on exit

  • Fully typed

  • Python 2 support (using pathlib2)

Installation

$ pip install dirlay[rich]
$ uv add dirlay[rich]

Use cases

>>> from dirlay import Dir

Define directory structure and files content:

>>> tree = Dir({'a': {'b/c.txt': 'ccc', 'd.txt': 'ddd'}})
>>> tree.data == {'a': {'b': {'c.txt': 'ccc'}, 'd.txt': 'ddd'}}
True
>>> tree / 'a/b/c.txt'
PosixPath('a/b/c.txt')
>>> tree['a/b/c.txt']
<Node 'a/b/c.txt': 'ccc'>
>>> 'z.txt' in tree
False

Content of files and directories can be updated:

>>> tree |= {'a/d.txt': {'e.txt': 'eee'}}
>>> tree['a/b/c.txt'].data *= 2
>>> tree.root()
<Node '.': {'a': {...}}>
>>> tree.data == {'a': {'b': {'c.txt': 'cccccc'}, 'd.txt': {'e.txt': 'eee'}}}
True

Instantiate on the file system (in temporary directory by default) and remove when exiting the context.

>>> with tree.mktree():
...     assert getcwd() != tree.basedir  # cwd not changed
...     str(tree['a/b/c.txt'].abspath.read_text())
'cccccc'

Optionally, change current working directory to a layout subdir, and change back after context manager is exited.

>>> with tree.mktree(chdir='a/b'):
...     assert getcwd() == tree.basedir / 'a/b'
...     str(Path('c.txt').read_text())
'cccccc'

Create directory layout tree

Directory layout can be constructed from dict:

>>> tree = Dir({'a': {'b/c.txt': 'ccc', 'd.txt': 'ddd'}})
>>> tree.basedir is None
True
>>> tree.mktree()
<Dir '/tmp/...': {'a': ...}>
>>> tree.basedir
PosixPath('/tmp/...')

And remove when not needed anymore:

>>> tree.rmtree()

Chdir to subdirectory

>>> import os
>>> os.chdir('/tmp')

When layout is instantiated, current directory remains unchanged:

>>> tree = Dir({'a/b/c.txt': 'ccc'})
>>> tree.mktree()
<Dir '/tmp/...': {'a': {'b': {'c.txt': 'ccc'}}}>
>>> getcwd()
PosixPath('/tmp')

On first chdir, initial working directory is stored internally, and will be restored on destroy. Without argument, chdir sets current directory to tree.basedir.

>>> tree.basedir
PosixPath('/tmp/...')
>>> tree.chdir()
>>> getcwd()
PosixPath('/tmp/...')

If chdir has argument, it must be a path relative to basedir.

>>> tree.chdir('a/b')
>>> getcwd()
PosixPath('/tmp/.../a/b')

When directory is removed, current directory is restored:

>>> tree.rmtree()
>>> getcwd()
PosixPath('/tmp')