diff --git a/docs/docs/develop/contributing.md b/docs/docs/develop/contributing.md index 5616ec5a40..4a404c5868 100644 --- a/docs/docs/develop/contributing.md +++ b/docs/docs/develop/contributing.md @@ -208,6 +208,21 @@ coverage html -i The coverage database is also generated in the CI-pipeline and exposd for 14 days as a artifact named `coverage`. +### Database Query Profiling + +It may be useful during development to profile parts of the backend code to see how many database queries are executed. To that end, the `count_queries` context manager can be used to count the number of queries executed in a specific code block. + +```python +from InvenTree.helpers import count_queries + +with count_queries("My code block"): + # Code block to profile + ... +``` + +A developer can use this to profile a specific code block, and the number of queries executed will be printed to the console. + + ## Code Style Code style is automatically checked as part of the project's CI pipeline on GitHub. This means that any pull requests which do not conform to the style guidelines will fail CI checks. diff --git a/src/backend/InvenTree/InvenTree/unit_test.py b/src/backend/InvenTree/InvenTree/unit_test.py index 4b202d264b..d87eac3133 100644 --- a/src/backend/InvenTree/InvenTree/unit_test.py +++ b/src/backend/InvenTree/InvenTree/unit_test.py @@ -8,6 +8,7 @@ import re import time from contextlib import contextmanager from pathlib import Path +from typing import Optional from unittest import mock from django.contrib.auth import get_user_model @@ -25,6 +26,33 @@ from plugin import registry from plugin.models import PluginConfig +@contextmanager +def count_queries( + msg: Optional[str] = None, log_to_file: bool = False, using: str = 'default' +): # pragma: no cover + """Helper function to count the number of queries executed. + + Arguments: + msg: Optional message to print after counting queries + log_to_file: If True, log the queries to a file (default = False) + using: The database connection to use (default = 'default') + """ + with CaptureQueriesContext(connections[using]) as context: + yield + + n = len(context.captured_queries) + + if log_to_file: + with open('queries.txt', 'w', encoding='utf-8') as f: + for q in context.captured_queries: + f.write(str(q['sql']) + '\n\n') + + if msg: + print(f'{msg}: Executed {n} queries') + else: + print(f'Executed {n} queries') + + def addUserPermission(user: User, app_name: str, model_name: str, perm: str) -> None: """Add a specific permission for the provided user. diff --git a/src/backend/InvenTree/plugin/test_api.py b/src/backend/InvenTree/plugin/test_api.py index 2433017d1f..b56ed50ef7 100644 --- a/src/backend/InvenTree/plugin/test_api.py +++ b/src/backend/InvenTree/plugin/test_api.py @@ -61,8 +61,6 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase): def test_plugin_install(self): """Test the plugin install command.""" - from django.conf import settings - url = reverse('api-plugin-install') # invalid package name @@ -87,23 +85,18 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase): {'confirm': True, 'packagename': self.PKG_NAME}, expected_code=201, max_query_time=30, - max_query_count=400, + max_query_count=450, ).data self.assertEqual(data['success'], 'Installed plugin successfully') - # If we are running in docker mode, the plugin file is reinstalled too - # In that case, the expected query count is higher - query_count = 450 if settings.DOCKER else 350 - query_time = 60 if settings.DOCKER else 30 - # valid - github url data = self.post( url, {'confirm': True, 'url': self.PKG_URL}, expected_code=201, - max_query_count=query_count, - max_query_time=query_time, + max_query_count=450, + max_query_time=60, ).data self.assertEqual(data['success'], 'Installed plugin successfully') @@ -613,31 +606,25 @@ class PluginFullAPITest(PluginMixin, InvenTreeAPITestCase): @override_settings(PLUGIN_TESTING_SETUP=True) def test_full_process(self): """Test the full plugin install/uninstall process via API.""" - from django.conf import settings - install_slug = 'inventree-brother-plugin' slug = 'brother' - # Note that if testing in docker mode, the plugin file is reinstalled too - # In that case, the expected query count is higher - query_count = 450 if settings.DOCKER else 350 - query_time = 60 if settings.DOCKER else 30 - # Install a plugin data = self.post( reverse('api-plugin-install'), {'confirm': True, 'packagename': install_slug}, expected_code=201, - max_query_time=query_time, - max_query_count=query_count, + max_query_time=30, + max_query_count=450, ).data + self.assertEqual(data['success'], 'Installed plugin successfully') # Activate the plugin data = self.patch( reverse('api-plugin-detail-activate', kwargs={'plugin': slug}), data={'active': True}, - max_query_count=320, + max_query_count=450, ).data self.assertEqual(data['active'], True)