diff --git a/.asv/results/benchmarks.json b/.asv/results/benchmarks.json deleted file mode 100644 index 98f9eb4f..00000000 --- a/.asv/results/benchmarks.json +++ /dev/null @@ -1,385 +0,0 @@ -{ - "Components vs Django.peakmem_render_lg_first": { - "code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - large - first render (mem)\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n setup=lambda renderer: setup_templating_memory_benchmark(renderer, \"lg\", \"first\", \"isolated\"),\n )\n def peakmem_render_lg_first(self, renderer: TemplatingRenderer):\n do_render()\n\nsetup=lambda renderer: setup_templating_memory_benchmark(renderer, \"lg\", \"first\", \"isolated\"),", - "name": "Components vs Django.peakmem_render_lg_first", - "param_names": [ - "renderer" - ], - "params": [ - [ - "'django'", - "'django-components'" - ] - ], - "pretty_name": "render - large - first render (mem)", - "type": "peakmemory", - "unit": "bytes", - "version": "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528" - }, - "Components vs Django.peakmem_render_lg_subsequent": { - "code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - large - second render (mem)\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n setup=lambda renderer: setup_templating_memory_benchmark(renderer, \"lg\", \"subsequent\", \"isolated\"),\n )\n def peakmem_render_lg_subsequent(self, renderer: TemplatingRenderer):\n do_render()\n\nsetup=lambda renderer: setup_templating_memory_benchmark(renderer, \"lg\", \"subsequent\", \"isolated\"),", - "name": "Components vs Django.peakmem_render_lg_subsequent", - "param_names": [ - "renderer" - ], - "params": [ - [ - "'django'", - "'django-components'" - ] - ], - "pretty_name": "render - large - second render (mem)", - "type": "peakmemory", - "unit": "bytes", - "version": "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b" - }, - "Components vs Django.peakmem_render_sm_first": { - "code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - small - first render (mem)\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n setup=lambda renderer: setup_templating_memory_benchmark(renderer, \"sm\", \"first\", \"isolated\"),\n )\n def peakmem_render_sm_first(self, renderer: TemplatingRenderer):\n do_render()\n\nsetup=lambda renderer: setup_templating_memory_benchmark(renderer, \"sm\", \"first\", \"isolated\"),", - "name": "Components vs Django.peakmem_render_sm_first", - "param_names": [ - "renderer" - ], - "params": [ - [ - "'django'", - "'django-components'" - ] - ], - "pretty_name": "render - small - first render (mem)", - "type": "peakmemory", - "unit": "bytes", - "version": "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036" - }, - "Components vs Django.peakmem_render_sm_subsequent": { - "code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - small - second render (mem)\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n setup=lambda renderer: setup_templating_memory_benchmark(renderer, \"sm\", \"subsequent\", \"isolated\"),\n )\n def peakmem_render_sm_subsequent(self, renderer: TemplatingRenderer):\n do_render()\n\nsetup=lambda renderer: setup_templating_memory_benchmark(renderer, \"sm\", \"subsequent\", \"isolated\"),", - "name": "Components vs Django.peakmem_render_sm_subsequent", - "param_names": [ - "renderer" - ], - "params": [ - [ - "'django'", - "'django-components'" - ] - ], - "pretty_name": "render - small - second render (mem)", - "type": "peakmemory", - "unit": "bytes", - "version": "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026" - }, - "Components vs Django.timeraw_render_lg_first": { - "code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - large - first render\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n include_in_quick_benchmark=True,\n )\n def timeraw_render_lg_first(self, renderer: TemplatingRenderer):\n return prepare_templating_benchmark(renderer, \"lg\", \"first\", \"isolated\")", - "min_run_count": 2, - "name": "Components vs Django.timeraw_render_lg_first", - "number": 1, - "param_names": [ - "renderer" - ], - "params": [ - [ - "'django'", - "'django-components'" - ] - ], - "pretty_name": "render - large - first render", - "repeat": 0, - "rounds": 5, - "sample_time": 0.01, - "type": "time", - "unit": "seconds", - "version": "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", - "warmup_time": -1 - }, - "Components vs Django.timeraw_render_lg_subsequent": { - "code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - large - second render\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n )\n def timeraw_render_lg_subsequent(self, renderer: TemplatingRenderer):\n return prepare_templating_benchmark(renderer, \"lg\", \"subsequent\", \"isolated\")", - "min_run_count": 2, - "name": "Components vs Django.timeraw_render_lg_subsequent", - "number": 1, - "param_names": [ - "renderer" - ], - "params": [ - [ - "'django'", - "'django-components'" - ] - ], - "pretty_name": "render - large - second render", - "repeat": 0, - "rounds": 5, - "sample_time": 0.01, - "type": "time", - "unit": "seconds", - "version": "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", - "warmup_time": -1 - }, - "Components vs Django.timeraw_render_sm_first": { - "code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - small - first render\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n )\n def timeraw_render_sm_first(self, renderer: TemplatingRenderer):\n return prepare_templating_benchmark(renderer, \"sm\", \"first\", \"isolated\")", - "min_run_count": 2, - "name": "Components vs Django.timeraw_render_sm_first", - "number": 1, - "param_names": [ - "renderer" - ], - "params": [ - [ - "'django'", - "'django-components'" - ] - ], - "pretty_name": "render - small - first render", - "repeat": 0, - "rounds": 5, - "sample_time": 0.01, - "type": "time", - "unit": "seconds", - "version": "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", - "warmup_time": -1 - }, - "Components vs Django.timeraw_render_sm_subsequent": { - "code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - small - second render\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n )\n def timeraw_render_sm_subsequent(self, renderer: TemplatingRenderer):\n return prepare_templating_benchmark(renderer, \"sm\", \"subsequent\", \"isolated\")", - "min_run_count": 2, - "name": "Components vs Django.timeraw_render_sm_subsequent", - "number": 1, - "param_names": [ - "renderer" - ], - "params": [ - [ - "'django'", - "'django-components'" - ] - ], - "pretty_name": "render - small - second render", - "repeat": 0, - "rounds": 5, - "sample_time": 0.01, - "type": "time", - "unit": "seconds", - "version": "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", - "warmup_time": -1 - }, - "Components vs Django.timeraw_startup_lg": { - "code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"startup - large\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n )\n def timeraw_startup_lg(self, renderer: TemplatingRenderer):\n return prepare_templating_benchmark(renderer, \"lg\", \"startup\", \"isolated\")", - "min_run_count": 2, - "name": "Components vs Django.timeraw_startup_lg", - "number": 1, - "param_names": [ - "renderer" - ], - "params": [ - [ - "'django'", - "'django-components'" - ] - ], - "pretty_name": "startup - large", - "repeat": 0, - "rounds": 5, - "sample_time": 0.01, - "type": "time", - "unit": "seconds", - "version": "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", - "warmup_time": -1 - }, - "Other.timeraw_import_time": { - "code": "class OtherTests:\n @benchmark(\n pretty_name=\"import time\",\n group_name=OTHER_GROUP,\n number=1,\n rounds=5,\n )\n def timeraw_import_time(self):\n return prepare_templating_benchmark(\"django-components\", \"lg\", \"startup\", \"isolated\", imports_only=True)", - "min_run_count": 2, - "name": "Other.timeraw_import_time", - "number": 1, - "param_names": [], - "params": [], - "pretty_name": "import time", - "repeat": 0, - "rounds": 5, - "sample_time": 0.01, - "type": "time", - "unit": "seconds", - "version": "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", - "warmup_time": -1 - }, - "isolated vs django modes.peakmem_render_lg_first": { - "code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - large - first render (mem)\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n setup=lambda context_mode: setup_templating_memory_benchmark(\n \"django-components\",\n \"lg\",\n \"first\",\n context_mode,\n ),\n )\n def peakmem_render_lg_first(self, context_mode: DjcContextMode):\n do_render()\n\nsetup=lambda context_mode: setup_templating_memory_benchmark(\n \"django-components\",\n \"lg\",\n \"first\",\n context_mode,\n),", - "name": "isolated vs django modes.peakmem_render_lg_first", - "param_names": [ - "context_mode" - ], - "params": [ - [ - "'isolated'", - "'django'" - ] - ], - "pretty_name": "render - large - first render (mem)", - "type": "peakmemory", - "unit": "bytes", - "version": "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9" - }, - "isolated vs django modes.peakmem_render_lg_subsequent": { - "code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - large - second render (mem)\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n setup=lambda context_mode: setup_templating_memory_benchmark(\n \"django-components\",\n \"lg\",\n \"subsequent\",\n context_mode,\n ),\n )\n def peakmem_render_lg_subsequent(self, context_mode: DjcContextMode):\n do_render()\n\nsetup=lambda context_mode: setup_templating_memory_benchmark(\n \"django-components\",\n \"lg\",\n \"subsequent\",\n context_mode,\n),", - "name": "isolated vs django modes.peakmem_render_lg_subsequent", - "param_names": [ - "context_mode" - ], - "params": [ - [ - "'isolated'", - "'django'" - ] - ], - "pretty_name": "render - large - second render (mem)", - "type": "peakmemory", - "unit": "bytes", - "version": "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf" - }, - "isolated vs django modes.peakmem_render_sm_first": { - "code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - small - first render (mem)\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n setup=lambda context_mode: setup_templating_memory_benchmark(\"django-components\", \"sm\", \"first\", context_mode),\n )\n def peakmem_render_sm_first(self, context_mode: DjcContextMode):\n do_render()\n\nsetup=lambda context_mode: setup_templating_memory_benchmark(\"django-components\", \"sm\", \"first\", context_mode),", - "name": "isolated vs django modes.peakmem_render_sm_first", - "param_names": [ - "context_mode" - ], - "params": [ - [ - "'isolated'", - "'django'" - ] - ], - "pretty_name": "render - small - first render (mem)", - "type": "peakmemory", - "unit": "bytes", - "version": "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840" - }, - "isolated vs django modes.peakmem_render_sm_subsequent": { - "code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - small - second render (mem)\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n setup=lambda context_mode: setup_templating_memory_benchmark(\n \"django-components\",\n \"sm\",\n \"subsequent\",\n context_mode,\n ),\n )\n def peakmem_render_sm_subsequent(self, context_mode: DjcContextMode):\n do_render()\n\nsetup=lambda context_mode: setup_templating_memory_benchmark(\n \"django-components\",\n \"sm\",\n \"subsequent\",\n context_mode,\n),", - "name": "isolated vs django modes.peakmem_render_sm_subsequent", - "param_names": [ - "context_mode" - ], - "params": [ - [ - "'isolated'", - "'django'" - ] - ], - "pretty_name": "render - small - second render (mem)", - "type": "peakmemory", - "unit": "bytes", - "version": "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260" - }, - "isolated vs django modes.timeraw_render_lg_first": { - "code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - large - first render\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n )\n def timeraw_render_lg_first(self, context_mode: DjcContextMode):\n return prepare_templating_benchmark(\"django-components\", \"lg\", \"first\", context_mode)", - "min_run_count": 2, - "name": "isolated vs django modes.timeraw_render_lg_first", - "number": 1, - "param_names": [ - "context_mode" - ], - "params": [ - [ - "'isolated'", - "'django'" - ] - ], - "pretty_name": "render - large - first render", - "repeat": 0, - "rounds": 5, - "sample_time": 0.01, - "type": "time", - "unit": "seconds", - "version": "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", - "warmup_time": -1 - }, - "isolated vs django modes.timeraw_render_lg_subsequent": { - "code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - large - second render\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n )\n def timeraw_render_lg_subsequent(self, context_mode: DjcContextMode):\n return prepare_templating_benchmark(\"django-components\", \"lg\", \"subsequent\", context_mode)", - "min_run_count": 2, - "name": "isolated vs django modes.timeraw_render_lg_subsequent", - "number": 1, - "param_names": [ - "context_mode" - ], - "params": [ - [ - "'isolated'", - "'django'" - ] - ], - "pretty_name": "render - large - second render", - "repeat": 0, - "rounds": 5, - "sample_time": 0.01, - "type": "time", - "unit": "seconds", - "version": "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", - "warmup_time": -1 - }, - "isolated vs django modes.timeraw_render_sm_first": { - "code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - small - first render\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n )\n def timeraw_render_sm_first(self, context_mode: DjcContextMode):\n return prepare_templating_benchmark(\"django-components\", \"sm\", \"first\", context_mode)", - "min_run_count": 2, - "name": "isolated vs django modes.timeraw_render_sm_first", - "number": 1, - "param_names": [ - "context_mode" - ], - "params": [ - [ - "'isolated'", - "'django'" - ] - ], - "pretty_name": "render - small - first render", - "repeat": 0, - "rounds": 5, - "sample_time": 0.01, - "type": "time", - "unit": "seconds", - "version": "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", - "warmup_time": -1 - }, - "isolated vs django modes.timeraw_render_sm_subsequent": { - "code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - small - second render\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n )\n def timeraw_render_sm_subsequent(self, context_mode: DjcContextMode):\n return prepare_templating_benchmark(\"django-components\", \"sm\", \"subsequent\", context_mode)", - "min_run_count": 2, - "name": "isolated vs django modes.timeraw_render_sm_subsequent", - "number": 1, - "param_names": [ - "context_mode" - ], - "params": [ - [ - "'isolated'", - "'django'" - ] - ], - "pretty_name": "render - small - second render", - "repeat": 0, - "rounds": 5, - "sample_time": 0.01, - "type": "time", - "unit": "seconds", - "version": "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", - "warmup_time": -1 - }, - "isolated vs django modes.timeraw_startup_lg": { - "code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"startup - large\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n )\n def timeraw_startup_lg(self, context_mode: DjcContextMode):\n return prepare_templating_benchmark(\"django-components\", \"lg\", \"startup\", context_mode)", - "min_run_count": 2, - "name": "isolated vs django modes.timeraw_startup_lg", - "number": 1, - "param_names": [ - "context_mode" - ], - "params": [ - [ - "'isolated'", - "'django'" - ] - ], - "pretty_name": "startup - large", - "repeat": 0, - "rounds": 5, - "sample_time": 0.01, - "type": "time", - "unit": "seconds", - "version": "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", - "warmup_time": -1 - }, - "version": 2 -} \ No newline at end of file diff --git a/.asv/results/ci-linux/06c89cf9-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/06c89cf9-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index edbf55d8..00000000 --- a/.asv/results/ci-linux/06c89cf9-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "06c89cf9e89a432197a4cabb5a1d6864dd6089ac", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1749546769000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.08026317200000221, 0.26819844099998136], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1749547226154, 31.969, [0.079592, 0.26712], [0.081627, 0.27054], [0.079806, 0.26758], [0.080908, 0.27014], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.04349111400000538, 0.15453611999998884], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1749547232510, 37.93, [0.043083, 0.15395], [0.044645, 0.15628], [0.04313, 0.15396], [0.044614, 0.15596], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.0036145729999930154, 0.004811176000004025], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1749547240091, 17.086, [0.0035701, 0.0047897], [0.0036399, 0.0048885], [0.0035736, 0.004795], [0.0036371, 0.0048827], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.00012332000000014887, 0.0005915369999911491], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1749547243508, 17.13, [0.00012078, 0.0005805], [0.00013759, 0.00060184], [0.00012127, 0.00058107], [0.00012742, 0.00059879], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.2252378419999843, 0.2247263650000093], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1749547246927, 19.999, [0.22441, 0.22363], [0.22729, 0.22686], [0.22452, 0.22369], [0.22639, 0.22556], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.20468290799999522], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1749547250936, 8.1567, [0.20338], [0.20624], [0.20374], [0.20582], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.26819597400000816, 0.2740507329999957], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1749547255664, 37.318, [0.26688, 0.27234], [0.26941, 0.28032], [0.26702, 0.2725], [0.26926, 0.279], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.15396625699997912, 0.16023626799997714], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1749547263180, 47.125, [0.15282, 0.15877], [0.15647, 0.16443], [0.15297, 0.15902], [0.15588, 0.16441], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.004825538000005736, 0.0047952310000027865], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1749547272622, 17.085, [0.0048028, 0.0047649], [0.0048467, 0.0048197], [0.0048091, 0.0047651], [0.0048402, 0.0048192], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.0005953940000154034, 0.0005933700000468889], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1749547276032, 17.147, [0.0005815, 0.00058741], [0.00060108, 0.00059832], [0.00058285, 0.0005878], [0.0006001, 0.00059759], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.22564571399999522, 0.22598634599995648], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1749547279452, 19.816, [0.22324, 0.22336], [0.22965, 0.22994], [0.22384, 0.22382], [0.22963, 0.22889], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[52932608, 55693312], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1749547223611, 0.79781], "Components vs Django.peakmem_render_lg_subsequent": [[53063680, 56180736], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1749547224409, 0.99222], "Components vs Django.peakmem_render_sm_first": [[44716032, 44834816], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1749547225402, 0.37525], "Components vs Django.peakmem_render_sm_subsequent": [[44716032, 44838912], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1749547225778, 0.37627], "isolated vs django modes.peakmem_render_lg_first": [[55263232, 54693888], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1749547252571, 1.0035], "isolated vs django modes.peakmem_render_lg_subsequent": [[55783424, 56160256], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1749547253575, 1.3187], "isolated vs django modes.peakmem_render_sm_first": [[44838912, 44838912], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1749547254894, 0.38522], "isolated vs django modes.peakmem_render_sm_subsequent": [[44965888, 44834816], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1749547255279, 0.38471]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/07f747d7-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/07f747d7-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index a7ada305..00000000 --- a/.asv/results/ci-linux/07f747d7-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "07f747d70500bbe3e135725b0e1b102815ab9416", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1744216267000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.0749189080000292, 0.26436952000000247], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1744216619486, 33.178, [0.07366, 0.26251], [0.075619, 0.26728], [0.073675, 0.26261], [0.075618, 0.26698], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.03752758399997447, 0.15356457899997622], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1744216625947, 38.386, [0.037126, 0.15125], [0.038391, 0.15573], [0.037176, 0.15202], [0.038319, 0.15564], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.0036766670000361046, 0.004929383999979109], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1744216633574, 17.798, [0.0036349, 0.0048414], [0.0037472, 0.0050383], [0.0036356, 0.0048456], [0.0037349, 0.0049943], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.0001176700000087294, 0.0005864990000077341], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1744216637046, 17.63, [0.00011614, 0.00057608], [0.00012402, 0.00060528], [0.00011638, 0.00058095], [0.00012214, 0.00060196], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.22985272800002576, 0.22544496099999378], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1744216640541, 20.874, [0.2257, 0.22291], [0.23126, 0.22794], [0.22584, 0.2231], [0.23102, 0.22667], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.20376683500001036], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1744216644673, 8.4126, [0.20226], [0.20665], [0.20247], [0.20632], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.265977821000007, 0.26914772099999595], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1744216649436, 38.086, [0.26238, 0.26661], [0.26855, 0.2726], [0.26253, 0.26679], [0.26818, 0.27222], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.15150350199996865, 0.1558047899999906], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1744216657009, 47.487, [0.15077, 0.15425], [0.1542, 0.15864], [0.15093, 0.15461], [0.15373, 0.15862], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.004901490999998259, 0.004895917999988342], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1744216666440, 17.693, [0.0048488, 0.0048302], [0.0049534, 0.0049506], [0.004858, 0.0048375], [0.0049529, 0.0049476], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.0005929909999622396, 0.000583071999983531], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1744216669983, 17.728, [0.00057117, 0.00057317], [0.00060263, 0.0006142], [0.00057741, 0.00057397], [0.00060104, 0.00060429], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.22394285699999728, 0.22330144500000415], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1744216673490, 20.287, [0.22183, 0.22116], [0.22692, 0.2269], [0.22188, 0.22124], [0.22587, 0.22687], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[53047296, 55373824], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1744216616926, 0.79581], "Components vs Django.peakmem_render_lg_subsequent": [[53948416, 55824384], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1744216617722, 1.0125], "Components vs Django.peakmem_render_sm_first": [[44326912, 44322816], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1744216618735, 0.37296], "Components vs Django.peakmem_render_sm_subsequent": [[44101632, 44310528], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1744216619108, 0.37753], "isolated vs django modes.peakmem_render_lg_first": [[55246848, 54898688], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1744216646319, 1.0346], "isolated vs django modes.peakmem_render_lg_subsequent": [[55812096, 55611392], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1744216647354, 1.3258], "isolated vs django modes.peakmem_render_sm_first": [[44322816, 44314624], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1744216648680, 0.37601], "isolated vs django modes.peakmem_render_sm_subsequent": [[44314624, 44318720], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1744216649056, 0.37944]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/1319a956-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/1319a956-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index a92be098..00000000 --- a/.asv/results/ci-linux/1319a956-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "1319a95627493fc0745b5af0600af2dc8c5117f9", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1744204166000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.07308550800001967, 0.26274096600002395], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1744204892087, 32.138, [0.072587, 0.26115], [0.074192, 0.26642], [0.072641, 0.26122], [0.073973, 0.26591], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.03723830100000214, 0.15196534300002895], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1744204898443, 37.515, [0.036587, 0.1496], [0.037873, 0.15446], [0.036668, 0.14991], [0.037851, 0.15324], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.003566315000000486, 0.004791812000007667], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1744204905997, 17.018, [0.0035472, 0.0047455], [0.0036147, 0.0048525], [0.0035495, 0.0047533], [0.0036128, 0.0048458], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.00011609900002440554, 0.0005779780000239043], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1744204909393, 17.165, [0.00011522, 0.00056305], [0.00011862, 0.00060154], [0.00011551, 0.00056367], [0.00011789, 0.0005929], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.22339861599999722, 0.22020213500002228], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1744204912773, 20.145, [0.22184, 0.21813], [0.22706, 0.22287], [0.22223, 0.21861], [0.22593, 0.22095], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.20073733000003813], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1744204916751, 8.1154, [0.1995], [0.2041], [0.19959], [0.20363], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.262679922000018, 0.2686107789999994], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1744204921359, 37.333, [0.25985, 0.2655], [0.2667, 0.27137], [0.26015, 0.26588], [0.26589, 0.27026], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.15071133700001837, 0.15540660900001058], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1744204928788, 47.002, [0.14963, 0.15366], [0.15432, 0.15808], [0.14986, 0.15409], [0.15418, 0.15789], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.00478528000002143, 0.0047485900000197034], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1744204938238, 17.062, [0.004736, 0.0047199], [0.0048543, 0.004812], [0.0047396, 0.0047303], [0.0048316, 0.0047851], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.0005853119999983392, 0.000582014999963576], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1744204941632, 17.123, [0.00056611, 0.00057364], [0.00059521, 0.00058681], [0.00056804, 0.00057454], [0.00059519, 0.00058642], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.22056839900000114, 0.21916191200000412], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1744204945049, 19.625, [0.21938, 0.21844], [0.22222, 0.22102], [0.21966, 0.21859], [0.22174, 0.221], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[53084160, 55382016], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1744204889613, 0.78176], "Components vs Django.peakmem_render_lg_subsequent": [[53739520, 55812096], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1744204890395, 0.96208], "Components vs Django.peakmem_render_sm_first": [[44322816, 44314624], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1744204891357, 0.36135], "Components vs Django.peakmem_render_sm_subsequent": [[44322816, 44310528], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1744204891719, 0.36764], "isolated vs django modes.peakmem_render_lg_first": [[55373824, 54894592], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1744204918353, 0.96762], "isolated vs django modes.peakmem_render_lg_subsequent": [[55545856, 55631872], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1744204919321, 1.2917], "isolated vs django modes.peakmem_render_sm_first": [[44318720, 44314624], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1744204920613, 0.3751], "isolated vs django modes.peakmem_render_sm_subsequent": [[44310528, 44314624], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1744204920988, 0.37048]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/2037ed20-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/2037ed20-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index b6c6accf..00000000 --- a/.asv/results/ci-linux/2037ed20-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "2037ed20b7252cc52008e69bdd3d8e095ad6ea08", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1742645505000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.07114163800000028, 0.26389872900000455], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1742645992571, 32.683, [0.069774, 0.25821], [0.073121, 0.27402], [0.069851, 0.25974], [0.072998, 0.26645], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.033918617999972867, 0.14395761299999776], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1742645999069, 37.874, [0.033132, 0.14143], [0.03718, 0.14845], [0.033146, 0.14223], [0.036758, 0.14717], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.0036137869999777195, 0.004807943000002979], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1742646006580, 17.577, [0.0035726, 0.0047711], [0.0037623, 0.005014], [0.0035734, 0.0047729], [0.0037603, 0.0049865], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.00010086800000408402, 0.0005549249999887707], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1742646010016, 17.508, [9.8133e-05, 0.00053722], [0.00010873, 0.00056563], [9.8243e-05, 0.00053799], [0.00010711, 0.00056262], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.22476057199997967, 0.22048105400000395], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1742646013452, 20.612, [0.22251, 0.21707], [0.22804, 0.22459], [0.22271, 0.21711], [0.22783, 0.22416], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.20217585500000723], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1742646017529, 8.2678, [0.20055], [0.20485], [0.20064], [0.20445], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.2600247560000071, 0.26185358800000813], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1742646022211, 37.548, [0.25553, 0.25903], [0.26378, 0.26742], [0.25572, 0.25907], [0.26268, 0.26457], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.14515931700000806, 0.14909453600000688], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1742646029641, 46.958, [0.14261, 0.147], [0.14958, 0.15597], [0.1427, 0.14746], [0.14895, 0.15119], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.004856270999994194, 0.00490694800001279], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1742646038981, 17.673, [0.0047454, 0.0047974], [0.0050872, 0.0050523], [0.0047616, 0.0048062], [0.005, 0.0050457], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.000547750000009728, 0.0005677989999810507], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1742646042461, 17.734, [0.00053762, 0.00054036], [0.00056353, 0.0005841], [0.00053867, 0.00054808], [0.00055477, 0.00058339], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.2221746719999942, 0.2222580240000127], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1742646045952, 20.444, [0.21901, 0.2187], [0.22925, 0.22649], [0.22017, 0.21932], [0.22825, 0.22535], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[52350976, 54599680], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1742645990033, 0.81782], "Components vs Django.peakmem_render_lg_subsequent": [[52289536, 55099392], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1742645990851, 0.96118], "Components vs Django.peakmem_render_sm_first": [[44056576, 44048384], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1742645991813, 0.37787], "Components vs Django.peakmem_render_sm_subsequent": [[44060672, 43917312], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1742645992191, 0.38003], "isolated vs django modes.peakmem_render_lg_first": [[54616064, 54140928], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1742646019154, 0.98924], "isolated vs django modes.peakmem_render_lg_subsequent": [[54849536, 54841344], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1742646020143, 1.3166], "isolated vs django modes.peakmem_render_sm_first": [[44048384, 44048384], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1742646021460, 0.37686], "isolated vs django modes.peakmem_render_sm_subsequent": [[44052480, 44052480], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1742646021838, 0.37297]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/2472c2ad-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/2472c2ad-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index 48ce9ff5..00000000 --- a/.asv/results/ci-linux/2472c2ad-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "2472c2ad338a23fba015d4d9816cb62d1325455f", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1742720064000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.06910802600003763, 0.25746033199999374], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1742720448344, 30.848, [0.068873, 0.25443], [0.070492, 0.26059], [0.068879, 0.25485], [0.069904, 0.25905], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.03317536700001256, 0.14245594600001255], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1742720454523, 36.264, [0.032774, 0.14105], [0.034001, 0.14496], [0.032835, 0.14147], [0.033931, 0.14484], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.0035223549999727766, 0.004706463999980315], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1742720461726, 16.586, [0.0034935, 0.0046795], [0.0035519, 0.0047641], [0.0034979, 0.0046875], [0.0035513, 0.0047525], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[9.818199998790078e-05, 0.0005511469999817109], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1742720465042, 16.608, [9.7351e-05, 0.00054462], [9.9656e-05, 0.00055608], [9.7582e-05, 0.00054577], [9.9445e-05, 0.00055587], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.21809406599999193, 0.2131839880000257], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1742720468367, 19.52, [0.21698, 0.21208], [0.21947, 0.21482], [0.21721, 0.21251], [0.21904, 0.21475], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.19726691500000015], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1742720472264, 7.9149, [0.19686], [0.1978], [0.19687], [0.19779], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.2567828300000201, 0.2602957870000182], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1742720476744, 36.159, [0.25481, 0.25713], [0.26079, 0.26229], [0.25484, 0.25769], [0.26043, 0.26225], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.1423055980000072, 0.14642362500001127], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1742720484000, 45.045, [0.14132, 0.14533], [0.14548, 0.14785], [0.14152, 0.14539], [0.14515, 0.14779], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.00473016699999107, 0.004734037999980956], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1742720493008, 16.726, [0.0046832, 0.0046931], [0.004798, 0.0047607], [0.0046901, 0.0046966], [0.0047946, 0.0047554], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.0005471899999918151, 0.0005447550000212686], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1742720496354, 16.725, [0.00053011, 0.00053386], [0.00056137, 0.00056687], [0.00053047, 0.00053669], [0.00055674, 0.00055495], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.2142312400000037, 0.21397752699999728], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1742720499699, 19.132, [0.21291, 0.213], [0.21765, 0.21644], [0.21297, 0.21307], [0.21719, 0.21607], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[52109312, 54779904], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1742720445954, 0.7513], "Components vs Django.peakmem_render_lg_subsequent": [[52142080, 55255040], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1742720446706, 0.92703], "Components vs Django.peakmem_render_sm_first": [[44191744, 44310528], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1742720447633, 0.35395], "Components vs Django.peakmem_render_sm_subsequent": [[44105728, 44310528], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1742720447987, 0.3569], "isolated vs django modes.peakmem_render_lg_first": [[54767616, 54296576], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1742720473841, 0.94212], "isolated vs django modes.peakmem_render_lg_subsequent": [[55271424, 55304192], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1742720474783, 1.2374], "isolated vs django modes.peakmem_render_sm_first": [[44314624, 44310528], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1742720476021, 0.36127], "isolated vs django modes.peakmem_render_sm_subsequent": [[44314624, 44310528], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1742720476383, 0.36101]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/42818ad6-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/42818ad6-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index 53ff3ee2..00000000 --- a/.asv/results/ci-linux/42818ad6-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "42818ad6ffb47bd650d8a379b84c3d48394f9f77", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1742765538000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.07048037500001669, 0.2598985070000026], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1742765993409, 32.579, [0.069842, 0.25804], [0.071131, 0.26492], [0.069874, 0.25825], [0.071078, 0.26365], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.034316510999985894, 0.1444248799999741], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1742765999750, 37.501, [0.033683, 0.14308], [0.034786, 0.14803], [0.033696, 0.14309], [0.034745, 0.14771], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.00364059099999281, 0.004926952999994683], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1742766007246, 17.513, [0.0036032, 0.0048467], [0.0037114, 0.0049811], [0.003609, 0.0048595], [0.0036794, 0.0049768], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.0001005780000014056, 0.0005555879999974422], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1742766010749, 17.458, [9.8725e-05, 0.00054444], [0.00010239, 0.00057191], [9.8835e-05, 0.00054676], [0.00010186, 0.00056928], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.22356123500000535, 0.22167734499998915], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1742766014253, 20.515, [0.2223, 0.21793], [0.22664, 0.22338], [0.22244, 0.21819], [0.22647, 0.22298], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.20350580199999513], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1742766018296, 8.3746, [0.20146], [0.20645], [0.20149], [0.20587], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.259077934000004, 0.2619792840000059], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1742766023061, 37.296, [0.25617, 0.26014], [0.26126, 0.2646], [0.25637, 0.26026], [0.26126, 0.26434], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.1436571560000175, 0.14915657599999577], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1742766030546, 46.343, [0.14315, 0.14755], [0.14626, 0.15157], [0.1432, 0.14763], [0.14512, 0.15085], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.004871503999993365, 0.0048899079999955575], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1742766039800, 17.572, [0.0048252, 0.0048469], [0.0049596, 0.0049328], [0.0048256, 0.0048546], [0.0049269, 0.0049254], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.0005559489999882317, 0.0005480739999939033], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1742766043333, 17.626, [0.00054656, 0.0005378], [0.00056755, 0.00057285], [0.00055044, 0.00053834], [0.00056621, 0.00056985], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.22129613300000983, 0.21942976399998315], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1742766046839, 20.159, [0.2193, 0.21771], [0.22394, 0.22324], [0.21938, 0.21789], [0.22387, 0.22275], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[52899840, 54730752], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1742765990932, 0.78775], "Components vs Django.peakmem_render_lg_subsequent": [[53796864, 55238656], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1742765991720, 0.95598], "Components vs Django.peakmem_render_sm_first": [[44183552, 44175360], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1742765992676, 0.3646], "Components vs Django.peakmem_render_sm_subsequent": [[44187648, 44183552], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1742765993041, 0.36782], "isolated vs django modes.peakmem_render_lg_first": [[54743040, 54087680], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1742766020000, 1.0194], "isolated vs django modes.peakmem_render_lg_subsequent": [[54984704, 54964224], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1742766021019, 1.2817], "isolated vs django modes.peakmem_render_sm_first": [[44179456, 44175360], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1742766022301, 0.37934], "isolated vs django modes.peakmem_render_sm_subsequent": [[44179456, 44179456], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1742766022681, 0.37941]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/4c909486-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/4c909486-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index 3d570a90..00000000 --- a/.asv/results/ci-linux/4c909486-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "4c909486069f3c3c8ee7915239174f820f081da4", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1745143092000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.07360306399999672, 0.2678246009999725], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1745143577594, 32.042, [0.072955, 0.26442], [0.074281, 0.27014], [0.073086, 0.26445], [0.074028, 0.27007], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.037022983000014165, 0.15138703899998518], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1745143584022, 37.559, [0.036649, 0.14991], [0.037827, 0.1536], [0.036668, 0.1502], [0.037733, 0.15306], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.003639607999986083, 0.004848561000017071], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1745143591495, 17.115, [0.0036116, 0.0048089], [0.0036876, 0.0048932], [0.0036147, 0.0048094], [0.0036803, 0.0048807], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.00011665800002447213, 0.000582710000003317], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1745143594930, 17.167, [0.00011593, 0.00056917], [0.00011882, 0.0005887], [0.0001162, 0.00057013], [0.00011876, 0.00058637], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.224061646999985, 0.2246476189999953], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1745143598346, 20.14, [0.22362, 0.22184], [0.22927, 0.22751], [0.22365, 0.22214], [0.22867, 0.22721], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.2053688209999791], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1745143602385, 8.1858, [0.20216], [0.20799], [0.20231], [0.20781], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.2658582709999848, 0.2712929850000023], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1745143607124, 37.445, [0.26292, 0.26717], [0.27104, 0.27439], [0.26301, 0.26741], [0.27007, 0.27412], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.15248822700002052, 0.15465820200000735], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1745143614605, 46.778, [0.15022, 0.15322], [0.1536, 0.15739], [0.15038, 0.15326], [0.15356, 0.15661], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.004847185000016907, 0.004857667999999649], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1745143624026, 17.241, [0.004817, 0.0048044], [0.004914, 0.0049081], [0.0048254, 0.0048148], [0.004905, 0.004893], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.0005717709999828458, 0.0005785939999896073], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1745143627490, 17.271, [0.00056467, 0.00056983], [0.00058564, 0.00058436], [0.00056901, 0.00057082], [0.00058274, 0.00058294], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.22378945699995256, 0.22211803700002974], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1745143630975, 19.832, [0.22243, 0.22109], [0.22609, 0.224], [0.22277, 0.22114], [0.2254, 0.22377], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[53153792, 55410688], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1745143575049, 0.80087], "Components vs Django.peakmem_render_lg_subsequent": [[53919744, 55799808], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1745143575851, 0.98861], "Components vs Django.peakmem_render_sm_first": [[44195840, 44453888], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1745143576839, 0.37918], "Components vs Django.peakmem_render_sm_subsequent": [[44191744, 44453888], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1745143577219, 0.37508], "isolated vs django modes.peakmem_render_lg_first": [[55382016, 54882304], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1745143604037, 0.99271], "isolated vs django modes.peakmem_render_lg_subsequent": [[55812096, 55902208], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1745143605030, 1.3237], "isolated vs django modes.peakmem_render_sm_first": [[44453888, 44453888], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1745143606354, 0.38012], "isolated vs django modes.peakmem_render_sm_subsequent": [[44449792, 44449792], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1745143606735, 0.3888]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/5d7e2357-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/5d7e2357-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index 5f591b2f..00000000 --- a/.asv/results/ci-linux/5d7e2357-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "5d7e235725449181f6b65b7bf97e43ea0e0f8552", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1753049108000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.08105427499998541, 0.29477426600001877], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1753049851954, 33.615, [0.079961, 0.29197], [0.082291, 0.29933], [0.080014, 0.29202], [0.08217, 0.29917], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.043648402000002307, 0.17461173199995983], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1753049858567, 39.742, [0.043109, 0.17344], [0.044493, 0.17591], [0.043193, 0.17344], [0.044363, 0.17568], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.0037106409999978496, 0.004899473999955717], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1753049866520, 17.599, [0.0036469, 0.0048514], [0.003775, 0.0049767], [0.0036506, 0.0048584], [0.0037527, 0.0049267], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.00012706900002967814, 0.0006100459999629493], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1753049870045, 17.527, [0.00012441, 0.0005963], [0.00012848, 0.00063084], [0.00012455, 0.00059677], [0.00012835, 0.0006285], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.22799248500001568, 0.22723498599998493], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1753049873538, 20.449, [0.22611, 0.22628], [0.22958, 0.22866], [0.22612, 0.2263], [0.22904, 0.22847], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.2056691309999792], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1753049877623, 8.3423, [0.20476], [0.20838], [0.20478], [0.20831], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.2920349000000044, 0.2976166970000236], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1753049882577, 39.421, [0.29051, 0.29507], [0.29462, 0.2996], [0.29057, 0.29561], [0.29428, 0.29935], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.17414895399997476, 0.178393189000019], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1753049890450, 50.139, [0.17304, 0.17805], [0.17616, 0.17928], [0.17314, 0.17806], [0.17595, 0.17923], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.004897051999989799, 0.004863266000029398], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1753049900457, 17.558, [0.0048512, 0.004838], [0.0049693, 0.0049278], [0.0048517, 0.0048435], [0.0049605, 0.0049223], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.0006159270000125616, 0.0006080119999865019], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1753049903955, 17.521, [0.00060693, 0.0005956], [0.00062901, 0.00061672], [0.00060764, 0.00059691], [0.00062683, 0.00061634], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.22777395400004252, 0.2292747939999913], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1753049907456, 20.282, [0.22683, 0.22736], [0.2295, 0.23339], [0.22692, 0.22764], [0.22924, 0.23178], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[52715520, 56090624], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1753049849303, 0.82982], "Components vs Django.peakmem_render_lg_subsequent": [[52736000, 56791040], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1753049850134, 1.0513], "Components vs Django.peakmem_render_sm_first": [[44871680, 44912640], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1753049851185, 0.38233], "Components vs Django.peakmem_render_sm_subsequent": [[44617728, 44986368], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1753049851568, 0.38596], "isolated vs django modes.peakmem_render_lg_first": [[56090624, 55582720], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1753049879302, 1.0575], "isolated vs django modes.peakmem_render_lg_subsequent": [[56786944, 56778752], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1753049880360, 1.4396], "isolated vs django modes.peakmem_render_sm_first": [[44843008, 44851200], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1753049881800, 0.38828], "isolated vs django modes.peakmem_render_sm_subsequent": [[44982272, 44986368], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1753049882189, 0.38775]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/7b24b86f-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/7b24b86f-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index 3acec4f5..00000000 --- a/.asv/results/ci-linux/7b24b86f-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "7b24b86f4a836c697acba926d9d6602afa45418d", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1749076521000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.07941284200001064, 0.26779402600004687], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1749076875010, 33.29, [0.078956, 0.26662], [0.081186, 0.27149], [0.079001, 0.26705], [0.080936, 0.27135], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.043317416999911984, 0.15457556900003055], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1749076881523, 38.751, [0.042862, 0.15282], [0.044278, 0.15833], [0.042896, 0.15289], [0.044184, 0.15671], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.0036632869999948525, 0.00493345400002454], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1749076889315, 17.497, [0.0036441, 0.0048964], [0.0037017, 0.0050018], [0.0036475, 0.0049028], [0.0037007, 0.0049814], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.00012153600005149201, 0.0005999570000199128], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1749076892858, 17.52, [0.00012055, 0.00057981], [0.00012289, 0.00061584], [0.00012056, 0.00058243], [0.00012282, 0.00061301], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.22743783699991127, 0.226070988999993], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1749076896345, 20.618, [0.22567, 0.22411], [0.22912, 0.22959], [0.22581, 0.22418], [0.22858, 0.22815], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.2063091950000171], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1749076900468, 8.3563, [0.20413], [0.20858], [0.20424], [0.20774], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.2675778039999841, 0.2724974679999832], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1749076905243, 38.085, [0.26574, 0.27164], [0.27197, 0.27673], [0.26585, 0.27165], [0.27065, 0.27581], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.15459265900005903, 0.15926110200007315], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1749076912924, 47.983, [0.15333, 0.15793], [0.15993, 0.16206], [0.1535, 0.15845], [0.15979, 0.16152], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.004923484000073586, 0.004925836999973399], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1749076922561, 17.577, [0.0048564, 0.0048656], [0.0050014, 0.004982], [0.0048761, 0.0048661], [0.004997, 0.004977], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.0005969709999362749, 0.0005864510000037626], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1749076926091, 17.606, [0.00058144, 0.00058324], [0.00060954, 0.00059625], [0.00058749, 0.00058422], [0.00060768, 0.00059614], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.22545313400001987, 0.22602228000005198], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1749076929634, 20.393, [0.22445, 0.22472], [0.22834, 0.22927], [0.2245, 0.22475], [0.22781, 0.22878], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[52957184, 55177216], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1749076872421, 0.81152], "Components vs Django.peakmem_render_lg_subsequent": [[52822016, 56242176], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1749076873233, 1.0095], "Components vs Django.peakmem_render_sm_first": [[44756992, 44744704], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1749076874243, 0.37927], "Components vs Django.peakmem_render_sm_subsequent": [[44527616, 44744704], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1749076874622, 0.38717], "isolated vs django modes.peakmem_render_lg_first": [[55222272, 54722560], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1749076902121, 1.0091], "isolated vs django modes.peakmem_render_lg_subsequent": [[56008704, 56143872], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1749076903130, 1.3368], "isolated vs django modes.peakmem_render_sm_first": [[44744704, 44744704], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1749076904467, 0.38687], "isolated vs django modes.peakmem_render_sm_subsequent": [[44744704, 44744704], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1749076904855, 0.38848]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/a6455d70-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/a6455d70-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index 9c06c27b..00000000 --- a/.asv/results/ci-linux/a6455d70-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "a6455d70f6c28ddbd4be8e58902f6cbc101e5ff3", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1743430242000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.07402671400001282, 0.26584690599997884], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1743430606684, 32.442, [0.072957, 0.26285], [0.075615, 0.26888], [0.073023, 0.26377], [0.07513, 0.26834], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.03742426899998463, 0.14901454800002512], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1743430613250, 37.315, [0.036847, 0.14808], [0.037742, 0.15105], [0.036915, 0.14816], [0.037683, 0.15098], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.003602947999979733, 0.004853936999950292], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1743430620772, 16.954, [0.0035642, 0.0047703], [0.0036621, 0.0048984], [0.0035757, 0.0047716], [0.0036581, 0.0048887], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.00012266099997759738, 0.0005711430000019391], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1743430624325, 16.779, [0.00012174, 0.00055344], [0.0001259, 0.00058069], [0.00012179, 0.00055434], [0.0001247, 0.00057942], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.22133603999998286, 0.21805855799999563], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1743430627696, 19.918, [0.21912, 0.21676], [0.2249, 0.22143], [0.21935, 0.21703], [0.22467, 0.22078], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.19950735400001918], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1743430631679, 8.1122, [0.19829], [0.20456], [0.1984], [0.20447], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.2646600410000133, 0.2676605120000204], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1743430636362, 37.078, [0.26139, 0.26374], [0.26862, 0.27072], [0.26167, 0.26378], [0.26789, 0.27042], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.14860135300000366, 0.15305296299999327], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1743430643868, 46.445, [0.1469, 0.15166], [0.15298, 0.15727], [0.1471, 0.15188], [0.15174, 0.15702], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.0048215560000244295, 0.004858458999990489], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1743430653175, 17.045, [0.0047768, 0.0047682], [0.0049063, 0.0049223], [0.0047773, 0.0047722], [0.0048597, 0.0049158], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.0005736080000247057, 0.0005720849999875099], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1743430656658, 17.201, [0.00055793, 0.00056215], [0.00059018, 0.0005847], [0.00056001, 0.00056312], [0.00058173, 0.00057813], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.2199001029999863, 0.22046102699999892], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1743430660197, 19.832, [0.21722, 0.21683], [0.22316, 0.225], [0.21767, 0.21732], [0.22306, 0.22354], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[52936704, 55009280], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1743430604182, 0.78721], "Components vs Django.peakmem_render_lg_subsequent": [[53768192, 55455744], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1743430604969, 0.97304], "Components vs Django.peakmem_render_sm_first": [[44191744, 44314624], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1743430605943, 0.36867], "Components vs Django.peakmem_render_sm_subsequent": [[44191744, 44437504], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1743430606312, 0.37196], "isolated vs django modes.peakmem_render_lg_first": [[55001088, 54312960], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1743430633331, 0.98374], "isolated vs django modes.peakmem_render_lg_subsequent": [[55439360, 55369728], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1743430634315, 1.2833], "isolated vs django modes.peakmem_render_sm_first": [[44314624, 44310528], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1743430635599, 0.37624], "isolated vs django modes.peakmem_render_sm_subsequent": [[44310528, 44314624], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1743430635975, 0.38685]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/ad402fc6-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/ad402fc6-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index 426b7f59..00000000 --- a/.asv/results/ci-linux/ad402fc6-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "ad402fc619922b6d2edf1e99b7082d2a58632076", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1744443333000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.07303507899999317, 0.2628890319999755], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1744443661806, 31.899, [0.072617, 0.26159], [0.074239, 0.26702], [0.072622, 0.26164], [0.073884, 0.26623], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.03678920999999491, 0.14955294699998944], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1744443668113, 37.058, [0.036485, 0.14786], [0.037805, 0.15173], [0.036589, 0.14859], [0.037797, 0.15139], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.0035613420000117912, 0.004760385999986738], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1744443675539, 16.816, [0.0035318, 0.0047156], [0.0035817, 0.0047979], [0.0035329, 0.004732], [0.0035726, 0.0047786], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.00011622699999236374, 0.0005842630000074678], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1744443678895, 16.88, [0.00011473, 0.00056683], [0.00011709, 0.00060847], [0.00011487, 0.0005685], [0.00011689, 0.00059169], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.22073260000001937, 0.2182690520000392], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1744443682320, 19.801, [0.21972, 0.21695], [0.22309, 0.22228], [0.22028, 0.21699], [0.22296, 0.22213], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.19919827600000417], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1744443686264, 8.0021, [0.19862], [0.20007], [0.19866], [0.19998], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.2626667089999728, 0.2663110299999971], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1744443690855, 37.02, [0.26033, 0.2649], [0.26828, 0.27124], [0.26038, 0.26525], [0.26827, 0.27069], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.14876902899999322, 0.15549233400000162], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1744443698232, 46.228, [0.1479, 0.15374], [0.15179, 0.15735], [0.14799, 0.15375], [0.15155, 0.15701], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.00480728600001612, 0.00472804499997892], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1744443707554, 16.94, [0.0047171, 0.0047064], [0.0048771, 0.004802], [0.0047325, 0.0047131], [0.0048683, 0.0047952], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.0005810670000130358, 0.000576186999978745], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1744443710989, 17.038, [0.00056804, 0.00056565], [0.00059006, 0.00058984], [0.00057126, 0.00056676], [0.00058908, 0.0005876], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.21867883100003382, 0.21859779499999377], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1744443714408, 19.48, [0.21759, 0.21774], [0.22135, 0.22052], [0.21759, 0.21791], [0.22105, 0.22031], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[52490240, 55361536], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1744443659333, 0.7721], "Components vs Django.peakmem_render_lg_subsequent": [[52097024, 55791616], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1744443660105, 0.97576], "Components vs Django.peakmem_render_sm_first": [[44183552, 44306432], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1744443661081, 0.36008], "Components vs Django.peakmem_render_sm_subsequent": [[44314624, 44437504], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1744443661442, 0.3642], "isolated vs django modes.peakmem_render_lg_first": [[55357440, 54874112], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1744443687861, 0.9722], "isolated vs django modes.peakmem_render_lg_subsequent": [[55640064, 55631872], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1744443688833, 1.2825], "isolated vs django modes.peakmem_render_sm_first": [[44306432, 44240896], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1744443690116, 0.37071], "isolated vs django modes.peakmem_render_sm_subsequent": [[44437504, 44437504], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1744443690487, 0.36747]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/c692b7a3-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/c692b7a3-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index 14392b99..00000000 --- a/.asv/results/ci-linux/c692b7a3-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "c692b7a3105c65414d2c23c357ffed9debdbf6e9", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1751538441000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.0814841690000776, 0.28364495499999975], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1751538832752, 34.343, [0.079655, 0.2763], [0.083151, 0.28972], [0.079684, 0.27776], [0.082952, 0.28852], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.04362213900003553, 0.16551773399999092], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1751538839695, 39.947, [0.042835, 0.16259], [0.044314, 0.1709], [0.04289, 0.16268], [0.044297, 0.17072], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.00375721499995052, 0.0049729269999261305], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1751538847853, 18.382, [0.0036459, 0.0048221], [0.0038196, 0.0051737], [0.0036541, 0.0048489], [0.0038098, 0.0051177], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.00012686900004155177, 0.0006182140000419167], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1751538851498, 18.239, [0.00012524, 0.00060676], [0.00013161, 0.0006405], [0.00012579, 0.00060829], [0.00012952, 0.00063066], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.23076480500003527, 0.23163660399995933], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1751538855102, 21.331, [0.228, 0.22934], [0.24129, 0.23958], [0.22861, 0.23032], [0.23462, 0.23903], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.21042045099989082], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1751538859357, 8.7502, [0.20296], [0.21809], [0.20498], [0.21758], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.2794132599999557, 0.28440619299999526], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1751538864363, 39.572, [0.27575, 0.28135], [0.28414, 0.29307], [0.2763, 0.28173], [0.2821, 0.29138], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.16650312799993117, 0.17177308600003016], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1751538872327, 50.556, [0.1622, 0.16821], [0.16877, 0.17739], [0.16257, 0.16909], [0.16869, 0.17637], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.005049280000093859, 0.004947880000145233], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1751538882427, 18.336, [0.0048311, 0.0047979], [0.0051732, 0.0051086], [0.0048842, 0.0047995], [0.005169, 0.0050803], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.0006160310000495883, 0.0006166809999967882], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1751538886046, 18.232, [0.00060088, 0.00060389], [0.00063506, 0.00063793], [0.00060598, 0.00060406], [0.00063334, 0.00063127], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.2295973340000046, 0.23030742100002044], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1751538889806, 21.076, [0.22658, 0.22629], [0.24075, 0.2421], [0.22709, 0.22647], [0.23956, 0.2393], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[53096448, 55484416], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1751538830129, 0.81232], "Components vs Django.peakmem_render_lg_subsequent": [[53018624, 56389632], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1751538830942, 1.0287], "Components vs Django.peakmem_render_sm_first": [[44716032, 44969984], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1751538831971, 0.39208], "Components vs Django.peakmem_render_sm_subsequent": [[44724224, 44969984], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1751538832363, 0.38781], "isolated vs django modes.peakmem_render_lg_first": [[55476224, 54968320], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1751538861133, 1.051], "isolated vs django modes.peakmem_render_lg_subsequent": [[56352768, 56516608], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1751538862185, 1.3815], "isolated vs django modes.peakmem_render_sm_first": [[44969984, 44969984], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1751538863566, 0.38739], "isolated vs django modes.peakmem_render_sm_subsequent": [[44974080, 44974080], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1751538863954, 0.40929]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/d0a42a26-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/d0a42a26-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index ca3e5fe8..00000000 --- a/.asv/results/ci-linux/d0a42a26-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "d0a42a2698f2ba21e7ab2dec750c5dbadeda0db5", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1742502414000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.06960565700001098, 0.25608221199996706], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1742503651927, 30.962, [0.069111, 0.25384], [0.071024, 0.25982], [0.069155, 0.25418], [0.07084, 0.25904], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.03327357099999517, 0.1421111020000012], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1742503658029, 35.986, [0.032901, 0.14037], [0.03367, 0.14348], [0.033021, 0.14072], [0.033649, 0.14344], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.0035443229999998493, 0.00467639600003622], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1742503665225, 16.51, [0.0035043, 0.0046301], [0.003593, 0.0047163], [0.0035112, 0.0046363], [0.0035924, 0.0047145], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.00010400499999718704, 0.0005328339999977061], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1742503668520, 16.499, [0.00010269, 0.00052112], [0.00010566, 0.00054112], [0.00010277, 0.00052114], [0.00010541, 0.00054099], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.21775109000003567, 0.21398552899995593], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1742503671830, 19.442, [0.21622, 0.2132], [0.22078, 0.217], [0.21658, 0.21324], [0.22069, 0.2155], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.19832900800003017], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1742503675760, 7.9408, [0.19702], [0.20107], [0.19715], [0.20055], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.2574955810000006, 0.2591010970000127], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1742503680343, 36.144, [0.25466, 0.25782], [0.25904, 0.26157], [0.25503, 0.25788], [0.25902, 0.2613], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.14273938200000202, 0.1464969190000147], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1742503687671, 45.015, [0.1414, 0.14565], [0.14564, 0.14848], [0.14161, 0.14579], [0.14534, 0.14838], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.004720848000005162, 0.004705489000002672], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1742503696639, 16.62, [0.0046697, 0.0046577], [0.00477, 0.0047524], [0.0046797, 0.0046634], [0.0047686, 0.0047508], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.0005377129999999397, 0.0005395769999836375], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1742503699956, 16.713, [0.00052335, 0.00052522], [0.00054538, 0.00054226], [0.00052633, 0.00052673], [0.00054385, 0.00054214], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.21402431699999624, 0.21364062999998623], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1742503703278, 19.101, [0.21272, 0.21292], [0.21716, 0.21483], [0.21299, 0.21294], [0.21691, 0.21473], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[52920320, 54566912], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1742503649537, 0.74708], "Components vs Django.peakmem_render_lg_subsequent": [[53800960, 54734848], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1742503650284, 0.93262], "Components vs Django.peakmem_render_sm_first": [[44191744, 44191744], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1742503651217, 0.35476], "Components vs Django.peakmem_render_sm_subsequent": [[44195840, 44187648], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1742503651572, 0.35467], "isolated vs django modes.peakmem_render_lg_first": [[54439936, 53968896], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1742503677373, 0.96761], "isolated vs django modes.peakmem_render_lg_subsequent": [[54968320, 54792192], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1742503678341, 1.2663], "isolated vs django modes.peakmem_render_sm_first": [[44187648, 44183552], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1742503679607, 0.36851], "isolated vs django modes.peakmem_render_sm_subsequent": [[44187648, 44187648], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1742503679976, 0.36668]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/fdd29baa-virtualenv-py3.13-django5.1-djc-core-html-parser.json b/.asv/results/ci-linux/fdd29baa-virtualenv-py3.13-django5.1-djc-core-html-parser.json deleted file mode 100644 index bae22ff4..00000000 --- a/.asv/results/ci-linux/fdd29baa-virtualenv-py3.13-django5.1-djc-core-html-parser.json +++ /dev/null @@ -1 +0,0 @@ -{"commit_hash": "fdd29baa65e9ef78eb24a0ad2ca0b5d7c624dad3", "env_name": "virtualenv-py3.13-django5.1-djc-core-html-parser", "date": 1743837055000, "params": {"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": ""}, "python": "3.13", "requirements": {"django": "5.1", "djc-core-html-parser": ""}, "env_vars": {}, "result_columns": ["result", "params", "version", "started_at", "duration", "stats_ci_99_a", "stats_ci_99_b", "stats_q_25", "stats_q_75", "stats_number", "stats_repeat", "samples", "profile"], "results": {"Components vs Django.timeraw_render_lg_first": [[0.07297276199997782, 0.2569234329999972], [["'django'", "'django-components'"]], "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", 1743837476879, 30.873, [0.071985, 0.25428], [0.074103, 0.26075], [0.072038, 0.25476], [0.07398, 0.26005], [1, 1], [25, 25]], "Components vs Django.timeraw_render_lg_subsequent": [[0.03658580800001232, 0.1459621130000528], [["'django'", "'django-components'"]], "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", 1743837482929, 36.367, [0.036404, 0.14414], [0.037604, 0.14778], [0.036407, 0.14431], [0.037516, 0.14769], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_first": [[0.0035008030000085455, 0.004695608999981005], [["'django'", "'django-components'"]], "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", 1743837490117, 16.492, [0.0034613, 0.0046476], [0.0035362, 0.0047717], [0.0034638, 0.0046525], [0.0035327, 0.0047464], [1, 1], [25, 25]], "Components vs Django.timeraw_render_sm_subsequent": [[0.00011641800000461444, 0.0005489540000098714], [["'django'", "'django-components'"]], "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", 1743837493363, 16.416, [0.0001146, 0.00053209], [0.00011763, 0.00055102], [0.00011495, 0.00053218], [0.00011722, 0.00055099], [1, 1], [25, 25]], "Components vs Django.timeraw_startup_lg": [[0.2166100470000174, 0.21420494400001644], [["'django'", "'django-components'"]], "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", 1743837496617, 19.507, [0.21578, 0.21377], [0.22038, 0.21873], [0.21585, 0.21391], [0.22012, 0.21833], [1, 1], [25, 25]], "Other.timeraw_import_time": [[0.19625152499997967], [], "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", 1743837500450, 7.846, [0.19508], [0.1987], [0.19556], [0.19776], [1], [25]], "isolated vs django modes.timeraw_render_lg_first": [[0.2570519909999689, 0.2606809000000112], [["'isolated'", "'django'"]], "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", 1743837504912, 35.956, [0.25574, 0.25887], [0.26068, 0.26247], [0.25581, 0.25948], [0.26037, 0.26206], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_lg_subsequent": [[0.14520097999997006, 0.14991973799999414], [["'isolated'", "'django'"]], "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", 1743837512048, 45.026, [0.14407, 0.14843], [0.14758, 0.15221], [0.14409, 0.1487], [0.14648, 0.15174], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_first": [[0.004671787999996013, 0.004672599999992144], [["'isolated'", "'django'"]], "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", 1743837521015, 16.514, [0.0046336, 0.00463], [0.0047529, 0.0047334], [0.0046393, 0.00464], [0.0047259, 0.0047218], [1, 1], [25, 25]], "isolated vs django modes.timeraw_render_sm_subsequent": [[0.000542692999999872, 0.0005430530000012368], [["'isolated'", "'django'"]], "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", 1743837524265, 16.535, [0.00053046, 0.00052986], [0.00055115, 0.00055085], [0.00053202, 0.00053211], [0.00055088, 0.00055006], [1, 1], [25, 25]], "isolated vs django modes.timeraw_startup_lg": [[0.2147675530000015, 0.21506381099999317], [["'isolated'", "'django'"]], "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", 1743837527532, 19.077, [0.21332, 0.21271], [0.21696, 0.21734], [0.21358, 0.21356], [0.21674, 0.21727], [1, 1], [25, 25]], "Components vs Django.peakmem_render_lg_first": [[52379648, 54992896], [["'django'", "'django-components'"]], "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528", 1743837474462, 0.76167], "Components vs Django.peakmem_render_lg_subsequent": [[51998720, 55451648], [["'django'", "'django-components'"]], "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b", 1743837475225, 0.95029], "Components vs Django.peakmem_render_sm_first": [[44195840, 44314624], [["'django'", "'django-components'"]], "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036", 1743837476175, 0.35143], "Components vs Django.peakmem_render_sm_subsequent": [[44322816, 44314624], [["'django'", "'django-components'"]], "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026", 1743837476527, 0.35175], "isolated vs django modes.peakmem_render_lg_first": [[54992896, 54345728], [["'isolated'", "'django'"]], "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9", 1743837501990, 0.94949], "isolated vs django modes.peakmem_render_lg_subsequent": [[55455744, 55177216], [["'isolated'", "'django'"]], "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf", 1743837502940, 1.2595], "isolated vs django modes.peakmem_render_sm_first": [[44314624, 44314624], [["'isolated'", "'django'"]], "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840", 1743837504200, 0.3547], "isolated vs django modes.peakmem_render_sm_subsequent": [[44314624, 44314624], [["'isolated'", "'django'"]], "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260", 1743837504555, 0.35682]}, "durations": {}, "version": 2} \ No newline at end of file diff --git a/.asv/results/ci-linux/machine.json b/.asv/results/ci-linux/machine.json deleted file mode 100644 index 2e8a88df..00000000 --- a/.asv/results/ci-linux/machine.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "machine": "ci-linux", - "version": 1 -} \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 9558bffd..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,32 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: -// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.0/containers/python-3 -{ - // Uncomment to run Python 3.13 or other specific version - // "image": "mcr.microsoft.com/devcontainers/python:3.13-bullseye", - - // Configure tool-specific properties. - "customizations": { - // Configure properties specific to VS Code. - "vscode": { - // Set *default* container specific settings.json values on container create. - "settings": { - "python.defaultInterpreterPath": "/usr/local/bin/python", - "python.linting.enabled": true - }, - - // Add the IDs of extensions you want installed when the container is created. - "extensions": [ - "ms-python.python", - "ms-python.vscode-pylance", - "ms-python.vscode-python-envs", - "jurooravec.python-inline-source-2" - ] - } - } - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Use 'postCreateCommand' to run commands after the container is created. - //"postCreateCommand": "" -} diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 97ca6884..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -github: ["EmilStenstrom"] - diff --git a/.github/dependabot.yml b/.github/dependabot.yml index aff066ed..cf7a39fb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,17 +1,11 @@ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: -# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: - interval: "weekly" - - package-ecosystem: github-actions - # This actually targets ./.github/workflows/ - # See https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference#directories-or-directory-- - directory: "/" - schedule: - interval: monthly + interval: "daily" diff --git a/.github/workflows/automate-dependabot.yml b/.github/workflows/automate-dependabot.yml deleted file mode 100644 index cad86071..00000000 --- a/.github/workflows/automate-dependabot.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Dependabot auto-merge -on: pull_request - -permissions: - contents: write - pull-requests: write - -jobs: - dependabot: - runs-on: ubuntu-latest - if: github.actor == 'dependabot[bot]' - steps: - - name: Dependabot metadata - id: metadata - uses: dependabot/fetch-metadata@v2 - with: - github-token: "${{ secrets.GITHUB_TOKEN }}" - - name: Enable auto-merge for Dependabot PRs - run: gh pr merge --auto --merge "$PR_URL" - env: - PR_URL: ${{github.event.pull_request.html_url}} - GH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/contributors.yml b/.github/workflows/contributors.yml new file mode 100644 index 00000000..947111bc --- /dev/null +++ b/.github/workflows/contributors.yml @@ -0,0 +1,19 @@ +name: Add contributors to readme + +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + contrib-readme-job: + runs-on: ubuntu-latest + name: Add contributors to readme + steps: + - name: Contribute List + uses: akhilmhdh/contributors-readme-action@v2.3.3 + with: + is_protected: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index d13f870d..00000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,195 +0,0 @@ ---- -name: Docs - build & deploy -on: - push: - tags: - # for versions 0.### (before 1.0.0) - - '0.[0-9]+' - # after 1.0.0 - - '[0-9]+.[0-9]+.[0-9]+' - branches: - - master - workflow_dispatch: - -jobs: - docs: - # Grant GITHUB_TOKEN the permissions required to make a Pages deployment - permissions: - contents: write # to let mkdocs write the new docs - pages: write # to deploy to Pages - id-token: write # to verify the deployment originates from an appropriate source - runs-on: ubuntu-latest - # Only run in original repo (not in forks) - if: github.repository == 'django-components/django-components' - steps: - - ############################## - # SETUP - ############################## - - # Authenticate with git with the Github App that has permission - # to push to master, in order to push benchmark results. - # See https://stackoverflow.com/a/79142962/9788634 - - uses: actions/create-github-app-token@v2 - id: app-token - with: - app-id: ${{ vars.RELEASE_BOT_APP_ID }} - private-key: ${{ secrets.RELEASE_BOT_APP_PRIVATE_KEY }} - - - name: Checkout - uses: actions/checkout@v4 - with: - token: ${{ steps.app-token.outputs.token }} - fetch-depth: 0 - - - name: Configure git account - run: | - git config user.name components-release-bot - git config user.email "components-release-bot@users.noreply.github.com" - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.13" - cache: 'pip' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip wheel - # NOTE: pin virtualenv to <20.31 until asv fixes it. - # See https://github.com/airspeed-velocity/asv/issues/1484 - python -m pip install -q hatch pre-commit asv virtualenv==20.30 - hatch --version - - ########################################### - # RECORD BENCHMARK - ONLY ON PUSH TO MASTER - ########################################### - - - name: Run benchmarks for tag - if: github.ref_type == 'tag' && github.event_name == 'push' - env: - # See https://github.com/github/docs/issues/21930 - # And https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # Get the master branch so we can run benchmarks on it - git remote add upstream https://github.com/${{ github.repository }}.git - git fetch origin master:master - git checkout master - - # Get tag name - TAG=${GITHUB_REF#refs/tags/} - echo "TAG: $TAG" - - # TODO: REMOVE ONCE FIXED UPSTREAM - # Fix for https://github.com/airspeed-velocity/asv_runner/issues/45 - # Prepare virtual environment - # Currently, we have to monkeypatch the `timeit` function in the `timeraw` benchmark. - # The problem is that `asv` passes the code to execute via command line, and when the - # code is too big, it fails with `OSError: [Errno 7] Argument list too long`. - # So we have to tweak it to pass the code via STDIN, which doesn't have this limitation. - # - # 1. First create the virtual environment, so that asv generates the directories where - # the monkeypatch can be applied. - echo "Creating virtual environment..." - asv setup -v || true - echo "Virtual environment created." - # 2. Now let's apply the monkeypatch by appending it to the `timeraw.py` files. - # First find all `timeraw.py` files - echo "Applying monkeypatch..." - find .asv/env -type f -path "*/site-packages/asv_runner/benchmarks/timeraw.py" | while read -r file; do - # Add a newline and then append the monkeypatch contents - echo "" >> "$file" - cat "benchmarks/monkeypatch_asv_ci.txt" >> "$file" - done - echo "Monkeypatch applied." - # END OF MONKEYPATCH - - # Prepare the profile under which the benchmarks will be saved. - # We assume that the CI machine has a name that is unique and stable. - # See https://github.com/airspeed-velocity/asv/issues/796#issuecomment-1188431794 - echo "Preparing benchmarks profile..." - asv machine --yes --machine ci-linux - echo "Benchmarks profile DONE." - - # Run benchmarks for the current tag - # - `^` means that we mean the COMMIT of the tag's branch, not the BRANCH itself. - # Without it, we would run benchmarks for the whole branch history. - # With it, we run benchmarks FROM the tag's commit (incl) TO ... - # - `!` means that we want to select range spanning a single commit. - # Without it, we would run benchmarks for all commits FROM the tag's commit - # TO the start of the branch history. - # With it, we run benchmarks ONLY FOR the tag's commit. - echo "Running benchmarks for tag ${TAG}..." - asv run master^! -v - echo "Benchmarks for tag ${TAG} DONE." - - # Generate benchmarks site - # This should save it in `docs/benchmarks/`, so we can then use it when - # building docs site with `mkdocs`. - echo "Generating benchmarks site..." - asv publish - echo "Benchmarks site DONE." - - # Commit benchmark results - echo "Staging and committing benchmark results..." - git add .asv/results/ - git add docs/benchmarks/ - git commit -m "Add benchmark results for ${TAG}" - echo "Benchmark results committed." - - # Push to the new branch - echo "Pushing benchmark results..." - git push origin master - echo "Benchmark results pushed to master." - - ########################################### - # BUILD & RELEASE DOCS - ########################################### - - # Change git authentication to Github Actions, so the rest of the - # workflow will have lower privileges. - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Configure git - run: | - # required for "mike deploy" command below which pushes to gh-pages - git config user.name github-actions - git config user.email github-actions@github.com - - # Conditions make sure to select the right step, depending on the job trigger. - # Only one of the steps below will run at a time. The others will be skipped. - - - name: Check docs in pull requests with strict mode - if: github.event_name == 'pull_request' - run: | - # XXX Enable strict mode once docs are clean - echo "Strict check of docs disabled." - # hatch run docs:build --strict - - - name: Build & deploy "dev" docs for a new commit to master - if: github.event_name == 'push' && github.ref_type != 'tag' - run: | - # Fetch and checkout gh-pages to ensure we have the latest version - git fetch origin gh-pages - git checkout gh-pages - git pull origin gh-pages - git checkout master - - export SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7) - hatch run docs:mike deploy --push --update-aliases --title "dev (${SHORT_SHA})" dev - - - name: Build & deploy docs for a new tag - if: github.ref_type == 'tag' && github.event_name == 'push' - run: | - # Fetch and checkout gh-pages to ensure we have the latest version - git fetch origin gh-pages - git checkout gh-pages - git pull origin gh-pages - git checkout master - - hatch run docs:mike deploy --push --update-aliases ${{ github.ref_name }} latest - hatch run docs:mike set-default latest --push diff --git a/.github/workflows/pr-benchmark-comment.yml b/.github/workflows/pr-benchmark-comment.yml deleted file mode 100644 index b80a13bd..00000000 --- a/.github/workflows/pr-benchmark-comment.yml +++ /dev/null @@ -1,99 +0,0 @@ -# Run benchmark report on pull requests to master. -# The report is added to the PR as a comment. -# -# NOTE: When making a PR from a fork, the worker doesn't have sufficient -# access to make comments on the target repo's PR. And so, this workflow -# is split to two parts: -# -# 1. Benchmarking and saving results as artifacts -# 2. Downloading the results and commenting on the PR -# -# See https://stackoverflow.com/a/71683208/9788634 - -name: PR benchmark comment - -on: - workflow_run: - # NOTE: The name here MUST match the name of the workflow that generates the data - workflows: [PR benchmarks generate] - types: - - completed - -jobs: - download: - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - repository-projects: write - steps: - ########## USE FOR DEBUGGING ########## - - name: Debug workflow run info - uses: actions/github-script@v7 - with: - script: | - console.log('Workflow Run ID:', context.payload.workflow_run.id); - const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: context.payload.workflow_run.id - }); - console.log('Available artifacts:'); - console.log(JSON.stringify(artifacts.data, null, 2)); - console.log(`PRs: ` + JSON.stringify(context.payload.workflow_run.pull_requests)); - ######################################### - - # NOTE: The next two steps (download and unzip) are equivalent to using `actions/download-artifact@v4` - # However, `download-artifact` was not picking up the artifact, while the REST client does. - - name: Download benchmark results - uses: actions/github-script@v7 - with: - script: | - const fs = require('fs'); - - // Find the artifact that was generated by the "pr-benchmark-generate" workflow - const allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ - owner: context.repo.owner, - repo: context.repo.repo, - // Explicitly search the workflow run that generated the the results - // (AKA the "pr-benchmark-generate" workflow). - run_id: context.payload.workflow_run.id, - }); - const matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { - return artifact.name == "benchmark_results" - })[0]; - - // Download the artifact - const download = await github.rest.actions.downloadArtifact({ - owner: context.repo.owner, - repo: context.repo.repo, - artifact_id: matchArtifact.id, - archive_format: 'zip', - }); - fs.writeFileSync( - `${process.env.GITHUB_WORKSPACE}/benchmark_results.zip`, - Buffer.from(download.data), - ); - - - name: Unzip artifact - run: unzip benchmark_results.zip - - - name: Comment on PR - # See https://github.com/actions/github-script - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const fs = require('fs'); - const results = fs.readFileSync('./benchmark_results.md', 'utf8'); - const issue_number = Number.parseInt(fs.readFileSync('./pr_number.txt', 'utf8')); - const body = `## Performance Benchmark Results\n\nComparing PR changes against master branch:\n\n${results}`; - - // See https://octokit.github.io/rest.js/v21/#issues-create-comment - await github.rest.issues.createComment({ - body: body, - // See https://github.com/actions/toolkit/blob/662b9d91f584bf29efbc41b86723e0e376010e41/packages/github/src/context.ts#L66 - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue_number, - }); diff --git a/.github/workflows/pr-benchmark-generate.yml b/.github/workflows/pr-benchmark-generate.yml deleted file mode 100644 index ff92ad5f..00000000 --- a/.github/workflows/pr-benchmark-generate.yml +++ /dev/null @@ -1,112 +0,0 @@ -# Run benchmark report on pull requests to master. -# The report is added to the PR as a comment. -# -# NOTE: When making a PR from a fork, the worker doesn't have sufficient -# access to make comments on the target repo's PR. And so, this workflow -# is split to two parts: -# -# 1. Benchmarking and saving results as artifacts -# 2. Downloading the results and commenting on the PR -# -# See https://stackoverflow.com/a/71683208/9788634 - -name: PR benchmarks generate - -on: - pull_request: - branches: [ master ] - -jobs: - benchmark: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Need full history for ASV - - - name: Fetch base branch - run: | - git remote add upstream https://github.com/${{ github.repository }}.git - git fetch upstream master - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.13' - cache: 'pip' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - # NOTE: pin virtualenv to <20.31 until asv fixes it. - # See https://github.com/airspeed-velocity/asv/issues/1484 - pip install asv virtualenv==20.30 - - - name: Run benchmarks - run: | - # TODO: REMOVE ONCE FIXED UPSTREAM - # Fix for https://github.com/airspeed-velocity/asv_runner/issues/45 - # Prepare virtual environment - # Currently, we have to monkeypatch the `timeit` function in the `timeraw` benchmark. - # The problem is that `asv` passes the code to execute via command line, and when the - # code is too big, it fails with `OSError: [Errno 7] Argument list too long`. - # So we have to tweak it to pass the code via STDIN, which doesn't have this limitation. - # - # 1. First create the virtual environment, so that asv generates the directories where - # the monkeypatch can be applied. - echo "Creating virtual environment..." - asv setup -v || true - echo "Virtual environment created." - # 2. Now let's apply the monkeypatch by appending it to the `timeraw.py` files. - # First find all `timeraw.py` files - echo "Applying monkeypatch..." - find .asv/env -type f -path "*/site-packages/asv_runner/benchmarks/timeraw.py" | while read -r file; do - # Add a newline and then append the monkeypatch contents - echo "" >> "$file" - cat "benchmarks/monkeypatch_asv_ci.txt" >> "$file" - done - echo "Monkeypatch applied." - # END OF MONKEYPATCH - - # Prepare the profile under which the benchmarks will be saved. - # We assume that the CI machine has a name that is unique and stable. - # See https://github.com/airspeed-velocity/asv/issues/796#issuecomment-1188431794 - echo "Preparing benchmarks profile..." - MACHINE="ci_benchmark_${{ github.event.pull_request.number }}" - asv machine --yes -v --machine ${MACHINE} - echo "Benchmarks profile DONE." - - # Generate benchmark data - # - `^` means that we mean the COMMIT of the branch, not the BRANCH itself. - # Without it, we would run benchmarks for the whole branch history. - # With it, we run benchmarks FROM the latest commit (incl) TO ... - # - `!` means that we want to select range spanning a single commit. - # Without it, we would run benchmarks for all commits FROM the latest commit - # TO the start of the branch history. - # With it, we run benchmarks ONLY FOR the latest commit. - echo "Running benchmarks for upstream/master..." - DJC_BENCHMARK_QUICK=1 asv run upstream/master^! -v --machine ${MACHINE} - echo "Benchmarks for upstream/master DONE." - echo "Running benchmarks for HEAD..." - DJC_BENCHMARK_QUICK=1 asv run HEAD^! -v --machine ${MACHINE} - echo "Benchmarks for HEAD DONE." - - echo "Creating pr directory..." - mkdir -p pr - # Save the PR number to a file, so that it can be used by the next step. - echo "${{ github.event.pull_request.number }}" > ./pr/pr_number.txt - - # Compare against master - # NOTE: The command is run twice, once so we can see the debug output, and once to save the results. - echo "Comparing benchmarks... (debug)" - asv compare upstream/master HEAD --factor 1.1 --split --machine ${MACHINE} --verbose - echo "Comparing benchmarks... (saving results)" - asv compare upstream/master HEAD --factor 1.1 --split --machine ${MACHINE} > ./pr/benchmark_results.md - echo "Benchmarks comparison DONE." - - - name: Save benchmark results - uses: actions/upload-artifact@v4 - with: - name: benchmark_results - path: pr/ diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml index f3e0b170..8fcc25e8 100644 --- a/.github/workflows/publish-to-pypi.yml +++ b/.github/workflows/publish-to-pypi.yml @@ -12,15 +12,15 @@ on: jobs: build: runs-on: ubuntu-latest - if: github.repository == 'django-components/django-components' + steps: - name: Checkout the repo - uses: actions/checkout@v4 - + uses: actions/checkout@v2 + - name: Setup python - uses: actions/setup-python@v5 + uses: actions/setup-python@v2 with: - python-version: '3.13' + python-version: '3.9' - name: Install pypa/build run: >- diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bc0ecb99..87780137 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,101 +1,28 @@ name: Run tests -on: - push: - branches: - - 'master' - - 'dev' - pull_request: - workflow_dispatch: +on: [push, pull_request, workflow_dispatch] jobs: - build: - runs-on: ${{ matrix.os }} + run-tests: + runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] - os: [ubuntu-latest, windows-latest] - + python-version: + - "3.6" + - "3.7" + - "3.8" + - "3.9" + - "3.10" steps: - # Configure git to handle long paths - # See https://stackoverflow.com/questions/22575662/filename-too-long-in-git-for-windows - # - # Long paths that are over the limit are because of the benchmarking data - # created by asv, as these may look like this: - # docs/benchmarks/graphs/arch-x86_64/branch-master/cpu-AMD EPYC 7763 64-Core Processor/django-5.1/djc-core-html-parser/machine-fv-az1693-854/num_cpu-4/os-Linux 6.8.0-1021-azure/python-3.13/ram-16373792/isolated vs django modes.timeraw_render_lg_subsequent.json - - name: Configure git - run: | - git config --global core.longpaths true - - - uses: actions/checkout@v4 - + - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - cache: "pip" - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install -r requirements-ci.txt - # See https://playwright.dev/python/docs/intro#installing-playwright-pytest - playwright install chromium --with-deps + pip install tox tox-gh-actions - name: Run tests - run: tox - - # Verify that docs build - test_docs: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.13'] - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: "pip" - - - name: Install dependencies run: | - python -m pip install --upgrade pip - python -m pip install -r requirements-docs.txt - # Install your package locally - python -m pip install -e . - - name: Build documentation - run: mkdocs build --verbose - - # Verify that the sample project works - test_sampleproject: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ['3.13'] - - steps: - - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: "pip" - - - name: Install dependencies - run: | - cd sampleproject - python -m pip install --upgrade pip - python -m pip install -r requirements.txt - # Install django-components locally - python -m pip install -e .. - - name: Check Django project - run: | - cd sampleproject - python manage.py check - python manage.py migrate --noinput - # Start the server, make request, and exit with error if it fails - python manage.py runserver & sleep 5 - curl http://127.0.0.1:8000/ || exit 1 + tox diff --git a/.gitignore b/.gitignore index 3d7aeebe..b642bdb9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,3 @@ -# Project-specific files -sampleproject/staticfiles/ - # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -46,7 +43,6 @@ htmlcov/ nosetests.xml coverage.xml *,cover -.pytest_cache/ # Translations *.mo @@ -54,7 +50,6 @@ coverage.xml # Django stuff: *.log -*.sqlite3 # Sphinx documentation docs/_build/ @@ -69,22 +64,4 @@ target/ # lock file is not needed for development # as project supports variety of Django versions poetry.lock - -# PyCharm -.idea/ - -# Python environment -.venv/ -.DS_Store -.python-version -site -.direnv/ -.envrc -.mypy_cache/ - -# JS, NPM Dependency directories -node_modules/ -jspm_packages/ - -# Cursor -.cursorrules \ No newline at end of file +pyproject.toml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 18541677..81533e79 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,14 +1,14 @@ repos: - repo: https://github.com/pycqa/isort - rev: 5.13.2 + rev: 5.10.1 hooks: - id: isort - repo: https://github.com/psf/black - rev: 24.10.0 + rev: 22.6.0 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 7.1.1 + rev: 4.0.1 hooks: - id: flake8 - additional_dependencies: [flake8-pyproject] + diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 9cabbd37..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,2655 +0,0 @@ -# Release notes - -## v0.141.2 - -#### Fix - -- Fix bug where JS and CSS were missing when `{% component %}` tag was inside `{% include %}` tag ([#1296](https://github.com/django-components/django-components/issues/1296)) - -## v0.141.1 - -#### Fix - -- Components' JS and CSS scripts (e.g. from `Component.js` or `Component.js_file`) are now cached at class creation time. - - This means that when you now restart the server while having a page opened in the browser, - the JS / CSS files are immediately available. - - Previously, the JS/CSS were cached only after the components were rendered. So you had to reload - the page to trigger the rendering, in order to make the JS/CSS files available. - -- Fix the default cache for JS / CSS scripts to be unbounded. - - Previously, the default cache for the JS/CSS scripts (`LocMemCache`) was accidentally limited to 300 entries (~150 components). - -- Do not send `template_rendered` signal when rendering a component with no template. ([#1277](https://github.com/django-components/django-components/issues/1277)) - -## v0.141.0 - -#### Feat - -- New extension hooks `on_template_loaded`, `on_js_loaded`, `on_css_loaded`, and `on_template_compiled` - - The first 3 hooks are called when Component's template / JS / CSS is loaded as a string. - - The `on_template_compiled` hook is called when Component's template is compiled to a Template. - - The `on_xx_loaded` hooks can modify the content by returning the new value. - - ```py - class MyExtension(ComponentExtension): - def on_template_loaded(self, ctx: OnTemplateLoadedContext) -> Optional[str]: - return ctx.content + "" - - def on_js_loaded(self, ctx: OnJsLoadedContext) -> Optional[str]: - return ctx.content + "// Hello!" - - def on_css_loaded(self, ctx: OnCssLoadedContext) -> Optional[str]: - return ctx.content + "/* Hello! */" - ``` - - See all [Extension hooks](https://django-components.github.io/django-components/0.141.0/reference/extension_hooks/). - -#### Fix - -- Subclassing - Previously, if a parent component defined `Component.template` or `Component.template_file`, it's subclass would use the same `Template` instance. - - This could lead to unexpected behavior, where a change to the template of the subclass would also change the template of the parent class. - - Now, each subclass has it's own `Template` instance, and changes to the template of the subclass do not affect the template of the parent class. - -- Fix Django failing to restart due to "TypeError: 'Dynamic' object is not iterable" ([#1232](https://github.com/django-components/django-components/issues/1232)) - -- Fix bug when error formatting failed when error value was not a string. - -#### Refactor - -- `components ext run` CLI command now allows to call only those extensions that actually have subcommands. - -## v0.140.1 - -#### Fix - -- Fix typo preventing benchmarking ([#1235](https://github.com/django-components/django-components/pull/1235)) - -## 🚨📢 v0.140.0 - -⚠️ Major release ⚠️ - Please test thoroughly before / after upgrading. - -This is the biggest step towards v1. While this version introduces -many small API changes, we don't expect to make further changes to -the affected parts before v1. - -For more details see [#433](https://github.com/django-components/django-components/issues/433). - -Summary: - -- Overhauled typing system -- Middleware removed, no longer needed -- `get_template_data()` is the new canonical way to define template data. - `get_context_data()` is now deprecated but will remain until v2. -- Slots API polished and prepared for v1. -- Merged `Component.Url` with `Component.View` -- Added `Component.args`, `Component.kwargs`, `Component.slots`, `Component.context` -- Added `{{ component_vars.args }}`, `{{ component_vars.kwargs }}`, `{{ component_vars.slots }}` -- You should no longer instantiate `Component` instances. Instead, call `Component.render()` or `Component.render_to_response()` directly. -- Component caching can now consider slots (opt-in) -- And lot more... - -#### 🚨📢 BREAKING CHANGES - -**Middleware** - -- The middleware `ComponentDependencyMiddleware` was removed as it is no longer needed. - - The middleware served one purpose - to render the JS and CSS dependencies of components - when you rendered templates with `Template.render()` or `django.shortcuts.render()` and those templates contained `{% component %}` tags. - - - NOTE: If you rendered HTML with `Component.render()` or `Component.render_to_response()`, the JS and CSS were already rendered. - - Now, the JS and CSS dependencies of components are automatically rendered, - even when you render Templates with `Template.render()` or `django.shortcuts.render()`. - - To disable this behavior, set the `DJC_DEPS_STRATEGY` context key to `"ignore"` - when rendering the template: - - ```py - # With `Template.render()`: - template = Template(template_str) - rendered = template.render(Context({"DJC_DEPS_STRATEGY": "ignore"})) - - # Or with django.shortcuts.render(): - from django.shortcuts import render - rendered = render( - request, - "my_template.html", - context={"DJC_DEPS_STRATEGY": "ignore"}, - ) - ``` - - In fact, you can set the `DJC_DEPS_STRATEGY` context key to any of the strategies: - - - `"document"` - - `"fragment"` - - `"simple"` - - `"prepend"` - - `"append"` - - `"ignore"` - - See [Dependencies rendering](https://django-components.github.io/django-components/0.140.1/concepts/advanced/rendering_js_css/) for more info. - -**Typing** - -- Component typing no longer uses generics. Instead, the types are now defined as class attributes of the component class. - - Before: - - ```py - Args = Tuple[float, str] - - class Button(Component[Args]): - pass - ``` - - After: - - ```py - class Button(Component): - class Args(NamedTuple): - size: float - text: str - ``` - - - See [Migrating from generics to class attributes](https://django-components.github.io/django-components/0.140.1/concepts/fundamentals/typing_and_validation/#migrating-from-generics-to-class-attributes) for more info. -- Removed `EmptyTuple` and `EmptyDict` types. Instead, there is now a single `Empty` type. - - ```py - from django_components import Component, Empty - - class Button(Component): - template = "Hello" - - Args = Empty - Kwargs = Empty - ``` - -**Component API** - -- The interface of the not-yet-released `get_js_data()` and `get_css_data()` methods has changed to - match `get_template_data()`. - - Before: - - ```py - def get_js_data(self, *args, **kwargs): - def get_css_data(self, *args, **kwargs): - ``` - - After: - - ```py - def get_js_data(self, args, kwargs, slots, context): - def get_css_data(self, args, kwargs, slots, context): - ``` - -- Arguments in `Component.render_to_response()` have changed - to match that of `Component.render()`. - - Please ensure that you pass the parameters as kwargs, not as positional arguments, - to avoid breaking changes. - - The signature changed, moving the `args` and `kwargs` parameters to 2nd and 3rd position. - - Next, the `render_dependencies` parameter was added to match `Component.render()`. - - Lastly: - - - Previously, any extra ARGS and KWARGS were passed to the `response_class`. - - Now, only extra KWARGS will be passed to the `response_class`. - - Before: - - ```py - def render_to_response( - cls, - context: Optional[Union[Dict[str, Any], Context]] = None, - slots: Optional[SlotsType] = None, - escape_slots_content: bool = True, - args: Optional[ArgsType] = None, - kwargs: Optional[KwargsType] = None, - deps_strategy: DependenciesStrategy = "document", - request: Optional[HttpRequest] = None, - *response_args: Any, - **response_kwargs: Any, - ) -> HttpResponse: - ``` - - After: - - ```py - def render_to_response( - context: Optional[Union[Dict[str, Any], Context]] = None, - args: Optional[Any] = None, - kwargs: Optional[Any] = None, - slots: Optional[Any] = None, - deps_strategy: DependenciesStrategy = "document", - type: Optional[DependenciesStrategy] = None, # Deprecated, use `deps_strategy` - render_dependencies: bool = True, # Deprecated, use `deps_strategy="ignore"` - outer_context: Optional[Context] = None, - request: Optional[HttpRequest] = None, - registry: Optional[ComponentRegistry] = None, - registered_name: Optional[str] = None, - node: Optional[ComponentNode] = None, - **response_kwargs: Any, - ) -> HttpResponse: - ``` - -- `Component.render()` and `Component.render_to_response()` NO LONGER accept `escape_slots_content` kwarg. - - Instead, slots are now always escaped. - - To disable escaping, wrap the result of `slots` in - [`mark_safe()`](https://docs.djangoproject.com/en/5.2/ref/utils/#django.utils.safestring.mark_safe). - - Before: - - ```py - html = component.render( - slots={"my_slot": "CONTENT"}, - escape_slots_content=False, - ) - ``` - - After: - - ```py - html = component.render( - slots={"my_slot": mark_safe("CONTENT")} - ) - ``` - -- `Component.template` no longer accepts a Template instance, only plain string. - - Before: - - ```py - class MyComponent(Component): - template = Template("{{ my_var }}") - ``` - - Instead, either: - - 1. Set `Component.template` to a plain string. - - ```py - class MyComponent(Component): - template = "{{ my_var }}" - ``` - - 2. Move the template to it's own HTML file and set `Component.template_file`. - - ```py - class MyComponent(Component): - template_file = "my_template.html" - ``` - - 3. Or, if you dynamically created the template, render the template inside `Component.on_render()`. - - ```py - class MyComponent(Component): - def on_render(self, context, template): - dynamic_template = do_something_dynamic() - return dynamic_template.render(context) - ``` - -- Subclassing of components with `None` values has changed: - - Previously, when a child component's template / JS / CSS attributes were set to `None`, the child component still inherited the parent's template / JS / CSS. - - Now, the child component will not inherit the parent's template / JS / CSS if it sets the attribute to `None`. - - Before: - - ```py - class Parent(Component): - template = "parent.html" - - class Child(Parent): - template = None - - # Child still inherited parent's template - assert Child.template == Parent.template - ``` - - After: - - ```py - class Parent(Component): - template = "parent.html" - - class Child(Parent): - template = None - - # Child does not inherit parent's template - assert Child.template is None - ``` - -- The `Component.Url` class was merged with `Component.View`. - - Instead of `Component.Url.public`, use `Component.View.public`. - - If you imported `ComponentUrl` from `django_components`, you need to update your import to `ComponentView`. - - Before: - - ```py - class MyComponent(Component): - class Url: - public = True - - class View: - def get(self, request): - return self.render_to_response() - ``` - - After: - - ```py - class MyComponent(Component): - class View: - public = True - - def get(self, request): - return self.render_to_response() - ``` - -- Caching - The function signatures of `Component.Cache.get_cache_key()` and `Component.Cache.hash()` have changed to enable passing slots. - - Args and kwargs are no longer spread, but passed as a list and a dict, respectively. - - Before: - - ```py - def get_cache_key(self, *args: Any, **kwargs: Any) -> str: - - def hash(self, *args: Any, **kwargs: Any) -> str: - ``` - - After: - - ```py - def get_cache_key(self, args: Any, kwargs: Any, slots: Any) -> str: - - def hash(self, args: Any, kwargs: Any) -> str: - ``` - -**Template tags** - -- Component name in the `{% component %}` tag can no longer be set as a kwarg. - - Instead, the component name MUST be the first POSITIONAL argument only. - - Before, it was possible to set the component name as a kwarg - and put it anywhere in the `{% component %}` tag: - - ```django - {% component rows=rows headers=headers name="my_table" ... / %} - ``` - - Now, the component name MUST be the first POSITIONAL argument: - - ```django - {% component "my_table" rows=rows headers=headers ... / %} - ``` - - Thus, the `name` kwarg can now be used as a regular input. - - ```django - {% component "profile" name="John" job="Developer" / %} - ``` - -**Slots** - -- If you instantiated `Slot` class with kwargs, you should now use `contents` instead of `content_func`. - - Before: - - ```py - slot = Slot(content_func=lambda *a, **kw: "CONTENT") - ``` - - After: - - ```py - slot = Slot(contents=lambda ctx: "CONTENT") - ``` - - Alternatively, pass the function / content as first positional argument: - - ```py - slot = Slot(lambda ctx: "CONTENT") - ``` - -- The undocumented `Slot.escaped` attribute was removed. - - Instead, slots are now always escaped. - - To disable escaping, wrap the result of `slots` in - [`mark_safe()`](https://docs.djangoproject.com/en/5.2/ref/utils/#django.utils.safestring.mark_safe). - -- Slot functions behavior has changed. See the new [Slots](https://django-components.github.io/django-components/latest/concepts/fundamentals/slots/) docs for more info. - - - Function signature: - - 1. All parameters are now passed under a single `ctx` argument. - - You can still access all the same parameters via `ctx.context`, `ctx.data`, and `ctx.fallback`. - - 2. `context` and `fallback` now may be `None` if the slot function was called outside of `{% slot %}` tag. - - Before: - - ```py - def slot_fn(context: Context, data: Dict, slot_ref: SlotRef): - isinstance(context, Context) - isinstance(data, Dict) - isinstance(slot_ref, SlotRef) - - return "CONTENT" - ``` - - After: - - ```py - def slot_fn(ctx: SlotContext): - assert isinstance(ctx.context, Context) # May be None - assert isinstance(ctx.data, Dict) - assert isinstance(ctx.fallback, SlotFallback) # May be None - - return "CONTENT" - ``` - - - Calling slot functions: - - 1. Rather than calling the slot functions directly, you should now call the `Slot` instances. - - 2. All parameters are now optional. - - 3. The order of parameters has changed. - - Before: - - ```py - def slot_fn(context: Context, data: Dict, slot_ref: SlotRef): - return "CONTENT" - - html = slot_fn(context, data, slot_ref) - ``` - - After: - - ```py - def slot_fn(ctx: SlotContext): - return "CONTENT" - - slot = Slot(slot_fn) - html = slot() - html = slot({"data1": "abc", "data2": "hello"}) - html = slot({"data1": "abc", "data2": "hello"}, fallback="FALLBACK") - ``` - - - Usage in components: - - Before: - - ```python - class MyComponent(Component): - def get_context_data(self, *args, **kwargs): - slots = self.input.slots - slot_fn = slots["my_slot"] - html = slot_fn(context, data, slot_ref) - return { - "html": html, - } - ``` - - After: - - ```python - class MyComponent(Component): - def get_template_data(self, args, kwargs, slots, context): - slot_fn = slots["my_slot"] - html = slot_fn(data) - return { - "html": html, - } - ``` - -**Miscellaneous** - -- The second argument to `render_dependencies()` is now `strategy` instead of `type`. - - Before: - - ```py - render_dependencies(content, type="document") - ``` - - After: - - ```py - render_dependencies(content, strategy="document") - ``` - -#### 🚨📢 Deprecation - -**Component API** - -- `Component.get_context_data()` is now deprecated. Use `Component.get_template_data()` instead. - - `get_template_data()` behaves the same way, but has a different function signature - to accept also slots and context. - - Since `get_context_data()` is widely used, it will remain available until v2. - -- `Component.get_template_name()` and `Component.get_template()` are now deprecated. Use `Component.template`, -`Component.template_file` or `Component.on_render()` instead. - - `Component.get_template_name()` and `Component.get_template()` will be removed in v1. - - In v1, each Component will have at most one static template. - This is needed to enable support for Markdown, Pug, or other pre-processing of templates by extensions. - - If you are using the deprecated methods to point to different templates, there's 2 ways to migrate: - - 1. Split the single Component into multiple Components, each with its own template. Then switch between them in `Component.on_render()`: - - ```py - class MyComponentA(Component): - template_file = "a.html" - - class MyComponentB(Component): - template_file = "b.html" - - class MyComponent(Component): - def on_render(self, context, template): - if context["a"]: - return MyComponentA.render(context) - else: - return MyComponentB.render(context) - ``` - - 2. Alternatively, use `Component.on_render()` with Django's `get_template()` to dynamically render different templates: - - ```py - from django.template.loader import get_template - - class MyComponent(Component): - def on_render(self, context, template): - if context["a"]: - template_name = "a.html" - else: - template_name = "b.html" - - actual_template = get_template(template_name) - return actual_template.render(context) - ``` - - Read more in [django-components#1204](https://github.com/django-components/django-components/discussions/1204). - -- The `type` kwarg in `Component.render()` and `Component.render_to_response()` is now deprecated. Use `deps_strategy` instead. The `type` kwarg will be removed in v1. - - Before: - - ```py - Calendar.render_to_response(type="fragment") - ``` - - After: - - ```py - Calendar.render_to_response(deps_strategy="fragment") - ``` - -- The `render_dependencies` kwarg in `Component.render()` and `Component.render_to_response()` is now deprecated. Use `deps_strategy="ignore"` instead. The `render_dependencies` kwarg will be removed in v1. - - Before: - - ```py - Calendar.render_to_response(render_dependencies=False) - ``` - - After: - - ```py - Calendar.render_to_response(deps_strategy="ignore") - ``` - -- Support for `Component` constructor kwargs `registered_name`, `outer_context`, and `registry` is deprecated, and will be removed in v1. - - Before, you could instantiate a standalone component, - and then call `render()` on the instance: - - ```py - comp = MyComponent( - registered_name="my_component", - outer_context=my_context, - registry=my_registry, - ) - comp.render( - args=[1, 2, 3], - kwargs={"a": 1, "b": 2}, - slots={"my_slot": "CONTENT"}, - ) - ``` - - Now you should instead pass all that data to `Component.render()` / `Component.render_to_response()`: - - ```py - MyComponent.render( - args=[1, 2, 3], - kwargs={"a": 1, "b": 2}, - slots={"my_slot": "CONTENT"}, - # NEW - registered_name="my_component", - outer_context=my_context, - registry=my_registry, - ) - ``` - -- `Component.input` (and its type `ComponentInput`) is now deprecated. The `input` property will be removed in v1. - - Instead, use attributes directly on the Component instance. - - Before: - - ```py - class MyComponent(Component): - def on_render(self, context, template): - assert self.input.args == [1, 2, 3] - assert self.input.kwargs == {"a": 1, "b": 2} - assert self.input.slots == {"my_slot": "CONTENT"} - assert self.input.context == {"my_slot": "CONTENT"} - assert self.input.deps_strategy == "document" - assert self.input.type == "document" - assert self.input.render_dependencies == True - ``` - - After: - - ```py - class MyComponent(Component): - def on_render(self, context, template): - assert self.args == [1, 2, 3] - assert self.kwargs == {"a": 1, "b": 2} - assert self.slots == {"my_slot": "CONTENT"} - assert self.context == {"my_slot": "CONTENT"} - assert self.deps_strategy == "document" - assert (self.deps_strategy != "ignore") is True - ``` - -- Component method `on_render_after` was updated to receive also `error` field. - - For backwards compatibility, the `error` field can be omitted until v1. - - Before: - - ```py - def on_render_after( - self, - context: Context, - template: Template, - html: str, - ) -> None: - pass - ``` - - After: - - ```py - def on_render_after( - self, - context: Context, - template: Template, - html: Optional[str], - error: Optional[Exception], - ) -> None: - pass - ``` - -- If you are using the Components as views, the way to access the component class is now different. - - Instead of `self.component`, use `self.component_cls`. `self.component` will be removed in v1. - - Before: - - ```py - class MyView(View): - def get(self, request): - return self.component.render_to_response(request=request) - ``` - - After: - - ```py - class MyView(View): - def get(self, request): - return self.component_cls.render_to_response(request=request) - ``` - -**Extensions** - -- In the `on_component_data()` extension hook, the `context_data` field of the context object was superseded by `template_data`. - - The `context_data` field will be removed in v1.0. - - Before: - - ```py - class MyExtension(ComponentExtension): - def on_component_data(self, ctx: OnComponentDataContext) -> None: - ctx.context_data["my_template_var"] = "my_value" - ``` - - After: - - ```py - class MyExtension(ComponentExtension): - def on_component_data(self, ctx: OnComponentDataContext) -> None: - ctx.template_data["my_template_var"] = "my_value" - ``` - -- When creating extensions, the `ComponentExtension.ExtensionClass` attribute was renamed to `ComponentConfig`. - - The old name is deprecated and will be removed in v1. - - Before: - - ```py - from django_components import ComponentExtension - - class MyExtension(ComponentExtension): - class ExtensionClass(ComponentExtension.ExtensionClass): - pass - ``` - - After: - - ```py - from django_components import ComponentExtension, ExtensionComponentConfig - - class MyExtension(ComponentExtension): - class ComponentConfig(ExtensionComponentConfig): - pass - ``` - -- When creating extensions, to access the Component class from within the methods of the extension nested classes, - use `component_cls`. - - Previously this field was named `component_class`. The old name is deprecated and will be removed in v1. - - `ComponentExtension.ExtensionClass` attribute was renamed to `ComponentConfig`. - - The old name is deprecated and will be removed in v1. - - Before: - - ```py - from django_components import ComponentExtension, ExtensionComponentConfig - - class LoggerExtension(ComponentExtension): - name = "logger" - - class ComponentConfig(ExtensionComponentConfig): - def log(self, msg: str) -> None: - print(f"{self.component_class.__name__}: {msg}") - ``` - - After: - - ```py - from django_components import ComponentExtension, ExtensionComponentConfig - - class LoggerExtension(ComponentExtension): - name = "logger" - - class ComponentConfig(ExtensionComponentConfig): - def log(self, msg: str) -> None: - print(f"{self.component_cls.__name__}: {msg}") - ``` - -**Slots** - -- `SlotContent` was renamed to `SlotInput`. The old name is deprecated and will be removed in v1. - -- `SlotRef` was renamed to `SlotFallback`. The old name is deprecated and will be removed in v1. - -- The `default` kwarg in `{% fill %}` tag was renamed to `fallback`. The old name is deprecated and will be removed in v1. - - Before: - - ```django - {% fill "footer" default="footer" %} - {{ footer }} - {% endfill %} - ``` - - After: - - ```django - {% fill "footer" fallback="footer" %} - {{ footer }} - {% endfill %} - ``` - -- The template variable `{{ component_vars.is_filled }}` is now deprecated. Will be removed in v1. Use `{{ component_vars.slots }}` instead. - - Before: - - ```django - {% if component_vars.is_filled.footer %} -
- {% slot "footer" / %} -
- {% endif %} - ``` - - After: - - ```django - {% if component_vars.slots.footer %} -
- {% slot "footer" / %} -
- {% endif %} - ``` - - NOTE: `component_vars.is_filled` automatically escaped slot names, so that even slot names that are - not valid python identifiers could be set as slot names. `component_vars.slots` no longer does that. - -- Component attribute `Component.is_filled` is now deprecated. Will be removed in v1. Use `Component.slots` instead. - - Before: - - ```py - class MyComponent(Component): - def get_template_data(self, args, kwargs, slots, context): - if self.is_filled.footer: - color = "red" - else: - color = "blue" - - return { - "color": color, - } - ``` - - After: - - ```py - class MyComponent(Component): - def get_template_data(self, args, kwargs, slots, context): - if "footer" in slots: - color = "red" - else: - color = "blue" - - return { - "color": color, - } - ``` - - NOTE: `Component.is_filled` automatically escaped slot names, so that even slot names that are - not valid python identifiers could be set as slot names. `Component.slots` no longer does that. - -**Miscellaneous** - -- Template caching with `cached_template()` helper and `template_cache_size` setting is deprecated. - These will be removed in v1. - - This feature made sense if you were dynamically generating templates for components using - `Component.get_template_string()` and `Component.get_template()`. - - However, in v1, each Component will have at most one static template. This static template - is cached internally per component class, and reused across renders. - - This makes the template caching feature obsolete. - - If you relied on `cached_template()`, you should either: - - 1. Wrap the templates as Components. - 2. Manage the cache of Templates yourself. - -- The `debug_highlight_components` and `debug_highlight_slots` settings are deprecated. - These will be removed in v1. - - The debug highlighting feature was re-implemented as an extension. - As such, the recommended way for enabling it has changed: - - Before: - - ```python - COMPONENTS = ComponentsSettings( - debug_highlight_components=True, - debug_highlight_slots=True, - ) - ``` - - After: - - Set `extensions_defaults` in your `settings.py` file. - - ```python - COMPONENTS = ComponentsSettings( - extensions_defaults={ - "debug_highlight": { - "highlight_components": True, - "highlight_slots": True, - }, - }, - ) - ``` - - Alternatively, you can enable highlighting for specific components by setting `Component.DebugHighlight.highlight_components` to `True`: - - ```python - class MyComponent(Component): - class DebugHighlight: - highlight_components = True - highlight_slots = True - ``` - -#### Feat - -- New method to render template variables - `get_template_data()` - - `get_template_data()` behaves the same way as `get_context_data()`, but has - a different function signature to accept also slots and context. - - ```py - class Button(Component): - def get_template_data(self, args, kwargs, slots, context): - return { - "val1": args[0], - "val2": kwargs["field"], - } - ``` - - If you define `Component.Args`, `Component.Kwargs`, `Component.Slots`, then - the `args`, `kwargs`, `slots` arguments will be instances of these classes: - - ```py - class Button(Component): - class Args(NamedTuple): - field1: str - - class Kwargs(NamedTuple): - field2: int - - def get_template_data(self, args: Args, kwargs: Kwargs, slots, context): - return { - "val1": args.field1, - "val2": kwargs.field2, - } - ``` - -- Input validation is now part of the render process. - - When you specify the input types (such as `Component.Args`, `Component.Kwargs`, etc), - the actual inputs to data methods (`Component.get_template_data()`, etc) will be instances of the types you specified. - - This practically brings back input validation, because the instantiation of the types - will raise an error if the inputs are not valid. - - Read more on [Typing and validation](https://django-components.github.io/django-components/latest/concepts/fundamentals/typing_and_validation/) - -- Render emails or other non-browser HTML with new "dependencies strategies" - - When rendering a component with `Component.render()` or `Component.render_to_response()`, - the `deps_strategy` kwarg (previously `type`) now accepts additional options: - - - `"simple"` - - `"prepend"` - - `"append"` - - `"ignore"` - - ```py - Calendar.render_to_response( - request=request, - kwargs={ - "date": request.GET.get("date", ""), - }, - deps_strategy="append", - ) - ``` - - Comparison of dependencies render strategies: - - - `"document"` - - Smartly inserts JS / CSS into placeholders or into `` and `` tags. - - Inserts extra script to allow `fragment` strategy to work. - - Assumes the HTML will be rendered in a JS-enabled browser. - - `"fragment"` - - A lightweight HTML fragment to be inserted into a document with AJAX. - - Ignores placeholders and any `` / `` tags. - - No JS / CSS included. - - `"simple"` - - Smartly insert JS / CSS into placeholders or into `` and `` tags. - - No extra script loaded. - - `"prepend"` - - Insert JS / CSS before the rendered HTML. - - Ignores placeholders and any `` / `` tags. - - No extra script loaded. - - `"append"` - - Insert JS / CSS after the rendered HTML. - - Ignores placeholders and any `` / `` tags. - - No extra script loaded. - - `"ignore"` - - Rendered HTML is left as-is. You can still process it with a different strategy later with `render_dependencies()`. - - Used for inserting rendered HTML into other components. - - See [Dependencies rendering](https://django-components.github.io/django-components/0.140.1/concepts/advanced/rendering_js_css/) for more info. - -- New `Component.args`, `Component.kwargs`, `Component.slots` attributes available on the component class itself. - - These attributes are the same as the ones available in `Component.get_template_data()`. - - You can use these in other methods like `Component.on_render_before()` or `Component.on_render_after()`. - - ```py - from django_components import Component, SlotInput - - class Table(Component): - class Args(NamedTuple): - page: int - - class Kwargs(NamedTuple): - per_page: int - - class Slots(NamedTuple): - content: SlotInput - - def on_render_before(self, context: Context, template: Optional[Template]) -> None: - assert self.args.page == 123 - assert self.kwargs.per_page == 10 - content_html = self.slots.content() - ``` - - Same as with the parameters in `Component.get_template_data()`, they will be instances of the `Args`, `Kwargs`, `Slots` classes - if defined, or plain lists / dictionaries otherwise. - -- 4 attributes that were previously available only under the `Component.input` attribute - are now available directly on the Component instance: - - - `Component.raw_args` - - `Component.raw_kwargs` - - `Component.raw_slots` - - `Component.deps_strategy` - - The first 3 attributes are the same as the deprecated `Component.input.args`, `Component.input.kwargs`, `Component.input.slots` properties. - - Compared to the `Component.args` / `Component.kwargs` / `Component.slots` attributes, - these "raw" attributes are not typed and will remain as plain lists / dictionaries - even if you define the `Args`, `Kwargs`, `Slots` classes. - - The `Component.deps_strategy` attribute is the same as the deprecated `Component.input.deps_strategy` property. - -- New template variables `{{ component_vars.args }}`, `{{ component_vars.kwargs }}`, `{{ component_vars.slots }}` - - These attributes are the same as the ones available in `Component.get_template_data()`. - - ```django - {# Typed #} - {% if component_vars.args.page == 123 %} -
- {% slot "content" / %} -
- {% endif %} - - {# Untyped #} - {% if component_vars.args.0 == 123 %} -
- {% slot "content" / %} -
- {% endif %} - ``` - - Same as with the parameters in `Component.get_template_data()`, they will be instances of the `Args`, `Kwargs`, `Slots` classes - if defined, or plain lists / dictionaries otherwise. - -- New component lifecycle hook `Component.on_render()`. - - This hook is called when the component is being rendered. - - You can override this method to: - - - Change what template gets rendered - - Modify the context - - Modify the rendered output after it has been rendered - - Handle errors - - See [on_render](https://django-components.github.io/django-components/0.140.1/concepts/advanced/hooks/#on_render) for more info. - -- `get_component_url()` now optionally accepts `query` and `fragment` arguments. - - ```py - from django_components import get_component_url - - url = get_component_url( - MyComponent, - query={"foo": "bar"}, - fragment="baz", - ) - # /components/ext/view/components/c1ab2c3?foo=bar#baz - ``` - -- The `BaseNode` class has a new `contents` attribute, which contains the raw contents (string) of the tag body. - - This is relevant when you define custom template tags with `@template_tag` decorator or `BaseNode` class. - - When you define a custom template tag like so: - - ```py - from django_components import BaseNode, template_tag - - @template_tag( - library, - tag="mytag", - end_tag="endmytag", - allowed_flags=["required"] - ) - def mytag(node: BaseNode, context: Context, name: str, **kwargs) -> str: - print(node.contents) - return f"Hello, {name}!" - ``` - - And render it like so: - - ```django - {% mytag name="John" %} - Hello, world! - {% endmytag %} - ``` - - Then, the `contents` attribute of the `BaseNode` instance will contain the string `"Hello, world!"`. - -- The `BaseNode` class also has two new metadata attributes: - - - `template_name` - the name of the template that rendered the node. - - `template_component` - the component class that the template belongs to. - - This is useful for debugging purposes. - -- `Slot` class now has 3 new metadata fields: - - 1. `Slot.contents` attribute contains the original contents: - - - If `Slot` was created from `{% fill %}` tag, `Slot.contents` will contain the body of the `{% fill %}` tag. - - If `Slot` was created from string via `Slot("...")`, `Slot.contents` will contain that string. - - If `Slot` was created from a function, `Slot.contents` will contain that function. - - 2. `Slot.extra` attribute where you can put arbitrary metadata about the slot. - - 3. `Slot.fill_node` attribute tells where the slot comes from: - - - `FillNode` instance if the slot was created from `{% fill %}` tag. - - `ComponentNode` instance if the slot was created as a default slot from a `{% component %}` tag. - - `None` if the slot was created from a string, function, or `Slot` instance. - - See [Slot metadata](https://django-components.github.io/django-components/0.140.1/concepts/fundamentals/slots/#slot-metadata). - -- `{% fill %}` tag now accepts `body` kwarg to pass a Slot instance to fill. - - First pass a `Slot` instance to the template - with the `get_template_data()` method: - - ```python - from django_components import component, Slot - - class Table(Component): - def get_template_data(self, args, kwargs, slots, context): - return { - "my_slot": Slot(lambda ctx: "Hello, world!"), - } - ``` - - Then pass the slot to the `{% fill %}` tag: - - ```django - {% component "table" %} - {% fill "pagination" body=my_slot / %} - {% endcomponent %} - ``` - -- You can now access the `{% component %}` tag (`ComponentNode` instance) from which a Component - was created. Use `Component.node` to access it. - - This is mostly useful for extensions, which can use this to detect if the given Component - comes from a `{% component %}` tag or from a different source (such as `Component.render()`). - - `Component.node` is `None` if the component is created by `Component.render()` (but you - can pass in the `node` kwarg yourself). - - ```py - class MyComponent(Component): - def get_template_data(self, context, template): - if self.node is not None: - assert self.node.name == "my_component" - ``` - -- Node classes `ComponentNode`, `FillNode`, `ProvideNode`, and `SlotNode` are part of the public API. - - These classes are what is instantiated when you use `{% component %}`, `{% fill %}`, `{% provide %}`, and `{% slot %}` tags. - - You can for example use these for type hints: - - ```py - from django_components import Component, ComponentNode - - class MyTable(Component): - def get_template_data(self, args, kwargs, slots, context): - if kwargs.get("show_owner"): - node: Optional[ComponentNode] = self.node - owner: Optional[Component] = self.node.template_component - else: - node = None - owner = None - - return { - "owner": owner, - "node": node, - } - ``` - -- Component caching can now take slots into account, by setting `Component.Cache.include_slots` to `True`. - - ```py - class MyComponent(Component): - class Cache: - enabled = True - include_slots = True - ``` - - In which case the following two calls will generate separate cache entries: - - ```django - {% component "my_component" position="left" %} - Hello, Alice - {% endcomponent %} - - {% component "my_component" position="left" %} - Hello, Bob - {% endcomponent %} - ``` - - Same applies to `Component.render()` with string slots: - - ```py - MyComponent.render( - kwargs={"position": "left"}, - slots={"content": "Hello, Alice"} - ) - MyComponent.render( - kwargs={"position": "left"}, - slots={"content": "Hello, Bob"} - ) - ``` - - Read more on [Component caching](https://django-components.github.io/django-components/0.140.1/concepts/advanced/component_caching/). - -- New extension hook `on_slot_rendered()` - - This hook is called when a slot is rendered, and allows you to access and/or modify the rendered result. - - This is used by the ["debug highlight" feature](https://django-components.github.io/django-components/0.140.1/guides/other/troubleshooting/#component-and-slot-highlighting). - - To modify the rendered result, return the new value: - - ```py - class MyExtension(ComponentExtension): - def on_slot_rendered(self, ctx: OnSlotRenderedContext) -> Optional[str]: - return ctx.result + "" - ``` - - If you don't want to modify the rendered result, return `None`. - - See all [Extension hooks](https://django-components.github.io/django-components/0.140.1/reference/extension_hooks/). - -- When creating extensions, the previous syntax with `ComponentExtension.ExtensionClass` was causing - Mypy errors, because Mypy doesn't allow using class attributes as bases: - - Before: - - ```py - from django_components import ComponentExtension - - class MyExtension(ComponentExtension): - class ExtensionClass(ComponentExtension.ExtensionClass): # Error! - pass - ``` - - Instead, you can import `ExtensionComponentConfig` directly: - - After: - - ```py - from django_components import ComponentExtension, ExtensionComponentConfig - - class MyExtension(ComponentExtension): - class ComponentConfig(ExtensionComponentConfig): - pass - ``` - -#### Refactor - -- When a component is being rendered, a proper `Component` instance is now created. - - Previously, the `Component` state was managed as half-instance, half-stack. - -- Component's "Render API" (args, kwargs, slots, context, inputs, request, context data, etc) - can now be accessed also outside of the render call. So now its possible to take the component - instance out of `get_template_data()` (although this is not recommended). - -- Components can now be defined without a template. - - Previously, the following would raise an error: - - ```py - class MyComponent(Component): - pass - ``` - - "Template-less" components can be used together with `Component.on_render()` to dynamically - pick what to render: - - ```py - class TableNew(Component): - template_file = "table_new.html" - - class TableOld(Component): - template_file = "table_old.html" - - class Table(Component): - def on_render(self, context, template): - if self.kwargs.get("feat_table_new_ui"): - return TableNew.render(args=self.args, kwargs=self.kwargs, slots=self.slots) - else: - return TableOld.render(args=self.args, kwargs=self.kwargs, slots=self.slots) - ``` - - "Template-less" components can be also used as a base class for other components, or as mixins. - -- Passing `Slot` instance to `Slot` constructor raises an error. - -- Extension hook `on_component_rendered` now receives `error` field. - - `on_component_rendered` now behaves similar to `Component.on_render_after`: - - - Raising error in this hook overrides what error will be returned from `Component.render()`. - - Returning new string overrides what will be returned from `Component.render()`. - - Before: - - ```py - class OnComponentRenderedContext(NamedTuple): - component: "Component" - component_cls: Type["Component"] - component_id: str - result: str - ``` - - After: - - ```py - class OnComponentRenderedContext(NamedTuple): - component: "Component" - component_cls: Type["Component"] - component_id: str - result: Optional[str] - error: Optional[Exception] - ``` - -#### Fix - -- Fix bug: Context processors data was being generated anew for each component. Now the data is correctly created once and reused across components with the same request ([#1165](https://github.com/django-components/django-components/issues/1165)). - -- Fix KeyError on `component_context_cache` when slots are rendered outside of the component's render context. ([#1189](https://github.com/django-components/django-components/issues/1189)) - -- Component classes now have `do_not_call_in_templates=True` to prevent them from being called as functions in templates. - -## v0.139.1 - -#### Fix - -- Fix compatibility of component caching with `{% extend %}` block ([#1135](https://github.com/django-components/django-components/issues/1135)) - -#### Refactor - -- Component ID is now prefixed with `c`, e.g. `c123456`. - -- When typing a Component, you can now specify as few or as many parameters as you want. - - ```py - Component[Args] - Component[Args, Kwargs] - Component[Args, Kwargs, Slots] - Component[Args, Kwargs, Slots, Data] - Component[Args, Kwargs, Slots, Data, JsData] - Component[Args, Kwargs, Slots, Data, JsData, CssData] - ``` - - All omitted parameters will default to `Any`. - -- Added `typing_extensions` to the project as a dependency - -- Multiple extensions with the same name (case-insensitive) now raise an error - -- Extension names (case-insensitive) also MUST NOT conflict with existing Component class API. - - So if you name an extension `render`, it will conflict with the `render()` method of the `Component` class, - and thus raise an error. - -## v0.139.0 - -#### Fix - -- Fix bug: Fix compatibility with `Finder.find()` in Django 5.2 ([#1119](https://github.com/django-components/django-components/issues/1119)) - -## v0.138 - -#### Fix - -- Fix bug: Allow components with `Url.public = True` to be defined before `django.setup()` - -## v0.137 - -#### Feat - -- Each Component class now has a `class_id` attribute, which is unique to the component subclass. - - NOTE: This is different from `Component.id`, which is unique to each rendered instance. - - To look up a component class by its `class_id`, use `get_component_by_class_id()`. - -- It's now easier to create URLs for component views. - - Before, you had to call `Component.as_view()` and pass that to `urlpatterns`. - - Now this can be done for you if you set `Component.Url.public` to `True`: - - ```py - class MyComponent(Component): - class Url: - public = True - ... - ``` - - Then, to get the URL for the component, use `get_component_url()`: - - ```py - from django_components import get_component_url - - url = get_component_url(MyComponent) - ``` - - This way you don't have to mix your app URLs with component URLs. - - Read more on [Component views and URLs](https://django-components.github.io/django-components/0.137/concepts/fundamentals/component_views_urls/). - -- Per-component caching - Set `Component.Cache.enabled` to `True` to enable caching for a component. - - Component caching allows you to store the rendered output of a component. Next time the component is rendered - with the same input, the cached output is returned instead of re-rendering the component. - - ```py - class TestComponent(Component): - template = "Hello" - - class Cache: - enabled = True - ttl = 0.1 # .1 seconds TTL - cache_name = "custom_cache" - - # Custom hash method for args and kwargs - # NOTE: The default implementation simply serializes the input into a string. - # As such, it might not be suitable for complex objects like Models. - def hash(self, *args, **kwargs): - return f"{json.dumps(args)}:{json.dumps(kwargs)}" - - ``` - - Read more on [Component caching](https://django-components.github.io/django-components/0.137/concepts/advanced/component_caching/). - -- `@djc_test` can now be called without first calling `django.setup()`, in which case it does it for you. - -- Expose `ComponentInput` class, which is a typing for `Component.input`. - -#### Deprecation - -- Currently, view request handlers such as `get()` and `post()` methods can be defined - directly on the `Component` class: - - ```py - class MyComponent(Component): - def get(self, request): - return self.render_to_response() - ``` - - Or, nested within the `Component.View` class: - - ```py - class MyComponent(Component): - class View: - def get(self, request): - return self.render_to_response() - ``` - - In v1, these methods should be defined only on the `Component.View` class instead. - -#### Refactor - -- `Component.get_context_data()` can now omit a return statement or return `None`. - -## 🚨📢 v0.136 - -#### 🚨📢 BREAKING CHANGES - -- Component input validation was moved to a separate extension [`djc-ext-pydantic`](https://github.com/django-components/djc-ext-pydantic). - - If you relied on components raising errors when inputs were invalid, you need to install `djc-ext-pydantic` and add it to extensions: - - ```python - # settings.py - COMPONENTS = { - "extensions": [ - "djc_pydantic.PydanticExtension", - ], - } - ``` - -#### Fix - -- Make it possible to resolve URLs added by extensions by their names - -## v0.135 - -#### Feat - -- Add defaults for the component inputs with the `Component.Defaults` nested class. Defaults - are applied if the argument is not given, or if it set to `None`. - - For lists, dictionaries, or other objects, wrap the value in `Default()` class to mark it as a factory - function: - - ```python - from django_components import Default - - class Table(Component): - class Defaults: - position = "left" - width = "200px" - options = Default(lambda: ["left", "right", "center"]) - - def get_context_data(self, position, width, options): - return { - "position": position, - "width": width, - "options": options, - } - - # `position` is used as given, `"right"` - # `width` uses default because it's `None` - # `options` uses default because it's missing - Table.render( - kwargs={ - "position": "right", - "width": None, - } - ) - ``` - -- `{% html_attrs %}` now offers a Vue-like granular control over `class` and `style` HTML attributes, -where each class name or style property can be managed separately. - - ```django - {% html_attrs - class="foo bar" - class={"baz": True, "foo": False} - class="extra" - %} - ``` - - ```django - {% html_attrs - style="text-align: center; background-color: blue;" - style={"background-color": "green", "color": None, "width": False} - style="position: absolute; height: 12px;" - %} - ``` - - Read more on [HTML attributes](https://django-components.github.io/django-components/0.135/concepts/fundamentals/html_attributes/). - -#### Fix - -- Fix compat with Windows when reading component files ([#1074](https://github.com/django-components/django-components/issues/1074)) -- Fix resolution of component media files edge case ([#1073](https://github.com/django-components/django-components/issues/1073)) - -## v0.134 - -#### Fix - -- HOTFIX: Fix the use of URLs in `Component.Media.js` and `Component.Media.css` - -## v0.133 - -⚠️ Attention ⚠️ - Please update to v0.134 to fix bugs introduced in v0.132. - -#### Fix - -- HOTFIX: Fix the use of URLs in `Component.Media.js` and `Component.Media.css` - -## v0.132 - -⚠️ Attention ⚠️ - Please update to v0.134 to fix bugs introduced in v0.132. - -#### Feat - -- Allow to use glob patterns as paths for additional JS / CSS in - `Component.Media.js` and `Component.Media.css` - - ```py - class MyComponent(Component): - class Media: - js = ["*.js"] - css = ["*.css"] - ``` - -#### Fix - -- Fix installation for Python 3.13 on Windows. - -## v0.131 - -#### Feat - -- Support for extensions (plugins) for django-components! - - - Hook into lifecycle events of django-components - - Pre-/post-process component inputs, outputs, and templates - - Add extra methods or attributes to Components - - Add custom extension-specific CLI commands - - Add custom extension-specific URL routes - - Read more on [Extensions](https://django-components.github.io/django-components/0.131/concepts/advanced/extensions/). - -- New CLI commands: - - `components list` - List all components - - `components create ` - Create a new component (supersedes `startcomponent`) - - `components upgrade` - Upgrade a component (supersedes `upgradecomponent`) - - `components ext list` - List all extensions - - `components ext run ` - Run a command added by an extension - -- `@djc_test` decorator for writing tests that involve Components. - - - The decorator manages global state, ensuring that tests don't leak. - - If using `pytest`, the decorator allows you to parametrize Django or Components settings. - - The decorator also serves as a stand-in for Django's `@override_settings`. - - See the API reference for [`@djc_test`](https://django-components.github.io/django-components/0.131/reference/testing_api/#django_components.testing.djc_test) for more details. - -- `ComponentRegistry` now has a `has()` method to check if a component is registered - without raising an error. - -- Get all created `Component` classes with `all_components()`. - -- Get all created `ComponentRegistry` instances with `all_registries()`. - -#### Refactor - -- The `startcomponent` and `upgradecomponent` commands are deprecated, and will be removed in v1. - - Instead, use `components create ` and `components upgrade`. - -#### Internal - -- Settings are now loaded only once, and thus are considered immutable once loaded. Previously, - django-components would load settings from `settings.COMPONENTS` on each access. The new behavior - aligns with Django's settings. - -## v0.130 - -#### Feat - -- Access the HttpRequest object under `Component.request`. - - To pass the request object to a component, either: - - Render a template or component with `RequestContext`, - - Or set the `request` kwarg to `Component.render()` or `Component.render_to_response()`. - - Read more on [HttpRequest](https://django-components.github.io/django-components/0.130/concepts/fundamentals/http_request/). - -- Access the context processors data under `Component.context_processors_data`. - - Context processors data is available only when the component has access to the `request` object, - either by: - - Passing the request to `Component.render()` or `Component.render_to_response()`, - - Or by rendering a template or component with `RequestContext`, - - Or being nested in another component that has access to the request object. - - The data from context processors is automatically available within the component's template. - - Read more on [HttpRequest](https://django-components.github.io/django-components/0.130/concepts/fundamentals/http_request/). - -## v0.129 - -#### Fix - -- Fix thread unsafe media resolve validation by moving it to ComponentMedia `__post_init` ([#977](https://github.com/django-components/django-components/pull/977) -- Fix bug: Relative path in extends and include does not work when using template_file ([#976](https://github.com/django-components/django-components/pull/976) -- Fix error when template cache setting (`template_cache_size`) is set to 0 ([#974](https://github.com/django-components/django-components/pull/974) - -## v0.128 - -#### Feat - -- Configurable cache - Set [`COMPONENTS.cache`](https://django-components.github.io/django-components/0.128/reference/settings/#django_components.app_settings.ComponentsSettings.cache) to change where and how django-components caches JS and CSS files. ([#946](https://github.com/django-components/django-components/pull/946)) - - Read more on [Caching](https://django-components.github.io/django-components/0.128/guides/setup/caching). - -- Highlight coponents and slots in the UI - We've added two boolean settings [`COMPONENTS.debug_highlight_components`](https://django-components.github.io/django-components/0.128/reference/settings/#django_components.app_settings.ComponentsSettings.debug_highlight_components) and [`COMPONENTS.debug_highlight_slots`](https://django-components.github.io/django-components/0.128/reference/settings/#django_components.app_settings.ComponentsSettings.debug_highlight_slots), which can be independently set to `True`. First will wrap components in a blue border, the second will wrap slots in a red border. ([#942](https://github.com/django-components/django-components/pull/942)) - - Read more on [Troubleshooting](https://django-components.github.io/django-components/0.128/guides/other/troubleshooting/#component-and-slot-highlighting). - -#### Refactor - -- Removed use of eval for node validation ([#944](https://github.com/django-components/django-components/pull/944)) - -#### Perf - -- Components can now be infinitely nested. ([#936](https://github.com/django-components/django-components/pull/936)) - -- Component input validation is now 6-7x faster on CPython and PyPy. This previously made up 10-30% of the total render time. ([#945](https://github.com/django-components/django-components/pull/945)) - -## v0.127 - -#### Fix - -- Fix component rendering when using `{% cache %}` with remote cache and multiple web servers ([#930](https://github.com/django-components/django-components/issues/930)) - -## v0.126 - -#### Refactor - -- Replaced [BeautifulSoup4](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) with a custom HTML parser. -- The heuristic for inserting JS and CSS dependenies into the default place has changed. - - JS is still inserted at the end of the ``, and CSS at the end of ``. - - However, we find end of `` by searching for **last** occurrence of `` - - And for the end of `` we search for the **first** occurrence of `` - -## v0.125 - -⚠️ Attention ⚠️ - We migrated from `EmilStenstrom/django-components` to `django-components/django-components`. - -**Repo name and documentation URL changed. Package name remains the same.** - -If you see any broken links or other issues, please report them in [#922](https://github.com/django-components/django-components/issues/922). - -#### Feat - -- `@template_tag` and `BaseNode` - A decorator and a class that allow you to define - custom template tags that will behave similarly to django-components' own template tags. - - Read more on [Template tags](https://django-components.github.io/django-components/0.125/concepts/advanced/template_tags/). - - Template tags defined with `@template_tag` and `BaseNode` will have the following features: - - - Accepting args, kwargs, and flags. - - - Allowing literal lists and dicts as inputs as: - - `key=[1, 2, 3]` or `key={"a": 1, "b": 2}` - - Using template tags tag inputs as: - - `{% my_tag key="{% lorem 3 w %}" / %}` - - Supporting the flat dictionary definition: - - `attr:key=value` - - Spreading args and kwargs with `...`: - - `{% my_tag ...args ...kwargs / %}` - - Being able to call the template tag as: - - `{% my_tag %} ... {% endmy_tag %}` or `{% my_tag / %}` - - -#### Refactor - -- Refactored template tag input validation. When you now call template tags like - `{% slot %}`, `{% fill %}`, `{% html_attrs %}`, and others, their inputs are now - validated the same way as Python function inputs are. - - So, for example - - ```django - {% slot "my_slot" name="content" / %} - ``` - - will raise an error, because the positional argument `name` is given twice. - - NOTE: Special kwargs whose keys are not valid Python variable names are not affected by this change. - So when you define: - - ```django - {% component data-id=123 / %} - ``` - - The `data-id` will still be accepted as a valid kwarg, assuming that your `get_context_data()` - accepts `**kwargs`: - - ```py - def get_context_data(self, **kwargs): - return { - "data_id": kwargs["data-id"], - } - ``` - -## v0.124 - -#### Feat - -- Instead of inlining the JS and CSS under `Component.js` and `Component.css`, you can move - them to their own files, and link the JS/CSS files with `Component.js_file` and `Component.css_file`. - - Even when you specify the JS/CSS with `Component.js_file` or `Component.css_file`, then you can still - access the content under `Component.js` or `Component.css` - behind the scenes, the content of the JS/CSS files - will be set to `Component.js` / `Component.css` upon first access. - - The same applies to `Component.template_file`, which will populate `Component.template` upon first access. - - With this change, the role of `Component.js/css` and the JS/CSS in `Component.Media` has changed: - - - The JS/CSS defined in `Component.js/css` or `Component.js/css_file` is the "main" JS/CSS - - The JS/CSS defined in `Component.Media.js/css` are secondary or additional - - See the updated ["Getting Started" tutorial](https://django-components.github.io/django-components/0.124/getting_started/adding_js_and_css/) - -#### Refactor - -- The canonical way to define a template file was changed from `template_name` to `template_file`, to align with the rest of the API. - - `template_name` remains for backwards compatibility. When you get / set `template_name`, - internally this is proxied to `template_file`. - -- The undocumented `Component.component_id` was removed. Instead, use `Component.id`. Changes: - - - While `component_id` was unique every time you instantiated `Component`, the new `id` is unique - every time you render the component (e.g. with `Component.render()`) - - The new `id` is available only during render, so e.g. from within `get_context_data()` - -- Component's HTML / CSS / JS are now resolved and loaded lazily. That is, if you specify `template_name`/`template_file`, - `js_file`, `css_file`, or `Media.js/css`, the file paths will be resolved only once you: - - 1. Try to access component's HTML / CSS / JS, or - 2. Render the component. - - Read more on [Accessing component's HTML / JS / CSS](https://django-components.github.io/django-components/0.124/concepts/fundamentals/defining_js_css_html_files/#customize-how-paths-are-rendered-into-html-tags). - -- Component inheritance: - - - When you subclass a component, the JS and CSS defined on parent's `Media` class is now inherited by the child component. - - You can disable or customize Media inheritance by setting `extend` attribute on the `Component.Media` nested class. This work similarly to Django's [`Media.extend`](https://docs.djangoproject.com/en/5.2/topics/forms/media/#extend). - - When child component defines either `template` or `template_file`, both of parent's `template` and `template_file` are ignored. The same applies to `js_file` and `css_file`. - -- Autodiscovery now ignores files and directories that start with an underscore (`_`), except `__init__.py` - -- The [Signals](https://docs.djangoproject.com/en/5.2/topics/signals/) emitted by or during the use of django-components are now documented, together the `template_rendered` signal. - -## v0.123 - -#### Fix - -- Fix edge cases around rendering components whose templates used the `{% extends %}` template tag ([#859](https://github.com/django-components/django-components/pull/859)) - -## v0.122 - -#### Feat - -- Add support for HTML fragments. HTML fragments can be rendered by passing `type="fragment"` to `Component.render()` or `Component.render_to_response()`. Read more on how to [use HTML fragments with HTMX, AlpineJS, or vanillaJS](https://django-components.github.io/django-components/latest/concepts/advanced/html_fragments). - -## v0.121 - -#### Fix - -- Fix the use of Django template filters (`|lower:"etc"`) with component inputs [#855](https://github.com/django-components/django-components/pull/855). - -## v0.120 - -⚠️ Attention ⚠️ - Please update to v0.121 to fix bugs introduced in v0.119. - -#### Fix - -- Fix the use of translation strings `_("bla")` as inputs to components [#849](https://github.com/django-components/django-components/pull/849). - -## v0.119 - -⚠️ Attention ⚠️ - This release introduced bugs [#849](https://github.com/django-components/django-components/pull/849), [#855](https://github.com/django-components/django-components/pull/855). Please update to v0.121. - -#### Fix - -- Fix compatibility with custom subclasses of Django's `Template` that need to access - `origin` or other initialization arguments. (https://github.com/django-components/django-components/pull/828) - -#### Refactor - -- Compatibility with `django-debug-toolbar-template-profiler`: - - Monkeypatching of Django's `Template` now happens at `AppConfig.ready()` (https://github.com/django-components/django-components/pull/825) - -- Internal parsing of template tags tag was updated. No API change. (https://github.com/django-components/django-components/pull/827) - -## v0.118 - -#### Feat - -- Add support for `context_processors` and `RenderContext` inside component templates - - `Component.render()` and `Component.render_to_response()` now accept an extra kwarg `request`. - - ```py - def my_view(request) - return MyTable.render_to_response( - request=request - ) - ``` - - - When you pass in `request`, the component will use `RenderContext` instead of `Context`. - Thus the context processors will be applied to the context. - - - NOTE: When you pass in both `request` and `context` to `Component.render()`, and `context` is already an instance of `Context`, the `request` kwarg will be ignored. - -## v0.117 - -#### Fix - -- The HTML parser no longer erronously inserts `` on some occasions, and - no longer tries to close unclosed HTML tags. - -#### Refactor - -- Replaced [Selectolax](https://github.com/rushter/selectolax) with [BeautifulSoup4](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) as project dependencies. - -## v0.116 - -⚠️ Attention ⚠️ - Please update to v0.117 to fix known bugs. See [#791](https://github.com/django-components/django-components/issues/791) and [#789](https://github.com/django-components/django-components/issues/789) and [#818](https://github.com/django-components/django-components/issues/818). - -#### Fix - -- Fix the order of execution of JS scripts: - - Scripts in `Component.Media.js` are executed in the order they are defined - - Scripts in `Component.js` are executed AFTER `Media.js` scripts - -- Fix compatibility with AlpineJS - - Scripts in `Component.Media.js` are now again inserted as ` - - - {% component 'my_alpine_component' / %} - {% component_js_dependencies %} - - - ``` - - Option 2 - AlpineJS loaded in `` AFTER `{% component_js_depenencies %}`: - ```html - - - {% component_css_dependencies %} - - - {% component 'my_alpine_component' / %} - {% component_js_dependencies %} - - - - - ``` - -## v0.115 - -⚠️ Attention ⚠️ - Please update to v0.117 to fix known bugs. See [#791](https://github.com/django-components/django-components/issues/791) and [#789](https://github.com/django-components/django-components/issues/789) and [#818](https://github.com/django-components/django-components/issues/818). - -#### Fix - -- Fix integration with ManifestStaticFilesStorage on Windows by resolving component filepaths - (like `Component.template_name`) to POSIX paths. - -## v0.114 - -⚠️ Attention ⚠️ - Please update to v0.117 to fix known bugs. See [#791](https://github.com/django-components/django-components/issues/791) and [#789](https://github.com/django-components/django-components/issues/789) and [#818](https://github.com/django-components/django-components/issues/818). - -#### Fix - -- Prevent rendering Slot tags during fill discovery stage to fix a case when a component inside a slot - fill tried to access provided data too early. - -## v0.113 - -⚠️ Attention ⚠️ - Please update to v0.117 to fix known bugs. See [#791](https://github.com/django-components/django-components/issues/791) and [#789](https://github.com/django-components/django-components/issues/789) and [#818](https://github.com/django-components/django-components/issues/818). - -#### Fix - -- Ensure consistent order of scripts in `Component.Media.js` - -## v0.112 - -⚠️ Attention ⚠️ - Please update to v0.117 to fix known bugs. See [#791](https://github.com/django-components/django-components/issues/791) and [#789](https://github.com/django-components/django-components/issues/789) and [#818](https://github.com/django-components/django-components/issues/818). - -#### Fix - -- Allow components to accept default fill even if no default slot was encountered during rendering - -## v0.111 - -⚠️ Attention ⚠️ - Please update to v0.117 to fix known bugs. See [#791](https://github.com/django-components/django-components/issues/791) and [#789](https://github.com/django-components/django-components/issues/789) and [#818](https://github.com/django-components/django-components/issues/818). - -#### Fix - -- Prevent rendering Component tags during fill discovery stage to fix a case when a component inside the default slot - tried to access provided data too early. - -## 🚨📢 v0.110 - -⚠️ Attention ⚠️ - Please update to v0.117 to fix known bugs. See [#791](https://github.com/django-components/django-components/issues/791) and [#789](https://github.com/django-components/django-components/issues/789) and [#818](https://github.com/django-components/django-components/issues/818). - -### General - -#### 🚨📢 BREAKING CHANGES - -- Installation changes: - - - If your components include JS or CSS, you now must use the middleware and add django-components' URLs to your `urlpatterns` - (See "[Adding support for JS and CSS](https://github.com/django-components/django-components#adding-support-for-js-and-css)") - -- Component typing signature changed from - - ```py - Component[Args, Kwargs, Data, Slots] - ``` - - to - - ```py - Component[Args, Kwargs, Slots, Data, JsData, CssData] - ``` - -- If you rendered a component A with `Component.render()` and then inserted that into another component B, now you must pass `render_dependencies=False` to component A: - - ```py - prerendered_a = CompA.render( - args=[...], - kwargs={...}, - render_dependencies=False, - ) - - html = CompB.render( - kwargs={ - content=prerendered_a, - }, - ) - ``` - -#### Feat - -- Intellisense and mypy validation for settings: - - Instead of defining the `COMPONENTS` settings as a plain dict, you can use `ComponentsSettings`: - - ```py - # settings.py - from django_components import ComponentsSettings - - COMPONENTS = ComponentsSettings( - autodiscover=True, - ... - ) - ``` - -- Use `get_component_dirs()` and `get_component_files()` to get the same list of dirs / files that would be imported by `autodiscover()`, but without actually -importing them. - -#### Refactor - -- For advanced use cases, use can omit the middleware and instead manage component JS and CSS dependencies yourself with [`render_dependencies`](https://github.com/django-components/django-components#render_dependencies-and-deep-dive-into-rendering-js--css-without-the-middleware) - -- The [`ComponentRegistry`](../api#django_components.ComponentRegistry) settings [`RegistrySettings`](../api#django_components.RegistrySettings) - were lowercased to align with the global settings: - - `RegistrySettings.CONTEXT_BEHAVIOR` -> `RegistrySettings.context_behavior` - - `RegistrySettings.TAG_FORMATTER` -> `RegistrySettings.tag_formatter` - - The old uppercase settings `CONTEXT_BEHAVIOR` and `TAG_FORMATTER` are deprecated and will be removed in v1. - -- The setting `reload_on_template_change` was renamed to - [`reload_on_file_change`](../settings#django_components.app_settings.ComponentsSettings#reload_on_file_change). - And now it properly triggers server reload when any file in the component dirs change. The old name `reload_on_template_change` - is deprecated and will be removed in v1. - -- The setting `forbidden_static_files` was renamed to - [`static_files_forbidden`](../settings#django_components.app_settings.ComponentsSettings#static_files_forbidden) - to align with [`static_files_allowed`](../settings#django_components.app_settings.ComponentsSettings#static_files_allowed) - The old name `forbidden_static_files` is deprecated and will be removed in v1. - -### Tags - -#### 🚨📢 BREAKING CHANGES - -- `{% component_dependencies %}` tag was removed. Instead, use `{% component_js_dependencies %}` and `{% component_css_dependencies %}` - - - The combined tag was removed to encourage the best practice of putting JS scripts at the end of ``, and CSS styles inside ``. - - On the other hand, co-locating JS script and CSS styles can lead to - a [flash of unstyled content](https://en.wikipedia.org/wiki/Flash_of_unstyled_content), - as either JS scripts will block the rendering, or CSS will load too late. - -- The undocumented keyword arg `preload` of `{% component_js_dependencies %}` and `{% component_css_dependencies %}` tags was removed. - This will be replaced with HTML fragment support. - -#### Fix - -- Allow using forward slash (`/`) when defining custom TagFormatter, - e.g. `{% MyComp %}..{% /MyComp %}`. - -#### Refactor - -- `{% component_dependencies %}` tags are now OPTIONAL - If your components use JS and CSS, but you don't use `{% component_dependencies %}` tags, the JS and CSS will now be, by default, inserted at the end of `` and at the end of `` respectively. - -### Slots - -#### Feat - -- Fills can now be defined within loops (`{% for %}`) or other tags (like `{% with %}`), - or even other templates using `{% include %}`. - - Following is now possible - - ```django - {% component "table" %} - {% for slot_name in slots %} - {% fill name=slot_name %} - {% endfill %} - {% endfor %} - {% endcomponent %} - ``` - -- If you need to access the data or the default content of a default fill, you can - set the `name` kwarg to `"default"`. - - Previously, a default fill would be defined simply by omitting the `{% fill %}` tags: - - ```django - {% component "child" %} - Hello world - {% endcomponent %} - ``` - - But in that case you could not access the slot data or the default content, like it's possible - for named fills: - - ```django - {% component "child" %} - {% fill name="header" data="data" %} - Hello {{ data.user.name }} - {% endfill %} - {% endcomponent %} - ``` - - Now, you can specify default tag by using `name="default"`: - - ```django - {% component "child" %} - {% fill name="default" data="data" %} - Hello {{ data.user.name }} - {% endfill %} - {% endcomponent %} - ``` - -- When inside `get_context_data()` or other component methods, the default fill - can now be accessed as `Component.input.slots["default"]`, e.g.: - - ```py - class MyTable(Component): - def get_context_data(self, *args, **kwargs): - default_slot = self.input.slots["default"] - ... - ``` - -- You can now dynamically pass all slots to a child component. This is similar to - [passing all slots in Vue](https://vue-land.github.io/faq/forwarding-slots#passing-all-slots): - - ```py - class MyTable(Component): - def get_context_data(self, *args, **kwargs): - return { - "slots": self.input.slots, - } - - template: """ -
- {% component "child" %} - {% for slot_name in slots %} - {% fill name=slot_name data="data" %} - {% slot name=slot_name ...data / %} - {% endfill %} - {% endfor %} - {% endcomponent %} -
- """ - ``` - -#### Fix - -- Slots defined with `{% fill %}` tags are now properly accessible via `self.input.slots` in `get_context_data()` - -- Do not raise error if multiple slots with same name are flagged as default - -- Slots can now be defined within loops (`{% for %}`) or other tags (like `{% with %}`), - or even other templates using `{% include %}`. - - Previously, following would cause the kwarg `name` to be an empty string: - - ```django - {% for slot_name in slots %} - {% slot name=slot_name %} - {% endfor %} - ``` - -#### Refactor - -- When you define multiple slots with the same name inside a template, - you now have to set the `default` and `required` flags individually. - - ```htmldjango -
-
- {% slot "image" default required %}Image here{% endslot %} -
-
- {% slot "image" default required %}Image here{% endslot %} -
-
- ``` - - This means you can also have multiple slots with the same name but - different conditions. - - E.g. in this example, we have a component that renders a user avatar - - a small circular image with a profile picture of name initials. - - If the component is given `image_src` or `name_initials` variables, - the `image` slot is optional. But if neither of those are provided, - you MUST fill the `image` slot. - - ```htmldjango -
- {% if image_src %} - {% slot "image" default %} - - {% endslot %} - {% elif name_initials %} - {% slot "image" default required %} -
- {{ name_initials }} -
- {% endslot %} - {% else %} - {% slot "image" default required / %} - {% endif %} -
- ``` - -- The slot fills that were passed to a component and which can be accessed as `Component.input.slots` - can now be passed through the Django template, e.g. as inputs to other tags. - - Internally, django-components handles slot fills as functions. - - Previously, if you tried to pass a slot fill within a template, Django would try to call it as a function. - - Now, something like this is possible: - - ```py - class MyTable(Component): - def get_context_data(self, *args, **kwargs): - return { - "child_slot": self.input.slots["child_slot"], - } - - template: """ -
- {% component "child" content=child_slot / %} -
- """ - ``` - - NOTE: Using `{% slot %}` and `{% fill %}` tags is still the preferred method, but the approach above - may be necessary in some complex or edge cases. - -- The `is_filled` variable (and the `{{ component_vars.is_filled }}` context variable) now returns - `False` when you try to access a slot name which has not been defined: - - Before: - - ```django - {{ component_vars.is_filled.header }} -> True - {{ component_vars.is_filled.footer }} -> False - {{ component_vars.is_filled.nonexist }} -> "" (empty string) - ``` - - After: - ```django - {{ component_vars.is_filled.header }} -> True - {{ component_vars.is_filled.footer }} -> False - {{ component_vars.is_filled.nonexist }} -> False - ``` - -- Components no longer raise an error if there are extra slot fills - -- Components will raise error when a slot is doubly-filled. - - E.g. if we have a component with a default slot: - - ```django - {% slot name="content" default / %} - ``` - - Now there is two ways how we can target this slot: Either using `name="default"` - or `name="content"`. - - In case you specify BOTH, the component will raise an error: - - ```django - {% component "child" %} - {% fill slot="default" %} - Hello from default slot - {% endfill %} - {% fill slot="content" data="data" %} - Hello from content slot - {% endfill %} - {% endcomponent %} - ``` - -## 🚨📢 v0.100 - -#### BREAKING CHANGES - -- `django_components.safer_staticfiles` app was removed. It is no longer needed. - -- Installation changes: - - - Instead of defining component directories in `STATICFILES_DIRS`, set them to [`COMPONENTS.dirs`](https://github.com/django-components/django-components#dirs). - - You now must define `STATICFILES_FINDERS` - - - [See here how to migrate your settings.py](https://github.com/django-components/django-components/blob/master/docs/migrating_from_safer_staticfiles.md) - -#### Feat - -- Beside the top-level `/components` directory, you can now define also app-level components dirs, e.g. `[app]/components` - (See [`COMPONENTS.app_dirs`](https://github.com/django-components/django-components#app_dirs)). - -#### Refactor - -- When you call `as_view()` on a component instance, that instance will be passed to `View.as_view()` - -## v0.97 - -#### Fix - -- Fixed template caching. You can now also manually create cached templates with [`cached_template()`](https://github.com/django-components/django-components#template_cache_size---tune-the-template-cache) - -#### Refactor - -- The previously undocumented `get_template` was made private. - -- In it's place, there's a new `get_template`, which supersedes `get_template_string` (will be removed in v1). The new `get_template` is the same as `get_template_string`, except - it allows to return either a string or a Template instance. - -- You now must use only one of `template`, `get_template`, `template_name`, or `get_template_name`. - -## v0.96 - -#### Feat - -- Run-time type validation for Python 3.11+ - If the `Component` class is typed, e.g. `Component[Args, Kwargs, ...]`, the args, kwargs, slots, and data are validated against the given types. (See [Runtime input validation with types](https://github.com/django-components/django-components#runtime-input-validation-with-types)) - -- Render hooks - Set `on_render_before` and `on_render_after` methods on `Component` to intercept or modify the template or context before rendering, or the rendered result afterwards. (See [Component hooks](https://github.com/django-components/django-components#component-hooks)) - -- `component_vars.is_filled` context variable can be accessed from within `on_render_before` and `on_render_after` hooks as `self.is_filled.my_slot` - -## 0.95 - -#### Feat - -- Added support for dynamic components, where the component name is passed as a variable. (See [Dynamic components](https://github.com/django-components/django-components#dynamic-components)) - -#### Refactor - -- Changed `Component.input` to raise `RuntimeError` if accessed outside of render context. Previously it returned `None` if unset. - -## v0.94 - -#### Feat - -- django_components now automatically configures Django to support multi-line tags. (See [Multi-line tags](https://github.com/django-components/django-components#multi-line-tags)) - -- New setting `reload_on_template_change`. Set this to `True` to reload the dev server on changes to component template files. (See [Reload dev server on component file changes](https://github.com/django-components/django-components#reload-dev-server-on-component-file-changes)) - -## v0.93 - -#### Feat - -- Spread operator `...dict` inside template tags. (See [Spread operator](https://github.com/django-components/django-components#spread-operator)) - -- Use template tags inside string literals in component inputs. (See [Use template tags inside component inputs](https://github.com/django-components/django-components#use-template-tags-inside-component-inputs)) - -- Dynamic slots, fills and provides - The `name` argument for these can now be a variable, a template expression, or via spread operator - -- Component library authors can now configure `CONTEXT_BEHAVIOR` and `TAG_FORMATTER` settings independently from user settings. - -## 🚨📢 v0.92 - -#### BREAKING CHANGES - -- `Component` class is no longer a subclass of `View`. To configure the `View` class, set the `Component.View` nested class. HTTP methods like `get` or `post` can still be defined directly on `Component` class, and `Component.as_view()` internally calls `Component.View.as_view()`. (See [Modifying the View class](https://github.com/django-components/django-components#modifying-the-view-class)) - -#### Feat - -- The inputs (args, kwargs, slots, context, ...) that you pass to `Component.render()` can be accessed from within `get_context_data`, `get_template` and `get_template_name` via `self.input`. (See [Accessing data passed to the component](https://github.com/django-components/django-components#accessing-data-passed-to-the-component)) - -- Typing: `Component` class supports generics that specify types for `Component.render` (See [Adding type hints with Generics](https://github.com/django-components/django-components#adding-type-hints-with-generics)) - -## v0.90 - -#### Feat - -- All tags (`component`, `slot`, `fill`, ...) now support "self-closing" or "inline" form, where you can omit the closing tag: - - ```django - {# Before #} - {% component "button" %}{% endcomponent %} - {# After #} - {% component "button" / %} - ``` - -- All tags now support the "dictionary key" or "aggregate" syntax (`kwarg:key=val`): - - ```django - {% component "button" attrs:class="hidden" %} - ``` - -- You can change how the components are written in the template with [TagFormatter](https://github.com/django-components/django-components#customizing-component-tags-with-tagformatter). - - The default is `django_components.component_formatter`: - - ```django - {% component "button" href="..." disabled %} - Click me! - {% endcomponent %} - ``` - - While `django_components.shorthand_component_formatter` allows you to write components like so: - - ```django - {% button href="..." disabled %} - Click me! - {% endbutton %} - ``` - -## 🚨📢 v0.85 - -#### BREAKING CHANGES - -- Autodiscovery module resolution changed. Following undocumented behavior was removed: - - - Previously, autodiscovery also imported any `[app]/components.py` files, and used `SETTINGS_MODULE` to search for component dirs. - - To migrate from: - - - `[app]/components.py` - Define each module in `COMPONENTS.libraries` setting, - or import each module inside the `AppConfig.ready()` hook in respective `apps.py` files. - - - `SETTINGS_MODULE` - Define component dirs using `STATICFILES_DIRS` - - - Previously, autodiscovery handled relative files in `STATICFILES_DIRS`. To align with Django, `STATICFILES_DIRS` now must be full paths ([Django docs](https://docs.djangoproject.com/en/5.2/ref/settings/#std-setting-STATICFILES_DIRS)). - -## 🚨📢 v0.81 - -#### BREAKING CHANGES - -- The order of arguments to `render_to_response` has changed, to align with the (now public) `render` method of `Component` class. - -#### Feat - -- `Component.render()` is public and documented - -- Slots passed `render_to_response` and `render` can now be rendered also as functions. - -## v0.80 - -#### Feat - -- Vue-like provide/inject with the `{% provide %}` tag and `inject()` method. - -## 🚨📢 v0.79 - -#### BREAKING CHANGES - -- Default value for the `COMPONENTS.context_behavior` setting was changes from `"isolated"` to `"django"`. If you did not set this value explicitly before, this may be a breaking change. See the rationale for change [here](https://github.com/django-components/django-components/issues/498). - -## 🚨📢 v0.77 - -#### BREAKING - -- The syntax for accessing default slot content has changed from - - ```django - {% fill "my_slot" as "alias" %} - {{ alias.default }} - {% endfill %} - - ``` - - to - - ```django - {% fill "my_slot" default="alias" %} - {{ alias }} - {% endfill %} - ``` - -## v0.74 - -#### Feat - -- `{% html_attrs %}` tag for formatting data as HTML attributes - -- `prefix:key=val` construct for passing dicts to components - -## 🚨📢 v0.70 - -#### BREAKING CHANGES - -- `{% if_filled "my_slot" %}` tags were replaced with `{{ component_vars.is_filled.my_slot }}` variables. - -- Simplified settings - `slot_context_behavior` and `context_behavior` were merged. See the [documentation](https://github.com/django-components/django-components#context-behavior) for more details. - -## v0.67 - -#### Refactor - -- Changed the default way how context variables are resolved in slots. See the [documentation](https://github.com/django-components/django-components/tree/0.67#isolate-components-slots) for more details. - -## 🚨📢 v0.50 - -#### BREAKING CHANGES - -- `{% component_block %}` is now `{% component %}`, and `{% component %}` blocks need an ending `{% endcomponent %}` tag. - - The new `python manage.py upgradecomponent` command can be used to upgrade a directory (use `--path` argument to point to each dir) of templates that use components to the new syntax automatically. - - This change is done to simplify the API in anticipation of a 1.0 release of django_components. After 1.0 we intend to be stricter with big changes like this in point releases. - -## v0.34 - -#### Feat - -- Components as views, which allows you to handle requests and render responses from within a component. See the [documentation](https://github.com/django-components/django-components#use-components-as-views) for more details. - -## v0.28 - -#### Feat - -- 'implicit' slot filling and the `default` option for `slot` tags. - -## v0.27 - -#### Feat - -- A second installable app `django_components.safer_staticfiles`. It provides the same behavior as `django.contrib.staticfiles` but with extra security guarantees (more info below in [Security Notes](https://github.com/django-components/django-components#security-notes)). - -## 🚨📢 v0.26 - -#### BREAKING CHANGES - -- Changed the syntax for `{% slot %}` tags. From now on, we separate defining a slot (`{% slot %}`) from filling a slot with content (`{% fill %}`). This means you will likely need to change a lot of slot tags to fill. - - We understand this is annoying, but it's the only way we can get support for nested slots that fill in other slots, which is a very nice feature to have access to. Hoping that this will feel worth it! - -## v0.22 - -#### Feat - -- All files inside components subdirectores are autoimported to simplify setup. - - An existing project might start to get `AlreadyRegistered` errors because of this. To solve this, either remove your custom loading of components, or set `"autodiscover": False` in `settings.COMPONENTS`. - -## v0.17 - -#### BREAKING CHANGES - -- Renamed `Component.context` and `Component.template` to `get_context_data` and `get_template_name`. The old methods still work, but emit a deprecation warning. - - This change was done to sync naming with Django's class based views, and make using django-components more familiar to Django users. `Component.context` and `Component.template` will be removed when version 1.0 is released. diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index ac35f2e0..0413beeb 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -55,7 +55,7 @@ further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at emil@emilstenstrom.se. All +reported by contacting the project team at em@kth.se. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 8b126249..00000000 --- a/MANIFEST.in +++ /dev/null @@ -1,5 +0,0 @@ -# MANIFEST.in is defined so we can include non-Python (e.g. JS) files -# in the built distribution. -# See https://setuptools.pypa.io/en/latest/userguide/miscellaneous.html -graft src/django_components/static -prune tests diff --git a/README.md b/README.md index 8df3262e..5b9e3c28 100644 --- a/README.md +++ b/README.md @@ -1,565 +1,420 @@ -# django-components +# django-components +Show test status +Show download stats -[![PyPI - Version](https://img.shields.io/pypi/v/django-components)](https://pypi.org/project/django-components/) [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-components)](https://pypi.org/project/django-components/) [![PyPI - License](https://img.shields.io/pypi/l/django-components)](https://github.com/django-components/django-components/blob/master/LICENSE/) [![PyPI - Downloads](https://img.shields.io/pypi/dm/django-components)](https://pypistats.org/packages/django-components) [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/django-components/django-components/tests.yml)](https://github.com/django-components/django-components/actions/workflows/tests.yml) [![asv](https://img.shields.io/badge/benchmarked%20by-asv-blue.svg?style=flat)](https://django-components.github.io/django-components/latest/benchmarks/) +A way to create simple reusable template components in Django. -###
[Read the full documentation](https://django-components.github.io/django-components/latest/)
+It lets you create "template components", that contains both the template, the Javascript and the CSS needed to generate the front end code you need for a modern app. Components look like this: -`django-components` is a modular and extensible UI framework for Django. - -It combines Django's templating system with the modularity seen -in modern frontend frameworks like Vue or React. - -With `django-components` you can support Django projects small and large without leaving the Django ecosystem. - -## Quickstart - -A component in django-components can be as simple as a Django template and Python code to declare the component: - -```django -{# components/calendar/calendar.html #} -
- Today's date is {{ date }} -
+```htmldjango +{% component "calendar" date="2015-06-19" %} ``` -```py -# components/calendar/calendar.py -from django_components import Component, register +And this is what gets rendered (plus the CSS and Javascript you've specified): -@register("calendar") -class Calendar(Component): - template_file = "calendar.html" +```html +
Today's date is 2015-06-19
``` -Or a combination of Django template, Python, CSS, and Javascript: +Read on to learn about the details! -```django -{# components/calendar/calendar.html #} -
- Today's date is {{ date }} -
+# Release notes + +*Version 0.17* renames `Component.context` and `Component.template` to `get_context_data` and `get_template_name`. The old methods still work, but emit a deprecation warning. This change was done to sync naming with Django's class based views, and make using django-components more familiar to Django users. `Component.context` and `Component.template` will be removed when version 1.0 is released. + +# Installation + +Install the app into your environment: + +> ```pip install django_components``` + +Then add the app into INSTALLED APPS in settings.py + +```python +INSTALLED_APPS = [ + ..., + "django_components", + ... +] ``` +Modify `TEMPLATES` section of settings.py as follows: +- Remove `'APP_DIRS': True,` +- add `loaders` to `OPTIONS` list and set it to following value: +```python +TEMPLATES = [ + { + ..., + 'OPTIONS': { + 'context_processors': [ + ... + ], + 'loaders':[( + 'django.template.loaders.cached.Loader', [ + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', + 'django_components.template_loader.Loader', + ] + )], + }, + }, +] +``` +## Optional + +To avoid loading the app in each template using ``` {% load django_components %} ```, you can add the tag as a 'builtin' in settings.py + +```python +TEMPLATES = [ + { + ..., + 'OPTIONS': { + 'context_processors': [ + ... + ], + 'builtins': [ + 'django_components.templatetags.component_tags', + ] + }, + }, +] +``` + +# Contributors + + + + + + + + + + + + + + + + + +
+ + EmilStenstrom +
+ Emil Stenström +
+
+ + hanifbirgani +
+ Hanif Birgani +
+
+ + ryanhiebert +
+ Ryan Hiebert +
+
+ + rbeard0330 +
+ Rbeard0330 +
+
+ + BradleyKirton +
+ Bradley Stuart Kirton +
+
+ + David-Guillot +
+ David Guillot +
+
+ + housUnus +
+ +
+
+ + danjac +
+ Dan Jacob +
+
+ + Real-Gecko +
+ Real-Gecko +
+
+ + simkimsia +
+ KimSia Sim +
+
+ + telenieko +
+ Marc Fargas +
+
+ + spollard +
+ Spollard +
+
+ + +# Compatiblity + +Django-components supports all officially supported versions of Django and Python. + +| Python version | Django version | +|----------------|--------------------------| +| 3.6 | 3.2 | +| 3.7 | 3.2 | +| 3.8 | 3.2, 4.0 | +| 3.9 | 3.2, 4.0 | +| 3.10 | 4.0 | + +# Create your first component + +A component in django-components is the combination of four things: CSS, Javascript, a Django template, and some Python code to put them all together. + +First you need a CSS file. Be sure to prefix all rules with a unique class so they don't clash with other rules. + ```css -/* components/calendar/calendar.css */ -.calendar { - width: 200px; - background: pink; -} +/* In a file called [your app]/components/calendar/style.css */ +.calendar-component { width: 200px; background: pink; } +.calendar-component span { font-weight: bold; } ``` +Then you need a javascript file that specifies how you interact with this component. You are free to use any javascript framework you want. A good way to make sure this component doesn't clash with other components is to define all code inside an anonymous function that calls itself. This makes all variables defined only be defined inside this component and not affect other components. + ```js -/* components/calendar/calendar.js */ -document.querySelector(".calendar").onclick = () => { - alert("Clicked calendar!"); -}; +/* In a file called [your app]/components/calendar/script.js */ +(function(){ + $(".calendar-component").click(function(){ alert("Clicked calendar!"); }) +})() ``` -```py -# components/calendar/calendar.py -from django_components import Component, register +Now you need a Django template for your component. Feel free to define more variables like `date` in this example. When creating an instance of this component we will send in the values for these variables. The template will be rendered with whatever template backend you've specified in your Django settings file. -@register("calendar") -class Calendar(Component): - template_file = "calendar.html" - js_file = "calendar.js" - css_file = "calendar.css" - - def get_template_data(self, args, kwargs, slots, context): - return {"date": kwargs["date"]} +```htmldjango +{# In a file called [your app]/components/calendar/calendar.html #} +
Today's date is {{ date }}
``` -Use the component like this: +Finally, we use django-components to tie this together. Start by creating a file called `components.py` in any of your apps. It will be auto-detected and loaded by the app. -```django -{% component "calendar" date="2024-11-06" %}{% endcomponent %} +Inside this file we create a Component by inheriting from the Component class and specifying the context method. We also register the global component registry so that we easily can render it anywhere in our templates. + +```python +from django_components import component + +@component.register("calendar") +class Calendar(component.Component): + # Note that Django will look for templates inside `[your app]/components` dir + # To customize which template to use based on context override get_template_name instead + template_name = "calendar/calendar.html" + + # This component takes one parameter, a date string to show in the template + def get_context_data(self, date): + return { + "date": date, + } + + class Media: + css = '[your app]/components/calendar/calendar.css' + js = '[your app]/components/calendar/calendar.js' ``` -And this is what gets rendered: +And voilá!! We've created our first component. + +# Use the component in a template + +First load the `component_tags` tag library, then use the `component_[js/css]_dependencies` and `component` tags to render the component to the page. + +```htmldjango +{% load component_tags %} + + + + My example calendar + {% component_css_dependencies %} + + + {% component "calendar" date="2015-06-19" %} + {% component_js_dependencies %} + + +``` + +The output from the above template will be: + +```html + + + + My example calendar + + + +
Today's date is 2015-06-19
+ + + +``` + +This makes it possible to organize your front-end around reusable components. Instead of relying on template tags and keeping your CSS and Javascript in the static directory. + +# Using slots in templates + +Components support something called slots. They work a lot like Django blocks, but only inside components you define. Let's update our calendar component to support more customization, by updating our calendar.html template: + +```htmldjango +
+
+ {% slot "header" %}Calendar header{% endslot %} +
+
+ {% slot "body" %}Today's date is {{ date }}{% endslot %} +
+
+``` + +When using the component, you specify what slots you want to fill and where you want to use the defaults from the template. It looks like this: + +```htmldjango +{% component_block "calendar" date="2020-06-06" %} + {% slot "body" %}Can you believe it's already {{ date }}??{% endslot %} +{% endcomponent_block %} +``` + +Since the header block is unspecified, it's taken from the base template. If you put this in a template, and send in date=2020-06-06, this is what's rendered: ```html
- Today's date is 2024-11-06 +
+ Calendar header +
+
+ Can you believe it's already 2020-06-06?? +
+
+ +``` + +As you can see, component slots lets you write reusable containers, that you fill out when you use a component. This makes for highly reusable components, that can be used in different circumstances. + +If you want to include a slot's default content while adding additional content, you can call `slot.super` to insert the base content, which works similarly to `block.super`. + +```htmldjango +{% component_block "calendar" date="2020-06-06" %} + {% slot "body" %}{{ slot.super }}. Have a great day!{% endslot %} +{% endcomponent_block %} +``` + +Produces: + +```html +
+
+ Calendar header +
+
+ Today's date is 2020-06-06. Have a great day! +
``` -Read on to learn about all the exciting details and configuration possibilities! +# Component context and scope -(If you instead prefer to jump right into the code, [check out the example project](https://github.com/django-components/django-components/tree/master/sampleproject)) +By default, components can access context variables from the parent template, just like templates that are included with the `{% include %}` tag. Just like with `{% include %}`, if you don't want the component template to have access to the parent context, add `only` to the end of the `{% component %}` (or `{% component_block %}` tag): -## Features +```htmldjango + {% component "calendar" date="2015-06-19" only %} +``` -### Modern and modular UI +NOTE: `{% csrf_token %}` tags need access to the top-level context, and they will not function properly if they are rendered in a component that is called with the `only` modifier. -- Create self-contained, reusable UI elements. -- Each component can include its own HTML, CSS, and JS, or additional third-party JS and CSS. -- HTML, CSS, and JS can be defined on the component class, or loaded from files. +Components can also access the outer context in their context methods by accessing the property `outer_context`. + + +# Available settings + +All library settings are handled from a global COMPONENTS variable that is read from settings.py. By default you don't need it set, there are resonable defaults. + +## Configure the module where components are loaded from + +Configure the location where components are loaded. To do this, add a COMPONENTS variable to you settings.py with a list of python paths to load. This allows you to build a structure of components that are independent from your apps. ```python -from django_components import Component - -@register("calendar") -class Calendar(Component): - template = """ -
- Today's date is - {{ date }} -
- """ - - css = """ - .calendar { - width: 200px; - background: pink; - } - """ - - js = """ - document.querySelector(".calendar") - .addEventListener("click", () => { - alert("Clicked calendar!"); - }); - """ - - # Additional JS and CSS - class Media: - js = ["https://cdn.jsdelivr.net/npm/htmx.org@2/dist/htmx.min.js"] - css = ["bootstrap/dist/css/bootstrap.min.css"] - - # Variables available in the template - def get_template_data(self, args, kwargs, slots, context): - return { - "date": kwargs["date"] - } -``` - -### Composition with slots - -- Render components inside templates with - [`{% component %}`](https://django-components.github.io/django-components/latest/reference/template_tags#component) tag. -- Compose them with [`{% slot %}`](https://django-components.github.io/django-components/latest/reference/template_tags#slot) - and [`{% fill %}`](https://django-components.github.io/django-components/latest/reference/template_tags#fill) tags. -- Vue-like slot system, including [scoped slots](https://django-components.github.io/django-components/latest/concepts/fundamentals/slots/#slot-data). - -```django -{% component "Layout" - bookmarks=bookmarks - breadcrumbs=breadcrumbs -%} - {% fill "header" %} -
-
-

{{ project.name }}

-
-
- {{ project.start_date }} - {{ project.end_date }} -
-
- {% endfill %} - - {# Access data passed to `{% slot %}` with `data` #} - {% fill "tabs" data="tabs_data" %} - {% component "TabItem" header="Project Info" %} - {% component "ProjectInfo" - project=project - project_tags=project_tags - attrs:class="py-5" - attrs:width=tabs_data.width - / %} - {% endcomponent %} - {% endfill %} -{% endcomponent %} -``` - -### Extended template tags - -`django-components` is designed for flexibility, making working with templates a breeze. - -It extends Django's template tags syntax with: - - -- Literal lists and dictionaries in the template -- [Self-closing tags](https://django-components.github.io/django-components/latest/concepts/fundamentals/template_tag_syntax#self-closing-tags) `{% mytag / %}` -- [Multi-line template tags](https://django-components.github.io/django-components/latest/concepts/fundamentals/template_tag_syntax#multiline-tags) -- [Spread operator](https://django-components.github.io/django-components/latest/concepts/fundamentals/template_tag_syntax#spread-operator) `...` to dynamically pass args or kwargs into the template tag -- [Template tags inside literal strings](https://django-components.github.io/django-components/latest/concepts/fundamentals/template_tag_syntax#template-tags-inside-literal-strings) like `"{{ first_name }} {{ last_name }}"` -- [Pass dictonaries by their key-value pairs](https://django-components.github.io/django-components/latest/concepts/fundamentals/template_tag_syntax#pass-dictonary-by-its-key-value-pairs) `attr:key=val` - -```django -{% component "table" - ...default_attrs - title="Friend list for {{ user.name }}" - headers=["Name", "Age", "Email"] - data=[ - { - "name": "John"|upper, - "age": 30|add:1, - "email": "john@example.com", - "hobbies": ["reading"], - }, - { - "name": "Jane"|upper, - "age": 25|add:1, - "email": "jane@example.com", - "hobbies": ["reading", "coding"], - }, +COMPONENTS = { + "libraries": [ + "mysite.components.forms", + "mysite.components.buttons", + "mysite.components.cards", ], - attrs:class="py-4 ma-2 border-2 border-gray-300 rounded-md" -/ %} +} ``` -You too can define template tags with these features by using -[`@template_tag()`](https://django-components.github.io/django-components/latest/reference/api/#django_components.template_tag) -or [`BaseNode`](https://django-components.github.io/django-components/latest/reference/api/#django_components.BaseNode). +## Disable autodiscovery -Read more on [Custom template tags](https://django-components.github.io/django-components/latest/concepts/advanced/template_tags/). - -### Full programmatic access - -When you render a component, you can access everything about the component: - -- Component input: [args, kwargs, slots and context](https://django-components.github.io/django-components/latest/concepts/fundamentals/render_api/#component-inputs) -- Component's template, CSS and JS -- Django's [context processors](https://django-components.github.io/django-components/latest/concepts/fundamentals/render_api/#request-and-context-processors) -- Unique [render ID](https://django-components.github.io/django-components/latest/concepts/fundamentals/render_api/#component-id) +If you specify all the component locations with the setting above and have a lot of apps, you can (very) slightly speed things up by disabling autodiscovery. ```python -class Table(Component): - js_file = "table.js" - css_file = "table.css" - - template = """ -
- {{ variable }} -
- """ - - def get_template_data(self, args, kwargs, slots, context): - # Access component's ID - assert self.id == "djc1A2b3c" - - # Access component's inputs and slots - assert self.args == [123, "str"] - assert self.kwargs == {"variable": "test", "another": 1} - footer_slot = self.slots["footer"] - some_var = self.context["some_var"] - - # Access the request object and Django's context processors, if available - assert self.request.GET == {"query": "something"} - assert self.context_processors_data['user'].username == "admin" - - return { - "variable": kwargs["variable"], - } - -# Access component's HTML / JS / CSS -Table.template -Table.js -Table.css - -# Render the component -rendered = Table.render( - kwargs={"variable": "test", "another": 1}, - args=(123, "str"), - slots={"footer": "MY_FOOTER"}, -) +COMPONENTS = { + "autodiscovery": False, +} ``` -### Granular HTML attributes +## Tune the template cache -Use the [`{% html_attrs %}`](https://django-components.github.io/django-components/latest/concepts/fundamentals/html_attributes/) template tag to render HTML attributes. - -It supports: - -- Defining attributes as whole dictionaries or keyword arguments -- Merging attributes from multiple sources -- Boolean attributes -- Appending attributes -- Removing attributes -- Defining default attributes - -```django -
-``` - -[`{% html_attrs %}`](https://django-components.github.io/django-components/latest/concepts/fundamentals/html_attributes/) offers a Vue-like granular control for -[`class`](https://django-components.github.io/django-components/latest/concepts/fundamentals/html_attributes/#merging-class-attributes) -and [`style`](https://django-components.github.io/django-components/latest/concepts/fundamentals/html_attributes/#merging-style-attributes) -HTML attributes, -where you can use a dictionary to manage each class name or style property separately. - -```django -{% html_attrs - class="foo bar" - class={ - "baz": True, - "foo": False, - } - class="extra" -%} -``` - -```django -{% html_attrs - style="text-align: center; background-color: blue;" - style={ - "background-color": "green", - "color": None, - "width": False, - } - style="position: absolute; height: 12px;" -%} -``` - -Read more about [HTML attributes](https://django-components.github.io/django-components/latest/concepts/fundamentals/html_attributes/). - -### HTML fragment support - -`django-components` makes integration with HTMX, AlpineJS or jQuery easy by allowing components to be rendered as [HTML fragments](https://django-components.github.io/django-components/latest/concepts/advanced/html_fragments/): - -- Components's JS and CSS files are loaded automatically when the fragment is inserted into the DOM. - -- Components can be [exposed as Django Views](https://django-components.github.io/django-components/latest/concepts/fundamentals/component_views_urls/) with `get()`, `post()`, `put()`, `patch()`, `delete()` methods - -- Automatically create an endpoint for a component with [`Component.View.public`](https://django-components.github.io/django-components/latest/concepts/fundamentals/component_views_urls/#register-urls-automatically) - -```py -# components/calendar/calendar.py -@register("calendar") -class Calendar(Component): - template_file = "calendar.html" - - class View: - # Register Component with `urlpatterns` - public = True - - # Define handlers - def get(self, request, *args, **kwargs): - page = request.GET.get("page", 1) - return self.component.render_to_response( - request=request, - kwargs={ - "page": page, - }, - ) - - def get_template_data(self, args, kwargs, slots, context): - return { - "page": kwargs["page"], - } - -# Get auto-generated URL for the component -url = get_component_url(Calendar) - -# Or define explicit URL in urls.py -path("calendar/", Calendar.as_view()) -``` - -### Provide / Inject - -`django-components` supports the provide / inject pattern, similarly to React's [Context Providers](https://react.dev/learn/passing-data-deeply-with-context) or Vue's [provide / inject](https://vuejs.org/guide/components/provide-inject): - -- Use the [`{% provide %}`](https://django-components.github.io/django-components/latest/reference/template_tags/#provide) tag to provide data to the component tree -- Use the [`Component.inject()`](https://django-components.github.io/django-components/latest/reference/api/#django_components.Component.inject) method to inject data into the component - -Read more about [Provide / Inject](https://django-components.github.io/django-components/latest/concepts/advanced/provide_inject). - -```django - - {% provide "theme" variant="light" %} - {% component "header" / %} - {% endprovide %} - -``` - -```djc_py -@register("header") -class Header(Component): - template = "..." - - def get_template_data(self, args, kwargs, slots, context): - theme = self.inject("theme").variant - return { - "theme": theme, - } -``` - -### Input validation and static type hints - -Avoid needless errors with [type hints and runtime input validation](https://django-components.github.io/django-components/latest/concepts/fundamentals/typing_and_validation/). - -To opt-in to input validation, define types for component's args, kwargs, slots, and more: - -```py -from typing import NamedTuple, Optional -from django.template import Context -from django_components import Component, Slot, SlotInput - -class Button(Component): - class Args(NamedTuple): - size: int - text: str - - class Kwargs(NamedTuple): - variable: str - another: int - maybe_var: Optional[int] = None # May be omitted - - class Slots(NamedTuple): - my_slot: Optional[SlotInput] = None - another_slot: SlotInput - - def get_template_data(self, args: Args, kwargs: Kwargs, slots: Slots, context: Context): - args.size # int - kwargs.variable # str - slots.my_slot # Slot[MySlotData] -``` - -To have type hints when calling -[`Button.render()`](https://django-components.github.io/django-components/latest/reference/api/#django_components.Component.render) or -[`Button.render_to_response()`](https://django-components.github.io/django-components/latest/reference/api/#django_components.Component.render_to_response), -wrap the inputs in their respective `Args`, `Kwargs`, and `Slots` classes: - -```py -Button.render( - # Error: First arg must be `int`, got `float` - args=Button.Args( - size=1.25, - text="abc", - ), - # Error: Key "another" is missing - kwargs=Button.Kwargs( - variable="text", - ), -) -``` - -### Extensions - -Django-components functionality can be extended with [Extensions](https://django-components.github.io/django-components/latest/concepts/advanced/extensions/). -Extensions allow for powerful customization and integrations. They can: - -- Tap into lifecycle events, such as when a component is created, deleted, or registered -- Add new attributes and methods to the components -- Add custom CLI commands -- Add custom URLs - -Some of the extensions include: - -- [Component caching](https://github.com/django-components/django-components/blob/master/src/django_components/extensions/cache.py) -- [Django View integration](https://github.com/django-components/django-components/blob/master/src/django_components/extensions/view.py) -- [Component defaults](https://github.com/django-components/django-components/blob/master/src/django_components/extensions/defaults.py) -- [Pydantic integration (input validation)](https://github.com/django-components/djc-ext-pydantic) - -Some of the planned extensions include: - -- AlpineJS integration -- Storybook integration -- Component-level benchmarking with asv - -### Caching - -- [Components can be cached](https://django-components.github.io/django-components/latest/concepts/advanced/component_caching/) using Django's cache framework. -- Caching rules can be configured on a per-component basis. -- Components are cached based on their input. Or you can write custom caching logic. - -```py -from django_components import Component - -class MyComponent(Component): - class Cache: - enabled = True - ttl = 60 * 60 * 24 # 1 day - - def hash(self, *args, **kwargs): - return hash(f"{json.dumps(args)}:{json.dumps(kwargs)}") -``` - -### Simple testing - -- Write tests for components with [`@djc_test`](https://django-components.github.io/django-components/latest/concepts/advanced/testing/) decorator. -- The decorator manages global state, ensuring that tests don't leak. -- If using `pytest`, the decorator allows you to parametrize Django or Components settings. -- The decorator also serves as a stand-in for Django's [`@override_settings`](https://docs.djangoproject.com/en/5.2/topics/testing/tools/#django.test.override_settings). +Each time a template is rendered it is cached to a global in-memory cache (using Python's lru_cache decorator). This speeds up the next render of the component. As the same component is often used many times on the same page, these savings add up. By default the cache holds 128 component templates in memory, which should be enough for most sites. But if you have a lot of components, or if you are using the `template` method of a component to render lots of dynamic templates, you can increase this number. To remove the cache limit altogether and cache everything, set template_cache_size to `None`. ```python -from django_components.testing import djc_test - -from components.my_table import MyTable - -@djc_test -def test_my_table(): - rendered = MyTable.render( - kwargs={ - "title": "My table", - }, - ) - assert rendered == "My table
" +COMPONENTS = { + "template_cache_size": 256, +} ``` -### Debugging features +# Install locally and run the tests -- **Visual component inspection**: Highlight components and slots directly in your browser. -- **Detailed tracing logs to supply AI-agents with context**: The logs include component and slot names and IDs, and their position in the tree. +Start by forking the project by clicking the **Fork button** up in the right corner in the GitHub . This makes a copy of the repository in your own name. Now you can clone this repository locally and start adding features: -
-Component debugging visualization showing slot highlighting -
+```sh +git clone https://github.com//django-components.git +``` -### Sharing components +To quickly run the tests install the local dependencies by running: -- Install and use third-party components from PyPI -- Or publish your own "component registry" -- Highly customizable - Choose how the components are called in the template (and more): +```sh +pip install -r requirements-dev.txt +``` - ```django - {% component "calendar" date="2024-11-06" %} - {% endcomponent %} +Now you can run the tests to make sure everything works as expected: - {% calendar date="2024-11-06" %} - {% endcalendar %} - ``` +```sh +pytest +``` -## Documentation +The library is also tested across many versions of Python and Django. To run tests that way: -[Read the full documentation here](https://django-components.github.io/django-components/latest/). +```sh +pyenv install 3.6.9 +pyenv install 3.7.9 +pyenv install 3.8.9 +pyenv install 3.9.4 +pyenv local 3.6.9 3.7.9 3.8.9 3.9.4 +tox -p +``` -... or jump right into the code, [check out the example project](https://github.com/django-components/django-components/tree/master/sampleproject). - -## Performance - -Our aim is to be at least as fast as Django templates. - -As of `0.130`, `django-components` is ~4x slower than Django templates. - -| | Render time| -|----------|----------------------| -| django | 68.9±0.6ms | -| django-components | 259±4ms | - -See the [full performance breakdown](https://django-components.github.io/django-components/latest/benchmarks/) for more information. - -## Release notes - -Read the [Release Notes](https://github.com/django-components/django-components/tree/master/CHANGELOG.md) -to see the latest features and fixes. - -## Community examples - -One of our goals with `django-components` is to make it easy to share components between projects. If you have a set of components that you think would be useful to others, please open a pull request to add them to the list below. - -- [django-htmx-components](https://github.com/iwanalabs/django-htmx-components): A set of components for use with [htmx](https://htmx.org/). - -- [djc-heroicons](https://pypi.org/project/djc-heroicons/): A component that renders icons from [Heroicons.com](https://heroicons.com/). - -## Contributing and development - -Get involved or sponsor this project - [See here](https://django-components.github.io/django-components/dev/overview/contributing/) - -Running django-components locally for development - [See here](https://django-components.github.io/django-components/dev/overview/development/) diff --git a/asv.conf.json b/asv.conf.json deleted file mode 100644 index 0ae16e29..00000000 --- a/asv.conf.json +++ /dev/null @@ -1,210 +0,0 @@ -{ - // The version of the config file format. Do not change, unless - // you know what you are doing - "version": 1, - - // The name of the project being benchmarked - "project": "django-components", - - // The project's homepage - // "project_url": "https://django-components.github.io/django-components/", - "project_url": "/django-components/", // Relative path, since benchmarks are nested under the docs site - - // The URL or local path of the source code repository for the - // project being benchmarked - "repo": ".", - - // The Python project's subdirectory in your repo. If missing or - // the empty string, the project is assumed to be located at the root - // of the repository. - // "repo_subdir": "", - - // Customizable commands for building the project. - // See asv.conf.json documentation. - // To build the package using pyproject.toml (PEP518), uncomment the following lines - // "build_command": [ - // "python -m pip install build", - // "python -m build", - // "python -mpip wheel -w {build_cache_dir} {build_dir}" - // ], - // To build the package using setuptools and a setup.py file, uncomment the following lines - // "build_command": [ - // "python setup.py build", - // "python -mpip wheel -w {build_cache_dir} {build_dir}" - // ], - - // Customizable commands for installing and uninstalling the project. - // See asv.conf.json documentation. - // "install_command": ["in-dir={env_dir} python -mpip install {wheel_file}"], - // "uninstall_command": ["return-code=any python -mpip uninstall -y {project}"], - "install_command": ["in-dir={env_dir} python -mpip install ./project"], - - // List of branches to benchmark. If not provided, defaults to "main" - // (for git) or "default" (for mercurial). - // "branches": ["main"], // for git - // "branches": ["default"], // for mercurial - "branches": [ - "master" - ], - - // The DVCS being used. If not set, it will be automatically - // determined from "repo" by looking at the protocol in the URL - // (if remote), or by looking for special directories, such as - // ".git" (if local). - // "dvcs": "git", - - // The tool to use to create environments. May be "conda", - // "virtualenv", "mamba" (above 3.8) - // or other value depending on the plugins in use. - // If missing or the empty string, the tool will be automatically - // determined by looking for tools on the PATH environment - // variable. - "environment_type": "virtualenv", - - // timeout in seconds for installing any dependencies in environment - // defaults to 10 min - //"install_timeout": 600, - - // the base URL to show a commit for the project. - // "show_commit_url": "http://github.com/owner/project/commit/", - - // The Pythons you'd like to test against. If not provided, defaults - // to the current version of Python used to run `asv`. - "pythons": [ - "3.13" - ], - - // The list of conda channel names to be searched for benchmark - // dependency packages in the specified order - // "conda_channels": ["conda-forge", "defaults"], - - // A conda environment file that is used for environment creation. - // "conda_environment_file": "environment.yml", - - // The matrix of dependencies to test. Each key of the "req" - // requirements dictionary is the name of a package (in PyPI) and - // the values are version numbers. An empty list or empty string - // indicates to just test against the default (latest) - // version. null indicates that the package is to not be - // installed. If the package to be tested is only available from - // PyPi, and the 'environment_type' is conda, then you can preface - // the package name by 'pip+', and the package will be installed - // via pip (with all the conda available packages installed first, - // followed by the pip installed packages). - // - // The ``@env`` and ``@env_nobuild`` keys contain the matrix of - // environment variables to pass to build and benchmark commands. - // An environment will be created for every combination of the - // cartesian product of the "@env" variables in this matrix. - // Variables in "@env_nobuild" will be passed to every environment - // during the benchmark phase, but will not trigger creation of - // new environments. A value of ``null`` means that the variable - // will not be set for the current combination. - // - // "matrix": { - // "req": { - // "numpy": ["1.6", "1.7"], - // "six": ["", null], // test with and without six installed - // "pip+emcee": [""] // emcee is only available for install with pip. - // }, - // "env": {"ENV_VAR_1": ["val1", "val2"]}, - // "env_nobuild": {"ENV_VAR_2": ["val3", null]}, - // }, - "matrix": { - "req": { - "django": [ - "5.1" - ], - "djc-core-html-parser": [""] // Empty string means the latest version - } - }, - - // Combinations of libraries/python versions can be excluded/included - // from the set to test. Each entry is a dictionary containing additional - // key-value pairs to include/exclude. - // - // An exclude entry excludes entries where all values match. The - // values are regexps that should match the whole string. - // - // An include entry adds an environment. Only the packages listed - // are installed. The 'python' key is required. The exclude rules - // do not apply to includes. - // - // In addition to package names, the following keys are available: - // - // - python - // Python version, as in the *pythons* variable above. - // - environment_type - // Environment type, as above. - // - sys_platform - // Platform, as in sys.platform. Possible values for the common - // cases: 'linux2', 'win32', 'cygwin', 'darwin'. - // - req - // Required packages - // - env - // Environment variables - // - env_nobuild - // Non-build environment variables - // - // "exclude": [ - // {"python": "3.2", "sys_platform": "win32"}, // skip py3.2 on windows - // {"environment_type": "conda", "req": {"six": null}}, // don't run without six on conda - // {"env": {"ENV_VAR_1": "val2"}}, // skip val2 for ENV_VAR_1 - // ], - // - // "include": [ - // // additional env for python3.12 - // {"python": "3.12", "req": {"numpy": "1.26"}, "env_nobuild": {"FOO": "123"}}, - // // additional env if run on windows+conda - // {"platform": "win32", "environment_type": "conda", "python": "3.12", "req": {"libpython": ""}}, - // ], - - // The directory (relative to the current directory) that benchmarks are - // stored in. If not provided, defaults to "benchmarks" - "benchmark_dir": "benchmarks", - - // The directory (relative to the current directory) to cache the Python - // environments in. If not provided, defaults to "env" - "env_dir": ".asv/env", - - // The directory (relative to the current directory) that raw benchmark - // results are stored in. If not provided, defaults to "results". - "results_dir": ".asv/results", - - // The directory (relative to the current directory) that the html tree - // should be written to. If not provided, defaults to "html". - // "html_dir": ".asv/html", - "html_dir": "docs/benchmarks", // # TODO - - // The number of characters to retain in the commit hashes. - // "hash_length": 8, - - // `asv` will cache results of the recent builds in each - // environment, making them faster to install next time. This is - // the number of builds to keep, per environment. - // "build_cache_size": 2, - - // The commits after which the regression search in `asv publish` - // should start looking for regressions. Dictionary whose keys are - // regexps matching to benchmark names, and values corresponding to - // the commit (exclusive) after which to start looking for - // regressions. The default is to start from the first commit - // with results. If the commit is `null`, regression detection is - // skipped for the matching benchmark. - // - // "regressions_first_commits": { - // "some_benchmark": "352cdf", // Consider regressions only after this commit - // "another_benchmark": null, // Skip regression detection altogether - // }, - - // The thresholds for relative change in results, after which `asv - // publish` starts reporting regressions. Dictionary of the same - // form as in ``regressions_first_commits``, with values - // indicating the thresholds. If multiple entries match, the - // maximum is taken. If no entry matches, the default is 5%. - // - // "regressions_thresholds": { - // "some_benchmark": 0.01, // Threshold of 1% - // "another_benchmark": 0.5, // Threshold of 50% - // }, -} diff --git a/benchmarks/README.md b/benchmarks/README.md deleted file mode 100644 index f5f5d524..00000000 --- a/benchmarks/README.md +++ /dev/null @@ -1,195 +0,0 @@ -# Benchmarks - -## Overview - -[`asv`](https://github.com/airspeed-velocity/) (Airspeed Velocity) is used for benchmarking performance. - -`asv` covers the entire benchmarking workflow. We can: - -1. Define benchmark tests similarly to writing pytest tests (supports both timing and memory benchmarks) -2. Run the benchmarks and generate results for individual git commits, tags, or entire branches -3. View results as an HTML report (dashboard with charts) -4. Compare performance between two commits / tags / branches for CI integration - -![asv dashboard](./assets/asv_dashboard.png) - -django-components uses `asv` for these use cases: - -- Benchmarking across releases: - - 1. When a git tag is created and pushed, this triggers a Github Action workflow (see `docs.yml`). - 2. The workflow runs the benchmarks with the latest release, and commits the results to the repository. - Thus, we can see how performance changes across releases. - -- Displaying performance results on the website: - - 1. When a git tag is created and pushed, we also update the documentation website (see `docs.yml`). - 2. Before we publish the docs website, we generate the HTML report for the benchmark results. - 3. The generated report is placed in the `docs/benchmarks/` directory, and is thus - published with the rest of the docs website and available under [`/benchmarks/`](https://django-components.github.io/django-components/latest/benchmarks). - - NOTE: The location where the report is placed is defined in `asv.conf.json`. - -- Compare performance between commits on pull requests: - 1. When a pull request is made, this triggers a Github Action workflow (see `benchmark.yml`). - 2. The workflow compares performance between commits. - 3. The report is added to the PR as a comment made by a bot. - -## Interpreting benchmarks - -The results CANNOT be taken as ABSOLUTE values e.g.: - -"This example took 200ms to render, so my page will also take 200ms to render." - -Each UI may consist of different number of Django templates, template tags, and components, and all these may influence the rendering time differently. - -Instead, the results MUST be understood as RELATIVE values. - -- If a commit is 10% slower than the master branch, that's valid. -- If Django components are 10% slower than vanilla Django templates, that's valid. -- If "isolated" mode is 10% slower than "django" mode, that's valid. - -## Development - -Let's say we want to generate results for the last 5 commits. - -1. Install `asv` - - ```bash - pip install asv - ``` - -2. Run benchmarks and generate results - - ```bash - asv run HEAD --steps 5 -e - ``` - - - `HEAD` means that we want to run benchmarks against the [current branch](https://stackoverflow.com/a/2304106/9788634). - - `--steps 5` means that we want to run benchmarks for the last 5 commits. - - `-e` to print out any errors. - - The results will be stored in `.asv/results/`, as configured in `asv.conf.json`. - -3. Generate HTML report - - ```bash - asv publish - asv preview - ``` - - - `publish` generates the HTML report and stores it in `docs/benchmarks/`, as configured in `asv.conf.json`. - - `preview` starts a local server and opens the report in the browser. - - NOTE: Since the results are stored in `docs/benchmarks/`, you can also view the results - with `mkdocs serve` and navigating to `http://localhost:9000/django-components/benchmarks/`. - - NOTE 2: Running `publish` will overwrite the existing contents of `docs/benchmarks/`. - -## Writing benchmarks - -`asv` supports writing different [types of benchmarks](https://asv.readthedocs.io/en/latest/writing_benchmarks.html#benchmark-types). What's relevant for us is: - -- [Raw timing benchmarks](https://asv.readthedocs.io/en/latest/writing_benchmarks.html#raw-timing-benchmarks) -- [Peak memory benchmarks](https://asv.readthedocs.io/en/latest/writing_benchmarks.html#peak-memory) - -Notes: - -- The difference between "raw timing" and "timing" tests is that "raw timing" is ran in a separate process. - And instead of running the logic within the test function itself, we return a script (string) - that will be executed in the separate process. - -- The difference between "peak memory" and "memory" tests is that "memory" calculates the memory - of the object returned from the test function. On the other hand, "peak memory" detects the - peak memory usage during the execution of the test function (including the setup function). - -You can write the test file anywhere in the `benchmarks/` directory, `asv` will automatically find it. - -Inside the file, write a test function. Depending on the type of the benchmark, -prefix the test function name with `timeraw_` or `peakmem_`. See [`benchmarks/benchmark_templating.py`](benchmark_templating.py) for examples. - -### Ensuring that the benchmarked logic is correct - -The approach I (Juro) took with benchmarking the overall template rendering is that -I've defined the actual logic in `tests/test_benchmark_*.py` files. So those files -are part of the normal pytest testing, and even contain a section with pytest tests. - -This ensures that the benchmarked logic remains functional and error-free. - -However, there's some caveats: - -1. I wasn't able to import files from `tests/`. -2. When running benchmarks, we don't want to run the pytest tests. - -To work around that, the approach I used for loading the files from the `tests/` directory is to: - -1. Get the file's source code as a string. -2. Cut out unwanted sections (like the pytest tests). -3. Append the benchmark-specific code to the file (e.g. to actually render the templates). -4. In case of "timeraw" benchmarks, we can simply return the remaining code as a string - to be run in a separate process. -5. In case of "peakmem" benchmarks, we need to access this modified source code as Python objects. - So the code is made available as a "virtual" module, which makes it possible to import Python objects like so: - ```py - from my_virtual_module import run_my_benchmark - ``` - -## Using `asv` - -### Compare latest commit against master - -Note: Before comparing, you must run the benchmarks first to generate the results. The `continuous` command does not generate the results by itself. - -```bash -asv continuous master^! HEAD^! --factor 1.1 -``` - -- Factor of `1.1` means that the new commit is allowed to be 10% slower/faster than the master commit. - -- `^` means that we mean the COMMIT of the branch, not the BRANCH itself. - - Without it, we would run benchmarks for the whole branch history. - - With it, we run benchmarks FROM the latest commit (incl) TO ... - -- `!` means that we want to select range spanning a single commit. - - Without it, we would run benchmarks for all commits FROM the latest commit - TO the start of the branch history. - - With it, we run benchmarks ONLY FOR the latest commit. - -### More Examples - -Notes: - -- Use `~1` to select the second-latest commit, `~2` for the third-latest, etc.. - -Generate benchmarks for the latest commit in `master` branch. - -```bash -asv run master^! -``` - -Generate benchmarks for second-latest commit in `master` branch. - -```bash -asv run master~1^! -``` - -Generate benchmarks for all commits in `master` branch. - -```bash -asv run master -``` - -Generate benchmarks for all commits in `master` branch, but exclude the latest commit. - -```bash -asv run master~1 -``` - -Generate benchmarks for the LAST 5 commits in `master` branch, but exclude the latest commit. - -```bash -asv run master~1 --steps 5 -``` diff --git a/benchmarks/assets/asv_dashboard.png b/benchmarks/assets/asv_dashboard.png deleted file mode 100644 index 524990b8..00000000 Binary files a/benchmarks/assets/asv_dashboard.png and /dev/null differ diff --git a/benchmarks/benchmark_templating.py b/benchmarks/benchmark_templating.py deleted file mode 100644 index 779c70aa..00000000 --- a/benchmarks/benchmark_templating.py +++ /dev/null @@ -1,446 +0,0 @@ -# Write the benchmarking functions here -# See "Writing benchmarks" in the asv docs for more information. - -import re -from pathlib import Path -from types import ModuleType -from typing import Literal - -# Fix for for https://github.com/airspeed-velocity/asv_runner/pull/44 -import benchmarks.monkeypatch_asv # noqa: F401 - -from benchmarks.utils import benchmark, create_virtual_module - - -DJC_VS_DJ_GROUP = "Components vs Django" -DJC_ISOLATED_VS_NON_GROUP = "isolated vs django modes" -OTHER_GROUP = "Other" - - -DjcContextMode = Literal["isolated", "django"] -TemplatingRenderer = Literal["django", "django-components", "none"] -TemplatingTestSize = Literal["lg", "sm"] -TemplatingTestType = Literal[ - "first", # Testing performance of the first time the template is rendered - "subsequent", # Testing performance of the subsequent times the template is rendered - "startup", # Testing performance of the startup time (e.g. defining classes and templates) -] - - -def _get_templating_filepath(renderer: TemplatingRenderer, size: TemplatingTestSize) -> Path: - if renderer == "none": - raise ValueError("Cannot get filepath for renderer 'none'") - elif renderer not in ["django", "django-components"]: - raise ValueError(f"Invalid renderer: {renderer}") - - if size not in ("lg", "sm"): - raise ValueError(f"Invalid size: {size}, must be one of ('lg', 'sm')") - - # At this point, we know the renderer is either "django" or "django-components" - root = file_path = Path(__file__).parent.parent - if renderer == "django": - if size == "lg": - file_path = root / "tests" / "test_benchmark_django.py" - else: - file_path = root / "tests" / "test_benchmark_django_small.py" - else: - if size == "lg": - file_path = root / "tests" / "test_benchmark_djc.py" - else: - file_path = root / "tests" / "test_benchmark_djc_small.py" - - return file_path - - -def _get_templating_script( - renderer: TemplatingRenderer, - size: TemplatingTestSize, - context_mode: DjcContextMode, - imports_only: bool, -) -> str: - if renderer == "none": - return "" - elif renderer not in ["django", "django-components"]: - raise ValueError(f"Invalid renderer: {renderer}") - - # At this point, we know the renderer is either "django" or "django-components" - file_path = _get_templating_filepath(renderer, size) - contents = file_path.read_text() - - # The files with benchmarked code also have a section for testing them with pytest. - # We remove that pytest section, so the script is only the benchmark code. - contents = contents.split("# ----------- TESTS START ------------ #")[0] - - if imports_only: - # There is a benchmark test for measuring the time it takes to import the module. - # For that, we exclude from the code everything AFTER this line - contents = contents.split("# ----------- IMPORTS END ------------ #")[0] - else: - # Set the context mode by replacing variable in the script - contents = re.sub(r"CONTEXT_MODE.*?\n", f"CONTEXT_MODE = '{context_mode}'\n", contents, count=1) - - return contents - - -def _get_templating_module( - renderer: TemplatingRenderer, - size: TemplatingTestSize, - context_mode: DjcContextMode, - imports_only: bool, -) -> ModuleType: - if renderer not in ("django", "django-components"): - raise ValueError(f"Invalid renderer: {renderer}") - - file_path = _get_templating_filepath(renderer, size) - script = _get_templating_script(renderer, size, context_mode, imports_only) - - # This makes it possible to import the module in the benchmark function - # as `import test_templating` - module = create_virtual_module("test_templating", script, str(file_path)) - return module - - -# The `timeraw_` tests run in separate processes. But when running memory benchmarks, -# the tested logic runs in the same process as the where we run the benchmark functions -# (e.g. `peakmem_render_lg_first()`). Thus, the `peakmem_` functions have access to this file -# when the tested logic runs. -# -# Secondly, `asv` doesn't offer any way to pass data from `setup` to actual test. -# -# And so we define this global, which, when running memory benchmarks, the `setup` function -# populates. And then we trigger the actual render from within the test body. -do_render = lambda: None # noqa: E731 - - -def setup_templating_memory_benchmark( - renderer: TemplatingRenderer, - size: TemplatingTestSize, - test_type: TemplatingTestType, - context_mode: DjcContextMode, - imports_only: bool = False, -): - global do_render - module = _get_templating_module(renderer, size, context_mode, imports_only) - data = module.gen_render_data() - render = module.render - do_render = lambda: render(data) # noqa: E731 - - # Do the first render as part of setup if we're testing the subsequent renders - if test_type == "subsequent": - do_render() - - -# The timing benchmarks run the actual code in a separate process, by using the `timeraw_` prefix. -# As such, we don't actually load the code in this file. Instead, we only prepare a script (raw string) -# that will be run in the new process. -def prepare_templating_benchmark( - renderer: TemplatingRenderer, - size: TemplatingTestSize, - test_type: TemplatingTestType, - context_mode: DjcContextMode, - imports_only: bool = False, -): - setup_script = _get_templating_script(renderer, size, context_mode, imports_only) - - # If we're testing the startup time, then the setup is actually the tested code - if test_type == "startup": - return setup_script - else: - # Otherwise include also data generation as part of setup - setup_script += "\n\n" "render_data = gen_render_data()\n" - - # Do the first render as part of setup if we're testing the subsequent renders - if test_type == "subsequent": - setup_script += "render(render_data)\n" - - benchmark_script = "render(render_data)\n" - return benchmark_script, setup_script - - -# - Group: django-components vs django -# - time: djc vs django (startup lg) -# - time: djc vs django (lg - FIRST) -# - time: djc vs django (sm - FIRST) -# - time: djc vs django (lg - SUBSEQUENT) -# - time: djc vs django (sm - SUBSEQUENT) -# - mem: djc vs django (lg - FIRST) -# - mem: djc vs django (sm - FIRST) -# - mem: djc vs django (lg - SUBSEQUENT) -# - mem: djc vs django (sm - SUBSEQUENT) -# -# NOTE: While the name suggests we're comparing Django and Django-components, be aware that -# in our "Django" tests, we still install and import django-components. We also use -# django-components's `{% html_attrs %}` tag in the Django scenario. `{% html_attrs %}` -# was used because the original sample code was from django-components. -# -# As such, these tests should seen not as "Using Django vs Using Components". But instead, -# it should be "What is the relative cost of using Components?". -# -# As an example, the benchmarking for the startup time and memory usage is not comparing -# two independent approaches. Rather, the test is checking if defining Components classes -# is more expensive than vanilla Django templates. -class DjangoComponentsVsDjangoTests: - # Testing startup time (e.g. defining classes and templates) - @benchmark( - pretty_name="startup - large", - group_name=DJC_VS_DJ_GROUP, - number=1, - rounds=5, - params={ - "renderer": ["django", "django-components"], - }, - ) - def timeraw_startup_lg(self, renderer: TemplatingRenderer): - return prepare_templating_benchmark(renderer, "lg", "startup", "isolated") - - @benchmark( - pretty_name="render - small - first render", - group_name=DJC_VS_DJ_GROUP, - number=1, - rounds=5, - params={ - "renderer": ["django", "django-components"], - }, - ) - def timeraw_render_sm_first(self, renderer: TemplatingRenderer): - return prepare_templating_benchmark(renderer, "sm", "first", "isolated") - - @benchmark( - pretty_name="render - small - second render", - group_name=DJC_VS_DJ_GROUP, - number=1, - rounds=5, - params={ - "renderer": ["django", "django-components"], - }, - ) - def timeraw_render_sm_subsequent(self, renderer: TemplatingRenderer): - return prepare_templating_benchmark(renderer, "sm", "subsequent", "isolated") - - @benchmark( - pretty_name="render - large - first render", - group_name=DJC_VS_DJ_GROUP, - number=1, - rounds=5, - params={ - "renderer": ["django", "django-components"], - }, - include_in_quick_benchmark=True, - ) - def timeraw_render_lg_first(self, renderer: TemplatingRenderer): - return prepare_templating_benchmark(renderer, "lg", "first", "isolated") - - @benchmark( - pretty_name="render - large - second render", - group_name=DJC_VS_DJ_GROUP, - number=1, - rounds=5, - params={ - "renderer": ["django", "django-components"], - }, - ) - def timeraw_render_lg_subsequent(self, renderer: TemplatingRenderer): - return prepare_templating_benchmark(renderer, "lg", "subsequent", "isolated") - - @benchmark( - pretty_name="render - small - first render (mem)", - group_name=DJC_VS_DJ_GROUP, - number=1, - rounds=5, - params={ - "renderer": ["django", "django-components"], - }, - setup=lambda renderer: setup_templating_memory_benchmark(renderer, "sm", "first", "isolated"), - ) - def peakmem_render_sm_first(self, renderer: TemplatingRenderer): - do_render() - - @benchmark( - pretty_name="render - small - second render (mem)", - group_name=DJC_VS_DJ_GROUP, - number=1, - rounds=5, - params={ - "renderer": ["django", "django-components"], - }, - setup=lambda renderer: setup_templating_memory_benchmark(renderer, "sm", "subsequent", "isolated"), - ) - def peakmem_render_sm_subsequent(self, renderer: TemplatingRenderer): - do_render() - - @benchmark( - pretty_name="render - large - first render (mem)", - group_name=DJC_VS_DJ_GROUP, - number=1, - rounds=5, - params={ - "renderer": ["django", "django-components"], - }, - setup=lambda renderer: setup_templating_memory_benchmark(renderer, "lg", "first", "isolated"), - ) - def peakmem_render_lg_first(self, renderer: TemplatingRenderer): - do_render() - - @benchmark( - pretty_name="render - large - second render (mem)", - group_name=DJC_VS_DJ_GROUP, - number=1, - rounds=5, - params={ - "renderer": ["django", "django-components"], - }, - setup=lambda renderer: setup_templating_memory_benchmark(renderer, "lg", "subsequent", "isolated"), - ) - def peakmem_render_lg_subsequent(self, renderer: TemplatingRenderer): - do_render() - - -# - Group: Django-components "isolated" vs "django" modes -# - time: Isolated vs django djc (startup lg) -# - time: Isolated vs django djc (lg - FIRST) -# - time: Isolated vs django djc (sm - FIRST) -# - time: Isolated vs django djc (lg - SUBSEQUENT) -# - time: Isolated vs django djc (sm - SUBSEQUENT) -# - mem: Isolated vs django djc (lg - FIRST) -# - mem: Isolated vs django djc (sm - FIRST) -# - mem: Isolated vs django djc (lg - SUBSEQUENT) -# - mem: Isolated vs django djc (sm - SUBSEQUENT) -class IsolatedVsDjangoContextModesTests: - # Testing startup time (e.g. defining classes and templates) - @benchmark( - pretty_name="startup - large", - group_name=DJC_ISOLATED_VS_NON_GROUP, - number=1, - rounds=5, - params={ - "context_mode": ["isolated", "django"], - }, - ) - def timeraw_startup_lg(self, context_mode: DjcContextMode): - return prepare_templating_benchmark("django-components", "lg", "startup", context_mode) - - @benchmark( - pretty_name="render - small - first render", - group_name=DJC_ISOLATED_VS_NON_GROUP, - number=1, - rounds=5, - params={ - "context_mode": ["isolated", "django"], - }, - ) - def timeraw_render_sm_first(self, context_mode: DjcContextMode): - return prepare_templating_benchmark("django-components", "sm", "first", context_mode) - - @benchmark( - pretty_name="render - small - second render", - group_name=DJC_ISOLATED_VS_NON_GROUP, - number=1, - rounds=5, - params={ - "context_mode": ["isolated", "django"], - }, - ) - def timeraw_render_sm_subsequent(self, context_mode: DjcContextMode): - return prepare_templating_benchmark("django-components", "sm", "subsequent", context_mode) - - @benchmark( - pretty_name="render - large - first render", - group_name=DJC_ISOLATED_VS_NON_GROUP, - number=1, - rounds=5, - params={ - "context_mode": ["isolated", "django"], - }, - ) - def timeraw_render_lg_first(self, context_mode: DjcContextMode): - return prepare_templating_benchmark("django-components", "lg", "first", context_mode) - - @benchmark( - pretty_name="render - large - second render", - group_name=DJC_ISOLATED_VS_NON_GROUP, - number=1, - rounds=5, - params={ - "context_mode": ["isolated", "django"], - }, - ) - def timeraw_render_lg_subsequent(self, context_mode: DjcContextMode): - return prepare_templating_benchmark("django-components", "lg", "subsequent", context_mode) - - @benchmark( - pretty_name="render - small - first render (mem)", - group_name=DJC_ISOLATED_VS_NON_GROUP, - number=1, - rounds=5, - params={ - "context_mode": ["isolated", "django"], - }, - setup=lambda context_mode: setup_templating_memory_benchmark("django-components", "sm", "first", context_mode), - ) - def peakmem_render_sm_first(self, context_mode: DjcContextMode): - do_render() - - @benchmark( - pretty_name="render - small - second render (mem)", - group_name=DJC_ISOLATED_VS_NON_GROUP, - number=1, - rounds=5, - params={ - "context_mode": ["isolated", "django"], - }, - setup=lambda context_mode: setup_templating_memory_benchmark( - "django-components", - "sm", - "subsequent", - context_mode, - ), - ) - def peakmem_render_sm_subsequent(self, context_mode: DjcContextMode): - do_render() - - @benchmark( - pretty_name="render - large - first render (mem)", - group_name=DJC_ISOLATED_VS_NON_GROUP, - number=1, - rounds=5, - params={ - "context_mode": ["isolated", "django"], - }, - setup=lambda context_mode: setup_templating_memory_benchmark( - "django-components", - "lg", - "first", - context_mode, - ), - ) - def peakmem_render_lg_first(self, context_mode: DjcContextMode): - do_render() - - @benchmark( - pretty_name="render - large - second render (mem)", - group_name=DJC_ISOLATED_VS_NON_GROUP, - number=1, - rounds=5, - params={ - "context_mode": ["isolated", "django"], - }, - setup=lambda context_mode: setup_templating_memory_benchmark( - "django-components", - "lg", - "subsequent", - context_mode, - ), - ) - def peakmem_render_lg_subsequent(self, context_mode: DjcContextMode): - do_render() - - -class OtherTests: - @benchmark( - pretty_name="import time", - group_name=OTHER_GROUP, - number=1, - rounds=5, - ) - def timeraw_import_time(self): - return prepare_templating_benchmark("django-components", "lg", "startup", "isolated", imports_only=True) diff --git a/benchmarks/component_rendering.py b/benchmarks/component_rendering.py new file mode 100644 index 00000000..2807e562 --- /dev/null +++ b/benchmarks/component_rendering.py @@ -0,0 +1,170 @@ +from time import perf_counter + +from django.template import Context, Template +from django.test import override_settings + +from django_components import component +from django_components.middleware import ( + CSS_DEPENDENCY_PLACEHOLDER, + JS_DEPENDENCY_PLACEHOLDER, +) +from tests.django_test_setup import * # NOQA +from tests.testutils import Django30CompatibleSimpleTestCase as SimpleTestCase +from tests.testutils import create_and_process_template_response + + +class SlottedComponent(component.Component): + template_name = "slotted_template.html" + + +class SimpleComponent(component.Component): + template_name = "simple_template.html" + + def get_context_data(self, variable, variable2="default"): + return { + "variable": variable, + "variable2": variable2, + } + + class Media: + css = {"all": ["style.css"]} + js = ["script.js"] + + +class BreadcrumbComponent(component.Component): + template_name = "mdn_component_template.html" + + LINKS = [ + ( + "https://developer.mozilla.org/en-US/docs/Learn", + "Learn web development", + ), + ( + "https://developer.mozilla.org/en-US/docs/Learn/HTML", + "Structuring the web with HTML", + ), + ( + "https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML", + "Introduction to HTML", + ), + ( + "https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML/Document_and_website_structure", + "Document and website structure", + ), + ] + + def get_context_data(self, items): + if items > 4: + items = 4 + elif items < 0: + items = 0 + return {"links": self.LINKS[: items - 1]} + + class Media: + css = {"all": ["test.css"]} + js = ["test.js"] + + +EXPECTED_CSS = """""" +EXPECTED_JS = """""" + + +@override_settings(COMPONENTS={"RENDER_DEPENDENCIES": True}) +class RenderBenchmarks(SimpleTestCase): + def setUp(self): + component.registry.clear() + component.registry.register("test_component", SlottedComponent) + component.registry.register("inner_component", SimpleComponent) + component.registry.register( + "breadcrumb_component", BreadcrumbComponent + ) + + @staticmethod + def timed_loop(func, iterations=1000): + """Run func iterations times, and return the time in ms per iteration.""" + start_time = perf_counter() + for _ in range(iterations): + func() + end_time = perf_counter() + total_elapsed = end_time - start_time # NOQA + return total_elapsed * 1000 / iterations + + def test_render_time_for_small_component(self): + template = Template( + "{% load component_tags %}{% component_block 'test_component' %}" + "{% slot \"header\" %}{% component 'inner_component' variable='foo' %}{% endslot %}" + "{% endcomponent_block %}", + name="root", + ) + + print( + f"{self.timed_loop(lambda: template.render(Context({})))} ms per iteration" + ) + + def test_middleware_time_with_dependency_for_small_page(self): + template = Template( + "{% load component_tags %}{% component_dependencies %}" + "{% component_block 'test_component' %}{% slot \"header\" %}" + "{% component 'inner_component' variable='foo' %}{% endslot %}{% endcomponent_block %}", + name="root", + ) + # Sanity tests + response_content = create_and_process_template_response(template) + self.assertNotIn(CSS_DEPENDENCY_PLACEHOLDER, response_content) + self.assertNotIn(JS_DEPENDENCY_PLACEHOLDER, response_content) + self.assertIn("style.css", response_content) + self.assertIn("script.js", response_content) + + without_middleware = self.timed_loop( + lambda: create_and_process_template_response( + template, use_middleware=False + ) + ) + with_middleware = self.timed_loop( + lambda: create_and_process_template_response( + template, use_middleware=True + ) + ) + + print("Small page middleware test") + self.report_results(with_middleware, without_middleware) + + def test_render_time_with_dependency_for_large_page(self): + from django.template.loader import get_template + + template = get_template("mdn_complete_page.html") + response_content = create_and_process_template_response(template, {}) + self.assertNotIn(CSS_DEPENDENCY_PLACEHOLDER, response_content) + self.assertNotIn(JS_DEPENDENCY_PLACEHOLDER, response_content) + self.assertIn("test.css", response_content) + self.assertIn("test.js", response_content) + + without_middleware = self.timed_loop( + lambda: create_and_process_template_response( + template, {}, use_middleware=False + ) + ) + with_middleware = self.timed_loop( + lambda: create_and_process_template_response( + template, {}, use_middleware=True + ) + ) + + print("Large page middleware test") + self.report_results(with_middleware, without_middleware) + + @staticmethod + def report_results(with_middleware, without_middleware): + print(f"Middleware active\t\t{with_middleware:.3f} ms per iteration") + print( + f"Middleware inactive\t{without_middleware:.3f} ms per iteration" + ) + time_difference = with_middleware - without_middleware + if without_middleware > with_middleware: + print( + f"Decrease of {-100 * time_difference / with_middleware:.2f}%" + ) + else: + print( + f"Increase of {100 * time_difference / without_middleware:.2f}%" + ) diff --git a/benchmarks/monkeypatch_asv.py b/benchmarks/monkeypatch_asv.py deleted file mode 100644 index 23003311..00000000 --- a/benchmarks/monkeypatch_asv.py +++ /dev/null @@ -1,29 +0,0 @@ -from asv_runner.benchmarks.timeraw import TimerawBenchmark, _SeparateProcessTimer - - -# Fix for https://github.com/airspeed-velocity/asv_runner/pull/44 -def _get_timer(self, *param): - """ - Returns a timer that runs the benchmark function in a separate process. - - #### Parameters - **param** (`tuple`) - : The parameters to pass to the benchmark function. - - #### Returns - **timer** (`_SeparateProcessTimer`) - : A timer that runs the function in a separate process. - """ - if param: - - def func(): - # ---------- OUR CHANGES: ADDED RETURN STATEMENT ---------- - return self.func(*param) - # ---------- OUR CHANGES END ---------- - - else: - func = self.func - return _SeparateProcessTimer(func) - - -TimerawBenchmark._get_timer = _get_timer diff --git a/benchmarks/monkeypatch_asv_ci.txt b/benchmarks/monkeypatch_asv_ci.txt deleted file mode 100644 index 30158b6d..00000000 --- a/benchmarks/monkeypatch_asv_ci.txt +++ /dev/null @@ -1,66 +0,0 @@ -# ------------ FIX FOR #45 ------------ -# See https://github.com/airspeed-velocity/asv_runner/issues/45 -# This fix is applied in CI in the `benchmark.yml` file. -# This file is intentionally named `monkeypatch_asv_ci.txt` to avoid being -# loaded as a python file by `asv`. -# ------------------------------------- - -def timeit(self, number): - """ - Run the function's code `number` times in a separate Python process, and - return the execution time. - - #### Parameters - **number** (`int`) - : The number of times to execute the function's code. - - #### Returns - **time** (`float`) - : The time it took to execute the function's code `number` times. - - #### Notes - The function's code is executed in a separate Python process to avoid - interference from the parent process. The function can return either a - single string of code to be executed, or a tuple of two strings: the - code to be executed and the setup code to be run before timing. - """ - stmt = self.func() - if isinstance(stmt, tuple): - stmt, setup = stmt - else: - setup = "" - stmt = textwrap.dedent(stmt) - setup = textwrap.dedent(setup) - stmt = stmt.replace(r'"""', r"\"\"\"") - setup = setup.replace(r'"""', r"\"\"\"") - - # TODO - # -----------ORIGINAL CODE----------- - # code = self.subprocess_tmpl.format(stmt=stmt, setup=setup, number=number) - - # res = subprocess.check_output([sys.executable, "-c", code]) - # return float(res.strip()) - - # -----------NEW CODE----------- - code = self.subprocess_tmpl.format(stmt=stmt, setup=setup, number=number) - - evaler = textwrap.dedent( - """ - import sys - code = sys.stdin.read() - exec(code) - """ - ) - - proc = subprocess.Popen([sys.executable, "-c", evaler], - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - stdout, stderr = proc.communicate(input=code.encode("utf-8")) - if proc.returncode != 0: - raise RuntimeError(f"Subprocess failed: {stderr.decode()}") - return float(stdout.decode("utf-8").strip()) - -_SeparateProcessTimer.timeit = timeit - -# ------------ END FIX #45 ------------ diff --git a/benchmarks/utils.py b/benchmarks/utils.py deleted file mode 100644 index eb160cb0..00000000 --- a/benchmarks/utils.py +++ /dev/null @@ -1,99 +0,0 @@ -import os -import sys -from importlib.abc import Loader -from importlib.util import spec_from_loader, module_from_spec -from types import ModuleType -from typing import Any, Dict, List, Optional - - -# NOTE: benchmark_name constraints: -# - MUST BE UNIQUE -# - MUST NOT CONTAIN `-` -# - MUST START WITH `time_`, `mem_`, `peakmem_` -# See https://github.com/airspeed-velocity/asv/pull/1470 -def benchmark( - *, - pretty_name: Optional[str] = None, - timeout: Optional[int] = None, - group_name: Optional[str] = None, - params: Optional[Dict[str, List[Any]]] = None, - number: Optional[int] = None, - min_run_count: Optional[int] = None, - include_in_quick_benchmark: bool = False, - **kwargs, -): - def decorator(func): - # For pull requests, we want to run benchmarks only for a subset of tests, - # because the full set of tests takes about 10 minutes to run (5 min per commit). - # This is done by setting DJC_BENCHMARK_QUICK=1 in the environment. - if os.getenv("DJC_BENCHMARK_QUICK") and not include_in_quick_benchmark: - # By setting the benchmark name to something that does NOT start with - # valid prefixes like `time_`, `mem_`, or `peakmem_`, this function will be ignored by asv. - func.benchmark_name = "noop" - return func - - # "group_name" is our custom field, which we actually convert to asv's "benchmark_name" - if group_name is not None: - benchmark_name = f"{group_name}.{func.__name__}" - func.benchmark_name = benchmark_name - - # Also "params" is custom, so we normalize it to "params" and "param_names" - if params is not None: - func.params, func.param_names = list(params.values()), list(params.keys()) - - if pretty_name is not None: - func.pretty_name = pretty_name - if timeout is not None: - func.timeout = timeout - if number is not None: - func.number = number - if min_run_count is not None: - func.min_run_count = min_run_count - - # Additional, untyped kwargs - for k, v in kwargs.items(): - setattr(func, k, v) - - return func - - return decorator - - -class VirtualModuleLoader(Loader): - def __init__(self, code_string): - self.code_string = code_string - - def exec_module(self, module): - exec(self.code_string, module.__dict__) - - -def create_virtual_module(name: str, code_string: str, file_path: str) -> ModuleType: - """ - To avoid the headaches of importing the tested code from another diretory, - we create a "virtual" module that we can import from anywhere. - - E.g. - ```py - from benchmarks.utils import create_virtual_module - - create_virtual_module("my_module", "print('Hello, world!')", __file__) - - # Now you can import my_module from anywhere - import my_module - ``` - """ - # Create the module specification - spec = spec_from_loader(name, VirtualModuleLoader(code_string)) - - # Create the module - module = module_from_spec(spec) # type: ignore[arg-type] - module.__file__ = file_path - module.__name__ = name - - # Add it to sys.modules - sys.modules[name] = module - - # Execute the module - spec.loader.exec_module(module) # type: ignore[union-attr] - - return module diff --git a/django_components/__init__.py b/django_components/__init__.py new file mode 100644 index 00000000..d87f1ffe --- /dev/null +++ b/django_components/__init__.py @@ -0,0 +1,17 @@ +from importlib import import_module + +import django +from django.utils.module_loading import autodiscover_modules + +if django.VERSION < (3, 2): + default_app_config = "django_components.apps.ComponentsConfig" + + +def autodiscover(): + # look for "components" module/pkg in each app + from . import app_settings + + if app_settings.AUTODISCOVER: + autodiscover_modules("components") + for path in app_settings.LIBRARIES: + import_module(path) diff --git a/django_components/app_settings.py b/django_components/app_settings.py new file mode 100644 index 00000000..5e2d8ee3 --- /dev/null +++ b/django_components/app_settings.py @@ -0,0 +1,25 @@ +import sys + +from django.conf import settings + + +class AppSettings: + def __init__(self): + self.settings = getattr(settings, "COMPONENTS", {}) + + @property + def AUTODISCOVER(self): + return self.settings.setdefault("autodiscover", True) + + @property + def LIBRARIES(self): + return self.settings.setdefault("libraries", []) + + @property + def TEMPLATE_CACHE_SIZE(self): + return self.settings.setdefault("template_cache_size", 128) + + +app_settings = AppSettings() +app_settings.__name__ = __name__ +sys.modules[__name__] = app_settings diff --git a/django_components/apps.py b/django_components/apps.py new file mode 100644 index 00000000..6cf3cb52 --- /dev/null +++ b/django_components/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig + + +class ComponentsConfig(AppConfig): + name = "django_components" + + def ready(self): + self.module.autodiscover() diff --git a/django_components/component.py b/django_components/component.py new file mode 100644 index 00000000..ed67bc6f --- /dev/null +++ b/django_components/component.py @@ -0,0 +1,179 @@ +import warnings +from functools import lru_cache + +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.forms.widgets import MediaDefiningClass +from django.template.base import Node, TokenType +from django.template.loader import get_template +from django.utils.safestring import mark_safe + +# Allow "component.AlreadyRegistered" instead of having to import these everywhere +from django_components.component_registry import ( # noqa + AlreadyRegistered, + ComponentRegistry, + NotRegistered, +) + +TEMPLATE_CACHE_SIZE = getattr(settings, "COMPONENTS", {}).get( + "TEMPLATE_CACHE_SIZE", 128 +) +ACTIVE_SLOT_CONTEXT_KEY = "_DJANGO_COMPONENTS_ACTIVE_SLOTS" + + +class SimplifiedInterfaceMediaDefiningClass(MediaDefiningClass): + def __new__(mcs, name, bases, attrs): + if "Media" in attrs: + media = attrs["Media"] + + # Allow: class Media: css = "style.css" + if isinstance(media.css, str): + media.css = [media.css] + + # Allow: class Media: css = ["style.css"] + if isinstance(media.css, list): + media.css = {"all": media.css} + + # Allow: class Media: css = {"all": "style.css"} + if isinstance(media.css, dict): + for media_type, path_list in media.css.items(): + if isinstance(path_list, str): + media.css[media_type] = [path_list] + + # Allow: class Media: js = "script.js" + if isinstance(media.js, str): + media.js = [media.js] + + return super().__new__(mcs, name, bases, attrs) + + +class Component(metaclass=SimplifiedInterfaceMediaDefiningClass): + template_name = None + + def __init__(self, component_name): + self._component_name = component_name + self.instance_template = None + self.slots = {} + + def get_context_data(self): + return {} + + def get_template_name(self, context=None): + if not self.template_name: + raise ImproperlyConfigured( + f"Template name is not set for Component {self.__class__.__name__}" + ) + + return self.template_name + + def render_dependencies(self): + """Helper function to access media.render()""" + + return self.media.render() + + def render_css_dependencies(self): + """Render only CSS dependencies available in the media class.""" + + return mark_safe("\n".join(self.media.render_css())) + + def render_js_dependencies(self): + """Render only JS dependencies available in the media class.""" + + return mark_safe("\n".join(self.media.render_js())) + + @staticmethod + def slots_in_template(template): + return { + node.name: node.nodelist + for node in template.template.nodelist + if Component.is_slot_node(node) + } + + @staticmethod + def is_slot_node(node): + return ( + isinstance(node, Node) + and node.token.token_type == TokenType.BLOCK + and node.token.split_contents()[0] == "slot" + ) + + @lru_cache(maxsize=TEMPLATE_CACHE_SIZE) + def get_processed_template(self, template_name): + """Retrieve the requested template and check for unused slots.""" + + component_template = get_template(template_name).template + + # Traverse template nodes and descendants + visited_nodes = set() + nodes_to_visit = list(component_template.nodelist) + slots_seen = set() + while nodes_to_visit: + current_node = nodes_to_visit.pop() + if current_node in visited_nodes: + continue + visited_nodes.add(current_node) + for nodelist_name in current_node.child_nodelists: + nodes_to_visit.extend(getattr(current_node, nodelist_name, [])) + if self.is_slot_node(current_node): + slots_seen.add(current_node.name) + + # Check and warn for unknown slots + if settings.DEBUG: + filled_slot_names = set(self.slots.keys()) + unused_slots = filled_slot_names - slots_seen + if unused_slots: + warnings.warn( + "Component {} was provided with slots that were not used in a template: {}".format( + self._component_name, unused_slots + ) + ) + + return component_template + + def render(self, context): + if hasattr(self, "context"): + warnings.warn( + f"{self.__class__.__name__}: `context` method is deprecated, use `get_context` instead", + DeprecationWarning, + ) + + if hasattr(self, "template"): + warnings.warn( + f"{self.__class__.__name__}: `template` method is deprecated, \ + set `template_name` or override `get_template_name` instead", + DeprecationWarning, + ) + template_name = self.template(context) + else: + template_name = self.get_template_name(context) + + instance_template = self.get_processed_template(template_name) + with context.update({ACTIVE_SLOT_CONTEXT_KEY: self.slots}): + return instance_template.render(context) + + class Media: + css = {} + js = [] + + +# This variable represents the global component registry +registry = ComponentRegistry() + + +def register(name): + """Class decorator to register a component. + + + Usage: + + @register("my_component") + class MyComponent(component.Component): + ... + + """ + + def decorator(component): + registry.register(name=name, component=component) + return component + + return decorator diff --git a/django_components/component_registry.py b/django_components/component_registry.py new file mode 100644 index 00000000..0f807ff0 --- /dev/null +++ b/django_components/component_registry.py @@ -0,0 +1,36 @@ +class AlreadyRegistered(Exception): + pass + + +class NotRegistered(Exception): + pass + + +class ComponentRegistry(object): + def __init__(self): + self._registry = {} # component name -> component_class mapping + + def register(self, name=None, component=None): + if name in self._registry: + raise AlreadyRegistered( + 'The component "%s" is already registered' % name + ) + + self._registry[name] = component + + def unregister(self, name): + self.get(name) + + del self._registry[name] + + def get(self, name): + if name not in self._registry: + raise NotRegistered('The component "%s" is not registered' % name) + + return self._registry[name] + + def all(self): + return self._registry + + def clear(self): + self._registry = {} diff --git a/django_components/middleware.py b/django_components/middleware.py new file mode 100644 index 00000000..fef98146 --- /dev/null +++ b/django_components/middleware.py @@ -0,0 +1,91 @@ +import re + +from django.conf import settings +from django.forms import Media +from django.http import StreamingHttpResponse + +RENDERED_COMPONENTS_CONTEXT_KEY = "_COMPONENT_DEPENDENCIES" +CSS_DEPENDENCY_PLACEHOLDER = '' +JS_DEPENDENCY_PLACEHOLDER = ' - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
-
-
-
-
-
-
-
-
-
- commits -
-
- -
-
- - -
-
-
-
-
-
-
-
-
-
-
-
- -
-
-
-
- - - - - - diff --git a/docs/benchmarks/index.json b/docs/benchmarks/index.json deleted file mode 100644 index d98e1855..00000000 --- a/docs/benchmarks/index.json +++ /dev/null @@ -1 +0,0 @@ -{"project": "django-components", "project_url": "/django-components/", "show_commit_url": "#", "hash_length": 8, "revision_to_hash": {"250": "45a18626d74b280aa05498aa380eba687010cc0f", "268": "c9f0068741871b24756045805aaab8fbf368601a", "343": "ed2cd57bdcc12bf390f42058061a6ed077e558b0", "359": "501a055fe21abb4c7ad9af1a3a1b42eea24afacf", "369": "1937d594100d0651b151c8edbe1189b5a88b358f", "380": "1d091f50999cca0fa8c7852da2ea71684eadda6d", "388": "f5c3f64c866d06dfa2435d1d18b936dddb9eb67c", "427": "24d9db39c3c55f5eb91c9ce2112ad22507ccdd60", "457": "9103eda2f4ed80f46ff03d215491009c5476b410", "487": "384ff79e0875ac3a64a2755010ef86ec09a090b5", "500": "c102019811586f40cb11952f283797ddee96de3c", "503": "a350ad1a869bb46c6bac093f54ac40ea85d8cd8f", "508": "16022429da16e459b8a1e6e9cd8ab41d95dcb59d", "536": "ea7beb518c435389d9daff7ea557eec9ddc2464e", "550": "2cfc7285e12677a54247e9998967081c0e031efc", "552": "fcbfae3c5f0611519636290ef99b25bc0a319825", "564": "316310e842f276fd43436b432781430bd255410b", "575": "e0a5c2a4bcf5e8f577e19333e3b719440c37b10b", "577": "c174aa9802cc73b5d8fef4b0c704de3fb68b1cbb", "598": "979781012532f18c147a570ff4441c65c7503b6f", "612": "9d7d0b40b9d3bbd8e5b754e3b1b20df982cdd184", "622": "24032ac2ea80627f0fdde88058a2c3fbcae0d3fe", "646": "4f1a8184465fc472995bc226bebd66c250d501f6", "651": "8ce649498ff63a64761c83cf713983b7e2f24b81", "663": "93facba53e050619a477fa214da89fccf118ec13", "675": "ea33e0db6c54486b67e2fa496ec1c3ec3de06ec7", "681": "6874b1531df0e8836000bbc76fcca5a3de42724a", "691": "9f4243232018708fdf330f0e922a09317c60234c", "704": "188a9dc7eef4c45ee4f87efa8a0d6728e94f210d", "724": "76a0cde3af7b3061127d149c138fbc78462a07fe", "729": "c932f28cb4840dc45d2d26b4d927c1480459272f", "742": "7058f05e0415965ddd162d0ba663c5106e7ffb66", "748": "95d6eacb5c03a611f92cd9a6e254cea7e0ce57eb", "752": "d512cfb0fec8a42ab58d2f106047fc4c0ec65427", "757": "04160f4e0b2ec4d901d6724c34ed8df883363064", "773": "20847b978b3ddb5979489fbb4bd8e971b9b16fbf", "775": "ae5cda9f727454a077ab16c51a237fec076bd442", "779": "cd791caa572c9acd6d56445c04d6c9b55a50c602", "787": "274be104789e31955f88e26f14c89ed648f25448", "806": "c3a80b729049b5ea1b4799e742e4100a7640efab", "845": "0abb5aa63ff0396328ef4dfdc2e78e872671f135", "855": "e4e787b29dc9cd2cf35ad02f22bad6fa15a7211c", "867": "e346c07298b268d9212fcf93d132968e69824730", "884": "2316f79dff66260b7a9fe91effb55c74524deca0", "887": "085c60a8c96442d9fd4f72d66a7eb295e7cfb2d9", "889": "ba86cee578490df944ad167674db32182b8bbf7b", "891": "dd292b03508b7c03b2ad4f9fb9c17dec747845ba", "898": "badffdda3538a8082137453c757509ed4f035a3e", "902": "c07f0e634121ced0119bb665ed544ca4447c416b", "904": "8bbe81d7171ec3512f250927653d8454c7ff005e", "908": "881c36219a45581086da35a17d3715ee0698ca88", "910": "9bfb50b8f27f7ff521226109ab30426633f6b514", "912": "3a7d5355cfa5d37641a4c80ee9fd2423cea10c1b", "930": "09a720009767f45bebcfa48f3587219c03082601", "935": "a4b4905bee778a51f9a99a042ce14ed2424cc9fb", "940": "31257a475dcfdacaaeb408f59ea92b736a668815", "942": "d47927054c820ecf3f9f97a62f03dfbb5496a11b", "956": "fbbbf6c694b8213832fc127ee8a3e31872308aff", "958": "d819f3ff491866abaeb9a7dbba6a6ca3c21da9f8", "960": "c202c5a901106392ccdde49100211182c986eca5", "965": "7bbfcf7565159d92de41bb0d738b7878d78f4533", "978": "b89c09aa5f13903b1d056c70afbfd327f0ed6362", "980": "03af25aad6841b6edb56c925d2599817e08ceb44", "982": "5cb649fae62a42e525c1ea9fb74d7838886cc1a8", "986": "f97717cdb35eaadb78b1f468728b1bd386e742d8", "1056": "b26a2011380c959bfc98043821f6b4aa337a281d", "1058": "8c5b088c31b7166932e18739f882c2eef632f3a4", "1060": "682bfc42397c0bdbeb1a2b6ccabb8aca89686d4f", "1089": "8f13a641ac096e93a0464f048a4fa53e591bb8db", "1098": "30d04fe1b053e6c5de0b6f34a7758a627517be8c", "1125": "f7846b9c0ae7a8fd0b7f6edf664316e485455e76", "1132": "a3d66586b19b7e2aacbf793edfba20bcd9858f4a", "1179": "0064de9c78db5eccc7c498e2a6d4c3c5ffa745ec", "1252": "d093bfb05212555e14c5cd46c94b0ba057cbeceb", "1270": "c0a4fd5f685d694218b583b2d0a3e8417925d53a", "1277": "2a4b0f52894d5d0bb8d325d6096a3950286db541", "1282": "5a23101038d7085959087b2352e71590eafe9529", "1287": "ce3305a5fff095e990707de851f85bc27120596c", "1312": "cdc830fca3bdd98669e0510841536be14d1bd728", "1328": "468a593a472c35379fe7567918e8a534b2d53748", "1337": "2f14e8e0908657459909c6338106e561edc5d0f4", "1352": "a5659691d0f947643ce6542b7f09075e3f931646", "1361": "aaeba99f54b206baed072ffd0572035ddf5118a7", "1366": "6813c9d7aa4acfb04df9665651a2864d04bbe8ba", "1381": "6681fc0085fdb4b76cb74c1bbf20d44fa22f40fe", "1387": "6bb73bd8afd350db84889efc09b74255cea08979", "1392": "c76f8198dd497b34735fc17a0f7a678bdd811a3f", "1447": "3bbd4326e6c1bfb4a02039e16021a3f720910308", "1488": "914576e68133bc6ec356f3918016c45691ed3534", "1505": "3d187d7abad80f8464bd70dfba847d27a8e173db", "1514": "e105500350a320dfccf36fc19cad7421d38c3736", "1546": "61515b34540d27311fc3286b50212c1e314ce39e", "1571": "691443a0a821eb1a02f9b42ad0e32bcd71d94943", "1591": "dcd4203eeadafc5d500b4b72c5cf04f1fe7317e7", "1662": "d0a42a2698f2ba21e7ab2dec750c5dbadeda0db5", "1672": "2037ed20b7252cc52008e69bdd3d8e095ad6ea08", "1687": "2472c2ad338a23fba015d4d9816cb62d1325455f", "1691": "42818ad6ffb47bd650d8a379b84c3d48394f9f77", "1709": "a6455d70f6c28ddbd4be8e58902f6cbc101e5ff3", "1726": "fdd29baa65e9ef78eb24a0ad2ca0b5d7c624dad3", "1766": "1319a95627493fc0745b5af0600af2dc8c5117f9", "1770": "07f747d70500bbe3e135725b0e1b102815ab9416", "1776": "ad402fc619922b6d2edf1e99b7082d2a58632076", "1801": "4c909486069f3c3c8ee7915239174f820f081da4", "1933": "593c66db7fce8f7d0e45768f516d1920e53d0967", "1937": "7b24b86f4a836c697acba926d9d6602afa45418d", "1960": "06c89cf9e89a432197a4cabb5a1d6864dd6089ac", "1996": "c692b7a3105c65414d2c23c357ffed9debdbf6e9", "2029": "5d7e235725449181f6b65b7bf97e43ea0e0f8552"}, "revision_to_date": {"250": 1630912817000, "268": 1631273531000, "343": 1654029104000, "359": 1657786741000, "369": 1657798498000, "380": 1658057264000, "388": 1658128984000, "427": 1670931380000, "457": 1673095465000, "487": 1675977478000, "500": 1678833725000, "503": 1678834812000, "508": 1679329468000, "536": 1680808865000, "550": 1681214445000, "552": 1681388770000, "564": 1682351069000, "575": 1684433482000, "577": 1684760359000, "598": 1693734426000, "612": 1695996512000, "622": 1696883504000, "646": 1702127213000, "651": 1702852887000, "663": 1705268832000, "675": 1705701480000, "681": 1706343740000, "691": 1706393538000, "704": 1707467055000, "724": 1708588716000, "729": 1708984309000, "742": 1709796107000, "748": 1710542375000, "752": 1711216985000, "757": 1711278671000, "773": 1711752109000, "775": 1711786713000, "779": 1712212506000, "787": 1712872453000, "806": 1713128940000, "845": 1713390260000, "855": 1713901297000, "867": 1714590581000, "884": 1714912319000, "887": 1715087715000, "889": 1715111097000, "891": 1715505986000, "898": 1716105516000, "902": 1716441018000, "904": 1716488913000, "908": 1717051130000, "910": 1717227227000, "912": 1717232115000, "930": 1718176374000, "935": 1718991590000, "940": 1720427258000, "942": 1720429478000, "956": 1722279535000, "958": 1722325182000, "960": 1722666822000, "965": 1722890429000, "978": 1723843975000, "980": 1723993321000, "982": 1724243667000, "986": 1724363119000, "1056": 1724732902000, "1058": 1724824521000, "1060": 1724924003000, "1089": 1725480929000, "1098": 1725655525000, "1125": 1726084755000, "1132": 1726347108000, "1179": 1728564427000, "1252": 1732525062000, "1270": 1732629701000, "1277": 1732644681000, "1282": 1732658332000, "1287": 1732726640000, "1312": 1733164405000, "1328": 1733471329000, "1337": 1733644953000, "1352": 1733834654000, "1361": 1734080521000, "1366": 1734245122000, "1381": 1734464187000, "1387": 1734600366000, "1392": 1734955786000, "1447": 1736287315000, "1488": 1737558545000, "1505": 1738157680000, "1514": 1738404847000, "1546": 1738662755000, "1571": 1739736734000, "1591": 1740050674000, "1662": 1742502414000, "1672": 1742645505000, "1687": 1742720064000, "1691": 1742765538000, "1709": 1743430242000, "1726": 1743837055000, "1766": 1744204166000, "1770": 1744216267000, "1776": 1744443333000, "1801": 1745143092000, "1933": 1749073294000, "1937": 1749076521000, "1960": 1749546769000, "1996": 1751538441000, "2029": 1753049108000}, "params": {"machine": ["ci-linux"], "python": ["3.13"], "django": ["5.1"], "djc-core-html-parser": [""], "branch": ["master"]}, "graph_param_list": [{"machine": "ci-linux", "python": "3.13", "django": "5.1", "djc-core-html-parser": "", "branch": "master"}], "benchmarks": {"Components vs Django.peakmem_render_lg_first": {"code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - large - first render (mem)\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n setup=lambda renderer: setup_templating_memory_benchmark(renderer, \"lg\", \"first\", \"isolated\"),\n )\n def peakmem_render_lg_first(self, renderer: TemplatingRenderer):\n do_render()\n\nsetup=lambda renderer: setup_templating_memory_benchmark(renderer, \"lg\", \"first\", \"isolated\"),", "name": "Components vs Django.peakmem_render_lg_first", "param_names": ["renderer"], "params": [["'django'", "'django-components'"]], "pretty_name": "render - large - first render (mem)", "type": "peakmemory", "unit": "bytes", "version": "301c396f017f45a5b3f71e85df58d15f54153fcfd951af7ef424641d4b31b528"}, "Components vs Django.peakmem_render_lg_subsequent": {"code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - large - second render (mem)\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n setup=lambda renderer: setup_templating_memory_benchmark(renderer, \"lg\", \"subsequent\", \"isolated\"),\n )\n def peakmem_render_lg_subsequent(self, renderer: TemplatingRenderer):\n do_render()\n\nsetup=lambda renderer: setup_templating_memory_benchmark(renderer, \"lg\", \"subsequent\", \"isolated\"),", "name": "Components vs Django.peakmem_render_lg_subsequent", "param_names": ["renderer"], "params": [["'django'", "'django-components'"]], "pretty_name": "render - large - second render (mem)", "type": "peakmemory", "unit": "bytes", "version": "9a44e9999ef3ef42ea7e01323727490244febb43d66a87a4d8f88c6b8a133b8b"}, "Components vs Django.peakmem_render_sm_first": {"code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - small - first render (mem)\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n setup=lambda renderer: setup_templating_memory_benchmark(renderer, \"sm\", \"first\", \"isolated\"),\n )\n def peakmem_render_sm_first(self, renderer: TemplatingRenderer):\n do_render()\n\nsetup=lambda renderer: setup_templating_memory_benchmark(renderer, \"sm\", \"first\", \"isolated\"),", "name": "Components vs Django.peakmem_render_sm_first", "param_names": ["renderer"], "params": [["'django'", "'django-components'"]], "pretty_name": "render - small - first render (mem)", "type": "peakmemory", "unit": "bytes", "version": "e93b7a5193681c883edf85bdb30b1bc0821263bf51033fdcee215b155085e036"}, "Components vs Django.peakmem_render_sm_subsequent": {"code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - small - second render (mem)\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n setup=lambda renderer: setup_templating_memory_benchmark(renderer, \"sm\", \"subsequent\", \"isolated\"),\n )\n def peakmem_render_sm_subsequent(self, renderer: TemplatingRenderer):\n do_render()\n\nsetup=lambda renderer: setup_templating_memory_benchmark(renderer, \"sm\", \"subsequent\", \"isolated\"),", "name": "Components vs Django.peakmem_render_sm_subsequent", "param_names": ["renderer"], "params": [["'django'", "'django-components'"]], "pretty_name": "render - small - second render (mem)", "type": "peakmemory", "unit": "bytes", "version": "b46e0820b18950aa7cc5e61306ff3425b76b4da9dca42d64fae5b1d25c6c9026"}, "Components vs Django.timeraw_render_lg_first": {"code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - large - first render\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n include_in_quick_benchmark=True,\n )\n def timeraw_render_lg_first(self, renderer: TemplatingRenderer):\n return prepare_templating_benchmark(renderer, \"lg\", \"first\", \"isolated\")", "min_run_count": 2, "name": "Components vs Django.timeraw_render_lg_first", "number": 1, "param_names": ["renderer"], "params": [["'django'", "'django-components'"]], "pretty_name": "render - large - first render", "repeat": 0, "rounds": 5, "sample_time": 0.01, "type": "time", "unit": "seconds", "version": "be3bf6236960046a028b6ea007aad28b2337fc2b906b8ce317a09a5d4f1a6193", "warmup_time": -1}, "Components vs Django.timeraw_render_lg_subsequent": {"code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - large - second render\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n )\n def timeraw_render_lg_subsequent(self, renderer: TemplatingRenderer):\n return prepare_templating_benchmark(renderer, \"lg\", \"subsequent\", \"isolated\")", "min_run_count": 2, "name": "Components vs Django.timeraw_render_lg_subsequent", "number": 1, "param_names": ["renderer"], "params": [["'django'", "'django-components'"]], "pretty_name": "render - large - second render", "repeat": 0, "rounds": 5, "sample_time": 0.01, "type": "time", "unit": "seconds", "version": "b98221c11a0ee6e9de0778d416d31b9dd514a674d9017a2bb9b2fc1cd0f01920", "warmup_time": -1}, "Components vs Django.timeraw_render_sm_first": {"code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - small - first render\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n )\n def timeraw_render_sm_first(self, renderer: TemplatingRenderer):\n return prepare_templating_benchmark(renderer, \"sm\", \"first\", \"isolated\")", "min_run_count": 2, "name": "Components vs Django.timeraw_render_sm_first", "number": 1, "param_names": ["renderer"], "params": [["'django'", "'django-components'"]], "pretty_name": "render - small - first render", "repeat": 0, "rounds": 5, "sample_time": 0.01, "type": "time", "unit": "seconds", "version": "f1fc17e4a31c71f4d9265f1122da52e7cf57addb4dfa02606e303b33d6431b9b", "warmup_time": -1}, "Components vs Django.timeraw_render_sm_subsequent": {"code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"render - small - second render\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n )\n def timeraw_render_sm_subsequent(self, renderer: TemplatingRenderer):\n return prepare_templating_benchmark(renderer, \"sm\", \"subsequent\", \"isolated\")", "min_run_count": 2, "name": "Components vs Django.timeraw_render_sm_subsequent", "number": 1, "param_names": ["renderer"], "params": [["'django'", "'django-components'"]], "pretty_name": "render - small - second render", "repeat": 0, "rounds": 5, "sample_time": 0.01, "type": "time", "unit": "seconds", "version": "6fce1cd85a9344fee383b40a22f27862120b9488a628420625592dc14e0307d3", "warmup_time": -1}, "Components vs Django.timeraw_startup_lg": {"code": "class DjangoComponentsVsDjangoTests:\n @benchmark(\n pretty_name=\"startup - large\",\n group_name=DJC_VS_DJ_GROUP,\n number=1,\n rounds=5,\n params={\n \"renderer\": [\"django\", \"django-components\"],\n },\n )\n def timeraw_startup_lg(self, renderer: TemplatingRenderer):\n return prepare_templating_benchmark(renderer, \"lg\", \"startup\", \"isolated\")", "min_run_count": 2, "name": "Components vs Django.timeraw_startup_lg", "number": 1, "param_names": ["renderer"], "params": [["'django'", "'django-components'"]], "pretty_name": "startup - large", "repeat": 0, "rounds": 5, "sample_time": 0.01, "type": "time", "unit": "seconds", "version": "53151821c128ad0ecfb0707fff3146e1abd8d0bcfa301aa056b5d3fae3d793e2", "warmup_time": -1}, "Other.timeraw_import_time": {"code": "class OtherTests:\n @benchmark(\n pretty_name=\"import time\",\n group_name=OTHER_GROUP,\n number=1,\n rounds=5,\n )\n def timeraw_import_time(self):\n return prepare_templating_benchmark(\"django-components\", \"lg\", \"startup\", \"isolated\", imports_only=True)", "min_run_count": 2, "name": "Other.timeraw_import_time", "number": 1, "param_names": [], "params": [], "pretty_name": "import time", "repeat": 0, "rounds": 5, "sample_time": 0.01, "type": "time", "unit": "seconds", "version": "a0a1c1c0db22509410b946d0d4384b52ea4a09b47b6048d7d1cfb89b0c7fe5c3", "warmup_time": -1}, "isolated vs django modes.peakmem_render_lg_first": {"code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - large - first render (mem)\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n setup=lambda context_mode: setup_templating_memory_benchmark(\n \"django-components\",\n \"lg\",\n \"first\",\n context_mode,\n ),\n )\n def peakmem_render_lg_first(self, context_mode: DjcContextMode):\n do_render()\n\nsetup=lambda context_mode: setup_templating_memory_benchmark(\n \"django-components\",\n \"lg\",\n \"first\",\n context_mode,\n),", "name": "isolated vs django modes.peakmem_render_lg_first", "param_names": ["context_mode"], "params": [["'isolated'", "'django'"]], "pretty_name": "render - large - first render (mem)", "type": "peakmemory", "unit": "bytes", "version": "c4bf0016d48d210f08b8db733b57c7dcba1cebbf548c458b93b86ace387067e9"}, "isolated vs django modes.peakmem_render_lg_subsequent": {"code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - large - second render (mem)\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n setup=lambda context_mode: setup_templating_memory_benchmark(\n \"django-components\",\n \"lg\",\n \"subsequent\",\n context_mode,\n ),\n )\n def peakmem_render_lg_subsequent(self, context_mode: DjcContextMode):\n do_render()\n\nsetup=lambda context_mode: setup_templating_memory_benchmark(\n \"django-components\",\n \"lg\",\n \"subsequent\",\n context_mode,\n),", "name": "isolated vs django modes.peakmem_render_lg_subsequent", "param_names": ["context_mode"], "params": [["'isolated'", "'django'"]], "pretty_name": "render - large - second render (mem)", "type": "peakmemory", "unit": "bytes", "version": "65bb1b8586487197a79bb6073e4c71642877b845b6eb42d1bd32398299daffbf"}, "isolated vs django modes.peakmem_render_sm_first": {"code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - small - first render (mem)\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n setup=lambda context_mode: setup_templating_memory_benchmark(\"django-components\", \"sm\", \"first\", context_mode),\n )\n def peakmem_render_sm_first(self, context_mode: DjcContextMode):\n do_render()\n\nsetup=lambda context_mode: setup_templating_memory_benchmark(\"django-components\", \"sm\", \"first\", context_mode),", "name": "isolated vs django modes.peakmem_render_sm_first", "param_names": ["context_mode"], "params": [["'isolated'", "'django'"]], "pretty_name": "render - small - first render (mem)", "type": "peakmemory", "unit": "bytes", "version": "c51b91fc583295776062822225e720b5ed71aef9c9288217c401c54283c62840"}, "isolated vs django modes.peakmem_render_sm_subsequent": {"code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - small - second render (mem)\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n setup=lambda context_mode: setup_templating_memory_benchmark(\n \"django-components\",\n \"sm\",\n \"subsequent\",\n context_mode,\n ),\n )\n def peakmem_render_sm_subsequent(self, context_mode: DjcContextMode):\n do_render()\n\nsetup=lambda context_mode: setup_templating_memory_benchmark(\n \"django-components\",\n \"sm\",\n \"subsequent\",\n context_mode,\n),", "name": "isolated vs django modes.peakmem_render_sm_subsequent", "param_names": ["context_mode"], "params": [["'isolated'", "'django'"]], "pretty_name": "render - small - second render (mem)", "type": "peakmemory", "unit": "bytes", "version": "54d747fb8f40179b7ff3d2fc49eb195909ad1c880b5ef7b82f82742b27b67260"}, "isolated vs django modes.timeraw_render_lg_first": {"code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - large - first render\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n )\n def timeraw_render_lg_first(self, context_mode: DjcContextMode):\n return prepare_templating_benchmark(\"django-components\", \"lg\", \"first\", context_mode)", "min_run_count": 2, "name": "isolated vs django modes.timeraw_render_lg_first", "number": 1, "param_names": ["context_mode"], "params": [["'isolated'", "'django'"]], "pretty_name": "render - large - first render", "repeat": 0, "rounds": 5, "sample_time": 0.01, "type": "time", "unit": "seconds", "version": "f94af83427c6346f88f8785a3cd2fc42415ac5a9fbbdb7de71d27e22e6a81699", "warmup_time": -1}, "isolated vs django modes.timeraw_render_lg_subsequent": {"code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - large - second render\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n )\n def timeraw_render_lg_subsequent(self, context_mode: DjcContextMode):\n return prepare_templating_benchmark(\"django-components\", \"lg\", \"subsequent\", context_mode)", "min_run_count": 2, "name": "isolated vs django modes.timeraw_render_lg_subsequent", "number": 1, "param_names": ["context_mode"], "params": [["'isolated'", "'django'"]], "pretty_name": "render - large - second render", "repeat": 0, "rounds": 5, "sample_time": 0.01, "type": "time", "unit": "seconds", "version": "9f7c2fde6b33f0451a1794ed903c48d96cd7822f67da502cec36fe8e977c2414", "warmup_time": -1}, "isolated vs django modes.timeraw_render_sm_first": {"code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - small - first render\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n )\n def timeraw_render_sm_first(self, context_mode: DjcContextMode):\n return prepare_templating_benchmark(\"django-components\", \"sm\", \"first\", context_mode)", "min_run_count": 2, "name": "isolated vs django modes.timeraw_render_sm_first", "number": 1, "param_names": ["context_mode"], "params": [["'isolated'", "'django'"]], "pretty_name": "render - small - first render", "repeat": 0, "rounds": 5, "sample_time": 0.01, "type": "time", "unit": "seconds", "version": "d15ca68909d7f1f43ff16863befb6f42681f17461417fc0069eefd6db3569296", "warmup_time": -1}, "isolated vs django modes.timeraw_render_sm_subsequent": {"code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"render - small - second render\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n )\n def timeraw_render_sm_subsequent(self, context_mode: DjcContextMode):\n return prepare_templating_benchmark(\"django-components\", \"sm\", \"subsequent\", context_mode)", "min_run_count": 2, "name": "isolated vs django modes.timeraw_render_sm_subsequent", "number": 1, "param_names": ["context_mode"], "params": [["'isolated'", "'django'"]], "pretty_name": "render - small - second render", "repeat": 0, "rounds": 5, "sample_time": 0.01, "type": "time", "unit": "seconds", "version": "7444bc9516dd087e3f420349345eae991ad6941bbd22fce45265b18034b7cf77", "warmup_time": -1}, "isolated vs django modes.timeraw_startup_lg": {"code": "class IsolatedVsDjangoContextModesTests:\n @benchmark(\n pretty_name=\"startup - large\",\n group_name=DJC_ISOLATED_VS_NON_GROUP,\n number=1,\n rounds=5,\n params={\n \"context_mode\": [\"isolated\", \"django\"],\n },\n )\n def timeraw_startup_lg(self, context_mode: DjcContextMode):\n return prepare_templating_benchmark(\"django-components\", \"lg\", \"startup\", context_mode)", "min_run_count": 2, "name": "isolated vs django modes.timeraw_startup_lg", "number": 1, "param_names": ["context_mode"], "params": [["'isolated'", "'django'"]], "pretty_name": "startup - large", "repeat": 0, "rounds": 5, "sample_time": 0.01, "type": "time", "unit": "seconds", "version": "eabe311ebee4a15c5816617be12f00ec30376f7506bd668219e1c50bc897c134", "warmup_time": -1}}, "machines": {"ci-linux": {"machine": "ci-linux", "version": 1}}, "tags": {"0.100": 1125, "0.101": 1132, "0.102": 1179, "0.110": 1252, "0.111": 1270, "0.112": 1277, "0.113": 1282, "0.114": 1287, "0.115": 1312, "0.116": 1328, "0.117": 1337, "0.118": 1352, "0.119": 1361, "0.120": 1366, "0.121": 1381, "0.122": 1387, "0.123": 1392, "0.124": 1447, "0.125": 1488, "0.126": 1505, "0.127": 1514, "0.128": 1546, "0.129": 1571, "0.130": 1591, "0.131": 1662, "0.132": 1672, "0.133": 1687, "0.134": 1691, "0.135": 1709, "0.136": 1726, "0.137": 1766, "0.138": 1770, "0.139": 1776, "0.139.1": 1801, "0.140.0": 1933, "0.140.1": 1937, "0.141.0": 1960, "0.141.1": 1996, "0.141.2": 2029, "0.16": 250, "0.17": 268, "0.26.2": 508, "0.27": 550, "0.27.1": 552, "0.27.2": 564, "0.28.0": 575, "0.28.1": 577, "0.67": 845, "0.68": 855, "0.70": 867, "0.71": 884, "0.72": 887, "0.73": 889, "0.74": 891, "0.75": 898, "0.76": 902, "0.77": 904, "0.78": 908, "0.79": 910, "0.80": 912, "0.81": 930, "0.82": 935, "0.83": 940, "0.84": 942, "0.85": 956, "0.86": 958, "0.87": 960, "0.88": 965, "0.89": 978, "0.90": 980, "0.91": 982, "0.92": 986, "0.93": 1056, "0.94": 1058, "0.95": 1060, "0.96": 1089, "0.97": 1098, "0.18": 343, "0.22": 388, "0.21": 380, "0.20": 369, "0.19": 359, "0.24": 457, "0.23": 427, "0.25": 487, "0.26": 500, "0.26.1": 503, "0.26.3": 536, "0.28.2": 598, "0.28.3": 612, "0.29": 622, "0.30": 646, "0.31": 651, "0.32": 663, "0.33": 675, "0.34": 681, "0.34.1": 691, "0.35": 704, "0.37": 724, "0.50": 729, "0.51": 742, "0.52": 748, "0.60": 752, "0.61": 757, "0.62": 773, "0.63": 775, "0.64": 779, "0.65": 787, "0.66": 806}, "pages": [["", "Grid view", "Display as a agrid"], ["summarylist", "List view", "Display as a list"], ["regressions", "Show regressions", "Display information about recent regressions"]]} \ No newline at end of file diff --git a/docs/benchmarks/info.json b/docs/benchmarks/info.json deleted file mode 100644 index 032015bd..00000000 --- a/docs/benchmarks/info.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "asv-version": "0.6.4", - "timestamp": 1753049912703 -} \ No newline at end of file diff --git a/docs/benchmarks/jquery.flot.axislabels.js b/docs/benchmarks/jquery.flot.axislabels.js deleted file mode 100644 index e8017e8e..00000000 --- a/docs/benchmarks/jquery.flot.axislabels.js +++ /dev/null @@ -1,140 +0,0 @@ -/* -CAxis Labels Plugin for flot. :P -Copyright (c) 2010 Xuan Luo - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - */ -(function ($) { - var options = { }; - - function init(plot) { - // This is kind of a hack. There are no hooks in Flot between - // the creation and measuring of the ticks (setTicks, measureTickLabels - // in setupGrid() ) and the drawing of the ticks and plot box - // (insertAxisLabels in setupGrid() ). - // - // Therefore, we use a trick where we run the draw routine twice: - // the first time to get the tick measurements, so that we can change - // them, and then have it draw it again. - var secondPass = false; - plot.hooks.draw.push(function (plot, ctx) { - if (!secondPass) { - // MEASURE AND SET OPTIONS - $.each(plot.getAxes(), function(axisName, axis) { - var opts = axis.options // Flot 0.7 - || plot.getOptions()[axisName]; // Flot 0.6 - if (!opts || !opts.axisLabel) - return; - - var w, h; - if (opts.axisLabelUseCanvas != false) - opts.axisLabelUseCanvas = true; - - if (opts.axisLabelUseCanvas) { - // canvas text - if (!opts.axisLabelFontSizePixels) - opts.axisLabelFontSizePixels = 14; - if (!opts.axisLabelFontFamily) - opts.axisLabelFontFamily = 'sans-serif'; - // since we currently always display x as horiz. - // and y as vertical, we only care about the height - w = opts.axisLabelFontSizePixels; - h = opts.axisLabelFontSizePixels; - - } else { - // HTML text - var elem = $('
' + opts.axisLabel + '
'); - plot.getPlaceholder().append(elem); - w = elem.outerWidth(true); - h = elem.outerHeight(true); - elem.remove(); - } - - if (axisName.charAt(0) == 'x') - axis.labelHeight += h; - else - axis.labelWidth += w; - opts.labelHeight = axis.labelHeight; - opts.labelWidth = axis.labelWidth; - }); - // re-draw with new label widths and heights - secondPass = true; - plot.setupGrid(); - plot.draw(); - - - } else { - // DRAW - $.each(plot.getAxes(), function(axisName, axis) { - var opts = axis.options // Flot 0.7 - || plot.getOptions()[axisName]; // Flot 0.6 - if (!opts || !opts.axisLabel) - return; - - if (opts.axisLabelUseCanvas) { - // canvas text - var ctx = plot.getCanvas().getContext('2d'); - ctx.save(); - ctx.font = opts.axisLabelFontSizePixels + 'px ' + - opts.axisLabelFontFamily; - var width = ctx.measureText(opts.axisLabel).width; - var height = opts.axisLabelFontSizePixels; - var x, y; - if (axisName.charAt(0) == 'x') { - x = plot.getPlotOffset().left + plot.width()/2 - width/2; - y = plot.getCanvas().height; - } else { - x = height * 0.72; - y = plot.getPlotOffset().top + plot.height()/2 - width/2; - } - ctx.translate(x, y); - ctx.rotate((axisName.charAt(0) == 'x') ? 0 : -Math.PI/2); - ctx.fillText(opts.axisLabel, 0, 0); - ctx.restore(); - - } else { - // HTML text - plot.getPlaceholder().find('#' + axisName + 'Label').remove(); - var elem = $('
' + opts.axisLabel + '
'); - if (axisName.charAt(0) == 'x') { - elem.css('left', plot.getPlotOffset().left + plot.width()/2 - elem.outerWidth()/2 + 'px'); - elem.css('bottom', '0px'); - } else { - elem.css('top', plot.getPlotOffset().top + plot.height()/2 - elem.outerHeight()/2 + 'px'); - elem.css('left', '0px'); - } - plot.getPlaceholder().append(elem); - } - }); - secondPass = false; - } - }); - } - - - - $.plot.plugins.push({ - init: init, - options: options, - name: 'axisLabels', - version: '1.0' - }); -})(jQuery); diff --git a/docs/benchmarks/regressions.css b/docs/benchmarks/regressions.css deleted file mode 100644 index 0455b77e..00000000 --- a/docs/benchmarks/regressions.css +++ /dev/null @@ -1,44 +0,0 @@ -#regressions-body { - margin-left: 2em; - margin-right: 2em; - margin-top: 1em; - margin-bottom: 2em; -} - -#regressions-body table thead th { - cursor: pointer; - white-space: nowrap; -} - -#regressions-body table thead th.desc:after { - content: ' \2191'; -} - -#regressions-body table thead th.asc:after { - content: ' \2193'; -} - -#regressions-body table.ignored { - padding-top: 1em; - color: #ccc; - background-color: #eee; -} - -#regressions-body table.ignored a { - color: #82abda; -} - -#regressions-body .feed-div { - float: right; -} - -#regressions-body table tbody td.date { - white-space: nowrap; -} - -#regressions-body table button { - margin-top: -2px; - padding-top: 2px; - padding-bottom: 0px; - white-space: nowrap; -} diff --git a/docs/benchmarks/regressions.js b/docs/benchmarks/regressions.js deleted file mode 100644 index 7572c4b4..00000000 --- a/docs/benchmarks/regressions.js +++ /dev/null @@ -1,618 +0,0 @@ -'use strict'; - -$(document).ready(function() { - /* Cached contents of downloaded regressions.json */ - var regression_data = null; - /* Current page title */ - var current_title = "All regressions"; - /* Whether HTML5 local storage is available */ - var local_storage_available = false; - /* Key prefix for ignored regressions. For each ignored regression, - a key "ignore_key_prefix + md5(benchmark_name + date_a + date_b)" - is added to HTML5 local storage. - */ - var ignore_key_prefix = null; - /* Set of ignored regressions, same information as in HTML5 local storage. - Useful if local storage runs out of space. */ - var ignored_regressions = {}; - /* Whether to force reload on next page update */ - var skip_reload = false; - - function load_data(params) { - $("#title").text(current_title); - - if (typeof(Storage) !== "undefined") { - /* html5 local storage available */ - local_storage_available = true; - } - - if (regression_data !== null) { - // already displayed - if (!skip_reload) { - var main_div = display_data(regression_data, params); - $('#regressions-body').empty(); - $('#regressions-body').append(main_div); - } - skip_reload = false; - } - else { - var message = $('
Loading...
'); - skip_reload = false; - $('#regressions-body').append(message); - $.ajax({ - url: 'regressions.json' + '?timestamp=' + $.asv.main_timestamp, - dataType: "json", - cache: true - }).done(function (data) { - regression_data = data; - var main_div = display_data(data, params); - $('#regressions-body').empty(); - $('#regressions-body').append(main_div); - }); - } - } - - function update_url(params, reload) { - var info = $.asv.parse_hash_string(window.location.hash); - $.each(params || {}, function(key, value) { - info.params[key] = value; - }); - - var new_hash = $.asv.format_hash_string(info); - if (new_hash != window.location.hash) { - if (reload === undefined) { - skip_reload = false; - } - else { - skip_reload = !reload; - } - window.location.hash = new_hash; - } - else { - skip_reload = false; - } - } - - function display_data(data, params) { - var main_div = $('
'); - var branches = $.asv.main_json.params['branch']; - var all_ignored_keys = {}; - - ignore_key_prefix = 'asv-r-' + $.asv.main_json.project; - - if (branches && branches.length > 1) { - /* Add a branch selector */ - var dropdown_menu = $('