"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"diagram = \"\"\"\n",
"flowchart TD\n",
" A[pip install dj-notebook]\n",
" B[from dj_notebook import activate]\n",
" C[\"plus = activate()\"]\n",
" D[\"plus.mermaid(diagram)\"]\n",
" \n",
" A -->|wait a few seconds| B\n",
" B -.-> C\n",
" C -.-> D\n",
"\"\"\"\n",
"\n",
"plus.mermaid(diagram)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Printing what dj-notebook loads from django-extensions\n",
"\n",
"_Vastly improved in dj-notebook 0.4.0_\n",
"\n",
"There are two ways to get a list of the loaded items by dj-notebook's `activate()` function:\n",
"\n",
"```python\n",
"# Print all the objects to the screen on activate\n",
"plus = activate(quiet_load=False)\n",
"# Print the objects to the screen at any time\n",
"plus.print()\n",
"```\n",
"\n",
"Here is `plus.print()` in action:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"# Shell Plus Model Imports \n",
"from book_outlet.models import Address, Author, Book, Country \n",
"from django.contrib.admin.models import LogEntry \n",
"from django.contrib.auth.models import Group, Permission, User \n",
"from django.contrib.contenttypes.models import ContentType \n",
"from django.contrib.sessions.models import Session \n",
"# Shell Plus Django Imports \n",
"from django.core.cache import cache \n",
"from django.conf import settings \n",
"from django.contrib.auth import get_user_model \n",
"from django.db import transaction \n",
"from django.db.models import Avg, Case, Count, F, Max, Min, Prefetch, Q, Sum, When \n",
"from django.utils import timezone \n",
"from django.urls import reverse \n",
"from django.db.models import Exists, OuterRef, Subquery \n",
" \n",
"\n"
],
"text/plain": [
"\u001b[38;2;149;144;119;48;2;39;40;34m# Shell Plus Model Imports\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mbook_outlet\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mAddress\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mAuthor\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mBook\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mCountry\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34madmin\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mLogEntry\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mauth\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mGroup\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mPermission\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mUser\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontenttypes\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mContentType\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34msessions\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mSession\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;149;144;119;48;2;39;40;34m# Shell Plus Django Imports\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcore\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcache\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcache\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mconf\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34msettings\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mauth\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mget_user_model\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdb\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mtransaction\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdb\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mAvg\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mCase\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mCount\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mF\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mMax\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mMin\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mPrefetch\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mQ\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mSum\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mWhen\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mutils\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mtimezone\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34murls\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mreverse\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdb\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mExists\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mOuterRef\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mSubquery\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[48;2;39;40;34m \u001b[0m\n"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plus.print()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Dataframes from CSVs\n",
"\n",
"_New in dj-notebook 0.7.0_\n",
"\n",
"This turns strings or files on defined paths into Dataframes.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"\n",
"
\n",
" \n",
" \n",
" | \n",
" Name | \n",
" FirstLetter | \n",
"
\n",
" \n",
" \n",
" \n",
" | 0 | \n",
" Daniel | \n",
" D | \n",
"
\n",
" \n",
" | 1 | \n",
" Audrey | \n",
" A | \n",
"
\n",
" \n",
"
\n",
"
"
],
"text/plain": [
" Name FirstLetter\n",
"0 Daniel D\n",
"1 Audrey A"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"csv_string = \"\"\"Name,FirstLetter\n",
"Daniel,D\n",
"Audrey,A\"\"\"\n",
"\n",
"# Also works with plus.csv_to_df(pathlib.path('path/to/data.csv'))\n",
"plus.csv_to_df(csv_string)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "dj-notebook",
"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.10.6"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: mkdocs.yml
================================================
---
site_name: dj-notebook
repo_url: https://github.com/pydanny/dj-notebook
theme:
name: material
features:
- navigation.instant
- navigation.instant.prefetch
- search.suggest
- search.highlight
- search.share
logo: img/dj-notebook-logo.png
icon:
repo: fontawesome/brands/github
markdown_extensions:
- admonition
- pymdownx.details
- pymdownx.superfences
plugins:
- mkdocs-jupyter:
remove_tag_config:
remove_input_tags: [hide_code]
- include-markdown:
start:
end:
- search
- social
- mkdocstrings:
handlers:
python:
options:
extensions: [griffe_typingdoc]
show_root_heading: true
show_if_no_docstring: true
inherited_members: true
members_order: source
separate_signature: true
unwrap_annotated: true
merge_init_into_class: true
docstring_section_style: spacy
signature_crossrefs: true
show_symbol_type_heading: true
show_symbol_type_toc: true
nav:
- Introduction: index.md
- Installation:
- Installation: installation.md
- Using with PyCharm: pycharm.md
- Activation: activation.md
- Usage: usage.ipynb
- Reference (Code API): [reference/index.md, reference/plus.md]
- Contributing: contributing.md
- Releasing: releasing.md
- Changelog: changelog.md
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "dj_notebook"
version = "0.7.0"
description = "Django + shell_plus + Jupyter notebooks made easy"
readme = "README.md"
authors = [
{name = "Daniel Roy Greenfeld", email = "daniel@feldroy.com"},
{name = "Anna Zhydko", email = "anna.zhydko@krakentechnologies.ltd"}
]
maintainers = [
{name = "Daniel Roy Greenfeld", email = "daniel@feldroy.com"},
{name = "Anna Zhydko", email = "anna.zhydko@krakentechnologies.ltd"}
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Framework :: Django :: 4.0",
"Framework :: Django :: 4.1",
"Framework :: Django :: 4.2",
"Framework :: Django :: 5.0",
"Framework :: Jupyter",
"License :: OSI Approved :: GNU General Public License (GPL)",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12"
]
license = {text = "GNU General Public License v3"}
dependencies = [
"django",
"django-extensions",
"django-pandas",
"django-schema-graph",
"ipython",
"jupyter",
"pandas",
"rich",
"python-dotenv"
]
[project.optional-dependencies]
dev = [
"black", # code auto-formatting
"black[jupyter]",
"coverage", # testing
"griffe-typingdoc==0.2.2",
"mkdocs-material",
"mkdocs-jupyter",
"mkdocs-material[imaging]",
"mkdocs-include-markdown-plugin",
"mkdocstrings[python]>=0.18",
"mypy", # linting
"pytest", # testing
"ruff", # linting
"yamlfix" # fixing the YAML
]
[project.urls]
bugs = "https://github.com/pydanny/dj-notebook/issues"
changelog = "https://github.com/pydanny/dj-notebook/blob/master/CHANGELOG.md"
homepage = "https://github.com/pydanny/dj-notebook"
documentation = "https://dj-notebook.readthedocs.io/"
[tool.setuptools]
package-dir = {"" = "src"}
# Mypy
# ----
[tool.mypy]
files = "."
exclude = [
"tests/*"
]
# Use strict defaults
strict = true
warn_unreachable = true
warn_no_return = true
[[tool.mypy.overrides]]
# Don't require test functions to include types
module = "tests.*"
allow_untyped_defs = true
disable_error_code = "attr-defined"
# Ruff
# ----
[tool.ruff]
select = [
"E", # pycodestyle
"F", # pyflakes
"I", # isort
]
ignore = [
"E501", # line too long - black takes care of this for us
]
[tool.ruff.per-file-ignores]
# Allow unused imports in __init__ files as these are convenience imports
"**/__init__.py" = [ "F401" ]
[tool.ruff.isort]
lines-after-imports = 2
section-order = [
"future",
"standard-library",
"third-party",
"first-party",
"project",
"local-folder",
]
[tool.ruff.isort.sections]
"project" = [
"src",
"tests",
]
================================================
FILE: src/dj_notebook/__init__.py
================================================
import importlib
import os
import sys
import warnings
from pathlib import Path
import django
from django.conf import settings as django_settings
from django.core.exceptions import ImproperlyConfigured
from django.core.management.color import no_style
from django_extensions.management import shells
from IPython.utils.capture import capture_output
from rich.status import Status
from .config_helper import StrPath, find_django_settings_module
from .shell_plus import Plus
__version__ = "0.7.0"
def activate(
settings: str | None = None,
quiet_load: bool = True,
*,
dotenv_file: StrPath | None = None,
search_dir: StrPath | None = None,
) -> Plus:
with Status(
"Loading dj-notebook...\n Use Plus.print() to see what's been loaded.",
spinner="bouncingBar",
):
if settings:
# If the caller specified a settings module explicitly, use that
os.environ["DJANGO_SETTINGS_MODULE"] = settings
else:
source, discovered_settings = find_django_settings_module(
dotenv_file=dotenv_file,
search_dir=search_dir,
)
if discovered_settings:
if not quiet_load:
print(
f"Using {discovered_settings} as DJANGO_SETTINGS_MODULE, discovered from {source}"
)
os.environ["DJANGO_SETTINGS_MODULE"] = discovered_settings
try:
_ = importlib.util.find_spec(discovered_settings)
except ModuleNotFoundError:
source_path = Path(source)
if source.endswith("manage.py") and source_path.is_file():
source_dir = Path(source).parent.absolute()
warnings.warn(
f"{discovered_settings} from {source} could not be loaded. Adding {str(source_dir)} to search path."
)
sys.path.append(str(source_dir))
else:
raise ImproperlyConfigured(
"DJANGO_SETTINGS_MODULE was not specified and could not be discovered."
)
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
try:
django.setup()
except ModuleNotFoundError as e:
raise ImproperlyConfigured(
f"DJANGO_SETTINGS_MODULE {e.name} could not be loaded in django.setup()"
)
with capture_output() as c:
plus = Plus(shells.import_objects({"quiet_load": False}, no_style()))
plus._import_object_history = c.stdout
# Log a warning message when DEBUG is set to False
if not plus.settings.DEBUG:
warnings.warn("Django is running in production mode with dj-notebook.")
if quiet_load is False:
plus.print()
return plus
================================================
FILE: src/dj_notebook/config_helper.py
================================================
import ast
import os
from pathlib import Path
from typing import Generator, Tuple
from dotenv import load_dotenv
# taken from dotenv, which declares a similar type (but it doesn't look public...)
# review note: the | syntax is new in python 3.10. If older pythons are generally being supported here, this should be
# rewritten as Union[str, os.PathLike[str]]
StrPath = str | os.PathLike[str]
def setdefault_calls(module_path: Path) -> Generator[ast.Call, None, None]:
"""Yields all calls to `os.environ.setdefault` within a module."""
with open(module_path, "r") as module_src:
parsed_module = ast.parse(module_src.read())
environ_id = None
for node in ast.walk(parsed_module):
if isinstance(node, ast.ImportFrom) and node.module == "os":
for name in node.names:
if isinstance(name, ast.alias) and name.name == "environ":
environ_id = name.asname if name.asname is not None else name.name
if (
isinstance(node, ast.Call)
and isinstance(node.func, ast.Attribute)
and node.func.attr == "setdefault"
):
if (
isinstance(node.func.value, ast.Attribute)
and node.func.value.attr == "environ"
):
if (
isinstance(node.func.value.value, ast.Name)
and node.func.value.value.id == "os"
):
yield node
elif (
isinstance(node.func.value, ast.Name)
and node.func.value.id == environ_id
):
yield node
def is_root(path: Path) -> bool:
"""
returns True if the supplied path is the root directory. This is only here because it seems clearer than
`path.samefile(path.parent)` when reading a loop that walks up a directory hierarchy.
"""
return path.samefile(path.parent)
# review note: the | syntax is new in python 3.10. If older pythons are generally being supported here, the type for
# dotenv_file should be rewritten as Optional[StrPath] = None and the return type should be annotated as
# Tuple[str, Optional[str]]
def find_django_settings_module(
*,
dotenv_file: StrPath | None = None,
search_dir: StrPath | None = None,
) -> Tuple[str, str | None]:
"""
Find the name of the first settings module from the environment or the closest `manage.py` file.
Returns: a tuple(source, module name) telling the caller where the module was found and the name of the module.
Optional keyword-only arguments:
dotenv_file: Absolute or relative path to .env file, loaded prior to searching the environment.
search_dir: Absolute or relative path to the directory to start searching for a `manage.py` file, used if
`dotenv_file` is `None`.
If both `dotenv_file` and `search_dir` are `None`, the environment variable DJANGO_SETTINGS_MODULE is checked,
and the current working directory (and its parents and immediate subdirectories) is searched for a `manage.py` file.
"""
settings_module = None
# First see if this has either already been set in the environment or put in a .env file that python-dotenv will
# treat that way
if dotenv_file:
# load with override=True if the caller has specified a dotenv file explicitly
source = "dotenv"
load_dotenv(dotenv_file, override=bool(dotenv_file))
settings_module = os.environ.get("DJANGO_SETTINGS_MODULE", None)
elif not search_dir:
source = "environment"
settings_module = os.environ.get("DJANGO_SETTINGS_MODULE", None)
# If we get nothing from the environment, look for a `manage.py` script containing a call that sets a default in the
# search directory, the current working directory, or any parent. This should accommodate the common pattern of
# - app1
# - app2
# - project
# --> settings.py
# - scripts
# - notebooks
# --> analysis_notebook.ipynb
# - manage.py
current_search_dir = Path(search_dir or Path.cwd()).resolve()
while settings_module is None:
manage_py = current_search_dir / "manage.py"
if manage_py.is_file():
for call in setdefault_calls(manage_py):
if (
len(call.args) == 2
and call.args[0].value == "DJANGO_SETTINGS_MODULE"
):
settings_module = call.args[1].value
source = f"{manage_py.resolve().absolute()}"
elif is_root(current_search_dir):
break
else:
current_search_dir = current_search_dir.parent.resolve()
if not settings_module:
# Finally, go one level down into children of the search directory to see if a `manage.py` with a default
# for `DJANGO_SETTINGS_MODULE` can be found there. This accommodates the common pattern of
# - analysis.ipynb
# - src
# --> manage.py
# --> project
# ----> settings.py
# ...
for p in [
Path(subdir)
for subdir in os.scandir(Path(search_dir or Path.cwd()).resolve())
]:
manage_py = p / "manage.py"
if manage_py.is_file():
for call in setdefault_calls(manage_py):
if (
len(call.args) == 2
and call.args[0].value == "DJANGO_SETTINGS_MODULE"
):
settings_module = call.args[1].value
source = manage_py.resolve().absolute()
break
return str(source), settings_module
================================================
FILE: src/dj_notebook/shell_plus.py
================================================
"""
This module is intended to be imported at the beginning of a jupyter notebook
to enable access to django objects with everything from django-extensions'
shell_plus command and other utilities.:
from dj_notebook import activate
plus = activate
As it accesses the database, it requires that:
- The database is running in the background
- The database connection variables are correctly configured
"""
import base64
import io
import pathlib
import typing
import IPython
import pandas as pd
from django.db import models as django_models
from django.db.models.query import QuerySet
from django.utils.functional import cached_property
from django_pandas.io import read_frame
from IPython.display import display
from rich.console import Console
from rich.status import Status
from rich.syntax import Syntax
from schema_graph import schema
console = Console()
def display_mermaid(graph: str) -> None:
"""Renders the display with Mermaid."""
graphbytes = graph.encode("ascii")
base64_bytes = base64.b64encode(graphbytes)
base64_string = base64_bytes.decode("ascii")
display(IPython.display.Image(url="https://mermaid.ink/img/" + base64_string))
class DiagramClass:
"""This class draws a class diagram for a given class and its ancestors."""
def __init__(self, base_class: type) -> None:
self.base_class = base_class
# To avoid duplicates the graph is a set
self.graph = set()
# Add the base_class to the graph
self.graph.add(
f' class {self.namify(self.base_class)}["{self.base_class.__module__}::{self.base_class.__name__}"]' # noqa: E501
)
# Draw connections between the base_class and its ancestors
self.draw_connections(self.base_class)
# Convert the set to a \n-seperated text file prefixed
# with the classDiagram keyword from mermaidjs
text = "classDiagram\n" + "\n".join(self.graph)
# Use Mermaid to render the graph and Ipthon to display it
display_mermaid(text)
def draw_connections(self, class_: type) -> None:
"""Draw connections between a class and its ancestors,
includes nodes and edges."""
for base in class_.__bases__:
if base is not object:
base_name = self.namify(base)
self.graph.add(
f' class {base_name}["{base.__module__}::{base.__name__}"]'
)
connection = f" {base_name} <|-- {self.namify(class_)}"
self.graph.add(connection)
self.draw_connections(base)
def namify(self, class_: object) -> str:
"""This provides a node name that keeps Mermaid happy."""
return f"{class_.__module__}_{class_.__name__}".replace(".", "_")
class Plus:
"""Location of all the objects loaded by shell_plus and extra
Jupyter-specific utilities."""
def __init__(self, helpers: dict[str, object]) -> None:
self.helpers = helpers
def __getattribute__(self, name: str) -> object:
try:
return object.__getattribute__(self, name)
except AttributeError:
helpers = object.__getattribute__(self, "helpers")
if name in helpers:
return helpers[name]
else:
raise
def diagram(self, class_: object) -> None:
"""Draw a class diagram for a given class and its ancestors."""
if not isinstance(class_, type):
class_ = type(class_)
DiagramClass(class_)
def print(self) -> None:
"""Print all the objects contained by the Plus object."""
console.print(Syntax(self._import_object_history, "python"))
def read_frame(self, qs: QuerySet) -> pd.DataFrame:
"""Converts a Django QuerySet into a Pandas DataFrame."""
return read_frame(qs)
def mermaid(self, diagram: str) -> None:
"""Render a mermaid diagram."""
display_mermaid(diagram)
@cached_property
def model_graph_schema(self) -> dict[typing.Any, typing.Any]:
"""Cached property for the graph data."""
with Status(
"Converting the models into a schema graph...",
spinner="bouncingBar",
):
graph = schema.get_schema()
return graph
def model_graph(self, model: django_models.Model, max_nodes: int = 20) -> None:
"""Draw a diagram of the specified model in the database."""
edges = get_edges_for_model(self.model_graph_schema, model)
if len(edges) > max_nodes:
console.print(
f"[red bold]Warning: Model {model} has more than {max_nodes} nodes. "
"The diagram may be too large to render."
)
output = """flowchart TD\n"""
for edge in edges:
output += (
f" {edge.source.split('.')[-1]} --- {edge.target.split('.')[-1]}\n"
)
display_mermaid(output)
def csv_to_df(self, filepath_or_string: pathlib.Path | str) -> pd.DataFrame:
"""Read a CSV file into a Pandas DataFrame."""
# Process as a Path object
if isinstance(filepath_or_string, pathlib.Path):
return pd.read_csv(filepath_or_string)
# Process as a string, which we convert to a filebuffer
buffer = io.StringIO(filepath_or_string)
return pd.read_csv(buffer)
def get_node_for_model(graph, model: django_models.Model):
try:
return next(
filter(lambda x: x.id == schema.get_model_id(model), graph.nodes), None
)
except StopIteration:
raise Exception("Model not found in graph")
def get_edges_for_model(graph, model: django_models.Model):
node = get_node_for_model(graph, model)
return list(
filter(lambda x: x.source == node.id or x.target == node.id, graph.edges)
)
================================================
FILE: tests/__init__.py
================================================
================================================
FILE: tests/config_debug_false.py
================================================
# This very incomplete configuration allows unit tests to take the same path as a standard call
# to activate(). This one enables the test for a warning when DEBUG == False to pass.
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
USE_TZ = True
DEBUG = False
================================================
FILE: tests/config_test_harness.py
================================================
# This very incomplete configuration allows unit tests to take the same path as a standard call
# to activate()
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
USE_TZ = True
DEBUG = True
================================================
FILE: tests/django_test_project/book_outlet/__init__.py
================================================
================================================
FILE: tests/django_test_project/book_outlet/admin.py
================================================
from django.contrib import admin
from .models import Address, Author, Book, Country
# Register your models here.
class BookAdmin(admin.ModelAdmin):
prepopulated_fields = {"slug": ("title",)}
list_filter = (
"title",
"rating",
)
list_display = (
"title",
"author",
)
admin.site.register(Book, BookAdmin)
admin.site.register(Author)
admin.site.register(Address)
admin.site.register(Country)
================================================
FILE: tests/django_test_project/book_outlet/apps.py
================================================
from django.apps import AppConfig
class BookOutletConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "book_outlet"
================================================
FILE: tests/django_test_project/book_outlet/migrations/0001_initial.py
================================================
# Generated by Django 4.2.2 on 2023-06-14 11:14
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="Book",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("title", models.CharField(max_length=50)),
("rating", models.IntegerField()),
],
),
]
================================================
FILE: tests/django_test_project/book_outlet/migrations/0002_book_author_book_is_bestselling_alter_book_rating.py
================================================
# Generated by Django 4.2.2 on 2023-06-14 16:20
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("book_outlet", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="book",
name="author",
field=models.CharField(max_length=100, null=True),
),
migrations.AddField(
model_name="book",
name="is_bestselling",
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name="book",
name="rating",
field=models.IntegerField(
validators=[
django.core.validators.MinValueValidator(1),
django.core.validators.MaxValueValidator(5),
]
),
),
]
================================================
FILE: tests/django_test_project/book_outlet/migrations/0003_book_slug.py
================================================
# Generated by Django 4.2.2 on 2023-06-28 10:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("book_outlet", "0002_book_author_book_is_bestselling_alter_book_rating"),
]
operations = [
migrations.AddField(
model_name="book",
name="slug",
field=models.SlugField(default=""),
),
]
================================================
FILE: tests/django_test_project/book_outlet/migrations/0004_author_alter_book_slug_alter_book_author.py
================================================
# Generated by Django 4.2.2 on 2023-07-05 11:09
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("book_outlet", "0003_book_slug"),
]
operations = [
migrations.CreateModel(
name="Author",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("first_name", models.CharField(max_length=100)),
("last_name", models.CharField(max_length=100)),
],
),
migrations.AlterField(
model_name="book",
name="slug",
field=models.SlugField(blank=True, default=""),
),
migrations.AlterField(
model_name="book",
name="author",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="book_outlet.author",
),
),
]
================================================
FILE: tests/django_test_project/book_outlet/migrations/0005_alter_book_author.py
================================================
# Generated by Django 4.2.2 on 2023-07-05 11:33
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("book_outlet", "0004_author_alter_book_slug_alter_book_author"),
]
operations = [
migrations.AlterField(
model_name="book",
name="author",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="books",
to="book_outlet.author",
),
),
]
================================================
FILE: tests/django_test_project/book_outlet/migrations/0006_address_author_address.py
================================================
# Generated by Django 4.2.2 on 2023-07-12 11:12
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("book_outlet", "0005_alter_book_author"),
]
operations = [
migrations.CreateModel(
name="Address",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("street", models.CharField(max_length=80)),
("postal_code", models.CharField(max_length=5)),
("city", models.CharField(max_length=50)),
],
),
migrations.AddField(
model_name="author",
name="address",
field=models.OneToOneField(
null=True,
on_delete=django.db.models.deletion.CASCADE,
to="book_outlet.address",
),
),
]
================================================
FILE: tests/django_test_project/book_outlet/migrations/0007_country_alter_address_options_and_more.py
================================================
# Generated by Django 4.2.2 on 2023-07-14 09:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("book_outlet", "0006_address_author_address"),
]
operations = [
migrations.CreateModel(
name="Country",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("name", models.CharField(max_length=80)),
("code", models.CharField(max_length=2)),
],
),
migrations.AlterModelOptions(
name="address",
options={"verbose_name_plural": "Address Entries"},
),
migrations.AddField(
model_name="book",
name="published_countries",
field=models.ManyToManyField(to="book_outlet.country"),
),
]
================================================
FILE: tests/django_test_project/book_outlet/migrations/__init__.py
================================================
================================================
FILE: tests/django_test_project/book_outlet/models.py
================================================
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.urls import reverse
from django.utils.text import slugify
class Country(models.Model):
name = models.CharField(max_length=80)
code = models.CharField(max_length=2)
def __str__(self):
return f"{self.name}, {self.code}"
class Meta:
verbose_name_plural = "Countries"
class Address(models.Model):
street = models.CharField(max_length=80)
postal_code = models.CharField(max_length=5)
city = models.CharField(max_length=50)
def __str__(self):
return f"{self.street}, {self.postal_code}, {self.city}"
class Meta:
verbose_name_plural = "Address Entries"
class Author(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
address = models.OneToOneField(Address, on_delete=models.CASCADE, null=True)
def full_name(self):
return f"{self.first_name} {self.last_name}"
def __str__(self):
return self.full_name()
class Book(models.Model):
title = models.CharField(max_length=50)
rating = models.IntegerField(
validators=[MinValueValidator(1), MaxValueValidator(5)]
)
author = models.ForeignKey(
Author, on_delete=models.CASCADE, null=True, related_name="books"
)
is_bestselling = models.BooleanField(default=False)
slug = models.SlugField(default="", blank=True, null=False, db_index=True)
published_countries = models.ManyToManyField(Country)
def get_absolute_url(self):
return reverse("book-detail", args=[self.slug])
def save(self, *args, **kwargs):
self.slug = slugify(self.title)
super().save(*args, **kwargs)
def __str__(self):
return f"{self.title} ({self.rating})"
================================================
FILE: tests/django_test_project/book_outlet/templates/book_outlet/base.html
================================================
{% block title %}{% endblock %}
{% block content %}
{% endblock %}
================================================
FILE: tests/django_test_project/book_outlet/templates/book_outlet/book_detail.html
================================================
{% extends "book_outlet/base.html" %}
{% block title %}
{{ title }}
{% endblock %}
{% block content %}
{{ title }}
{{ author }}
The book has a rating of {{ rating }}
{% if is_bestseller %}
and is a bestseller.
{% else %}
but isn't a bestseller.
{% endif %}
{% endblock %}
================================================
FILE: tests/django_test_project/book_outlet/templates/book_outlet/index.html
================================================
{% extends "book_outlet/base.html" %}
{% block title %} All books {% endblock %}
{% block content %}
Total Nuber of Books: {{ total_number_of_books }}
Average Rating: {{ average_rating.rating__avg }}
{% endblock %}
================================================
FILE: tests/django_test_project/book_outlet/tests.py
================================================
# Create your tests here.
================================================
FILE: tests/django_test_project/book_outlet/urls.py
================================================
from django.urls import path
from . import views
urlpatterns = [
path("", views.index),
path("", views.book_detail, name="book-detail"),
]
================================================
FILE: tests/django_test_project/book_outlet/views.py
================================================
from django.db.models import Avg
from django.http import Http404
from django.shortcuts import get_object_or_404, render
from .models import Book
# Create your views here.
def index(request):
books = Book.objects.all().order_by("-title")
num_books = books.count()
avg_rating = books.aggregate(Avg("rating"))
return render(
request,
"book_outlet/index.html",
{
"books": books,
"total_number_of_books": num_books,
"average_rating": avg_rating,
},
)
def book_detail(request, slug):
try:
book = Book.objects.get(slug=slug)
except Book.DoesNotExist:
raise Http404()
book = get_object_or_404(Book, slug=slug)
return render(
request,
"book_outlet/book_detail.html",
{
"title": book.title,
"author": book.author,
"rating": book.rating,
"is_bestseller": book.is_bestselling,
},
)
================================================
FILE: tests/django_test_project/book_store/__init__.py
================================================
================================================
FILE: tests/django_test_project/book_store/asgi.py
================================================
"""
ASGI config for book_store project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "book_store.settings")
application = get_asgi_application()
================================================
FILE: tests/django_test_project/book_store/settings.py
================================================
"""
Django settings for book_store project.
Generated by 'django-admin startproject' using Django 4.1.7.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-4_!#@9yicst-z-*7mtf026@qp0+modu41si78gg88h_f12n(y1"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
"book_outlet",
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "book_store.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.debug",
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "book_store.wsgi.application"
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "data.sqlite3",
}
}
# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
"NAME": "django.contrib.auth.password_validation."
"UserAttributeSimilarityValidator",
},
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
STATIC_URL = "static/"
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
================================================
FILE: tests/django_test_project/book_store/urls.py
================================================
"""book_store URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.1/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import include, path
urlpatterns = [path("admin/", admin.site.urls), path("", include("book_outlet.urls"))]
================================================
FILE: tests/django_test_project/book_store/wsgi.py
================================================
"""
WSGI config for book_store project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "book_store.settings")
application = get_wsgi_application()
================================================
FILE: tests/django_test_project/example_fk.ipynb
================================================
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "a6a7088ce9fc45e3bb46a3c6dbf1f2cf",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Output()"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n"
],
"text/plain": []
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from dj_notebook import activate\n",
"\n",
"\n",
"plus = activate(\"book_store.settings\")"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n"
],
"text/plain": []
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from rich import print\n",
"\n",
"# print(plus.graph_data)\n",
"plus.model_graph(plus.Book)\n",
"# for model, relations in plus.graph_data.items():\n",
"# print(model, [x for x in relations])"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plus.model_graph(plus.Country)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plus.model_graph(plus.User)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "dj-notebook",
"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.10.6"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: tests/django_test_project/example_long_form.ipynb
================================================
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import django\n",
"import os\n",
"\n",
"os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"book_store.settings\")\n",
"os.environ[\"DJANGO_ALLOW_ASYNC_UNSAFE\"] = \"true\"\n",
"django.setup()"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from book_outlet.models import Book\n",
"\n",
"Book.objects.all()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "dj-notebook",
"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.10.6"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: tests/django_test_project/example_mermaid.ipynb
================================================
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n"
],
"text/plain": []
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from dj_notebook import activate\n",
"\n",
"plus = activate(\"book_store.settings\")"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"diagram = \"\"\"\n",
"flowchart TD\n",
" B(from dj_notebook import activate)\n",
" C[\"plus = activate()\"]\n",
" D(\"plus.mermaid(diagram)\")\n",
" A[pip install dj-notebook] -->|wait a few seconds| B\n",
" B -.-> C\n",
" C -.-> D\n",
"\"\"\"\n",
"\n",
"plus.mermaid(diagram)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "dj-notebook",
"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.10.6"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: tests/django_test_project/example_print.ipynb
================================================
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "eb7342be620e4f32a22efe83e24b19b0",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Output()"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"# Shell Plus Model Imports \n",
"from book_outlet.models import Address, Author, Book, Country \n",
"from django.contrib.admin.models import LogEntry \n",
"from django.contrib.auth.models import Group, Permission, User \n",
"from django.contrib.contenttypes.models import ContentType \n",
"from django.contrib.sessions.models import Session \n",
"# Shell Plus Django Imports \n",
"from django.core.cache import cache \n",
"from django.conf import settings \n",
"from django.contrib.auth import get_user_model \n",
"from django.db import transaction \n",
"from django.db.models import Avg, Case, Count, F, Max, Min, Prefetch, Q, Sum, When \n",
"from django.utils import timezone \n",
"from django.urls import reverse \n",
"from django.db.models import Exists, OuterRef, Subquery \n",
" \n",
"\n"
],
"text/plain": [
"\u001b[38;2;149;144;119;48;2;39;40;34m# Shell Plus Model Imports\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mbook_outlet\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mAddress\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mAuthor\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mBook\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mCountry\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34madmin\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mLogEntry\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mauth\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mGroup\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mPermission\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mUser\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontenttypes\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mContentType\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34msessions\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mSession\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;149;144;119;48;2;39;40;34m# Shell Plus Django Imports\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcore\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcache\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcache\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mconf\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34msettings\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mauth\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mget_user_model\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdb\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mtransaction\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdb\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mAvg\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mCase\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mCount\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mF\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mMax\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mMin\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mPrefetch\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mQ\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mSum\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mWhen\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mutils\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mtimezone\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34murls\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mreverse\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdb\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mExists\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mOuterRef\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mSubquery\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[48;2;39;40;34m \u001b[0m\n"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n"
],
"text/plain": []
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from dj_notebook import activate\n",
"\n",
"plus = activate(\"book_store.settings\", quiet_load=False)"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
", ]>"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"plus.User.objects.all()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"# Shell Plus Model Imports \n",
"from book_outlet.models import Address, Author, Book, Country \n",
"from django.contrib.admin.models import LogEntry \n",
"from django.contrib.auth.models import Group, Permission, User \n",
"from django.contrib.contenttypes.models import ContentType \n",
"from django.contrib.sessions.models import Session \n",
"# Shell Plus Django Imports \n",
"from django.core.cache import cache \n",
"from django.conf import settings \n",
"from django.contrib.auth import get_user_model \n",
"from django.db import transaction \n",
"from django.db.models import Avg, Case, Count, F, Max, Min, Prefetch, Q, Sum, When \n",
"from django.utils import timezone \n",
"from django.urls import reverse \n",
"from django.db.models import Exists, OuterRef, Subquery \n",
" \n",
"\n"
],
"text/plain": [
"\u001b[38;2;149;144;119;48;2;39;40;34m# Shell Plus Model Imports\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mbook_outlet\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mAddress\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mAuthor\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mBook\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mCountry\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34madmin\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mLogEntry\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mauth\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mGroup\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mPermission\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mUser\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontenttypes\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mContentType\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34msessions\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mSession\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;149;144;119;48;2;39;40;34m# Shell Plus Django Imports\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcore\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcache\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcache\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mconf\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34msettings\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mcontrib\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mauth\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mget_user_model\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdb\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mtransaction\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdb\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mAvg\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mCase\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mCount\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mF\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mMax\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mMin\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mPrefetch\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mQ\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mSum\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mWhen\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mutils\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mtimezone\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34murls\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mreverse\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[38;2;255;70;137;48;2;39;40;34mfrom\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdjango\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mdb\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m.\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mmodels\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;255;70;137;48;2;39;40;34mimport\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mExists\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mOuterRef\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m,\u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34m \u001b[0m\u001b[38;2;248;248;242;48;2;39;40;34mSubquery\u001b[0m\u001b[48;2;39;40;34m \u001b[0m\n",
"\u001b[48;2;39;40;34m \u001b[0m\n"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plus.print()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "dj-notebook",
"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.10.6"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: tests/django_test_project/example_short_form.ipynb
================================================
{
"cells": [
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"from dj_notebook import activate\n",
"\n",
"plus = activate(\"book_store.settings\")"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"plus.User.objects.all()"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
""
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from django.contrib.auth import get_user_model\n",
"\n",
"User = get_user_model()\n",
"User.objects.all()"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"
"
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plus.diagram(plus.User)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "dj-notebook",
"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.10.6"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}
================================================
FILE: tests/django_test_project/manage.py
================================================
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "book_store.settings")
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
================================================
FILE: tests/env.django_settings_module
================================================
DJANGO_SETTINGS_MODULE=bip.config
================================================
FILE: tests/fake_manage_alias_environ.py
================================================
from os import environ as os_environ
if __name__ == "__main__":
os_environ.setdefault("DJANGO_SETTINGS_MODULE", "baz.settings")
================================================
FILE: tests/fake_manage_fully_qualified.py
================================================
import os
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "foo.settings")
================================================
FILE: tests/fake_manage_import_environ.py
================================================
from os import environ
if __name__ == "__main__":
environ.setdefault("DJANGO_SETTINGS_MODULE", "bar.settings")
================================================
FILE: tests/fake_other_environ_and_real_environ_setdefault.py
================================================
import os
class Environ:
def setdefault(self, k: str, v: str) -> None:
print(f"{k}={v}")
environ = Environ()
if __name__ == "__main__":
environ.setdefault("DJANGO_SETTINGS_MODULE", "foo.bar.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "foo.bar.os.environ.settings")
================================================
FILE: tests/fake_other_environ_setdefault.py
================================================
class Environ:
def setdefault(self, k: str, v: str) -> None:
print(f"{k}={v}")
environ = Environ()
if __name__ == "__main__":
environ.setdefault("DJANGO_SETTINGS_MODULE", "foo.bar.settings")
================================================
FILE: tests/sample.csv
================================================
Name,Age,Weight
A,1,100
B,2,200
================================================
FILE: tests/test_config_helper.py
================================================
import os
from pathlib import Path
from dj_notebook.config_helper import find_django_settings_module, setdefault_calls
class EnvironmentGuard:
def __enter__(self):
self.original_environment = {}
for k in os.environ.keys():
self.original_environment[k] = os.environ[k]
return None
def __exit__(self, exc_type, exc_val, exc_tb):
for k in os.environ.keys():
del os.environ[k]
for k in self.original_environment.keys():
os.environ[k] = self.original_environment[k]
def test_setdefault_calls_fully_qualified():
script_dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
manage_py = script_dir_path / "fake_manage_fully_qualified.py"
calls = list(setdefault_calls(manage_py))
assert len(calls) == 1
assert calls[0].args[0].value == "DJANGO_SETTINGS_MODULE"
assert calls[0].args[1].value == "foo.settings"
def test_setdefault_calls_import_function():
script_dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
manage_py = script_dir_path / "fake_manage_import_environ.py"
calls = list(setdefault_calls(manage_py))
assert len(calls) == 1
assert calls[0].args[0].value == "DJANGO_SETTINGS_MODULE"
assert calls[0].args[1].value == "bar.settings"
def test_setdefault_calls_import_alias():
script_dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
manage_py = script_dir_path / "fake_manage_alias_environ.py"
calls = list(setdefault_calls(manage_py))
assert len(calls) == 1
assert calls[0].args[0].value == "DJANGO_SETTINGS_MODULE"
assert calls[0].args[1].value == "baz.settings"
def test_setdefault_calls_skips_wrong_function():
script_dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
manage_py = script_dir_path / "fake_other_environ_setdefault.py"
calls = list(setdefault_calls(manage_py))
assert len(calls) == 0
def test_setdefault_calls_skips_wrong_function_finds_right_function():
script_dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
manage_py = script_dir_path / "fake_other_environ_and_real_environ_setdefault.py"
calls = list(setdefault_calls(manage_py))
assert len(calls) == 1
assert calls[0].args[0].value == "DJANGO_SETTINGS_MODULE"
assert calls[0].args[1].value == "foo.bar.os.environ.settings"
def test_find_django_settings_module_dotenv():
script_dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
env_file = script_dir_path / "env.django_settings_module"
with EnvironmentGuard():
source, found = find_django_settings_module(dotenv_file=env_file)
assert source == "dotenv"
assert os.environ["DJANGO_SETTINGS_MODULE"] == found
assert found == "bip.config"
def test_find_django_settings_module_dotenv_overrides():
script_dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
env_file = script_dir_path / "env.django_settings_module"
with EnvironmentGuard():
os.environ["DJANGO_SETTINGS_MODULE"] = "something.else"
source, found = find_django_settings_module(dotenv_file=env_file)
assert source == "dotenv"
assert os.environ["DJANGO_SETTINGS_MODULE"] == found
assert found == "bip.config"
def test_find_django_settings_module_os_environment():
with EnvironmentGuard():
os.environ["DJANGO_SETTINGS_MODULE"] = "something.else"
source, found = find_django_settings_module()
assert source == "environment"
assert os.environ["DJANGO_SETTINGS_MODULE"] == found
assert found == "something.else"
def test_find_django_settings_module_remote_path():
script_dir_path = Path(os.path.dirname(os.path.realpath(__file__)))
django_project_path = script_dir_path / "django_test_project"
old_cwd = os.getcwd()
# Change to a directory that is not the django project or adjacent.
os.chdir(script_dir_path / "..")
with EnvironmentGuard():
source, found = find_django_settings_module(search_dir=django_project_path)
assert source == str(django_project_path / "manage.py")
assert found == "book_store.settings"
# Change to the parent directory in order to search children
source, found = None, None
with EnvironmentGuard():
source, found = find_django_settings_module(
search_dir=django_project_path / ".."
)
assert source == str(django_project_path / "manage.py")
assert found == "book_store.settings"
# Change to a child directory in order to search parents
source, found = None, None
with EnvironmentGuard():
source, found = find_django_settings_module(
search_dir=django_project_path / "book_store"
)
assert source == str(django_project_path / "manage.py")
assert found == "book_store.settings"
os.chdir(old_cwd)
================================================
FILE: tests/test_dj_notebook.py
================================================
import os
from os import environ
from pathlib import Path
from unittest.mock import patch
import django.conf
import pandas
import pytest
from dj_notebook import Plus, activate
from dj_notebook.shell_plus import DiagramClass
from django import conf as django_conf
from django.core.exceptions import ImproperlyConfigured
from dotenv import load_dotenv
class SettingsCleaner:
"""
A context manager that saves and restores the value of `DJANGO_SETTINGS_MODULE` along with resetting
`djang.conf.settings` on both enter and exit, to prevent tests that rely on different django settings
from accidentally depending on each other.
"""
def __enter__(self):
self.old_settings_env = environ.get("DJANGO_SETTINGS_MODULE", None)
if self.old_settings_env is not None:
del environ["DJANGO_SETTINGS_MODULE"]
django_conf.settings = django.conf.LazySettings()
return None
def __exit__(self, exc_type, exc_val, exc_tb):
if self.old_settings_env is not None:
environ["DJANGO_SETTINGS_MODULE"] = self.old_settings_env
elif "DJANGO_SETTINGS_MODULE" in environ:
del environ["DJANGO_SETTINGS_MODULE"]
django.conf.settings = django.conf.LazySettings()
def test_thing():
with SettingsCleaner():
plus = activate("tests.config_test_harness")
# TODO capture STDOUT and assert on it
assert plus.print() is None
def test_namify():
"""
Test the `namify` method of the `DiagramClass`.
Checks if the `namify` method correctly converts the class
name and its module into a format that replaces dots with underscores.
Test covers three scenarios:
1. Built-in classes (e.g., `str`).
2. Custom classes (e.g., `DiagramClass`).
3. Nested classes (e.g., `OuterClass.InnerClass`).
TODO: Maybe add scenarios of periods included in naming to
test conversion
"""
# Create an instance of DiagramClass - include a sample class
diagram = DiagramClass(str)
# Test built-in class
assert diagram.namify(str) == "builtins_str"
# Test custom class
assert diagram.namify(DiagramClass) == "dj_notebook_shell_plus_DiagramClass"
# Test nested class
class OuterClass:
class InnerClass:
pass
assert diagram.namify(OuterClass.InnerClass) == "tests_test_dj_notebook_InnerClass"
def test_draw_connections():
"""
Test the `draw_connections` functionality of the `DiagramClass`.
Verifies that the graph generated by `DiagramClass` correctly
reflects the relationships between a sample class (`SampleClass`) and its
direct base classes (`TestClassA` and `TestClassB`).
TODO: There is an oddity with needing to add 2 leading spaces to the
assertions... look on line 47 in the `draw_connections` definition
which it needs to match.
"""
# Define base classes
class TestClassA:
pass
class TestClassB:
pass
# Create a sample class that inherits from the base classes
class SampleClass(TestClassA, TestClassB):
pass
diagram = DiagramClass(SampleClass)
# Check if the graph has nodes for the SampleClass and its ancestors
sample_class_node = (
f" class {diagram.namify(SampleClass)}"
f'["{SampleClass.__module__}::{SampleClass.__name__}"]'
)
assert sample_class_node in diagram.graph
test_class_a_node = (
f" class {diagram.namify(TestClassA)}"
f'["{TestClassA.__module__}::{TestClassA.__name__}"]'
)
assert test_class_a_node in diagram.graph
test_class_b_node = (
f" class {diagram.namify(TestClassB)}"
f'["{TestClassB.__module__}::{TestClassB.__name__}"]'
)
assert test_class_b_node in diagram.graph
# Check if the graph has connections between the SampleClass and its ancestors
assert (
f" {diagram.namify(TestClassA)} <|-- {diagram.namify(SampleClass)}"
in diagram.graph
)
assert (
f" {diagram.namify(TestClassB)} <|-- {diagram.namify(SampleClass)}"
in diagram.graph
)
# Create a mock for QuerySet.
class MockQuerySet:
pass
@pytest.fixture
def mock_read_frame():
# Mock the external read_frame function from django_pandas.io
# since this proj uses a wrapper around it - test directly
with patch("django_pandas.io.read_frame") as mock_rf:
mock_rf.return_value = "Mocked DataFrame"
yield mock_rf
def test_read_frame(mock_read_frame):
"""
Tests the `read_frame` method of the `Plus`
class to ensure it properly delegates to the
`django_pandas.io` wrapper around pandas,
using a provided QuerySet.
The test mocks this function to return "Mocked DataFrame"
and checks if the `Plus` method returns this when given a mock QuerySet.
"""
plus_instance = Plus(helpers={})
mock_qs = MockQuerySet()
# Bypass __getattribute__ and directly set the read_frame method to the mock
plus_instance.read_frame = mock_read_frame
result = plus_instance.read_frame(mock_qs)
# assert mocked query called
mock_read_frame.assert_called_once_with(mock_qs)
assert result == "Mocked DataFrame"
def test_csv_to_df():
"""
Tests the `csv_to_df` method of the `Plus`
class to ensure it returns a CSV.
The test mocks this function to return "Mocked DataFrame"
and checks if the `Plus` method returns this when given a mock CSV.
"""
plus_instance = Plus(helpers={})
csv_path = Path("tests/sample.csv")
with open(csv_path) as f:
csv_string = f.read()
result_from_string = plus_instance.csv_to_df(csv_string)
result_from_path = plus_instance.csv_to_df(csv_path)
# assert results are dataframes
assert isinstance(result_from_string, pandas.DataFrame)
assert isinstance(result_from_path, pandas.DataFrame)
# assert content is correct
assert result_from_string.at[0, "Name"] == "A"
assert result_from_path.at[0, "Name"] == "A"
def test_warning_when_debug_false(capfd):
"""
Test if the correct warning and message are displayed when DEBUG is False.
Checks for error string message in both stout and warnings.
Test assumes that calling activate("fake_settings") results in
a state where DEBUG is False.
Args:
capfd: Pytest fixture to capture stdout and stderr.
"""
with SettingsCleaner():
with pytest.warns(UserWarning) as record:
activate("tests.config_debug_false")
# Capture STDOUT and STDERR
capfd.readouterr()
# Check warning message
assert "Django is running in production mode with dj-notebook." in [
str(r.message) for r in record.list
]
def test_settings_discovery_subdirectory():
# when run from the makefile, this gets its cwd set to the project root. for discovery to work as intended, the cwd
# needs to be tests
old_cwd = os.getcwd()
os.chdir(os.path.dirname(os.path.realpath(__file__)))
try:
with SettingsCleaner():
activate()
assert environ["DJANGO_SETTINGS_MODULE"] == "book_store.settings"
finally:
os.chdir(old_cwd)
def test_settings_discovery_envfile_invalid_module():
# Create a .env file next to this test script with an invalid module, and make sure it fails with an ImproperlyConfigured exception
script_dir = os.path.dirname(os.path.realpath(__file__))
old_cwd = os.getcwd()
os.chdir(script_dir)
env_path = Path(script_dir) / ".env"
assert not env_path.exists(), "cowardly refusing to overwrite existing .env file"
try:
with open(env_path, "w") as envfile:
envfile.write("DJANGO_SETTINGS_MODULE=blockbuster_video.settings")
with SettingsCleaner():
load_dotenv(str(env_path))
with pytest.raises(ImproperlyConfigured):
activate()
finally:
os.chdir(old_cwd)
os.unlink(env_path)
================================================
FILE: utils/update_changelog.py
================================================
import json
import pathlib
import typing
def main() -> None:
changes: dict[str, typing.Any] = json.loads(
pathlib.Path("changelog.json").read_text()
)
previous_changelog: str = pathlib.Path("CHANGELOG.md").read_text()
new_changelog: str = f"""
# [{changes["tag_name"]}]({changes['html_url']})
{changes['created_at']} by
[@{changes['author']['login']}]({changes['author']['html_url']})
## {changes["body"]}
---
{previous_changelog}
"""
new_changelog = new_changelog.replace("## ##", "##")
pathlib.Path("CHANGELOG.md").write_text(new_changelog)
if __name__ == "__main__":
main()