Skip to content

Commit 28bc2aa

Browse files
committed
flush out performance page
1 parent 058f983 commit 28bc2aa

File tree

9 files changed

+186
-125
lines changed

9 files changed

+186
-125
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ just release x.x.x
9494
## Just Recipes
9595

9696
```bash
97+
benchmark # generate and document benchmarks
9798
build # build src package and wheel
9899
build-docs # build the docs
99100
build-docs-html # build html documentation

doc/source/_static/img/minimal_profile.svg

Lines changed: 31 additions & 31 deletions
Loading

doc/source/_static/img/polls_profile.svg

Lines changed: 49 additions & 49 deletions
Loading

doc/source/changelog.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
Change Log
55
==========
66

7-
v3.3 (2025-08-31)
7+
v3.3.0 (2025-08-31)
88
===================
99

10+
* Documented `Add note on startup performance to docs. <https://github.com/django-commons/django-typer/issues/227>`_
1011
* Fixed `Support typer 0.17 <https://github.com/django-commons/django-typer/issues/225>`_
1112

1213
v3.2.2 (2025-07-17)

doc/source/conf.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,15 +108,24 @@
108108
}
109109

110110

111+
def pypi_role(name, rawtext, text, lineno, inliner, options={}, content=[]):
112+
from docutils import nodes
113+
114+
url = f"https://pypi.org/project/{text}/"
115+
node = nodes.reference(rawtext, text, refuri=url, **options)
116+
return [node], []
117+
118+
111119
def setup(app):
120+
from docutils.parsers.rst import roles
112121
# Register a sphinx.ext.autodoc.between listener to ignore everything
113122
# between lines that contain the word IGNORE
114123
app.connect(
115124
'autodoc-process-docstring',
116125
between('^.*[*]{79}.*$', exclude=True)
117126
)
118127
# app.connect('html-page-context', add_page_class)
119-
128+
roles.register_local_role("pypi", pypi_role)
120129
app.add_directive("literalinclude", ExtendedLiteralInclude)
121130
app.add_crossref_type(directivename="django-admin", rolename="django-admin")
122131
# https://sphinxcontrib-typer.readthedocs.io/en/latest/howto.html#build-to-multiple-formats

doc/source/performance.rst

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,45 @@
44
Performance
55
===========
66

7-
Django management command startup time can be noticeably slow, especially for large projects with
8-
many models. We can perceive time at roughly 100 ms, bare
7+
Django management command startup time has the potential to be noticeably slow, especially for large
8+
projects with many apps and models. Django must be fully bootstrapped before management commands can
9+
run. This can make the CLI feel sluggish and presents particular challenges for shell completion as
10+
this delay is incurred each time ``<tab>`` is pressed.
911

10-
----------------
12+
If you are experiencing difficulty with shell completion we recommend:
13+
14+
1. Enabling menu based completion navigation in your terminal. This can reduce the number of calls
15+
to :django-admin:`shellcompletion`. For z-shell this would look like:
16+
17+
.. code-block:: bash
18+
19+
zstyle ':completion:*' menu select
20+
21+
2. Use import and performance analysis tools to determine if there are import bottle necks that can
22+
be lazily loaded.
23+
24+
The following benchmarks show total module loads, import and runtimes. All times are in seconds.
25+
26+
The first benchmark is for a minimal project that uses the settings file generated by
27+
:django-admin:`startproject`. You can see that when :pypi:`rich` is installed several hundred extra
28+
modules must be imported. This adds a few 10s of milliseconds to the import time. To avoid this,
29+
uninstall :pypi:`rich`. As of :pypi:`Typer` 0.17, :pypi:`rich` is lazily loaded, but until certain
30+
improvements are made to :pypi:`Typer`, the import time penalty is still incurred by
31+
:pypi:`django-typer`.
1132

1233
.. raw:: html
1334
:file: _static/img/minimal_profile.svg
1435

