Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-django-silk for openSUSE:Factory checked in at 2026-03-29 20:00:57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-django-silk (Old) and /work/SRC/openSUSE:Factory/.python-django-silk.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-silk" Sun Mar 29 20:00:57 2026 rev:18 rq:1343411 version:5.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-django-silk/python-django-silk.changes 2026-03-23 17:11:59.766249816 +0100 +++ /work/SRC/openSUSE:Factory/.python-django-silk.new.8177/python-django-silk.changes 2026-03-29 20:01:20.771048437 +0200 @@ -1,0 +2,141 @@ +Sun Mar 29 10:32:45 UTC 2026 - Dirk Müller <[email protected]> + +- update to 5.5.0: + * Fix context manager for `_process_response` (#827) + * Fix mouse event for sql navigation (#847) + * Add support for Django 6.0 (#836) + * Add support for Python 3.14 (#834) + * Get paginator limit from URL params (#646) + * Hide pagination when there's only one page (#844) + * Remove official support for Python 3.9 (#834) + * Fix double EXPLAIN when calling explain on queryset (#654) + * Fix serialization issues for binary and json fields (#821) + * Reverts #798 which causes issues when serializing JSONFields + * Also reverts #798 which has a race condition when modifying + `execute_sql` + * Catch and ignore sql encoding errors (#810) @albertyw + * Document that context_processors.request is required (#815) + * Fix documentation formatting (#810) @albertyw + * Test refactors (#814) @albertyw + * Fixes curl/client values rendering in request_detail (#797) + * Fix serialization of non-unicode binary data, add cleanup in + middleware (#798) @glennmatthews + * Make transactions target the DB alias selected by the router + (#801) @OscarVanL + * Add support for Django 5.2 (#784) @albertyw + * Support opening SQL details in a new window (#788) + * Avoid timeouts when deserializing large jsons (#768) + * Make autopep8 optional (#782) @albertyw + * Fix masking sensitive data when an empty + `SILKY_SENSITIVE_KEYS` is provided (#777) @ahsanshafiq742 + * Remove support for Django 5.0 (#783) @albertyw + * Fix logger deprecations (#766) @rjdebastiani + * Update dependencies and various autoupdate cleanups + +------------------------------------------------------------------- +Sun Mar 29 10:32:14 UTC 2026 - Dirk Müller <[email protected]> + +- update to 5.5.0: + * ## 5.5.0 (2026-03-06) + * :release-by: Albert Wang (@albertyw) + * Full Changelog + + * **Fixes:** + * Fix context manager for `_process_response` (#827) + @izabala033 + * Fix mouse event for sql navigation (#847) @albertyw + + * **Features/Enhancements:** + + * Add support for Django 6.0 (#836) @albertyw + * Add support for Python 3.14 (#834) @albertyw + * Get paginator limit from URL params (#646) @strig + * Hide pagination when there's only one page (#844) @ShlomoCode + + * **Maintenance and Cleanup:** + * Remove official support for Python 3.9 (#834) @albertyw + * Dependency updates + + + * ## 5.4.3 (2025-09-08) + * :release-by: Albert Wang (@albertyw) + * Full Changelog + + * **Fixes:** + + * Fix double EXPLAIN when calling explain on queryset (#654) + @stereodamage + * Fix serialization issues for binary and json fields (#821) + @albertyw + + + * ## 5.4.2 (2025-08-17) + * :release-by: Albert Wang (@albertyw) + * Full Changelog + + * **Fixes:** + + * Reverts #798 which causes issues when serializing JSONFields + (#807) @albertyw + * Also reverts #798 which has a race condition when modifying + `execute_sql` (#816) @albertyw + * Catch and ignore sql encoding errors (#810) @albertyw + @bpascard + + * **Maintenance and Cleanup:** + + * Document that context_processors.request is required (#815) + @albertyw + * Fix documentation formatting (#810) @albertyw + * Test refactors (#814) @albertyw + + + * ## 5.4.1 (2025-08-10) + * :release-by: Albert Wang (@albertyw) + * Full Changelog + + * **Fixes:** + + * Fixes curl/client values rendering in request_detail (#797) + @bcmyguest + * Fix serialization of non-unicode binary data, add cleanup in + middleware (#798) @glennmatthews + * Make transactions target the DB alias selected by the router + (#801) @OscarVanL + + * **Maintenance and Cleanup:** + + * Dependency updates + * Documentation updates + + + * ## 5.4.0 (2025-05-03) + * :release-by: Albert Wang (@albertyw) + * Full Changelog + + * **Note: this release removes support for Django 5.0** + * **Note: this release removes autoformatting of python + snippets; continue formatting by pip installing `django- + silk[formatting]`** + + * **Features/Enhancements:** + + * Add support for Django 5.2 (#784) @albertyw + * Support opening SQL details in a new window (#788) + @joaopedroalbq + * Avoid timeouts when deserializing large jsons (#768) + @quertenmont + * Make autopep8 optional (#782) @albertyw + + * **Fixes:** + + * Fix masking sensitive data when an empty + `SILKY_SENSITIVE_KEYS` is provided (#777) @ahsanshafiq742 + + * **Maintenance and Cleanup:** + + * Remove support for Django 5.0 (#783) @albertyw + * Fix logger deprecations (#766) @rjdebastiani + * Update dependencies and various autoupdate cleanups + +------------------------------------------------------------------- Old: ---- django_silk-5.3.2.tar.gz New: ---- django_silk-5.5.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-django-silk.spec ++++++ --- /var/tmp/diff_new_pack.Wm2tpN/_old 2026-03-29 20:01:21.659085024 +0200 +++ /var/tmp/diff_new_pack.Wm2tpN/_new 2026-03-29 20:01:21.663085188 +0200 @@ -17,7 +17,7 @@ Name: python-django-silk -Version: 5.3.2 +Version: 5.5.0 Release: 0 Summary: Profiling for the Django Framework License: MIT ++++++ django_silk-5.3.2.tar.gz -> django_silk-5.5.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/.github/workflows/release.yml new/django_silk-5.5.0/.github/workflows/release.yml --- old/django_silk-5.3.2/.github/workflows/release.yml 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/.github/workflows/release.yml 2026-03-07 09:15:38.000000000 +0100 @@ -18,7 +18,7 @@ - name: Set up Python uses: actions/setup-python@v5 with: - python-version: 3.13 + python-version: 3.14 - name: Install dependencies run: | diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/.github/workflows/test.yml new/django_silk-5.5.0/.github/workflows/test.yml --- old/django_silk-5.3.2/.github/workflows/test.yml 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/.github/workflows/test.yml 2026-03-07 09:15:38.000000000 +0100 @@ -9,22 +9,30 @@ strategy: fail-fast: false matrix: - python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] - django-version: ['4.2', '5.0', '5.1', 'main'] - postgres-version: ['13', '17'] - mariadb-version: ['10.6', '10.11', '11.4'] + python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] + django-version: ['4.2', '5.1', '5.2', '6.0', 'main'] + postgres-version: ['14', '18'] + mariadb-version: ['10.6', '10.11', '11.4', '11.8'] exclude: - # Django 5.0 doesn't support python <=3.9 (https://docs.djangoproject.com/en/5.0/faq/install/) - - python-version: '3.9' - django-version: '5.0' + # Django 4.2 doesn't support Python >= 3.13 + - django-version: '4.2' + python-version: '3.13' + - django-version: '4.2' + python-version: '3.14' - # Django 5.1 doesn't support python <=3.9 (https://docs.djangoproject.com/en/5.1/faq/install/) - - python-version: '3.9' - django-version: '5.1' + # Django 5.1 doesn't support Python >= 3.14 + - django-version: '5.1' + python-version: '3.14' - # Django main doesn't support python <=3.9 (https://docs.djangoproject.com/en/5.1/faq/install/) - - python-version: '3.9' - django-version: 'main' + # Django 6.0 doesn't support Python <3.12 (https://docs.djangoproject.com/en/dev/releases/6.0/#python-compatibility) + - django-version: '6.0' + python-version: '3.10' + - django-version: '6.0' + python-version: '3.11' + - django-version: 'main' + python-version: '3.10' + - django-version: 'main' + python-version: '3.11' services: postgres: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/.pre-commit-config.yaml new/django_silk-5.5.0/.pre-commit-config.yaml --- old/django_silk-5.3.2/.pre-commit-config.yaml 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/.pre-commit-config.yaml 2026-03-07 09:15:38.000000000 +0100 @@ -1,10 +1,10 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: 'v5.0.0' + rev: 'v6.0.0' hooks: - id: check-merge-conflict - repo: https://github.com/hadialqattan/pycln - rev: v2.4.0 + rev: v2.6.0 hooks: - id: pycln args: ['--all'] @@ -13,12 +13,12 @@ hooks: - id: yesqa - repo: https://github.com/pycqa/isort - rev: '5.13.2' + rev: '8.0.1' hooks: - id: isort args: ['--profile', 'black'] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: 'v5.0.0' + rev: 'v6.0.0' hooks: - id: end-of-file-fixer exclude: >- @@ -47,21 +47,21 @@ - id: detect-private-key exclude: ^examples|(?:tests/ssl)/ - repo: https://github.com/asottile/pyupgrade - rev: 'v3.19.0' + rev: 'v3.21.2' hooks: - id: pyupgrade args: ['--keep-mock'] - repo: https://github.com/adamchainz/django-upgrade - rev: '1.22.2' + rev: '1.30.0' hooks: - id: django-upgrade args: [--target-version, '4.2'] - repo: https://github.com/hhatto/autopep8 - rev: 'v2.3.1' + rev: 'v2.3.2' hooks: - id: autopep8 - repo: https://github.com/PyCQA/flake8 - rev: '7.1.1' + rev: '7.3.0' hooks: - id: flake8 exclude: '^docs/' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/CHANGELOG.md new/django_silk-5.5.0/CHANGELOG.md --- old/django_silk-5.3.2/CHANGELOG.md 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/CHANGELOG.md 2026-03-07 09:15:38.000000000 +0100 @@ -1,6 +1,94 @@ # Change Log ## Unreleased +## [5.5.0](https://github.com/jazzband/django-silk/tree/5.5.0) (2026-03-06) +:release-by: Albert Wang (@albertyw) +[Full Changelog](https://github.com/jazzband/django-silk/compare/5.4.3..5.5.0) + +**Fixes:** + - Fix context manager for `_process_response` (#827) @izabala033 + - Fix mouse event for sql navigation (#847) @albertyw + +**Features/Enhancements:** + + - Add support for Django 6.0 (#836) @albertyw + - Add support for Python 3.14 (#834) @albertyw + - Get paginator limit from URL params (#646) @strig + - Hide pagination when there's only one page (#844) @ShlomoCode + +**Maintenance and Cleanup:** + - Remove official support for Python 3.9 (#834) @albertyw + - Dependency updates + + +## [5.4.3](https://github.com/jazzband/django-silk/tree/5.4.3) (2025-09-08) +:release-by: Albert Wang (@albertyw) +[Full Changelog](https://github.com/jazzband/django-silk/compare/5.4.2..5.4.3) + +**Fixes:** + + - Fix double EXPLAIN when calling explain on queryset (#654) @stereodamage + - Fix serialization issues for binary and json fields (#821) @albertyw + + +## [5.4.2](https://github.com/jazzband/django-silk/tree/5.4.2) (2025-08-17) +:release-by: Albert Wang (@albertyw) +[Full Changelog](https://github.com/jazzband/django-silk/compare/5.4.1..5.4.2) + +**Fixes:** + + - Reverts #798 which causes issues when serializing JSONFields (#807) @albertyw + - Also reverts #798 which has a race condition when modifying `execute_sql` (#816) @albertyw + - Catch and ignore sql encoding errors (#810) @albertyw @bpascard + +**Maintenance and Cleanup:** + + - Document that context_processors.request is required (#815) @albertyw + - Fix documentation formatting (#810) @albertyw + - Test refactors (#814) @albertyw + + +## [5.4.1](https://github.com/jazzband/django-silk/tree/5.4.1) (2025-08-10) +:release-by: Albert Wang (@albertyw) +[Full Changelog](https://github.com/jazzband/django-silk/compare/5.4.0..5.4.1) + +**Fixes:** + + - Fixes curl/client values rendering in request_detail (#797) @bcmyguest + - Fix serialization of non-unicode binary data, add cleanup in middleware (#798) @glennmatthews + - Make transactions target the DB alias selected by the router (#801) @OscarVanL + +**Maintenance and Cleanup:** + + - Dependency updates + - Documentation updates + + +## [5.4.0](https://github.com/jazzband/django-silk/tree/5.4.0) (2025-05-03) +:release-by: Albert Wang (@albertyw) +[Full Changelog](https://github.com/jazzband/django-silk/compare/5.3.2..5.4.0) + +**Note: this release removes support for Django 5.0** +**Note: this release removes autoformatting of python snippets; continue formatting by pip installing `django-silk[formatting]`** + +**Features/Enhancements:** + + - Add support for Django 5.2 (#784) @albertyw + - Support opening SQL details in a new window (#788) @joaopedroalbq + - Avoid timeouts when deserializing large jsons (#768) @quertenmont + - Make autopep8 optional (#782) @albertyw + +**Fixes:** + + - Fix masking sensitive data when an empty `SILKY_SENSITIVE_KEYS` is provided (#777) @ahsanshafiq742 + +**Maintenance and Cleanup:** + + - Remove support for Django 5.0 (#783) @albertyw + - Fix logger deprecations (#766) @rjdebastiani + - Update dependencies and various autoupdate cleanups + + ## [5.3.2](https://github.com/jazzband/django-silk/tree/5.3.2) (2024-12-05) :release-by: Albert Wang (@albertyw) [Full Changelog](https://github.com/jazzband/django-silk/compare/5.3.1..5.3.2) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/PKG-INFO new/django_silk-5.5.0/PKG-INFO --- old/django_silk-5.3.2/PKG-INFO 2024-12-14 06:54:07.738027600 +0100 +++ new/django_silk-5.5.0/PKG-INFO 2026-03-07 09:15:50.052632300 +0100 @@ -1,6 +1,6 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: django-silk -Version: 5.3.2 +Version: 5.5.0 Summary: Silky smooth profiling for the Django Framework Home-page: https://github.com/jazzband/django-silk Author: Michael Ford @@ -10,25 +10,39 @@ Classifier: Environment :: Web Environment Classifier: Framework :: Django Classifier: Framework :: Django :: 4.2 -Classifier: Framework :: Django :: 5.0 Classifier: Framework :: Django :: 5.1 +Classifier: Framework :: Django :: 5.2 +Classifier: Framework :: Django :: 6.0 Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content -Requires-Python: >=3.9 +Requires-Python: >=3.10 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: Django>=4.2 Requires-Dist: sqlparse -Requires-Dist: autopep8 Requires-Dist: gprof2dot>=2017.09.19 +Provides-Extra: formatting +Requires-Dist: autopep8; extra == "formatting" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: description-content-type +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: provides-extra +Dynamic: requires-dist +Dynamic: requires-python +Dynamic: summary # Silk @@ -62,8 +76,8 @@ Silk has been tested with: -* Django: 4.2, 5.0, 5.1 -* Python: 3.9, 3.10, 3.11, 3.12, 3.13 +* Django: 4.2, 5.1, 5.2, 6.0 +* Python: 3.10, 3.11, 3.12, 3.13, 3.14 ## Installation @@ -73,6 +87,12 @@ pip install django-silk ``` +To including optional formatting of python snippets: + +```bash +pip install django-silk[formatting] +``` + In `settings.py` add the following: ```python @@ -82,6 +102,17 @@ ... ] +TEMPLATES = [{ + ... + 'OPTIONS': { + 'context_processors': [ + ... + 'django.template.context_processors.request', + ], + }, +}] + + INSTALLED_APPS = ( ... 'silk' @@ -129,14 +160,14 @@ Via [github tags](https://github.com/jazzband/django-silk/releases): ```bash -pip install https://github.com/jazzband/silk/archive/<version>.tar.gz +pip install git+https://github.com/jazzband/django-silk.git@<version>#egg=django_silk ``` You can install from master using the following, but please be aware that the version in master may not be working for all versions specified in [requirements](#requirements) ```bash -pip install -e git+https://github.com/jazzband/django-silk.git#egg=django-silk +pip install -e git+https://github.com/jazzband/django-silk.git#egg=django_silk ``` ## Features diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/README.md new/django_silk-5.5.0/README.md --- old/django_silk-5.3.2/README.md 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/README.md 2026-03-07 09:15:38.000000000 +0100 @@ -30,8 +30,8 @@ Silk has been tested with: -* Django: 4.2, 5.0, 5.1 -* Python: 3.9, 3.10, 3.11, 3.12, 3.13 +* Django: 4.2, 5.1, 5.2, 6.0 +* Python: 3.10, 3.11, 3.12, 3.13, 3.14 ## Installation @@ -41,6 +41,12 @@ pip install django-silk ``` +To including optional formatting of python snippets: + +```bash +pip install django-silk[formatting] +``` + In `settings.py` add the following: ```python @@ -50,6 +56,17 @@ ... ] +TEMPLATES = [{ + ... + 'OPTIONS': { + 'context_processors': [ + ... + 'django.template.context_processors.request', + ], + }, +}] + + INSTALLED_APPS = ( ... 'silk' @@ -97,14 +114,14 @@ Via [github tags](https://github.com/jazzband/django-silk/releases): ```bash -pip install https://github.com/jazzband/silk/archive/<version>.tar.gz +pip install git+https://github.com/jazzband/django-silk.git@<version>#egg=django_silk ``` You can install from master using the following, but please be aware that the version in master may not be working for all versions specified in [requirements](#requirements) ```bash -pip install -e git+https://github.com/jazzband/django-silk.git#egg=django-silk +pip install -e git+https://github.com/jazzband/django-silk.git#egg=django_silk ``` ## Features diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/django_silk.egg-info/PKG-INFO new/django_silk-5.5.0/django_silk.egg-info/PKG-INFO --- old/django_silk-5.3.2/django_silk.egg-info/PKG-INFO 2024-12-14 06:54:07.000000000 +0100 +++ new/django_silk-5.5.0/django_silk.egg-info/PKG-INFO 2026-03-07 09:15:49.000000000 +0100 @@ -1,6 +1,6 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: django-silk -Version: 5.3.2 +Version: 5.5.0 Summary: Silky smooth profiling for the Django Framework Home-page: https://github.com/jazzband/django-silk Author: Michael Ford @@ -10,25 +10,39 @@ Classifier: Environment :: Web Environment Classifier: Framework :: Django Classifier: Framework :: Django :: 4.2 -Classifier: Framework :: Django :: 5.0 Classifier: Framework :: Django :: 5.1 +Classifier: Framework :: Django :: 5.2 +Classifier: Framework :: Django :: 6.0 Classifier: Intended Audience :: Developers Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content -Requires-Python: >=3.9 +Requires-Python: >=3.10 Description-Content-Type: text/markdown License-File: LICENSE Requires-Dist: Django>=4.2 Requires-Dist: sqlparse -Requires-Dist: autopep8 Requires-Dist: gprof2dot>=2017.09.19 +Provides-Extra: formatting +Requires-Dist: autopep8; extra == "formatting" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: description-content-type +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: provides-extra +Dynamic: requires-dist +Dynamic: requires-python +Dynamic: summary # Silk @@ -62,8 +76,8 @@ Silk has been tested with: -* Django: 4.2, 5.0, 5.1 -* Python: 3.9, 3.10, 3.11, 3.12, 3.13 +* Django: 4.2, 5.1, 5.2, 6.0 +* Python: 3.10, 3.11, 3.12, 3.13, 3.14 ## Installation @@ -73,6 +87,12 @@ pip install django-silk ``` +To including optional formatting of python snippets: + +```bash +pip install django-silk[formatting] +``` + In `settings.py` add the following: ```python @@ -82,6 +102,17 @@ ... ] +TEMPLATES = [{ + ... + 'OPTIONS': { + 'context_processors': [ + ... + 'django.template.context_processors.request', + ], + }, +}] + + INSTALLED_APPS = ( ... 'silk' @@ -129,14 +160,14 @@ Via [github tags](https://github.com/jazzband/django-silk/releases): ```bash -pip install https://github.com/jazzband/silk/archive/<version>.tar.gz +pip install git+https://github.com/jazzband/django-silk.git@<version>#egg=django_silk ``` You can install from master using the following, but please be aware that the version in master may not be working for all versions specified in [requirements](#requirements) ```bash -pip install -e git+https://github.com/jazzband/django-silk.git#egg=django-silk +pip install -e git+https://github.com/jazzband/django-silk.git#egg=django_silk ``` ## Features diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/django_silk.egg-info/SOURCES.txt new/django_silk-5.5.0/django_silk.egg-info/SOURCES.txt --- old/django_silk-5.3.2/django_silk.egg-info/SOURCES.txt 2024-12-14 06:54:07.000000000 +0100 +++ new/django_silk-5.5.0/django_silk.egg-info/SOURCES.txt 2026-03-07 09:15:49.000000000 +0100 @@ -213,6 +213,7 @@ silk/static/silk/js/pages/request.js silk/static/silk/js/pages/requests.js silk/static/silk/js/pages/root_base.js +silk/static/silk/js/pages/sql.js silk/static/silk/js/pages/sql_detail.js silk/static/silk/js/pages/summary.js silk/static/silk/lib/bootstrap-datetimepicker.min.css @@ -286,6 +287,7 @@ silk/templatetags/silk_filters.py silk/templatetags/silk_inclusion.py silk/templatetags/silk_nav.py +silk/templatetags/silk_urls.py silk/utils/__init__.py silk/utils/data_deletion.py silk/utils/pagination.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/django_silk.egg-info/requires.txt new/django_silk-5.5.0/django_silk.egg-info/requires.txt --- old/django_silk-5.3.2/django_silk.egg-info/requires.txt 2024-12-14 06:54:07.000000000 +0100 +++ new/django_silk-5.5.0/django_silk.egg-info/requires.txt 2026-03-07 09:15:49.000000000 +0100 @@ -1,4 +1,6 @@ Django>=4.2 sqlparse -autopep8 gprof2dot>=2017.09.19 + +[formatting] +autopep8 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/docs/index.rst new/django_silk-5.5.0/docs/index.rst --- old/django_silk-5.3.2/docs/index.rst 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/docs/index.rst 2026-03-07 09:15:38.000000000 +0100 @@ -57,5 +57,5 @@ Requirements ------------ -* Django: 4.2, 5.0, 5.1 -* Python: 3.9, 3.10, 3.11, 3.12, 3.13 +* Django: 4.2, 5.1, 5.2, 6.0 +* Python: 3.10, 3.11, 3.12, 3.13, 3.14 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/docs/quickstart.rst new/django_silk-5.5.0/docs/quickstart.rst --- old/django_silk-5.3.2/docs/quickstart.rst 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/docs/quickstart.rst 2026-03-07 09:15:38.000000000 +0100 @@ -12,13 +12,24 @@ Add the following to your ``settings.py``: .. code-block:: python - + MIDDLEWARE = [ ... 'silk.middleware.SilkyMiddleware', ... ] + TEMPLATES = [{ + ... + 'OPTIONS': { + 'context_processors': [ + ... + 'django.template.context_processors.request', + ], + }, + }] + + INSTALLED_APPS = [ ... 'silk.apps.SilkAppConfig' @@ -27,7 +38,7 @@ Add the following to your ``urls.py``: .. code-block:: python - + urlpatterns += [path('silk', include('silk.urls', namespace='silk'))] Run ``migrate`` to create Silk's database tables: @@ -38,6 +49,16 @@ And voila! Silk will begin intercepting requests and queries which you can inspect by visiting ``/silk/`` +Python Snippet Formatting +------------------------- + +Silk supports generating Python snippets to reproduce requests. +To enable autopep8 formatting of these snippets, install Silk with the `formatting` extras: + +.. code-block:: bash + + pip install django-silk[formatting] + Other Installation Options -------------------------- @@ -51,4 +72,4 @@ .. code-block:: bash - pip install -e git+https://github.com/jazzband/django-silk.git#egg=silk + pip install -e git+https://github.com/jazzband/django-silk.git#egg=django_silk diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/docs/troubleshooting.rst new/django_silk-5.5.0/docs/troubleshooting.rst --- old/django_silk-5.3.2/docs/troubleshooting.rst 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/docs/troubleshooting.rst 2026-03-07 09:15:38.000000000 +0100 @@ -18,6 +18,22 @@ See this `github issue <https://github.com/jazzband/django-silk/issues/21>`_ for more details and workarounds. +Context Processor +----------------- + +Silk requires the template context to include a ``request`` object in order to save and analyze it. + +If you see errors like: + +.. code-block:: text + + File "/service/venv/lib/python3.12/site-packages/silk/templatetags/silk_nav.py", line 9, in navactive + path = request.path + ^^^^^^^^^^^^ + AttributeError: 'str' object has no attribute 'path' + +Include ``django.template.context_processors.request`` in your Django settings' ``TEMPLATES`` context processors as `recommended <https://github.com/jazzband/django-silk/issues/805>`_. + Middleware ---------- @@ -29,4 +45,5 @@ To `avoid <https://github.com/jazzband/django-silk/issues/265>`_ `deadlock <https://github.com/jazzband/django-silk/issues/294>`_ `issues <https://github.com/jazzband/django-silk/issues/371>`_, you might want to decouple silk's garbage collection from your webserver's request processing, set ``SILKY_MAX_RECORDED_REQUESTS_CHECK_PERCENT=0`` and trigger it manually, e.g. in a cron job: .. code-block:: bash + python manage.py silk_request_garbage_collect diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/package.json new/django_silk-5.5.0/package.json --- old/django_silk-5.3.2/package.json 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/package.json 2026-03-07 09:15:38.000000000 +0100 @@ -1,6 +1,6 @@ { "name": "silk", - "version": "5.3.2", + "version": "5.5.0", "description": "https://github.com/jazzband/django-silk", "main": "index.js", "directories": { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/project/project/settings.py new/django_silk-5.5.0/project/project/settings.py --- old/django_silk-5.3.2/project/project/settings.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/project/project/settings.py 2026-03-07 09:15:38.000000000 +0100 @@ -106,10 +106,7 @@ 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ - 'django.template.context_processors.debug', 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', ], }, }, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/project/tests/test_end_points.py new/django_silk-5.5.0/project/tests/test_end_points.py --- old/django_silk-5.3.2/project/tests/test_end_points.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/project/tests/test_end_points.py 2026-03-07 09:15:38.000000000 +0100 @@ -61,7 +61,16 @@ .filter(request_id__isnull=False) ) request_id = request_query_data['request_id'] - response = self.client.get(silky_reverse('request_sql', kwargs={'request_id': request_id})) + base_url = silky_reverse('request_sql', kwargs={'request_id': request_id}) + response = self.client.get(base_url) + self.assertTrue(response.status_code == 200) + + # Test with valid page size + response = self.client.get(base_url + "?per_page=100") + self.assertTrue(response.status_code == 200) + + # Test with invalid page size + response = self.client.get(base_url + "?per_page=notanumber") self.assertTrue(response.status_code == 200) def test_request_sql_detail(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/project/tests/test_execute_sql.py new/django_silk-5.5.0/project/tests/test_execute_sql.py --- old/django_silk-5.3.2/project/tests/test_execute_sql.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/project/tests/test_execute_sql.py 2026-03-07 09:15:38.000000000 +0100 @@ -1,6 +1,7 @@ from unittest.mock import Mock, NonCallableMagicMock, NonCallableMock, patch from django.test import TestCase +from django.utils.encoding import force_str from silk.collector import DataCollector from silk.models import Request, SQLQuery @@ -8,14 +9,18 @@ from .util import delete_all_models +_simple_mock_query_sql = 'SELECT * FROM table_name WHERE column1 = %s' +_simple_mock_query_params = ('asdf',) +_non_unicode_binary_mock_query_params = (b'\x0a\x00\x00\xff',) +_unicode_binary_mock_query_params = ('🫠'.encode(),) -def mock_sql(): + +def mock_sql(mock_query_params): mock_sql_query = Mock(spec_set=['_execute_sql', 'query', 'as_sql', 'connection']) mock_sql_query._execute_sql = Mock() mock_sql_query.query = NonCallableMock(spec_set=['model']) mock_sql_query.query.model = Mock() - query_string = 'SELECT * from table_name' - mock_sql_query.as_sql = Mock(return_value=(query_string, ())) + mock_sql_query.as_sql = Mock(return_value=(_simple_mock_query_sql, mock_query_params)) mock_sql_query.connection = NonCallableMock( spec_set=['cursor', 'features', 'ops'], @@ -27,34 +32,35 @@ spec_set=['supports_explaining_query_execution'], supports_explaining_query_execution=True ), - ops=NonCallableMock(spec_set=['explain_query_prefix']), + ops=NonCallableMock(spec_set=['explain_query_prefix'], + explain_query_prefix=Mock(return_value='')), ) - return mock_sql_query, query_string - + return mock_sql_query, mock_query_params -def call_execute_sql(cls, request): - DataCollector().configure(request=request) - delete_all_models(SQLQuery) - cls.mock_sql, cls.query_string = mock_sql() - kwargs = { - 'one': 1, - 'two': 2 - } - cls.args = [1, 2] - cls.kwargs = kwargs - execute_sql(cls.mock_sql, *cls.args, **cls.kwargs) - - -class TestCallNoRequest(TestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - call_execute_sql(cls, None) +class BaseTestCase(TestCase): def tearDown(self): DataCollector().stop_python_profiler() + def call_execute_sql(self, request, mock_query_params): + DataCollector().configure(request=request) + delete_all_models(SQLQuery) + self.query_string = _simple_mock_query_sql + self.mock_sql, self.query_params = mock_sql(mock_query_params) + self.kwargs = { + 'one': 1, + 'two': 2 + } + self.args = [1, 2] + execute_sql(self.mock_sql, *self.args, **self.kwargs) + + +class TestCallNoRequest(BaseTestCase): + def setUp(self): + super().setUp() + self.call_execute_sql(None, _simple_mock_query_params) + def test_called(self): self.mock_sql._execute_sql.assert_called_once_with(*self.args, **self.kwargs) @@ -62,33 +68,33 @@ self.assertEqual(0, len(DataCollector().queries)) -class TestCallRequest(TestCase): - @classmethod - def setUpClass(cls): - super().setUpClass() - call_execute_sql(cls, Request()) - - def tearDown(self): - DataCollector().stop_python_profiler() - - def test_called(self): +class TestCallRequest(BaseTestCase): + def test_query_simple(self): + self.call_execute_sql(Request(), _simple_mock_query_params) self.mock_sql._execute_sql.assert_called_once_with(*self.args, **self.kwargs) - - def test_count(self): self.assertEqual(1, len(DataCollector().queries)) + query = list(DataCollector().queries.values())[0] + expected = self.query_string % tuple(force_str(param) for param in self.query_params) + self.assertEqual(query['query'], expected) - def test_query(self): + def test_query_unicode(self): + self.call_execute_sql(Request(), _unicode_binary_mock_query_params) + self.mock_sql._execute_sql.assert_called_once_with(*self.args, **self.kwargs) + self.assertEqual(1, len(DataCollector().queries)) query = list(DataCollector().queries.values())[0] - self.assertEqual(query['query'], self.query_string) + expected = self.query_string % tuple(force_str(param) for param in self.query_params) + self.assertEqual(query['query'], expected) + def test_query_non_unicode(self): + self.call_execute_sql(Request(), _non_unicode_binary_mock_query_params) + self.mock_sql._execute_sql.assert_called_once_with(*self.args, **self.kwargs) + self.assertEqual(0, len(DataCollector().queries)) -class TestCallSilky(TestCase): - def tearDown(self): - DataCollector().stop_python_profiler() +class TestCallSilky(BaseTestCase): def test_no_effect(self): DataCollector().configure() - sql, _ = mock_sql() + sql, _ = mock_sql(_simple_mock_query_params) sql.query.model = NonCallableMagicMock(spec_set=['__module__']) sql.query.model.__module__ = 'silk.models' # No SQLQuery models should be created for silk requests for obvious reasons @@ -97,10 +103,7 @@ self.assertFalse(mock_DataCollector().register_query.call_count) -class TestCollectorInteraction(TestCase): - def tearDown(self): - DataCollector().stop_python_profiler() - +class TestCollectorInteraction(BaseTestCase): def _query(self): try: query = list(DataCollector().queries.values())[0] @@ -110,23 +113,44 @@ def test_request(self): DataCollector().configure(request=Request.objects.create(path='/path/to/somewhere')) - sql, _ = mock_sql() + sql, _ = mock_sql(_simple_mock_query_params) execute_sql(sql) query = self._query() self.assertEqual(query['request'], DataCollector().request) def test_registration(self): DataCollector().configure(request=Request.objects.create(path='/path/to/somewhere')) - sql, _ = mock_sql() + sql, _ = mock_sql(_simple_mock_query_params) execute_sql(sql) query = self._query() self.assertIn(query, DataCollector().queries.values()) - def test_explain(self): + def test_explain_simple(self): + DataCollector().configure(request=Request.objects.create(path='/path/to/somewhere')) + sql, params = mock_sql(_simple_mock_query_params) + prefix = "EXPLAIN" + mock_cursor = sql.connection.cursor.return_value.__enter__.return_value + sql.connection.ops.explain_query_prefix.return_value = prefix + execute_sql(sql) + self.assertNotIn(prefix, params) + mock_cursor.execute.assert_called_once_with(f"{prefix} {_simple_mock_query_sql}", params) + + def test_explain_unicode(self): + DataCollector().configure(request=Request.objects.create(path='/path/to/somewhere')) + sql, params = mock_sql(_unicode_binary_mock_query_params) + prefix = "EXPLAIN" + mock_cursor = sql.connection.cursor.return_value.__enter__.return_value + sql.connection.ops.explain_query_prefix.return_value = prefix + execute_sql(sql) + self.assertNotIn(prefix, params) + mock_cursor.execute.assert_called_once_with(f"{prefix} {_simple_mock_query_sql}", params) + + def test_explain_non_unicode(self): DataCollector().configure(request=Request.objects.create(path='/path/to/somewhere')) - sql, qs = mock_sql() + sql, params = mock_sql(_non_unicode_binary_mock_query_params) prefix = "EXPLAIN" mock_cursor = sql.connection.cursor.return_value.__enter__.return_value sql.connection.ops.explain_query_prefix.return_value = prefix execute_sql(sql) - mock_cursor.execute.assert_called_once_with(f"{prefix} {qs}", ()) + self.assertNotIn(prefix, params) + self.assertFalse(mock_cursor.execute.called) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/project/tests/test_sensitive_data_in_request.py new/django_silk-5.5.0/project/tests/test_sensitive_data_in_request.py --- old/django_silk-5.3.2/project/tests/test_sensitive_data_in_request.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/project/tests/test_sensitive_data_in_request.py 2026-03-07 09:15:38.000000000 +0100 @@ -75,6 +75,12 @@ expected = f"foo={CLEANSED}" self.assertEqual(expected, self._mask(body)) + def test_sensitive_values_remain_unmasked_with_empty_settings(self): + SilkyConfig().SILKY_SENSITIVE_KEYS = {} + body = "foo=hidethis" + expected = "foo=hidethis" + self.assertEqual(expected, self._mask(body)) + class MaskCredentialsInJsonTest(TestCase): def tearDown(self): @@ -122,6 +128,10 @@ SilkyConfig().SILKY_SENSITIVE_KEYS = {"foo"} self.assertNotIn("hidethis", self._mask({"foo": "hidethis"})) + def test_sensitive_values_remain_unmasked_with_empty_settings(self): + SilkyConfig().SILKY_SENSITIVE_KEYS = {} + self.assertIn("hidethis", self._mask({"foo": "hidethis"})) + class TestEncodingForRequests(TestCase): """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/requirements.txt new/django_silk-5.5.0/requirements.txt --- old/django_silk-5.3.2/requirements.txt 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/requirements.txt 2026-03-07 09:15:38.000000000 +0100 @@ -1,9 +1,9 @@ -coverage==7.6.8 -factory-boy==3.3.1 -freezegun==1.5.1 -networkx==3.2.1 -pillow==11.0.0 -pydot==3.0.3 -pygments==2.18.0 -pytest-cov==6.0.0 -pytest-django==4.9.0 +coverage==7.13.0 +factory-boy==3.3.3 +freezegun==1.5.5 +networkx==3.4.2 +pillow==12.1.1 +pydot==3.0.4 +pygments==2.19.2 +pytest-cov==7.0.0 +pytest-django==4.11.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/setup.py new/django_silk-5.5.0/setup.py --- old/django_silk-5.3.2/setup.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/setup.py 2026-03-07 09:15:38.000000000 +0100 @@ -22,25 +22,28 @@ 'Environment :: Web Environment', 'Framework :: Django', 'Framework :: Django :: 4.2', - 'Framework :: Django :: 5.0', 'Framework :: Django :: 5.1', + 'Framework :: Django :: 5.2', + 'Framework :: Django :: 6.0', 'Intended Audience :: Developers', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', ], install_requires=[ 'Django>=4.2', 'sqlparse', - 'autopep8', 'gprof2dot>=2017.09.19', ], - python_requires='>=3.9', + extras_require={ + 'formatting': ['autopep8'], + }, + python_requires='>=3.10', setup_requires=['setuptools_scm'], ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/silk/code_generation/django_test_client.py new/django_silk-5.5.0/silk/code_generation/django_test_client.py --- old/django_silk-5.3.2/silk/code_generation/django_test_client.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/silk/code_generation/django_test_client.py 2026-03-07 09:15:38.000000000 +0100 @@ -1,6 +1,9 @@ from urllib.parse import urlencode -import autopep8 +try: + import autopep8 +except ImportError: + autopep8 = None from django.template import Context, Template from silk.profiling.dynamic import is_str_typ @@ -42,7 +45,13 @@ data = "'%s'" % data context['data'] = data context['query_params'] = query_params - return autopep8.fix_code( - t.render(Context(context, autoescape=False)), - options=autopep8.parse_args(['--aggressive', '']), - ) + code = t.render(Context(context, autoescape=False)) + if autopep8: + # autopep8 is not a hard requirement, so we check if it's available + # if autopep8 is available, we use it to format the code and do things + # like remove long lines and improve readability + code = autopep8.fix_code( + code, + options=autopep8.parse_args(['--aggressive', '']), + ) + return code diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/silk/middleware.py new/django_silk-5.5.0/silk/middleware.py --- old/django_silk-5.3.2/silk/middleware.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/silk/middleware.py 2026-03-07 09:15:38.000000000 +0100 @@ -2,12 +2,13 @@ import random from django.conf import settings -from django.db import DatabaseError, transaction +from django.db import DatabaseError, router, transaction from django.db.models.sql.compiler import SQLCompiler from django.urls import NoReverseMatch, reverse from django.utils import timezone from django.utils.translation import gettext_lazy as _ +from silk import models from silk.collector import DataCollector from silk.config import SilkyConfig from silk.errors import SilkNotConfigured @@ -142,29 +143,31 @@ request_model = RequestModelFactory(request).construct_request_model() DataCollector().configure(request_model, should_profile=should_profile) - @transaction.atomic() def _process_response(self, request, response): - Logger.debug('Process response') - with silk_meta_profiler(): - collector = DataCollector() - collector.stop_python_profiler() - silk_request = collector.request + # Use a context manager instead of a decorator so db_for_write is evaluated at runtime, + # which is important for dynamic database configurations (e.g., multitenancy). + with transaction.atomic(using=router.db_for_write(models.SQLQuery)): + Logger.debug('Process response') + with silk_meta_profiler(): + collector = DataCollector() + collector.stop_python_profiler() + silk_request = collector.request + if silk_request: + ResponseModelFactory(response).construct_response_model() + silk_request.end_time = timezone.now() + collector.finalise() + else: + Logger.error( + 'No request model was available when processing response. ' + 'Did something go wrong in process_request/process_view?' + '\n' + str(request) + '\n\n' + str(response) + ) + # Need to save the data outside the silk_meta_profiler + # Otherwise the meta time collected in the context manager + # is not taken in account if silk_request: - ResponseModelFactory(response).construct_response_model() - silk_request.end_time = timezone.now() - collector.finalise() - else: - Logger.error( - 'No request model was available when processing response. ' - 'Did something go wrong in process_request/process_view?' - '\n' + str(request) + '\n\n' + str(response) - ) - # Need to save the data outside the silk_meta_profiler - # Otherwise the meta time collected in the context manager - # is not taken in account - if silk_request: - silk_request.save() - Logger.debug('Process response done.') + silk_request.save() + Logger.debug('Process response done.') def process_response(self, request, response): max_attempts = 2 @@ -178,7 +181,7 @@ break except (AttributeError, DatabaseError): if attempts >= max_attempts: - Logger.warn('Exhausted _process_response attempts; not processing request') + Logger.warning('Exhausted _process_response attempts; not processing request') break attempts += 1 return response diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/silk/model_factory.py new/django_silk-5.5.0/silk/model_factory.py --- old/django_silk-5.3.2/silk/model_factory.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/silk/model_factory.py 2026-03-07 09:15:38.000000000 +0100 @@ -91,7 +91,7 @@ pattern = re.compile(key_string, re.I) if isinstance(obj, dict): for key in obj.keys(): - if pattern.search(key): + if key_string and pattern.search(key): obj[key] = RequestModelFactory.CLEANSED_SUBSTITUTE else: obj[key] = replace_pattern_values(obj[key]) @@ -99,18 +99,19 @@ for index, item in enumerate(obj): obj[index] = replace_pattern_values(item) else: - if pattern.search(str(obj)): + if key_string and pattern.search(str(obj)): return RequestModelFactory.CLEANSED_SUBSTITUTE return obj try: json_body = json.loads(body) except Exception as e: - pattern = re.compile(fr'(({key_string})[^=]*)=(.*?)(&|$)', re.M | re.I) - try: - body = re.sub(pattern, f'\\1={RequestModelFactory.CLEANSED_SUBSTITUTE}\\4', body) - except Exception: - Logger.debug(f'{str(e)}') + if key_string: + pattern = re.compile(fr'(({key_string})[^=]*)=(.*?)(&|$)', re.M | re.I) + try: + body = re.sub(pattern, f'\\1={RequestModelFactory.CLEANSED_SUBSTITUTE}\\4', body) + except Exception: + Logger.debug(f'{str(e)}') else: body = json.dumps(replace_pattern_values(json_body), ensure_ascii=SilkyConfig().SILKY_JSON_ENSURE_ASCII) @@ -278,15 +279,11 @@ ) if content and content_type in content_types_json: # TODO: Perhaps theres a way to format the JSON without parsing it? - if not isinstance(content, str): - # byte string is not compatible with json.loads(...) - # and json.dumps(...) in python3 - content = content.decode() try: body = json.dumps(json.loads(content), sort_keys=True, indent=4 , ensure_ascii=SilkyConfig().SILKY_JSON_ENSURE_ASCII) except (TypeError, ValueError): - Logger.warn( + Logger.warning( 'Response to request with pk %s has content type %s but was unable to parse it' % (self.request.pk, content_type) ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/silk/models.py new/django_silk-5.5.0/silk/models.py --- old/django_silk-5.3.2/silk/models.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/silk/models.py 2026-03-07 09:15:38.000000000 +0100 @@ -8,7 +8,7 @@ from django.conf import settings from django.core.files.storage import storages from django.core.files.storage.handler import InvalidStorageError -from django.db import models, transaction +from django.db import models, router, transaction from django.db.models import ( BooleanField, CharField, @@ -226,18 +226,18 @@ # TODO rewrite docstring class SQLQueryManager(models.Manager): - @transaction.atomic def bulk_create(self, *args, **kwargs): """ensure that num_sql_queries remains consistent. Bulk create does not call the model save() method and hence we must add this logic here too""" - if len(args): - objs = args[0] - else: - objs = kwargs.get('objs') - for obj in objs: - obj.prepare_save() + with transaction.atomic(using=router.db_for_write(SQLQuery)): + if len(args): + objs = args[0] + else: + objs = kwargs.get('objs') + for obj in objs: + obj.prepare_save() - return super().bulk_create(*args, **kwargs) + return super().bulk_create(*args, **kwargs) class SQLQuery(models.Model): @@ -322,16 +322,16 @@ self.request.num_sql_queries += 1 self.request.save(update_fields=['num_sql_queries']) - @transaction.atomic() def save(self, *args, **kwargs): - self.prepare_save() - super().save(*args, **kwargs) + with transaction.atomic(using=router.db_for_write(self)): + self.prepare_save() + super().save(*args, **kwargs) - @transaction.atomic() def delete(self, *args, **kwargs): - self.request.num_sql_queries -= 1 - self.request.save() - super().delete(*args, **kwargs) + with transaction.atomic(using=router.db_for_write(self)): + self.request.num_sql_queries -= 1 + self.request.save() + super().delete(*args, **kwargs) class BaseProfile(models.Model): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/silk/sql.py new/django_silk-5.5.0/silk/sql.py --- old/django_silk-5.3.2/silk/sql.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/silk/sql.py 2026-03-07 09:15:38.000000000 +0100 @@ -53,7 +53,11 @@ # currently we cannot use explain() method # for queries other than `select` - prefixed_query = f"{prefix} {q}" + if q.upper().startswith(prefix.upper()): + # to avoid "EXPLAIN EXPLAIN", do not add prefix + prefixed_query = q + else: + prefixed_query = f"{prefix} {q}" with connection.cursor() as cur: cur.execute(prefixed_query, params) result = _unpack_explanation(cur.fetchall()) @@ -77,7 +81,13 @@ return iter([]) else: return - sql_query = q % tuple(force_str(param) for param in params) + try: + sql_query = q % tuple(force_str(param) for param in params) + except UnicodeDecodeError: + # Sometimes `force_str` can still raise a UnicodeDecodeError + # Reference: https://github.com/jazzband/django-silk/issues?q=encoding + # This could log a warning but given this is run in the hot path, logging could be too expensive. + return self._execute_sql(*args, **kwargs) if _should_wrap(sql_query): tb = ''.join(reversed(traceback.format_stack())) query_dict = { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/silk/static/silk/js/pages/sql.js new/django_silk-5.5.0/silk/static/silk/js/pages/sql.js --- old/django_silk-5.3.2/silk/static/silk/js/pages/sql.js 1970-01-01 01:00:00.000000000 +0100 +++ new/django_silk-5.5.0/silk/static/silk/js/pages/sql.js 2026-03-07 09:15:38.000000000 +0100 @@ -0,0 +1,17 @@ +$(document).ready(function () { + document.querySelectorAll(".data-row").forEach((rowElement) => { + let sqlDetailUrl = rowElement.dataset.sqlDetailUrl; + rowElement.addEventListener("mouseup", (e) => { + switch (e.button) { + case 0: + window.location = sqlDetailUrl; + break; + case 1: + window.open(sqlDetailUrl); + break; + default: + break; + } + }); + }); +}); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/silk/templates/silk/sql.html new/django_silk-5.5.0/silk/templates/silk/sql.html --- old/django_silk-5.3.2/silk/templates/silk/sql.html 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/silk/templates/silk/sql.html 2026-03-07 09:15:38.000000000 +0100 @@ -4,12 +4,14 @@ {% load silk_filters %} {% load static %} {% load silk_inclusion %} +{% load silk_urls %} {% block pagetitle %}Silky - SQL - {{ silk_request.path }}{% endblock %} {% block js %} <script type="text/javascript" src="{% static 'silk/lib/sortable.js' %}"></script> {{ block.super }} + <script src="{% static 'silk/js/pages/sql.js' %}"></script> {% endblock %} {% block style %} @@ -20,6 +22,26 @@ <link rel="stylesheet" href="{% static 'silk/css/components/cell.css' %}"> {% endblock %} +{% block filter %} + <form id="filter-form" action="." method="get"></form> + + <div class="menu-item"> + <div class="menu-item-outer"> + <div class="menu-item-inner"> + <label>Show: + <select name="per_page" form="filter-form" onchange="this.form.submit();"> + {% for option in options_page_size %} + <option value="{{ option }}" + {% if option == per_page %}selected{% endif %}>{{ option }}</option> + {% endfor %} + </select> + </label> + </div> + </div> + </div> + {{ block.super }} +{% endblock %} + {% block menu %} {% if profile %} {% profile_menu request profile silk_request %} @@ -57,16 +79,8 @@ <th class="right-aligned">Execution Time (ms)</th> </tr> {% for sql_query in items %} - <!-- TODO: Pretty grimy... --> - <tr class="data-row" onclick="window.location=' \ - {% if profile and silk_request %}\ - {% url "silk:request_and_profile_sql_detail" silk_request.id profile.id sql_query.id %}\ - {% elif profile %}\ - {% url "silk:profile_sql_detail" profile.id sql_query.id %}\ - {% elif silk_request %}\ - {% url "silk:request_sql_detail" silk_request.id sql_query.id %}\ - {% endif %}\ - ';"> + {% sql_detail_url silk_request profile sql_query as detail_url %} + <tr class="data-row" data-sql-detail-url="{{ detail_url }}"> <td class="left-aligned">+{{ sql_query.start_time_relative }}</td> <td class="left-aligned">{{ sql_query.first_keywords }}</td> <td class="left-aligned">{{ sql_query.tables_involved|join:", " }}</td> @@ -77,6 +91,7 @@ </table> + {% if items.paginator.num_pages > 1 %} <div id="table-pagination" class="pagination"> <div class="current"> Page {{ items.number }} of {{ items.paginator.num_pages }}. @@ -84,18 +99,19 @@ <div class="step-links"> {% if items.has_previous %} - <a href="?page={{ items.previous_page_number }}">previous</a> + <a href="?page={{ items.previous_page_number }}&per_page={{ per_page }}">previous</a> {% else %} previous {% endif %} | {% if items.has_next %} - <a href="?page={{ items.next_page_number }}">next</a> + <a href="?page={{ items.next_page_number }}&per_page={{ per_page }}">next</a> {% else %} next {% endif %} </div> </div> + {% endif %} </div> </div> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/silk/templatetags/silk_urls.py new/django_silk-5.5.0/silk/templatetags/silk_urls.py --- old/django_silk-5.3.2/silk/templatetags/silk_urls.py 1970-01-01 01:00:00.000000000 +0100 +++ new/django_silk-5.5.0/silk/templatetags/silk_urls.py 2026-03-07 09:15:38.000000000 +0100 @@ -0,0 +1,17 @@ +from django.template import Library +from django.urls import reverse + +register = Library() + + [email protected]_tag +def sql_detail_url(silk_request, profile, sql_query): + if profile and silk_request: + return reverse( + "silk:request_and_profile_sql_detail", + args=[silk_request.id, profile.id, sql_query.id], + ) + elif profile: + return reverse("silk:profile_sql_detail", args=[profile.id, sql_query.id]) + elif silk_request: + return reverse("silk:request_sql_detail", args=[silk_request.id, sql_query.id]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/silk/utils/pagination.py new/django_silk-5.5.0/silk/utils/pagination.py --- old/django_silk-5.3.2/silk/utils/pagination.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/silk/utils/pagination.py 2026-03-07 09:15:38.000000000 +0100 @@ -3,8 +3,8 @@ __author__ = 'mtford' -def _page(request, query_set): - paginator = Paginator(query_set, 200) +def _page(request, query_set, per_page=200): + paginator = Paginator(query_set, per_page) page_number = request.GET.get('page') try: page = paginator.page(page_number) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/silk/views/request_detail.py new/django_silk-5.5.0/silk/views/request_detail.py --- old/django_silk-5.3.2/silk/views/request_detail.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/silk/views/request_detail.py 2026-03-07 09:15:38.000000000 +0100 @@ -19,24 +19,28 @@ query_params = None if silk_request.query_params: query_params = json.loads(silk_request.query_params) - body = silk_request.raw_body - try: - body = json.loads(body) # Incase encoded as JSON - except (ValueError, TypeError): - pass + context = { 'silk_request': silk_request, - 'curl': curl_cmd(url=request.build_absolute_uri(silk_request.path), - method=silk_request.method, - query_params=query_params, - body=body, - content_type=silk_request.content_type), 'query_params': json.dumps(query_params, sort_keys=True, indent=4) if query_params else None, - 'client': gen(path=silk_request.path, - method=silk_request.method, - query_params=query_params, - data=body, - content_type=silk_request.content_type), 'request': request } + + if len(silk_request.raw_body) < 20000: # Don't do this for large request + body = silk_request.raw_body + try: + body = json.loads(body) # Incase encoded as JSON + except (ValueError, TypeError): + pass + context['curl'] = curl_cmd(url=request.build_absolute_uri(silk_request.path), + method=silk_request.method, + query_params=query_params, + body=body, + content_type=silk_request.content_type) + context['client'] = gen(path=silk_request.path, + method=silk_request.method, + query_params=query_params, + data=body, + content_type=silk_request.content_type) + return render(request, 'silk/request.html', context) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/silk/views/sql.py new/django_silk-5.5.0/silk/views/sql.py --- old/django_silk-5.3.2/silk/views/sql.py 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/silk/views/sql.py 2026-03-07 09:15:38.000000000 +0100 @@ -10,25 +10,33 @@ class SQLView(View): + page_sizes = [5, 10, 25, 100, 200, 500, 1000] + default_page_size = 200 @method_decorator(login_possibly_required) @method_decorator(permissions_possibly_required) def get(self, request, *_, **kwargs): request_id = kwargs.get('request_id') profile_id = kwargs.get('profile_id') + try: + per_page = int(request.GET.get('per_page', self.default_page_size)) + except (TypeError, ValueError): + per_page = self.default_page_size context = { 'request': request, + 'options_page_size': self.page_sizes, + 'per_page': per_page, } if request_id: silk_request = Request.objects.get(id=request_id) query_set = SQLQuery.objects.filter(request=silk_request).order_by('-start_time') for q in query_set: q.start_time_relative = q.start_time - silk_request.start_time - page = _page(request, query_set) + page = _page(request, query_set, per_page) context['silk_request'] = silk_request if profile_id: p = Profile.objects.get(id=profile_id) - page = _page(request, p.queries.order_by('-start_time').all()) + page = _page(request, p.queries.order_by('-start_time').all(), per_page) context['profile'] = p if not (request_id or profile_id): raise KeyError('no profile_id or request_id') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django_silk-5.3.2/tox.ini new/django_silk-5.5.0/tox.ini --- old/django_silk-5.3.2/tox.ini 2024-12-14 06:53:50.000000000 +0100 +++ new/django_silk-5.5.0/tox.ini 2026-03-07 09:15:38.000000000 +0100 @@ -1,22 +1,23 @@ [gh-actions] python = - 3.9: py39 3.10: py310 3.11: py311 3.12: py312 3.13: py313 + 3.14: py314 [gh-actions:env] DJANGO = 4.2: dj42 - 5.0: dj50 5.1: dj51 + 5.2: dj52 + 6.0: dj60 main: djmain [tox] envlist = - py{38,39,310,311,312,313}-dj42-{sqlite3,mysql,postgresql} - py{310,311,312,313}-dj{50,51,main}-{sqlite3,mysql,postgresql} + py{310,311,312,313,314}-dj{42,50,51,52}-{sqlite3,mysql,postgresql} + py{312,313,314}-dj{60,main}-{sqlite3,mysql,postgresql} [testenv] usedevelop = True @@ -28,11 +29,14 @@ mysql: mysqlclient postgresql: psycopg2-binary dj42: django>=4.2,<4.3 - dj50: django>=5.0,<5.1 dj51: django>=5.1,<5.2 + dj52: django>=5.2,<5.3 + dj60: django>=6.0,<6.1 djmain: https://github.com/django/django/archive/main.tar.gz py312: setuptools py313: setuptools + py314: setuptools +extras = formatting setenv = PYTHONPATH={toxinidir}:{toxinidir} PYTHONDONTWRITEBYTECODE=1
