diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36ee66a1..83213b47 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: fail-fast: false matrix: python-version: ["3.11", "3.12"] - toxenv: ["django42"] # "quality", "pii_check", "check_keywords" + toxenv: ["django42", "quality", "pii_check", "check_keywords"] services: mysql: diff --git a/.github/workflows/trivy-code-scanning.yml b/.github/workflows/trivy-code-scanning.yml index bbaef153..72abad83 100644 --- a/.github/workflows/trivy-code-scanning.yml +++ b/.github/workflows/trivy-code-scanning.yml @@ -23,6 +23,7 @@ jobs: scan-type: "fs" format: "sarif" output: "trivy-results.sarif" + args: --skip-update - name: Upload Trivy scan results to GitHub Security tab uses: github/codeql-action/upload-sarif@v3 diff --git a/.gitignore b/.gitignore index 998519fd..05e4de47 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ venv/ .idea/ - # Helm dependencies /helmcharts/**/*.tgz + +reports/ diff --git a/.pycodestyle b/.pycodestyle new file mode 100644 index 00000000..d53c4097 --- /dev/null +++ b/.pycodestyle @@ -0,0 +1,4 @@ +[pycodestyle] +exclude = .git, .tox, migrations +ignore = E731 +max-line-length = 120 diff --git a/notesapi/v1/management/commands/bulk_create_notes.py b/notesapi/v1/management/commands/bulk_create_notes.py index c13cb818..5f52b2e8 100644 --- a/notesapi/v1/management/commands/bulk_create_notes.py +++ b/notesapi/v1/management/commands/bulk_create_notes.py @@ -3,45 +3,47 @@ import os import random import uuid -from optparse import make_option from django.core.management.base import BaseCommand, CommandError -from django.db import transaction from notesapi.v1.models import Note -def extract_comma_separated_list(option, opt_str, value, parser): +def extract_comma_separated_list(option, value, parser): """Parse an option string as a comma separated list""" setattr(parser.values, option.dest, [course_id.strip() for course_id in value.split(',')]) class Command(BaseCommand): args = '' + def add_arguments(self, parser): parser.add_argument( - '--per_user', + '--per_user', action='store', - type='int', + type=int, default=50, help='number of notes that should be attributed to each user (default 50)' - ), + ) + parser.add_argument( '--course_ids', action='callback', callback=extract_comma_separated_list, - type='string', + type=str, default=['edX/DemoX/Demo_Course'], help='comma-separated list of course_ids for which notes should be randomly attributed' - ), + ) + parser.add_argument( '--batch_size', action='store', - type='int', + type=int, default=1000, help='number of notes that should be bulk inserted at a time - useful for getting around the maximum SQL ' 'query size' ) + help = 'Add N random notes to the database' def handle(self, *args, **options): @@ -58,6 +60,7 @@ def handle(self, *args, **options): for notes_chunk in grouper_it(note_iter(total_notes, notes_per_user, course_ids), batch_size): Note.objects.bulk_create(notes_chunk) + def note_iter(total_notes, notes_per_user, course_ids): """ Return an iterable of random notes data of length `total_notes`. @@ -85,7 +88,9 @@ def weighted_get_words(weighted_num_words): random.choice([word_count for word_count, weight in weighted_num_words for i in range(weight)]) ) - get_new_user_id = lambda: uuid.uuid4().hex + def get_new_user_id(): + return uuid.uuid4().hex + user_id = get_new_user_id() for note_count in range(total_notes): @@ -108,7 +113,6 @@ def grouper_it(iterable, batch_size): Return an iterator of iterators. Each child iterator yields the next `batch_size`-many elements from `iterable`. """ - iterator = iter(iterable) while True: chunk_it = itertools.islice(iterable, batch_size) try: diff --git a/notesapi/v1/models.py b/notesapi/v1/models.py index 72f8548a..3a2b276c 100644 --- a/notesapi/v1/models.py +++ b/notesapi/v1/models.py @@ -33,13 +33,13 @@ def create(cls, note_dict): if len(note_dict) == 0: raise ValidationError('Note must have a body.') - ranges = note_dict.get('ranges', list()) + ranges = note_dict.get('ranges', []) if len(ranges) < 1: raise ValidationError('Note must contain at least one range.') note_dict['ranges'] = json.dumps(ranges) note_dict['user_id'] = note_dict.pop('user', None) - note_dict['tags'] = json.dumps(note_dict.get('tags', list()), ensure_ascii=False) + note_dict['tags'] = json.dumps(note_dict.get('tags', []), ensure_ascii=False) return cls(**note_dict) diff --git a/notesapi/v1/search_indexes/backends/note.py b/notesapi/v1/search_indexes/backends/note.py index f2cf9fe5..416db9dc 100644 --- a/notesapi/v1/search_indexes/backends/note.py +++ b/notesapi/v1/search_indexes/backends/note.py @@ -9,6 +9,7 @@ __all__ = ('CompoundSearchFilterBackend', 'FilteringFilterBackend') +# pylint: disable=abstract-method class CompoundSearchFilterBackend(CompoundSearchFilterBackendOrigin): """ Extends compound search backend. diff --git a/notesapi/v1/search_indexes/serializers/note.py b/notesapi/v1/search_indexes/serializers/note.py index 1f8fd171..ee02fc2a 100644 --- a/notesapi/v1/search_indexes/serializers/note.py +++ b/notesapi/v1/search_indexes/serializers/note.py @@ -54,6 +54,6 @@ def get_tags(self, note): Return note tags. """ if hasattr(note.meta, 'highlight') and hasattr(note.meta.highlight, 'tags'): - return [i for i in note.meta.highlight.tags] + return list(note.meta.highlight.tags) - return [i for i in note.tags] if note.tags else [] + return list(note.tags) if note.tags else [] diff --git a/notesapi/v1/tests/test_update_index.py b/notesapi/v1/tests/test_update_index.py index 8cc42cff..f5506e3c 100644 --- a/notesapi/v1/tests/test_update_index.py +++ b/notesapi/v1/tests/test_update_index.py @@ -1,10 +1,10 @@ from unittest import skipIf +import factory from django.conf import settings from django.core.management import call_command -from django.urls import reverse from django.db.models import signals -import factory +from django.urls import reverse from .test_views import BaseAnnotationViewTests @@ -51,7 +51,7 @@ def test_delete(self): # Delete first note. url = reverse('api:v1:annotations_detail', kwargs={'annotation_id': first_note['id']}) - response = self.client.delete(url, self.headers) + response = self.client.delete(url, self.headers) # pylint: disable=unused-variable # Delete second note. url = reverse('api:v1:annotations_detail', kwargs={'annotation_id': second_note['id']}) diff --git a/notesapi/v1/tests/test_views.py b/notesapi/v1/tests/test_views.py index 20df1e48..750a61b9 100644 --- a/notesapi/v1/tests/test_views.py +++ b/notesapi/v1/tests/test_views.py @@ -1,17 +1,15 @@ -import sys import unittest from calendar import timegm from datetime import datetime, timedelta +from unittest.mock import patch from urllib import parse +import ddt +import jwt from django.conf import settings from django.core.management import call_command from django.test.utils import override_settings from django.urls import reverse -from unittest.mock import patch - -import ddt -import jwt from rest_framework import status from rest_framework.test import APITestCase @@ -21,9 +19,9 @@ TEST_OTHER_USER = "test_other_user_id" if not settings.ES_DISABLED: - from notesapi.v1.search_indexes.documents import NoteDocument + from notesapi.v1.search_indexes.documents import NoteDocument # pylint: disable=unused-import else: - def call_command(*args, **kwargs): + def call_command(*args, **kwargs): # pylint: disable=function-redefined pass @@ -111,7 +109,7 @@ def get_annotations(self, query_parameters=None, expected_status=200): self.assertEqual(expected_status, response.status_code) return response.data - # pylint: disable=too-many-arguments + # pylint: disable=too-many-positional-arguments def verify_pagination_info( self, response, total_annotations, @@ -161,7 +159,7 @@ def get_page_value(url, current_page): self.assertEqual(get_page_value(response['next'], response['current_page']), next_page) self.assertEqual(response['start'], start) - # pylint: disable=too-many-arguments + # pylint: disable=too-many-positional-arguments def verify_list_view_pagination( self, query_parameters, @@ -176,7 +174,6 @@ def verify_list_view_pagination( """ Verify pagination information for AnnotationListView """ - total_annotations = total_annotations for i in range(total_annotations): self._create_annotation(text=f'annotation {i}') @@ -192,7 +189,7 @@ def verify_list_view_pagination( start=start ) - # pylint: disable=too-many-arguments + # pylint: disable=too-many-positional-arguments def verify_search_view_pagination( self, query_parameters, @@ -207,7 +204,6 @@ def verify_search_view_pagination( """ Verify pagination information for AnnotationSearchView """ - total_annotations = total_annotations for i in range(total_annotations): self._create_annotation(text=f'annotation {i}') @@ -370,7 +366,7 @@ def test_create_maximum_allowed(self): # if user tries to create note in a different course it should succeed kwargs = {'course_id': 'test-course-id-2'} response = self._create_annotation(**kwargs) - self.assertTrue('id' in response) + self.assertIn('id', response) # if another user to tries to create note in first course it should succeed token = get_id_token(TEST_OTHER_USER) @@ -378,7 +374,7 @@ def test_create_maximum_allowed(self): self.headers = {'user': TEST_OTHER_USER} kwargs = {'user': TEST_OTHER_USER} response = self._create_annotation(**kwargs) - self.assertTrue('id' in response) + self.assertIn('id', response) def test_read_all_no_annotations(self): """ @@ -437,7 +433,7 @@ def test_read_all_no_query_param(self): {'page': 2, 'annotations_per_page': 10, 'previous_page': 1, 'next_page': 3, 'start': 10}, {'page': 3, 'annotations_per_page': 3, 'previous_page': 2, 'next_page': None, 'start': 20} ) - # pylint: disable=too-many-arguments + # pylint: disable=too-many-positional-arguments def test_pagination_multiple_pages(self, page, annotations_per_page, previous_page, next_page, start): """ Verify that pagination info is correct when we have data spanned on multiple pages. @@ -1081,7 +1077,7 @@ def test_search_highlight_tag(self): {'page': 2, 'annotations_per_page': 10, 'previous_page': 1, 'next_page': 3, 'start': 10}, {'page': 3, 'annotations_per_page': 3, 'previous_page': 2, 'next_page': None, 'start': 20} ) - # pylint: disable=too-many-arguments + # pylint: disable=too-many-positional-arguments def test_pagination_multiple_pages(self, page, annotations_per_page, previous_page, next_page, start): """ Verify that pagination info is correct when we have data spanned on multiple pages. @@ -1221,7 +1217,7 @@ def test_no_token(self): """ 403 when no token is provided """ - self.client._credentials = {} + self.client._credentials = {} # pylint: disable=protected-access response = self.client.get(self.url, self.headers) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) diff --git a/notesapi/v1/utils.py b/notesapi/v1/utils.py index f1186708..14e20a72 100644 --- a/notesapi/v1/utils.py +++ b/notesapi/v1/utils.py @@ -42,7 +42,7 @@ def dict_to_querydict(dict_): query_dict.setlist(name, value) else: query_dict.appendlist(name, value) - query_dict._mutable = False + query_dict._mutable = False # pylint: disable=protected-access return query_dict diff --git a/notesapi/v1/views.py b/notesapi/v1/views.py index 872c5c17..7f479f18 100644 --- a/notesapi/v1/views.py +++ b/notesapi/v1/views.py @@ -1,3 +1,4 @@ +# pylint:disable=possibly-used-before-assignment import json import logging import newrelic.agent @@ -171,9 +172,10 @@ def initiate_es_specific_state_if_is_enabled(self): Should be called in the class `__init__` method. """ if not settings.ES_DISABLED: - self.client = connections.get_connection(self.document._get_using()) - self.index = self.document._index._name - self.mapping = self.document._doc_type.mapping.properties.name + self.client = connections.get_connection(self.document._get_using()) # pylint: disable=protected-access + self.index = self.document._index._name # pylint: disable=protected-access + self.mapping = self.document._doc_type.mapping.properties.name # pylint: disable=protected-access + # pylint: disable=protected-access self.search = Search(using=self.client, index=self.index, doc_type=self.document._doc_type.name) @property @@ -216,9 +218,13 @@ def paginator(self): """ if not hasattr(self, '_paginator'): if self.pagination_class is None: - self._paginator = None + self._paginator = None # pylint: disable=attribute-defined-outside-init else: - self._paginator = self.pagination_class() if self.is_es_disabled else ESNotesPagination() + self._paginator = ( # pylint: disable=attribute-defined-outside-init + self.pagination_class() + if self.is_es_disabled + else ESNotesPagination() + ) return self._paginator @@ -279,7 +285,7 @@ def build_query_params_state(self): else: self.query_params['user'] = self.params['user'] - def get(self, *args, **kwargs): # pylint: disable=unused-argument + def get(self, *args, **kwargs): """ Search annotations in most appropriate storage """ @@ -294,7 +300,7 @@ class AnnotationRetireView(GenericAPIView): Administrative functions for the notes service. """ - def post(self, *args, **kwargs): # pylint: disable=unused-argument + def post(self, *args, **kwargs): """ Delete all annotations for a user. """ @@ -422,7 +428,7 @@ class AnnotationListView(GenericAPIView): serializer_class = NoteSerializer - def get(self, *args, **kwargs): # pylint: disable=unused-argument + def get(self, *args, **kwargs): """ Get paginated list of all annotations. """ @@ -440,7 +446,7 @@ def get(self, *args, **kwargs): # pylint: disable=unused-argument response = self.get_paginated_response(serializer.data) return response - def post(self, *args, **kwargs): # pylint: disable=unused-argument + def post(self, *args, **kwargs): """ Create a new annotation. @@ -549,7 +555,7 @@ class AnnotationDetailView(APIView): * HTTP_204_NO_CONTENT is returned """ - def get(self, *args, **kwargs): # pylint: disable=unused-argument + def get(self, *args, **kwargs): """ Get an existing annotation. """ @@ -563,7 +569,7 @@ def get(self, *args, **kwargs): # pylint: disable=unused-argument serializer = NoteSerializer(note) return Response(serializer.data) - def put(self, *args, **kwargs): # pylint: disable=unused-argument + def put(self, *args, **kwargs): """ Update an existing annotation. """ @@ -587,7 +593,7 @@ def put(self, *args, **kwargs): # pylint: disable=unused-argument serializer = NoteSerializer(note) return Response(serializer.data) - def delete(self, *args, **kwargs): # pylint: disable=unused-argument + def delete(self, *args, **kwargs): """ Delete an annotation. """ diff --git a/notesserver/docker_gunicorn_configuration.py b/notesserver/docker_gunicorn_configuration.py index fb612f73..2efedcd1 100644 --- a/notesserver/docker_gunicorn_configuration.py +++ b/notesserver/docker_gunicorn_configuration.py @@ -1,7 +1,9 @@ """ gunicorn configuration file: http://docs.gunicorn.org/en/develop/configure.html """ -import multiprocessing +from django.conf import settings +from django.core import cache as django_cache + preload_app = True timeout = 300 @@ -9,6 +11,7 @@ workers = 2 + def pre_request(worker, req): worker.log.info("%s %s" % (req.method, req.path)) @@ -20,12 +23,10 @@ def close_all_caches(): # another worker. # We do this in a way that is safe for 1.4 and 1.8 while we still have some # 1.4 installations. - from django.conf import settings - from django.core import cache as django_cache if hasattr(django_cache, 'caches'): get_cache = django_cache.caches.__getitem__ else: - get_cache = django_cache.get_cache + get_cache = django_cache.get_cache # pylint: disable=no-member for cache_name in settings.CACHES: cache = get_cache(cache_name) if hasattr(cache, 'close'): @@ -41,5 +42,5 @@ def close_all_caches(): cache.close() -def post_fork(server, worker): +def post_fork(server, worker): # pylint: disable=unused-argument close_all_caches() diff --git a/notesserver/settings/common.py b/notesserver/settings/common.py index dd05f231..7afeee7c 100644 --- a/notesserver/settings/common.py +++ b/notesserver/settings/common.py @@ -1,12 +1,12 @@ import os -import sys +from django.conf import settings DEBUG = False TEMPLATE_DEBUG = False DISABLE_TOKEN_CHECK = False USE_TZ = True TIME_ZONE = 'UTC' -AUTH_USER_MODEL = 'auth.User' +AUTH_USER_MODEL = settings.AUTH_USER_MODEL # This value needs to be overriden in production. SECRET_KEY = 'CHANGEME' @@ -127,7 +127,7 @@ DEFAULT_NOTES_PAGE_SIZE = 25 -### Maximum number of allowed notes for each student per course ### +# Maximum number of allowed notes for each student per course MAX_NOTES_PER_COURSE = 500 ELASTICSEARCH_URL = 'localhost:9200' diff --git a/notesserver/settings/dev.py b/notesserver/settings/dev.py index 628082f4..54277e3c 100644 --- a/notesserver/settings/dev.py +++ b/notesserver/settings/dev.py @@ -1,6 +1,6 @@ from notesserver.settings.logger import build_logging_config -from .common import * +from .common import * # pylint: disable=wildcard-import DEBUG = True LOG_SETTINGS_DEBUG = True diff --git a/notesserver/settings/devstack.py b/notesserver/settings/devstack.py index 718df924..39922fbf 100644 --- a/notesserver/settings/devstack.py +++ b/notesserver/settings/devstack.py @@ -1,6 +1,8 @@ +import os + from notesserver.settings.logger import build_logging_config -from .common import * +from .common import * # pylint: disable=wildcard-import DEBUG = True LOG_SETTINGS_DEBUG = True diff --git a/notesserver/settings/test.py b/notesserver/settings/test.py index 343ae73a..74c8c208 100644 --- a/notesserver/settings/test.py +++ b/notesserver/settings/test.py @@ -1,4 +1,7 @@ -from .common import * +import os +import sys + +from .common import * # pylint: disable=wildcard-import DATABASES = { 'default': { diff --git a/notesserver/settings/test_es_disabled.py b/notesserver/settings/test_es_disabled.py index 353b2244..58feab1d 100644 --- a/notesserver/settings/test_es_disabled.py +++ b/notesserver/settings/test_es_disabled.py @@ -1,4 +1,4 @@ -from .test import * +from .test import * # pylint: disable=wildcard-import ES_DISABLED = True ELASTICSEARCH_DSL = {'default': {}} diff --git a/notesserver/settings/yaml_config.py b/notesserver/settings/yaml_config.py index c1a8c17d..fe6e7dcc 100644 --- a/notesserver/settings/yaml_config.py +++ b/notesserver/settings/yaml_config.py @@ -6,7 +6,7 @@ from notesserver.settings.logger import build_logging_config -from .common import * # pylint: disable=unused-wildcard-import, wildcard-import +from .common import * # pylint: disable=wildcard-import ############################################################################### # Explicitly declare here in case someone changes common.py. @@ -16,7 +16,7 @@ DISABLE_TOKEN_CHECK = False ############################################################################### -EDXNOTES_CONFIG_ROOT = os.environ.get('EDXNOTES_CONFIG_ROOT') +EDXNOTES_CONFIG_ROOT = environ.get('EDXNOTES_CONFIG_ROOT') if not EDXNOTES_CONFIG_ROOT: raise ImproperlyConfigured("EDXNOTES_CONFIG_ROOT must be defined in the environment.") @@ -29,14 +29,14 @@ vars().update(config_from_yaml) # Support environment overrides for migrations -DB_OVERRIDES = dict( - PASSWORD=environ.get('DB_MIGRATION_PASS', DATABASES['default']['PASSWORD']), - ENGINE=environ.get('DB_MIGRATION_ENGINE', DATABASES['default']['ENGINE']), - USER=environ.get('DB_MIGRATION_USER', DATABASES['default']['USER']), - NAME=environ.get('DB_MIGRATION_NAME', DATABASES['default']['NAME']), - HOST=environ.get('DB_MIGRATION_HOST', DATABASES['default']['HOST']), - PORT=environ.get('DB_MIGRATION_PORT', DATABASES['default']['PORT']), -) +DB_OVERRIDES = { + "PASSWORD": environ.get("DB_MIGRATION_PASS", DATABASES["default"]["PASSWORD"]), + "ENGINE": environ.get("DB_MIGRATION_ENGINE", DATABASES["default"]["ENGINE"]), + "USER": environ.get("DB_MIGRATION_USER", DATABASES["default"]["USER"]), + "NAME": environ.get("DB_MIGRATION_NAME", DATABASES["default"]["NAME"]), + "HOST": environ.get("DB_MIGRATION_HOST", DATABASES["default"]["HOST"]), + "PORT": environ.get("DB_MIGRATION_PORT", DATABASES["default"]["PORT"]), +} for override, value in DB_OVERRIDES.items(): DATABASES['default'][override] = value diff --git a/notesserver/test_views.py b/notesserver/test_views.py index 41569341..23a8dee9 100644 --- a/notesserver/test_views.py +++ b/notesserver/test_views.py @@ -1,14 +1,13 @@ import datetime import json from unittest import skipIf +from unittest.mock import Mock, patch from django.conf import settings from django.urls import reverse from elasticsearch.exceptions import TransportError from rest_framework.test import APITestCase -from unittest.mock import Mock, patch - class OperationalEndpointsTest(APITestCase): """ diff --git a/notesserver/views.py b/notesserver/views.py index 88c9b071..994cd8fc 100644 --- a/notesserver/views.py +++ b/notesserver/views.py @@ -18,14 +18,13 @@ if not settings.ES_DISABLED: from elasticsearch_dsl.connections import connections - def get_es(): return connections.get_connection() @api_view(['GET']) @permission_classes([AllowAny]) -def root(request): # pylint: disable=unused-argument +def root(request): """ Root view. """ @@ -34,9 +33,10 @@ def root(request): # pylint: disable=unused-argument "version": "1" }) + @api_view(['GET']) @permission_classes([AllowAny]) -def robots(request): # pylint: disable=unused-argument +def robots(request): """ robots.txt """ @@ -45,7 +45,7 @@ def robots(request): # pylint: disable=unused-argument @api_view(['GET']) @permission_classes([AllowAny]) -def heartbeat(request): # pylint: disable=unused-argument +def heartbeat(request): """ ElasticSearch and database are reachable and ready to handle requests. """ @@ -53,7 +53,7 @@ def heartbeat(request): # pylint: disable=unused-argument newrelic.agent.ignore_transaction() try: db_status() - except Exception: + except Exception: # pylint: disable=broad-exception-caught return JsonResponse({"OK": False, "check": "db"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) if not settings.ES_DISABLED and not get_es().ping(): @@ -64,7 +64,7 @@ def heartbeat(request): # pylint: disable=unused-argument @api_view(['GET']) @permission_classes([AllowAny]) -def selftest(request): # pylint: disable=unused-argument +def selftest(request): """ Manual test endpoint. """ @@ -82,7 +82,7 @@ def selftest(request): # pylint: disable=unused-argument try: db_status() database = "OK" - except Exception: + except Exception: # pylint: disable=broad-exception-caught return Response( {"db_error": traceback.format_exc()}, status=status.HTTP_500_INTERNAL_SERVER_ERROR diff --git a/notesserver/wsgi.py b/notesserver/wsgi.py index 078c6ffd..8435af2f 100644 --- a/notesserver/wsgi.py +++ b/notesserver/wsgi.py @@ -1,4 +1,3 @@ -import os from django.core.wsgi import get_wsgi_application application = get_wsgi_application() diff --git a/pylintrc b/pylintrc index fd6c4a45..d57722f0 100644 --- a/pylintrc +++ b/pylintrc @@ -67,9 +67,9 @@ # Generated by edx-lint version: 5.4.1 # ------------------------------ [MASTER] -ignore = +ignore = .git, .tox, migrations persistent = yes -load-plugins = edx_lint.pylint,pylint_django,pylint_celery +load-plugins = edx_lint.pylint,pylint_django [MESSAGES CONTROL] enable = @@ -287,6 +287,9 @@ disable = illegal-waffle-usage, logging-fstring-interpolation, + missing-function-docstring, + missing-module-docstring, + missing-class-docstring [REPORTS] output-format = text @@ -383,4 +386,4 @@ int-import-graph = [EXCEPTIONS] overgeneral-exceptions = builtins.Exception -# 81327117d32c61cfe23ffd4c7fca0bc96d5c118a +# 4d62b5911eb751d4b086b0c163b1f165d0680bae diff --git a/pylintrc_tweaks b/pylintrc_tweaks new file mode 100644 index 00000000..85167007 --- /dev/null +++ b/pylintrc_tweaks @@ -0,0 +1,10 @@ +# pylintrc tweaks for use with edx_lint. +[MASTER] +ignore+ = .git, .tox, migrations +load-plugins = edx_lint.pylint,pylint_django + +[MESSAGES CONTROL] +disable+ = + missing-function-docstring, + missing-module-docstring, + missing-class-docstring diff --git a/requirements/quality.in b/requirements/quality.in index 44745233..e6451e75 100644 --- a/requirements/quality.in +++ b/requirements/quality.in @@ -1,6 +1,7 @@ -c constraints.txt -r base.txt +-r test.txt code-annotations pycodestyle diff --git a/requirements/quality.txt b/requirements/quality.txt index e08019f2..5e256f3f 100644 --- a/requirements/quality.txt +++ b/requirements/quality.txt @@ -7,34 +7,50 @@ asgiref==3.8.1 # via # -r requirements/base.txt + # -r requirements/test.txt # django # django-cors-headers astroid==3.3.5 # via + # -r requirements/test.txt # pylint # pylint-celery attrs==24.2.0 # via # -r requirements/base.txt + # -r requirements/test.txt # jsonschema # referencing +cachetools==5.5.0 + # via + # -r requirements/test.txt + # tox certifi==2024.8.30 # via # -r requirements/base.txt + # -r requirements/test.txt # elasticsearch # requests cffi==1.17.1 # via # -r requirements/base.txt + # -r requirements/test.txt # cryptography # pynacl +chardet==5.2.0 + # via + # -r requirements/test.txt + # diff-cover + # tox charset-normalizer==3.4.0 # via # -r requirements/base.txt + # -r requirements/test.txt # requests click==8.1.7 # via # -r requirements/base.txt + # -r requirements/test.txt # click-log # code-annotations # edx-django-utils @@ -44,17 +60,38 @@ click-log==0.4.0 code-annotations==1.8.0 # via # -r requirements/quality.in + # -r requirements/test.txt # edx-lint +colorama==0.4.6 + # via + # -r requirements/test.txt + # tox +coverage[toml]==7.6.4 + # via + # -r requirements/test.txt + # pytest-cov cryptography==43.0.3 # via # -r requirements/base.txt + # -r requirements/test.txt # pyjwt +ddt==1.7.2 + # via -r requirements/test.txt +diff-cover==9.2.0 + # via -r requirements/test.txt dill==0.3.9 - # via pylint + # via + # -r requirements/test.txt + # pylint +distlib==0.3.9 + # via + # -r requirements/test.txt + # virtualenv django==4.2.16 # via # -c https://raw.githubusercontent.com/openedx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/base.txt + # -r requirements/test.txt # django-cors-headers # django-crum # django-nine @@ -66,29 +103,38 @@ django==4.2.16 # edx-django-utils # edx-drf-extensions django-cors-headers==4.6.0 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # -r requirements/test.txt django-crum==0.7.9 # via # -r requirements/base.txt + # -r requirements/test.txt # edx-django-utils django-elasticsearch-dsl==7.4 # via # -r requirements/base.txt + # -r requirements/test.txt # django-elasticsearch-dsl-drf django-elasticsearch-dsl-drf==0.22.5 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # -r requirements/test.txt django-nine==0.2.7 # via # -r requirements/base.txt + # -r requirements/test.txt # django-elasticsearch-dsl-drf django-waffle==4.1.0 # via # -r requirements/base.txt + # -r requirements/test.txt # edx-django-utils # edx-drf-extensions djangorestframework==3.15.2 # via # -r requirements/base.txt + # -r requirements/test.txt # django-elasticsearch-dsl-drf # drf-jwt # drf-spectacular @@ -96,105 +142,180 @@ djangorestframework==3.15.2 dnspython==2.7.0 # via # -r requirements/base.txt + # -r requirements/test.txt # pymongo drf-jwt==1.19.2 # via # -r requirements/base.txt + # -r requirements/test.txt # edx-drf-extensions drf-spectacular==0.27.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # -r requirements/test.txt edx-django-release-util==1.4.0 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # -r requirements/test.txt edx-django-utils==7.0.0 # via # -r requirements/base.txt + # -r requirements/test.txt # edx-drf-extensions edx-drf-extensions==10.5.0 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # -r requirements/test.txt edx-lint==5.4.1 # via -r requirements/quality.in edx-opaque-keys==2.11.0 # via # -r requirements/base.txt + # -r requirements/test.txt # edx-drf-extensions elasticsearch==7.13.4 # via # -c https://raw.githubusercontent.com/openedx/edx-lint/master/edx_lint/files/common_constraints.txt # -r requirements/base.txt + # -r requirements/test.txt # django-elasticsearch-dsl-drf # elasticsearch-dsl elasticsearch-dsl==7.4.1 # via # -r requirements/base.txt + # -r requirements/test.txt # django-elasticsearch-dsl # django-elasticsearch-dsl-drf +factory-boy==3.3.1 + # via -r requirements/test.txt +faker==30.8.2 + # via + # -r requirements/test.txt + # factory-boy +filelock==3.16.1 + # via + # -r requirements/test.txt + # tox + # virtualenv gunicorn==23.0.0 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # -r requirements/test.txt idna==3.10 # via # -r requirements/base.txt + # -r requirements/test.txt # requests inflection==0.5.1 # via # -r requirements/base.txt + # -r requirements/test.txt # drf-spectacular +iniconfig==2.0.0 + # via + # -r requirements/test.txt + # pytest isort==5.13.2 - # via pylint + # via + # -r requirements/test.txt + # pylint jinja2==3.1.4 - # via code-annotations + # via + # -r requirements/test.txt + # code-annotations + # diff-cover jsonschema==4.23.0 # via # -r requirements/base.txt + # -r requirements/test.txt # drf-spectacular jsonschema-specifications==2024.10.1 # via # -r requirements/base.txt + # -r requirements/test.txt # jsonschema markupsafe==3.0.2 - # via jinja2 + # via + # -r requirements/test.txt + # jinja2 mccabe==0.7.0 - # via pylint + # via + # -r requirements/test.txt + # pylint +more-itertools==10.5.0 + # via -r requirements/test.txt mysqlclient==2.2.5 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # -r requirements/test.txt newrelic==10.2.0 # via # -r requirements/base.txt + # -r requirements/test.txt # edx-django-utils packaging==24.1 # via # -r requirements/base.txt + # -r requirements/test.txt # django-nine # gunicorn + # pyproject-api + # pytest + # tox path==17.0.0 # via # -r requirements/base.txt + # -r requirements/test.txt # path-py path-py==12.5.0 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # -r requirements/test.txt pbr==6.1.0 # via # -r requirements/base.txt + # -r requirements/test.txt # stevedore +pep8==1.7.1 + # via -r requirements/test.txt platformdirs==4.3.6 - # via pylint + # via + # -r requirements/test.txt + # pylint + # tox + # virtualenv +pluggy==1.5.0 + # via + # -r requirements/test.txt + # diff-cover + # pytest + # tox psutil==6.1.0 # via # -r requirements/base.txt + # -r requirements/test.txt # edx-django-utils pycodestyle==2.12.1 # via -r requirements/quality.in pycparser==2.22 # via # -r requirements/base.txt + # -r requirements/test.txt # cffi +pygments==2.18.0 + # via + # -r requirements/test.txt + # diff-cover pyjwt[crypto]==2.9.0 # via # -r requirements/base.txt + # -r requirements/test.txt # drf-jwt # edx-drf-extensions pylint==3.3.1 # via # -r requirements/quality.in + # -r requirements/test.txt # edx-lint # pylint-celery # pylint-django @@ -210,46 +331,73 @@ pylint-plugin-utils==0.8.2 pymongo==4.10.1 # via # -r requirements/base.txt + # -r requirements/test.txt # edx-opaque-keys pynacl==1.5.0 # via # -r requirements/base.txt + # -r requirements/test.txt # edx-django-utils +pyproject-api==1.8.0 + # via + # -r requirements/test.txt + # tox +pytest==8.3.3 + # via + # -r requirements/test.txt + # pytest-cov + # pytest-django +pytest-cov==6.0.0 + # via -r requirements/test.txt +pytest-django==4.9.0 + # via -r requirements/test.txt python-dateutil==2.9.0.post0 # via # -r requirements/base.txt + # -r requirements/test.txt # elasticsearch-dsl + # faker python-slugify==8.0.4 - # via code-annotations + # via + # -r requirements/test.txt + # code-annotations pytz==2024.2 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # -r requirements/test.txt pyyaml==6.0.2 # via # -r requirements/base.txt + # -r requirements/test.txt # code-annotations # drf-spectacular # edx-django-release-util referencing==0.35.1 # via # -r requirements/base.txt + # -r requirements/test.txt # jsonschema # jsonschema-specifications requests==2.32.3 # via # -r requirements/base.txt + # -r requirements/test.txt # edx-drf-extensions rpds-py==0.20.1 # via # -r requirements/base.txt + # -r requirements/test.txt # jsonschema # referencing semantic-version==2.10.0 # via # -r requirements/base.txt + # -r requirements/test.txt # edx-drf-extensions six==1.16.0 # via # -r requirements/base.txt + # -r requirements/test.txt # django-elasticsearch-dsl # django-elasticsearch-dsl-drf # edx-django-release-util @@ -259,31 +407,49 @@ six==1.16.0 sqlparse==0.5.1 # via # -r requirements/base.txt + # -r requirements/test.txt # django stevedore==5.3.0 # via # -r requirements/base.txt + # -r requirements/test.txt # code-annotations # edx-django-utils # edx-opaque-keys text-unidecode==1.3 - # via python-slugify + # via + # -r requirements/test.txt + # python-slugify tomlkit==0.13.2 - # via pylint + # via + # -r requirements/test.txt + # pylint +tox==4.23.2 + # via -r requirements/test.txt typing-extensions==4.12.2 # via # -r requirements/base.txt + # -r requirements/test.txt # edx-opaque-keys + # faker uritemplate==4.1.1 # via # -r requirements/base.txt + # -r requirements/test.txt # drf-spectacular urllib3==1.26.20 # via # -r requirements/base.txt + # -r requirements/test.txt # elasticsearch # requests +virtualenv==20.27.1 + # via + # -r requirements/test.txt + # tox # The following packages are considered to be unsafe in a requirements file: setuptools==75.3.0 - # via -r requirements/base.txt + # via + # -r requirements/base.txt + # -r requirements/test.txt diff --git a/tox.ini b/tox.ini index 838605e8..a9688cfb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py{311,312}-django{42} #, quality, pii_check, check_keywords +envlist = py{311,312}-django{42}, quality, pii_check, check_keywords skipsdist = true isolated_build = true # Enable isolated build environments @@ -24,6 +24,8 @@ commands = [testenv:quality] envdir = {toxworkdir}/{envname} +setenv = + DJANGO_SETTINGS_MODULE = notesserver.settings.test allowlist_externals = make deps =