36+
The second benchmark shows the same stats for the :ref:`polls example code <building_commands>`.
37+
These benchmarks compare the :class:`~django.core.management.BaseCommand` native implementation
38+
to the :class:`~django_typer.management.TyperCommand` implementation. They show that the overhead of
39+
using :pypi:`django-typer` is minimal. On the order of a few 10s of milliseconds. Adding
40+
:pypi:`rich` increases this overhead slightly, but the impact is still barely noticeable.
41+
1542
.. raw:: html
16-
:file: _static/img/polls_profile.svg
43+
:file: _static/img/polls_profile.svg
44+
45+
46+
.. success::
47+
48+
These benchmarks illustrate that adding

justfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,12 @@ run +ARGS:
340340
translate: install-translate
341341
@just manage translate --settings tests.settings.translate
342342

343+
# generate and document benchmarks
344+
benchmark:
345+
@just run --no-dev --no-extra rich --exact ./profiling/profile.py generate
346+
@just run --no-dev --extra rich --exact ./profiling/profile.py generate
347+
@just run --group profiling ./profiling/profile.py document
348+
343349
# validate the given version string against the lib version
344350
[script]
345351
validate_version VERSION:

profiling/profile.json

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"env": {
44
"django_typer": [
55
3,
6-
2,
7-
2
6+
3,
7+
0
88
],
99
"python": [
1010
3,
@@ -28,8 +28,8 @@
2828
"rich": false,
2929
"help": false
3030
},
31-
"time": 0.13760000000000003,
32-
"import_time": 0.126537,
31+
"time": 0.1598,
32+
"import_time": 0.10276,
3333
"modules": 550
3434
},
3535
"156897129890272181": {
@@ -40,8 +40,8 @@
4040
"rich": false,
4141
"help": false
4242
},
43-
"time": 0.1502,
44-
"import_time": 0.109779,
43+
"time": 0.1642,
44+
"import_time": 0.118827,
4545
"modules": 592
4646
},
4747
"2176052577945945477": {
@@ -52,8 +52,8 @@
5252
"rich": false,
5353
"help": false
5454
},
55-
"time": 0.1494,
56-
"import_time": 0.10648,
55+
"time": 0.1728,
56+
"import_time": 0.115422,
5757
"modules": 592
5858
},
5959
"675072097072274051": {
@@ -64,8 +64,8 @@
6464
"rich": false,
6565
"help": false
6666
},
67-
"time": 0.18419999999999997,
68-
"import_time": 0.108559,
67+
"time": 0.19879999999999998,
68+
"import_time": 0.122619,
6969
"modules": 606
7070
},
7171
"2303909322008428434": {
@@ -76,8 +76,8 @@
7676
"rich": false,
7777
"help": false
7878
},
79-
"time": 0.1494,
80-
"import_time": 0.100977,
79+
"time": 0.15259999999999999,
80+
"import_time": 0.106744,
8181
"modules": 573
8282
},
8383
"274962866163047735": {
@@ -88,8 +88,8 @@
8888
"rich": false,
8989
"help": false
9090
},
91-
"time": 0.168,
92-
"import_time": 0.109771,
91+
"time": 0.1734,
92+
"import_time": 0.115975,
9393
"modules": 615
9494
},
9595
"942508120957984993": {
@@ -100,8 +100,8 @@
100100
"rich": false,
101101
"help": true
102102
},
103-
"time": 0.1382,
104-
"import_time": 0.102393,
103+
"time": 0.1482,
104+
"import_time": 0.104318,
105105
"modules": 550
106106
},
107107
"506589674454807823": {
@@ -112,8 +112,8 @@
112112
"rich": false,
113113
"help": true
114114
},
115-
"time": 0.1484,
116-
"import_time": 0.108232,
115+
"time": 0.159,
116+
"import_time": 0.115199,
117117
"modules": 593
118118
},
119119
"426905945237278128": {
@@ -124,8 +124,8 @@
124124
"rich": false,
125125
"help": true
126126
},
127-
"time": 0.1476,
128-
"import_time": 0.109507,
127+
"time": 0.16260000000000002,
128+
"import_time": 0.122901,
129129
"modules": 593
130130
},
131131
"174008683672669482": {
@@ -136,8 +136,8 @@
136136
"rich": false,
137137
"help": true
138138
},
139-
"time": 0.13520000000000001,
140-
"import_time": 0.109942,
139+
"time": 0.1508,
140+
"import_time": 0.104901,
141141
"modules": 551
142142
},
143143
"1942175217068420967": {
@@ -148,8 +148,8 @@
148148
"rich": false,
149149
"help": true
150150
},
151-
"time": 0.15039999999999998,
152-
"import_time": 0.108588,
151+
"time": 0.1636,
152+
"import_time": 0.116833,
153153
"modules": 594
154154
},
155155
"864373478621873697": {
@@ -160,8 +160,8 @@
160160
"rich": true,
161161
"help": false
162162
},
163-
"time": 0.1864,
164-
"import_time": 0.141692,
163+
"time": 0.214,
164+
"import_time": 0.159158,
165165
"modules": 744
166166
},
167167
"226255260169180632": {
@@ -172,8 +172,8 @@
172172
"rich": true,
173173
"help": false
174174
},
175-
"time": 0.1886,
176-
"import_time": 0.138694,
175+
"time": 0.2352,
176+
"import_time": 0.163347,
177177
"modules": 745
178178
},
179179
"428097589213665847": {
@@ -184,8 +184,8 @@
184184
"rich": true,
185185
"help": false
186186
},
187-
"time": 0.2218,
188-
"import_time": 0.138445,
187+
"time": 0.265,
188+
"import_time": 0.149312,
189189
"modules": 755
190190
},
191191
"2100452363596306712": {
@@ -196,8 +196,8 @@
196196
"rich": true,
197197
"help": false
198198
},
199-
"time": 0.2316,
200-
"import_time": 0.143397,
199+
"time": 0.2404,
200+
"import_time": 0.156697,
201201
"modules": 768
202202
},
203203
"1284199138412852409": {
@@ -208,8 +208,8 @@
208208
"rich": true,
209209
"help": true
210210
},
211-
"time": 0.22920000000000001,
212-
"import_time": 0.153081,
211+
"time": 0.2214,
212+
"import_time": 0.153582,
213213
"modules": 745
214214
},
215215
"2137499222435047482": {
@@ -220,8 +220,8 @@
220220
"rich": true,
221221
"help": true
222222
},
223-
"time": 0.195,
224-
"import_time": 0.139081,
223+
"time": 0.2194,
224+
"import_time": 0.16577,
225225
"modules": 746
226226
},
227227
"477819850357282012": {
@@ -232,8 +232,8 @@
232232
"rich": true,
233233
"help": true
234234
},
235-
"time": 0.2008,
236-
"import_time": 0.138571,
235+
"time": 0.217,
236+
"import_time": 0.148613,
237237
"modules": 747
238238
}
239239
},

profiling/profile.py

100644100755
Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#!python
2+
13
import bisect
24
import json
35
import os
@@ -419,7 +421,7 @@ def minimal_table():
419421
).write_text(svg, encoding="utf-8")
420422

421423
def polls_table():
422-
console = Console(record=True, width=80)
424+
console = Console(record=True, width=100)
423425

424426
# Top "header"
425427
console.print(
@@ -457,6 +459,16 @@ def polls_table():
457459
"polls --help (tutorial)",
458460
RunKey(cmd="polls", typer=False, app=False, rich=False, help=True),
459461
),
462+
(
463+
"polls (typer)",
464+
RunKey(cmd="polls", typer=True, app=True, rich=False, help=False),
465+
),
466+
(
467+
"shell completion",
468+
RunKey(
469+
cmd="shellcompletion", typer=True, app=True, rich=False, help=False
470+
),
471+
),
460472
]
461473
rich_cols = [
462474
(

0 commit comments

Comments
 (0)