diff --git a/backend/api/views.py b/backend/api/views.py index 0976485..90afd8d 100644 --- a/backend/api/views.py +++ b/backend/api/views.py @@ -170,9 +170,7 @@ class FilterDatasetSerializer(serializers.Serializer): class DataItemViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet): - """ - Return OCDS data that passed or failed a check. - """ + """Return OCDS data that passed or failed a check.""" queryset = DataItem.objects.all() serializer_class = DataItemSerializer @@ -215,9 +213,7 @@ def get_report(self, model, fields): # https://github.com/encode/django-rest-framework/blob/2db0c0b/rest_framework/mixins.py#L35 @extend_schema(responses=DatasetSerializer) def list(self, request, *args, **kwargs): - """ - Return all datasets with their status and filter metadata. - """ + """Return all datasets with their status and filter metadata.""" queryset = self.get_annotated_queryset() serializer = DatasetSerializer(queryset, many=True) return Response(serializer.data) @@ -225,18 +221,14 @@ def list(self, request, *args, **kwargs): # https://github.com/encode/django-rest-framework/blob/2db0c0b/rest_framework/mixins.py#L51 @extend_schema(responses=DatasetSerializer) def retrieve(self, request, *args, **kwargs): - """ - Return the dataset with its status and filter metadata. - """ + """Return the dataset with its status and filter metadata.""" instance = self.get_annotated_object() serializer = DatasetSerializer(instance) return Response(serializer.data) @extend_schema(request=CreateDatasetSerializer, responses={202: None}) def create(self, request): - """ - Publish a message to RabbitMQ to create a dataset. - """ + """Publish a message to RabbitMQ to create a dataset.""" serializer = CreateDatasetSerializer(data=request.data) serializer.is_valid(raise_exception=True) message = { @@ -251,9 +243,7 @@ def create(self, request): @extend_schema(request=FilterDatasetSerializer, responses={202: None}) @action(detail=True, methods=["post"]) def filter(self, request, pk=None): - """ - Publish a message to RabbitMQ to create a filtered dataset. - """ + """Publish a message to RabbitMQ to create a filtered dataset.""" serializer = FilterDatasetSerializer(data=request.data) serializer.is_valid(raise_exception=True) message = {"dataset_id_original": pk, "filter_message": serializer.data} @@ -262,9 +252,7 @@ def filter(self, request, pk=None): @extend_schema(responses={202: None}) def destroy(self, request, pk=None): - """ - Publish a message to RabbitMQ to wipe the dataset. - """ + """Publish a message to RabbitMQ to wipe the dataset.""" publish({"dataset_id": pk}, "wiper_init") return Response(status=status.HTTP_202_ACCEPTED) @@ -272,8 +260,9 @@ def destroy(self, request, pk=None): @action(detail=False) def find_by_name(self, request): """ - Return the ID of the dataset with the name given in the `name` query string parameter, as an object like - `{"id": 123}`, or `{}` if no name matches. + Return the ID of the dataset with the name given in the `name` query string parameter, as an object. + + ``{"id": 123}`` for example, or ``{}`` if no name matches. """ try: dataset = self.get_queryset().get(name=request.query_params.get("name")) @@ -284,25 +273,19 @@ def find_by_name(self, request): @extend_schema(responses={200: {"type": "object"}}) @action(detail=True) def field_level_report(self, request, pk=None): - """ - Return a report of the dataset's field-level checks. - """ + """Return a report of the dataset's field-level checks.""" return Response(get_object_or_404(Report, dataset=pk, type="field_level_check").data) @extend_schema(responses={200: {"type": "object"}}) @action(detail=True) def compiled_release_level_report(self, request, pk=None): - """ - Return a report of the dataset's compiled release-level checks. - """ + """Return a report of the dataset's compiled release-level checks.""" return Response(get_object_or_404(Report, dataset=pk, type="resource_level_check").data) @extend_schema(responses={200: {"type": "object"}}) @action(detail=True) def dataset_level_report(self, request, pk=None): - """ - Return a report of the dataset's dataset-level checks. - """ + """Return a report of the dataset's dataset-level checks.""" return self.get_report( DatasetLevelCheck, [ @@ -315,9 +298,7 @@ def dataset_level_report(self, request, pk=None): @extend_schema(responses={200: {"type": "object"}}) @action(detail=True) def time_based_report(self, request, pk=None): - """ - Return a report of the dataset's time-based checks. - """ + """Return a report of the dataset's time-based checks.""" return self.get_report( TimeVarianceLevelCheck, [ @@ -332,9 +313,7 @@ def time_based_report(self, request, pk=None): @extend_schema(responses=StatusSerializer) @action(detail=True) def status(self, request, pk=None): - """ - Return the dataset's status, as an object like `{"phase": "CHECKED", "state": "OK"}`, or `{}` if not set. - """ + """Return the dataset's status, as an object like `{"phase": "CHECKED", "state": "OK"}`, or `{}` if not set.""" try: progress = self.get_object_or_404(self.get_queryset().select_related("progress")).progress return Response({"phase": progress.phase, "state": progress.state}) @@ -344,18 +323,14 @@ def status(self, request, pk=None): @extend_schema(responses={200: {"type": "object"}}) @action(detail=True) def metadata(self, request, pk=None): - """ - Return the dataset's collection metadata. - """ + """Return the dataset's collection metadata.""" meta = self.get_object_or_404(self.get_queryset().values_list("meta__collection_metadata", flat=True)) return Response(meta or {}) @extend_schema(responses={200: {"type": "object"}}) @action(detail=True) def coverage(self, request, pk=None): - """ - Return the dataset's coverage statistics. - """ + """Return the dataset's coverage statistics.""" self.get_object() # trigger 404 if no dataset # The lists of fields must match the names of field-level checks in pelican-backend. @@ -421,9 +396,7 @@ def coverage(self, request, pk=None): class FieldLevelDetail(views.APIView): @extend_schema(responses={200: {"type": "object"}}) def get(self, request, pk, name, format=None): - """ - Return a report and examples of one field-level check. - """ + """Return a report and examples of one field-level check.""" start_time = time.time() detail = get_object_or_404(Report, dataset=pk, type="field_level_check", data__has_key=name).data[name] @@ -444,9 +417,7 @@ def get(self, request, pk, name, format=None): class ResourceLevelDetail(views.APIView): @extend_schema(responses={200: {"type": "object"}}) def get(self, request, pk, name, format=None): - """ - Return a report and examples of one compiled release-level check. - """ + """Return a report and examples of one compiled release-level check.""" start_time = time.time() detail = get_object_or_404(Report, dataset=pk, type="resource_level_check", data__has_key=name).data[name] diff --git a/backend/exporter/exceptions.py b/backend/exporter/exceptions.py index 1eabb8f..a53da21 100644 --- a/backend/exporter/exceptions.py +++ b/backend/exporter/exceptions.py @@ -46,9 +46,7 @@ def fill(self, full_tag: str, template_id: str) -> "TagError": return self def as_dict(self) -> dict[str, str | None]: - """ - Return the exception as a dictionary (e.g. to serialize as JSON for the browser). - """ + """Return the exception as a dictionary (e.g. to serialize as JSON for the browser).""" return { "reason": self.reason, "full_tag": self.full_tag, diff --git a/backend/exporter/graphs.py b/backend/exporter/graphs.py index b445975..eb1ac6e 100644 --- a/backend/exporter/graphs.py +++ b/backend/exporter/graphs.py @@ -29,9 +29,7 @@ def build_fig(aspect_ratio): - """ - Remember to call .close() in the figure. - """ + """Remember to call .close() in the figure.""" plt.rcdefaults() fig, ax = plt.subplots() fig.set_size_inches(6, 6 * aspect_ratio) diff --git a/backend/exporter/leaf_tags/generic.py b/backend/exporter/leaf_tags/generic.py index ef9ed54..aa59c27 100644 --- a/backend/exporter/leaf_tags/generic.py +++ b/backend/exporter/leaf_tags/generic.py @@ -1,6 +1,4 @@ -""" -Factories for leaf tags. -""" +"""Factories for leaf tags.""" import datetime from typing import Any @@ -18,9 +16,7 @@ def generate_key_leaf_tag(key: str) -> type[LeafTag]: - """ - Build a :class:`~exporter.tag.LeafTag` named ``key``, that returns the ``key`` from the context. - """ + """Build a :class:`~exporter.tag.LeafTag` named ``key``, that returns the ``key`` from the context.""" @leaf(key) def _tag(tag: LeafTag, data: dict[str, Any]) -> str: diff --git a/backend/exporter/tag.py b/backend/exporter/tag.py index e512eb7..abe124c 100644 --- a/backend/exporter/tag.py +++ b/backend/exporter/tag.py @@ -112,9 +112,7 @@ def render(self, data: dict[str, Any]) -> Any: # Keep this class, because render()'s signature differs between LeafTag and TemplateTag. class LeafTag(Tag): - """ - A leaf tag renders itself, using the data ("context") provided by a template tag. - """ + """A leaf tag renders itself, using the data ("context") provided by a template tag.""" def render(self, data: dict[str, Any]) -> str | etree._Element | list[etree._Element]: """ @@ -145,9 +143,7 @@ def __init__(self, gdocs: Gdocs, dataset_id: int): self.argument_defaults["template"] = self.default_template def get_context(self) -> dict[str, Any]: - """ - Return the data ("context") to be provided to sub-tags. - """ + """Return the data ("context") to be provided to sub-tags.""" return {} def get_tags_mapping(self, texts: list[etree._Element]) -> tuple[dict[str, Tag], list[str]]: @@ -410,9 +406,7 @@ def _argument(cls: builtins.type[Tag]) -> builtins.type[Tag]: def generate_error_template_tag(message: str) -> type[TemplateTag]: - """ - Build a :class:`~exporter.tag.TemplateTag` for the error template and set the error ``message``. - """ + """Build a :class:`~exporter.tag.TemplateTag` for the error template and set the error ``message``.""" @template("error", settings.GDOCS_TEMPLATES["DEFAULT_ERROR_TEMPLATE"], (value_tag,)) def _tag(tag: TemplateTag) -> dict[str, Any]: diff --git a/backend/exporter/util.py b/backend/exporter/util.py index 3130d08..6d15cbd 100644 --- a/backend/exporter/util.py +++ b/backend/exporter/util.py @@ -45,9 +45,7 @@ def sample_and_format(population, arguments: dict[str, Any]) -> str | list[etree def box_image(tag, function, filename: str, *args, **kwargs) -> etree._Element: - """ - Add an image to the OpenDocument file and return an image within a frame. - """ + """Add an image to the OpenDocument file and return an image within a frame.""" buffer, aspect_ratio = function(*args, **kwargs) path = tag.gdocs.add_image_file(buffer, filename) diff --git a/backend/manage.py b/backend/manage.py index ccea163..bd9928d 100755 --- a/backend/manage.py +++ b/backend/manage.py @@ -8,14 +8,9 @@ def main(): """Run administrative tasks.""" os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") - try: - from django.core.management import execute_from_command_line - except ImportError as exc: - raise ImportError( - "Couldn't import Django. Are you sure it's installed and " - "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" - ) from exc + + from django.core.management import execute_from_command_line + execute_from_command_line(sys.argv) diff --git a/pyproject.toml b/pyproject.toml index 41e757b..3d1d9a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,33 +10,41 @@ src = ["backend"] [tool.ruff.lint] select = ["ALL"] ignore = [ - "ANN", "COM", "EM", - # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules - "W191", "D206", "Q000", "Q001", "Q002", "Q003", "ISC001", - "D203", "D212", # ignore incompatible rules - "D200", "D205", # documentation preferences - "C901", "PLR091", # complexity preferences - "D1", # docstrings - "PTH", # pathlib - "A001", # Django REST framework - "A002", # tag type - "ARG001", "ARG002", "DJ008", "RUF012", "SLF001", # Django - "PLR2004", # magic values - "PLW2901", # for-loop variable - "TRY003", # error messages + "ANN", "C901", "COM812", "D203", "D212", "D415", "EM", "PERF203", "PLR091", "Q000", + "D1", + "DJ008", + "PTH", ] [tool.ruff.lint.flake8-builtins] -builtins-ignorelist = ["copyright"] +builtins-ignorelist = ["copyright", "format", "type"] + +[tool.ruff.lint.flake8-self] +extend-ignore-names = ["_meta", "_Element"] + +[tool.ruff.lint.flake8-unused-arguments] +ignore-variadic-names = true [tool.ruff.lint.per-file-ignores] -"docs/conf.py" = ["INP001"] # no __init__.py file +"docs/*" = ["D100", "INP001"] +"{*/signals,*/views,*/migrations/*}.py" = ["ARG001"] +"{*/admin,*/routers,*/views,*/commands/*}.py" = ["ARG002"] +"{*/admin,*/forms,*/models,*/routers,*/serializers,*/translation,*/migrations/*,*/tests/*}.py" = ["RUF012"] +"*/migrations/*" = ["E501"] "*/tests/*" = [ - "D", # docstring - "PLR0913", # Too many arguments to function call - "PLR2004", # Magic value used - "PT", # Django - "S101", # assert + "D", "FBT003", "INP001", "PLR2004", "PT", "S", "TRY003", +] +"*/views.py" = [ + "RUF012", # djangorestframework +] +"*/exporter/*" = [ + "D205", + "ARG001", # tag + "ARG002", # data + "RUF012", + "PLR2004", + "PLW2901", + "TRY003", # errors ] [tool.coverage.run]