diff --git a/docs/jupyter/tutorial.ipynb b/docs/jupyter/tutorial.ipynb
new file mode 100644
index 0000000..390428c
--- /dev/null
+++ b/docs/jupyter/tutorial.ipynb
@@ -0,0 +1,310 @@
+{
+ "cells": [
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Nutree Tutorial"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Nutree organizes arbitrary object instances in an unobtrusive way.
\n",
+ "That means, we can add existing objects without havin to derrive from a common \n",
+ "base class or having them implement a specific protocol."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Setup some sample classes and objects"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 31,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import uuid\n",
+ "\n",
+ "\n",
+ "class Department:\n",
+ " def __init__(self, name: str):\n",
+ " self.guid = uuid.uuid4()\n",
+ " self.name = name\n",
+ "\n",
+ " def __str__(self):\n",
+ " return f\"Department<{self.name}>\"\n",
+ "\n",
+ "\n",
+ "class Person:\n",
+ " def __init__(self, name: str, age: int):\n",
+ " self.guid = uuid.uuid4()\n",
+ " self.name = name\n",
+ " self.age = age\n",
+ "\n",
+ " def __str__(self):\n",
+ " return f\"Person<{self.name} ({self.age})>\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Now create some instances"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 32,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "development_dep = Department(\"Development\")\n",
+ "test__dep = Department(\"Test\")\n",
+ "marketing_dep = Department(\"Marketing\")\n",
+ "\n",
+ "alice = Person(\"Alice\", 25)\n",
+ "bob = Person(\"Bob\", 35)\n",
+ "claire = Person(\"Claire\", 45)\n",
+ "dave = Person(\"Dave\", 55)"
+ ]
+ },
+ {
+ "attachments": {},
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Let's organize these objects in a hierarchical structure:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 33,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tree<'Organization'>\n",
+ "├── <__main__.Department object at 0x111f04e00>\n",
+ "│ ├── <__main__.Department object at 0x111b89f70>\n",
+ "│ │ ╰── <__main__.Person object at 0x111f05520>\n",
+ "│ ╰── <__main__.Person object at 0x111f04da0>\n",
+ "├── <__main__.Department object at 0x111edac90>\n",
+ "│ ╰── <__main__.Person object at 0x111f06a50>\n",
+ "╰── <__main__.Person object at 0x111ed9880>\n"
+ ]
+ }
+ ],
+ "source": [
+ "from nutree import Tree\n",
+ "\n",
+ "tree = Tree(\"Organization\")\n",
+ "\n",
+ "dev_node = tree.add(development_dep)\n",
+ "test_node = dev_node.add(test__dep)\n",
+ "mkt_node = tree.add(marketing_dep)\n",
+ "\n",
+ "tree.add(alice)\n",
+ "dev_node.add(bob)\n",
+ "test_node.add(claire)\n",
+ "mkt_node.add(dave)\n",
+ "\n",
+ "tree.print()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Tree nodes store a reference to the object in the `node.data` attribute.\n",
+ "\n",
+ "The nodes are formatted by the object's `__repr__` implementation by default.
\n",
+ "We can overide ths by passing an f-string as `repr` argument:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 34,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tree<'Organization'>\n",
+ "├── Department\n",
+ "│ ├── Department\n",
+ "│ │ ╰── Person\n",
+ "│ ╰── Person\n",
+ "├── Department\n",
+ "│ ╰── Person\n",
+ "╰── Person\n"
+ ]
+ }
+ ],
+ "source": [
+ "tree.print(repr=\"{node.data}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Iteration and Searching"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Mutation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Data IDs and Clones"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 35,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Node<'Department', data_id=287245536>\n",
+ "├── Node<'Department', data_id=287017463>\n",
+ "│ ╰── Node<'Person', data_id=287245650>\n",
+ "╰── Node<'Person', data_id=287245530>\n",
+ "Node<'Department', data_id=287234761>\n",
+ "╰── Node<'Person', data_id=287245989>\n",
+ "Node<'Person', data_id=287234440>\n"
+ ]
+ }
+ ],
+ "source": [
+ "tree.print(repr=\"{node}\", title=False)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Serialization"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 36,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[{'data': 'Department',\n",
+ " 'children': [{'data': 'Department',\n",
+ " 'children': [{'data': 'Person'}]},\n",
+ " {'data': 'Person'}]},\n",
+ " {'data': 'Department',\n",
+ " 'children': [{'data': 'Person'}]},\n",
+ " {'data': 'Person'}]"
+ ]
+ },
+ "execution_count": 36,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "tree.to_dict_list()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 37,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[(0, {}), (1, {}), (2, {}), (1, {}), (0, {}), (5, {}), (0, {})]"
+ ]
+ },
+ "execution_count": 37,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "list(tree.to_list_iter())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 38,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Tree<'4595934048'>\n",
+ "╰── 'A'\n"
+ ]
+ }
+ ],
+ "source": [
+ "t = Tree._from_list([(0, \"A\")])\n",
+ "print(t.format())"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": ".venv",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.6"
+ },
+ "orig_nbformat": 4
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/docs/tutorial.ipynb b/docs/tutorial.ipynb
deleted file mode 100644
index 787c2cd..0000000
--- a/docs/tutorial.ipynb
+++ /dev/null
@@ -1,157 +0,0 @@
-{
- "cells": [
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Nutree Tutorial"
- ]
- },
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Start by creating an instance of the `Tree` class:"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Tree<'Demo'>\n",
- "╰── 'A'\n",
- " ├── 'a'\n",
- " ╰── 'b'\n"
- ]
- }
- ],
- "source": [
- "from nutree import Tree\n",
- "\n",
- "tree = Tree(\"Demo\")\n",
- "\n",
- "node_a = tree.add(\"A\")\n",
- "node_a.add(\"a\")\n",
- "node_a.add(\"b\")\n",
- "tree.print()\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[{'data': 'A', 'children': [{'data': 'a'}, {'data': 'b'}]}]"
- ]
- },
- "execution_count": 3,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "tree.to_dict_list()\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 4,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "[(0, 'A'), (1, 'a'), (1, 'b')]"
- ]
- },
- "execution_count": 4,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "list(tree.to_list_iter())\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Node<'A', data_id=-6155505832273219541>\n",
- "├── Node<'a', data_id=1939327296338879458>\n",
- "╰── Node<'b', data_id=6853019497100189332>\n"
- ]
- }
- ],
- "source": [
- "print(tree.format(repr=\"{node}\", title=False))\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 7,
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Tree<'4407606288'>\n",
- "╰── 'A'\n"
- ]
- }
- ],
- "source": [
- "t = Tree._from_list([(0, \"A\")])\n",
- "print(t.format())\n"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "interpreter": {
- "hash": "f600cddd0c1baa98c0960b46a2f454b2dd6263de5ae4ff509241484089717206"
- },
- "kernelspec": {
- "display_name": "Python 3.9.1 64-bit ('nutree-usdBtVUd': pipenv)",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.11.1"
- },
- "orig_nbformat": 4
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/pyproject.toml b/pyproject.toml
index 2b172f9..f61843a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,6 +20,12 @@ ignore = [
"E721", # Do not compare types, use `isinstance()`
]
+[tool.ruff.lint.per-file-ignores]
+"*.ipynb" = [ # Jupyter Notebooks
+ "T20", # print statement
+ "E402", # module level import not at top of file
+]
+
# [tool.ruff.lint.isort]
# case-sensitive = true
diff --git a/tox.ini b/tox.ini
index 9d64302..cfcbd0a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -66,8 +66,8 @@ deps =
ruff
commands =
ruff -V
- ruff check nutree tests setup.py
- ruff format --check nutree tests setup.py
+ ruff check nutree tests docs/jupyter setup.py
+ ruff format --check nutree tests docs/jupyter setup.py
[testenv:format]
@@ -77,8 +77,8 @@ deps =
ruff
changedir = {toxinidir}
commands =
- ruff check --fix nutree tests setup.py
- ruff format nutree tests setup.py
+ ruff check --fix nutree tests docs/jupyter setup.py
+ ruff format nutree tests docs/jupyter setup.py
{[testenv:lint]commands}