diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f7cd6d9d..eeb9e0b94 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,15 +8,25 @@ jobs: format_python_code: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Check out the repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: "recursive" ref: ${{ github.ref }} - - name: Blacken Python code - uses: jpetrucciani/black-check@master + + - name: Install uv + uses: astral-sh/setup-uv@v6 + + - name: Run Ruff Checks + uses: astral-sh/ruff-action@39f75e526a505e26a302f8796977b50c13720edf # v3.2.1 with: - path: "." - black_flags: "--safe --verbose --diff" + args: "check --select I ." + + - name: Run Ruff Format + uses: astral-sh/ruff-action@39f75e526a505e26a302f8796977b50c13720edf # v3.2.1 + with: + args: "format --check --diff" + build: runs-on: ubuntu-latest if: "!contains(github.event.head_commit.message, '[ci skip]')" diff --git a/app/airflow/dags/SR_processing_dag.py b/app/airflow/dags/SR_processing_dag.py index ee217fb48..12c242a1c 100644 --- a/app/airflow/dags/SR_processing_dag.py +++ b/app/airflow/dags/SR_processing_dag.py @@ -1,13 +1,13 @@ from datetime import datetime, timedelta + from airflow import DAG from airflow.operators.empty import EmptyOperator -from libs.utils import create_task, validate_params_SR_processing +from libs.settings import AIRFLOW_DAGRUN_TIMEOUT, AIRFLOW_DEBUG_MODE from libs.SR_processing.core import ( - process_data_dictionary, process_and_create_scan_report_entries, + process_data_dictionary, ) -from libs.utils import connect_to_storage -from libs.settings import AIRFLOW_DEBUG_MODE, AIRFLOW_DAGRUN_TIMEOUT +from libs.utils import connect_to_storage, create_task, validate_params_SR_processing """ This DAG automates the process of creating scan report tables, fields and values diff --git a/app/airflow/dags/auto_mapping_dag.py b/app/airflow/dags/auto_mapping_dag.py index 3438624e6..92c9da4f9 100644 --- a/app/airflow/dags/auto_mapping_dag.py +++ b/app/airflow/dags/auto_mapping_dag.py @@ -1,36 +1,35 @@ from datetime import datetime, timedelta + from airflow import DAG from airflow.operators.empty import EmptyOperator -from libs.auto_mapping.find_R_concepts_to_reuse import ( - find_matching_field, - find_matching_value, - create_reusing_concepts, - delete_R_concepts, -) - -from libs.auto_mapping.find_standard_V_concepts import ( - find_standard_concepts, - create_standard_concepts, -) - from libs.auto_mapping.core_get_existing_concepts import ( delete_mapping_rules, find_existing_concepts, ) from libs.auto_mapping.core_prep_rules_creation import ( - find_dest_table_and_person_field_id, - find_date_fields, - find_concept_fields, find_additional_fields, + find_concept_fields, + find_date_fields, + find_dest_table_and_person_field_id, ) from libs.auto_mapping.core_rules_creation import create_mapping_rules +from libs.auto_mapping.find_R_concepts_to_reuse import ( + create_reusing_concepts, + delete_R_concepts, + find_matching_field, + find_matching_value, +) +from libs.auto_mapping.find_standard_V_concepts import ( + create_standard_concepts, + find_standard_concepts, +) from libs.auto_mapping.search_recommendations import process_search_recommendations +from libs.settings import AIRFLOW_DAGRUN_TIMEOUT, AIRFLOW_DEBUG_MODE, SEARCH_ENABLED from libs.utils import ( create_task, - validate_params_auto_mapping, update_job_status_on_failure, + validate_params_auto_mapping, ) -from libs.settings import AIRFLOW_DEBUG_MODE, SEARCH_ENABLED, AIRFLOW_DAGRUN_TIMEOUT """ This DAG automates the process of creating and reusing concepts from scan reports and generating mapping rules. diff --git a/app/airflow/dags/libs/SR_processing/core.py b/app/airflow/dags/libs/SR_processing/core.py index 70d026298..f6b145d0e 100644 --- a/app/airflow/dags/libs/SR_processing/core.py +++ b/app/airflow/dags/libs/SR_processing/core.py @@ -1,28 +1,28 @@ +import csv import logging import os -from openpyxl import load_workbook -from libs.utils import pull_validated_params -import csv from io import StringIO +from typing import List, Tuple + +from airflow.providers.postgres.hooks.postgres import PostgresHook +from libs.enums import JobStageType, StageStatusType +from libs.queries import create_temp_data_dictionary_table_query, create_values_query +from libs.settings import AIRFLOW_DAGRUN_TIMEOUT from libs.SR_processing.db_services import ( create_field_entries, - update_temp_data_dictionary_table, create_temp_field_values_table, delete_temp_tables, + update_temp_data_dictionary_table, ) from libs.SR_processing.helpers import ( - remove_BOM, - process_four_item_dict, get_unique_table_names, + process_four_item_dict, + remove_BOM, transform_scan_report_sheet_table, ) from libs.storage_services import download_blob_to_tmp -from airflow.providers.postgres.hooks.postgres import PostgresHook -from typing import List, Tuple -from libs.queries import create_values_query, create_temp_data_dictionary_table_query -from libs.enums import JobStageType, StageStatusType -from libs.utils import update_job_status -from libs.settings import AIRFLOW_DAGRUN_TIMEOUT +from libs.utils import pull_validated_params, update_job_status +from openpyxl import load_workbook # PostgreSQL connection hook pg_hook = PostgresHook( diff --git a/app/airflow/dags/libs/SR_processing/db_services.py b/app/airflow/dags/libs/SR_processing/db_services.py index 06fa27991..8705526a0 100644 --- a/app/airflow/dags/libs/SR_processing/db_services.py +++ b/app/airflow/dags/libs/SR_processing/db_services.py @@ -1,12 +1,13 @@ -from typing import List, Any, Dict, Tuple -from openpyxl.worksheet.worksheet import Worksheet -from libs.queries import create_fields_query import logging from collections import defaultdict +from typing import Any, Dict, List, Tuple + from airflow.providers.postgres.hooks.postgres import PostgresHook from libs.enums import JobStageType, StageStatusType +from libs.queries import create_fields_query +from libs.settings import AIRFLOW_DAGRUN_TIMEOUT, AIRFLOW_DEBUG_MODE from libs.utils import update_job_status -from libs.settings import AIRFLOW_DEBUG_MODE, AIRFLOW_DAGRUN_TIMEOUT +from openpyxl.worksheet.worksheet import Worksheet # PostgreSQL connection hook pg_hook = PostgresHook( diff --git a/app/airflow/dags/libs/SR_processing/helpers.py b/app/airflow/dags/libs/SR_processing/helpers.py index 5a0067c31..dbe7cf95a 100644 --- a/app/airflow/dags/libs/SR_processing/helpers.py +++ b/app/airflow/dags/libs/SR_processing/helpers.py @@ -1,7 +1,8 @@ -from typing import List, Any, Dict -from openpyxl.worksheet.worksheet import Worksheet import logging from collections import defaultdict +from typing import Any, Dict, List + +from openpyxl.worksheet.worksheet import Worksheet def get_unique_table_names(worksheet: Worksheet) -> List[str]: diff --git a/app/airflow/dags/libs/auto_mapping/core_get_existing_concepts.py b/app/airflow/dags/libs/auto_mapping/core_get_existing_concepts.py index 96b3909ae..41fdad7ce 100644 --- a/app/airflow/dags/libs/auto_mapping/core_get_existing_concepts.py +++ b/app/airflow/dags/libs/auto_mapping/core_get_existing_concepts.py @@ -1,17 +1,18 @@ -from libs.utils import ( - update_job_status, - JobStageType, - StageStatusType, - pull_validated_params, -) -from airflow.providers.postgres.hooks.postgres import PostgresHook import logging + +from airflow.providers.postgres.hooks.postgres import PostgresHook from libs.queries import ( create_existing_concepts_table_query, find_existing_concepts_query, find_source_field_id_query, ) from libs.settings import AIRFLOW_DAGRUN_TIMEOUT +from libs.utils import ( + JobStageType, + StageStatusType, + pull_validated_params, + update_job_status, +) # PostgreSQL connection hook pg_hook = PostgresHook( diff --git a/app/airflow/dags/libs/auto_mapping/core_prep_rules_creation.py b/app/airflow/dags/libs/auto_mapping/core_prep_rules_creation.py index eef7c6fd0..4fd02325a 100644 --- a/app/airflow/dags/libs/auto_mapping/core_prep_rules_creation.py +++ b/app/airflow/dags/libs/auto_mapping/core_prep_rules_creation.py @@ -1,17 +1,18 @@ -from libs.utils import ( - update_job_status, - JobStageType, - StageStatusType, - pull_validated_params, -) -from airflow.providers.postgres.hooks.postgres import PostgresHook import logging + +from airflow.providers.postgres.hooks.postgres import PostgresHook from libs.queries import ( - find_dest_table_and_person_field_id_query, - find_dates_fields_query, find_concept_fields_query, + find_dates_fields_query, + find_dest_table_and_person_field_id_query, ) from libs.settings import AIRFLOW_DAGRUN_TIMEOUT +from libs.utils import ( + JobStageType, + StageStatusType, + pull_validated_params, + update_job_status, +) # PostgreSQL connection hook pg_hook = PostgresHook( diff --git a/app/airflow/dags/libs/auto_mapping/core_rules_creation.py b/app/airflow/dags/libs/auto_mapping/core_rules_creation.py index 0cc70b73b..a7199d11c 100644 --- a/app/airflow/dags/libs/auto_mapping/core_rules_creation.py +++ b/app/airflow/dags/libs/auto_mapping/core_rules_creation.py @@ -1,14 +1,14 @@ -from airflow.providers.postgres.hooks.postgres import PostgresHook import logging + from airflow.exceptions import AirflowException +from airflow.providers.postgres.hooks.postgres import PostgresHook +from libs.settings import AIRFLOW_DAGRUN_TIMEOUT, AIRFLOW_DEBUG_MODE from libs.utils import ( - update_job_status, JobStageType, StageStatusType, pull_validated_params, + update_job_status, ) -from libs.settings import AIRFLOW_DEBUG_MODE, AIRFLOW_DAGRUN_TIMEOUT - # PostgreSQL connection hook pg_hook = PostgresHook( diff --git a/app/airflow/dags/libs/auto_mapping/find_R_concepts_to_reuse.py b/app/airflow/dags/libs/auto_mapping/find_R_concepts_to_reuse.py index 31ceb0539..9feba0d95 100644 --- a/app/airflow/dags/libs/auto_mapping/find_R_concepts_to_reuse.py +++ b/app/airflow/dags/libs/auto_mapping/find_R_concepts_to_reuse.py @@ -1,20 +1,21 @@ -from libs.utils import ( - update_job_status, - JobStageType, - StageStatusType, - pull_validated_params, -) -from airflow.providers.postgres.hooks.postgres import PostgresHook import logging + +from airflow.providers.postgres.hooks.postgres import PostgresHook from libs.queries import ( + create_reuse_concept_query, create_temp_reuse_table_query, find_m_concepts_query, find_m_concepts_query_field_level, find_object_id_query, validate_reused_concepts_query, - create_reuse_concept_query, ) from libs.settings import AIRFLOW_DAGRUN_TIMEOUT +from libs.utils import ( + JobStageType, + StageStatusType, + pull_validated_params, + update_job_status, +) # PostgreSQL connection hook pg_hook = PostgresHook( diff --git a/app/airflow/dags/libs/auto_mapping/find_standard_V_concepts.py b/app/airflow/dags/libs/auto_mapping/find_standard_V_concepts.py index 9c2547abb..141584631 100644 --- a/app/airflow/dags/libs/auto_mapping/find_standard_V_concepts.py +++ b/app/airflow/dags/libs/auto_mapping/find_standard_V_concepts.py @@ -1,13 +1,14 @@ +import logging + +from airflow.exceptions import AirflowException +from airflow.providers.postgres.hooks.postgres import PostgresHook +from libs.settings import AIRFLOW_DAGRUN_TIMEOUT from libs.utils import ( - update_job_status, JobStageType, StageStatusType, pull_validated_params, + update_job_status, ) -from airflow.providers.postgres.hooks.postgres import PostgresHook -import logging -from airflow.exceptions import AirflowException -from libs.settings import AIRFLOW_DAGRUN_TIMEOUT # PostgreSQL connection hook pg_hook = PostgresHook( diff --git a/app/airflow/dags/libs/auto_mapping/search_recommendations.py b/app/airflow/dags/libs/auto_mapping/search_recommendations.py index 485a6c06f..cfa4dda59 100644 --- a/app/airflow/dags/libs/auto_mapping/search_recommendations.py +++ b/app/airflow/dags/libs/auto_mapping/search_recommendations.py @@ -1,9 +1,10 @@ +import logging + +from airflow.providers.postgres.hooks.postgres import PostgresHook +from libs.settings import AIRFLOW_DAGRUN_TIMEOUT from libs.utils import ( pull_validated_params, ) -from airflow.providers.postgres.hooks.postgres import PostgresHook -import logging -from libs.settings import AIRFLOW_DAGRUN_TIMEOUT # PostgreSQL connection hook pg_hook = PostgresHook( diff --git a/app/airflow/dags/libs/rules_export/core.py b/app/airflow/dags/libs/rules_export/core.py index f6d544078..3d61444ca 100644 --- a/app/airflow/dags/libs/rules_export/core.py +++ b/app/airflow/dags/libs/rules_export/core.py @@ -1,23 +1,23 @@ import logging -from libs.utils import pull_validated_params +from datetime import datetime +from typing import Dict + from airflow.providers.postgres.hooks.postgres import PostgresHook -from libs.types import FileHandlerConfig +from libs.enums import JobStageType, StageStatusType +from libs.queries import create_file_entry_query, create_update_temp_rules_table_query from libs.rules_export.file_services import ( - build_rules_json, build_rules_csv, + build_rules_json, build_rules_json_v2, ) -from typing import Dict -from datetime import datetime -from libs.queries import create_update_temp_rules_table_query, create_file_entry_query -from libs.storage_services import upload_blob_to_storage -from libs.enums import JobStageType, StageStatusType -from libs.utils import update_job_status from libs.settings import ( + AIRFLOW_DAGRUN_TIMEOUT, AIRFLOW_DEBUG_MODE, AIRFLOW_VAR_JSON_VERSION, - AIRFLOW_DAGRUN_TIMEOUT, ) +from libs.storage_services import upload_blob_to_storage +from libs.types import FileHandlerConfig +from libs.utils import pull_validated_params, update_job_status # PostgreSQL connection hook pg_hook = PostgresHook( diff --git a/app/airflow/dags/libs/rules_export/file_services.py b/app/airflow/dags/libs/rules_export/file_services.py index 72e0e4551..fa7651d9c 100644 --- a/app/airflow/dags/libs/rules_export/file_services.py +++ b/app/airflow/dags/libs/rules_export/file_services.py @@ -1,16 +1,16 @@ -from typing import Any, Dict, List -from libs.enums import JobStageType, StageStatusType -from libs.utils import update_job_status +import csv +import io import json -import pandas as pd -from datetime import datetime, timezone +import logging +from datetime import date, datetime, timezone from io import BytesIO +from typing import Any, Dict, List + +import pandas as pd from airflow.providers.postgres.hooks.postgres import PostgresHook -import io -import csv -from datetime import date -import logging +from libs.enums import JobStageType, StageStatusType from libs.settings import AIRFLOW_DAGRUN_TIMEOUT +from libs.utils import update_job_status # PostgreSQL connection hook pg_hook = PostgresHook( @@ -353,19 +353,19 @@ def build_rules_json_v2(scan_report_name: str, scan_report_id: int) -> BytesIO: # Adding the person_id_mapping, date_mapping, concept_mapping to the result if person_id_mappings: - result[dest_table_str][source_table_clean][ - "person_id_mapping" - ] = person_id_mappings + result[dest_table_str][source_table_clean]["person_id_mapping"] = ( + person_id_mappings + ) if date_mappings: - result[dest_table_str][source_table_clean][ - "date_mapping" - ] = date_mappings + result[dest_table_str][source_table_clean]["date_mapping"] = ( + date_mappings + ) if concept_mappings: - result[dest_table_str][source_table_clean][ - "concept_mappings" - ] = concept_mappings + result[dest_table_str][source_table_clean]["concept_mappings"] = ( + concept_mappings + ) cdm = { "metadata": metadata, diff --git a/app/airflow/dags/libs/settings.py b/app/airflow/dags/libs/settings.py index 05ffa22fa..06d43b81a 100644 --- a/app/airflow/dags/libs/settings.py +++ b/app/airflow/dags/libs/settings.py @@ -1,4 +1,5 @@ import os + from libs.enums import StorageType storage_type = os.getenv("STORAGE_TYPE", StorageType.MINIO) diff --git a/app/airflow/dags/libs/storage_services.py b/app/airflow/dags/libs/storage_services.py index 19c2d4fd5..0f40beefa 100644 --- a/app/airflow/dags/libs/storage_services.py +++ b/app/airflow/dags/libs/storage_services.py @@ -1,12 +1,14 @@ -from airflow.providers.microsoft.azure.hooks.wasb import WasbHook -from airflow.providers.amazon.aws.hooks.s3 import S3Hook -from libs.enums import StorageType -from libs.settings import storage_type -from pathlib import Path import logging +from pathlib import Path from typing import Any + +from airflow.providers.amazon.aws.hooks.s3 import S3Hook +from airflow.providers.microsoft.azure.hooks.wasb import WasbHook from azure.storage.blob import ContentSettings +from libs.enums import StorageType +from libs.settings import storage_type + def get_storage_hook(): """ diff --git a/app/airflow/dags/libs/utils.py b/app/airflow/dags/libs/utils.py index 75dd8d00e..8ad26f14d 100644 --- a/app/airflow/dags/libs/utils.py +++ b/app/airflow/dags/libs/utils.py @@ -1,23 +1,24 @@ -import logging import ast import json -from airflow.providers.postgres.hooks.postgres import PostgresHook -from airflow.operators.python import PythonOperator -from typing import TypedDict, List, Optional, Dict, Any +import logging +from typing import Any, Dict, List, Optional, TypedDict + +from airflow.models.connection import Connection from airflow.models.taskinstance import TaskInstance -from libs.enums import JobStageType, StageStatusType, StorageType +from airflow.operators.python import PythonOperator +from airflow.providers.postgres.hooks.postgres import PostgresHook from airflow.utils.session import create_session -from airflow.models.connection import Connection + +from libs.enums import JobStageType, StageStatusType, StorageType from libs.settings import ( - storage_type, - AIRFLOW_VAR_WASB_CONNECTION_STRING, - AIRFLOW_VAR_MINIO_ENDPOINT, + AIRFLOW_DAGRUN_TIMEOUT, AIRFLOW_VAR_MINIO_ACCESS_KEY, + AIRFLOW_VAR_MINIO_ENDPOINT, AIRFLOW_VAR_MINIO_SECRET_KEY, - AIRFLOW_DAGRUN_TIMEOUT, + AIRFLOW_VAR_WASB_CONNECTION_STRING, + storage_type, ) - # PostgreSQL connection hook pg_hook = PostgresHook( postgres_conn_id="postgres_db_conn", diff --git a/app/airflow/dags/rules_export_dag.py b/app/airflow/dags/rules_export_dag.py index 10bd50765..c95fbebf4 100644 --- a/app/airflow/dags/rules_export_dag.py +++ b/app/airflow/dags/rules_export_dag.py @@ -1,14 +1,15 @@ from datetime import datetime, timedelta + from airflow import DAG from airflow.operators.empty import EmptyOperator +from libs.rules_export.core import build_and_upload_rules_file, pre_process_rules +from libs.settings import AIRFLOW_DAGRUN_TIMEOUT, AIRFLOW_DEBUG_MODE from libs.utils import ( + connect_to_storage, create_task, - validate_params_rules_export, update_job_status_on_failure, + validate_params_rules_export, ) -from libs.rules_export.core import pre_process_rules, build_and_upload_rules_file -from libs.utils import connect_to_storage -from libs.settings import AIRFLOW_DEBUG_MODE, AIRFLOW_DAGRUN_TIMEOUT """ This DAG automates the process of retrieving the mapping rules from the database, diff --git a/app/api/api/filters.py b/app/api/api/filters.py index 6baaf8531..12f82d97b 100644 --- a/app/api/api/filters.py +++ b/app/api/api/filters.py @@ -1,7 +1,7 @@ from django.db.models.query_utils import Q -from rest_framework import filters -from mapping.models import VisibilityChoices, ScanReportValue from django_filters import rest_framework as django_filters +from mapping.models import ScanReportValue, VisibilityChoices +from rest_framework import filters class HasConceptsFilter(django_filters.BooleanFilter): diff --git a/app/api/api/logger.py b/app/api/api/logger.py index 2cc0fa585..69fb135cb 100644 --- a/app/api/api/logger.py +++ b/app/api/api/logger.py @@ -5,7 +5,7 @@ stream_handler = logging.StreamHandler() stream_handler.setFormatter( logging.Formatter( - fmt="%(asctime)s - %(levelname)s - %(message)s", datefmt="%d/%m/%Y " "%H:%M:%S" + fmt="%(asctime)s - %(levelname)s - %(message)s", datefmt="%d/%m/%Y %H:%M:%S" ) ) logger.addHandler(stream_handler) diff --git a/app/api/api/serializers.py b/app/api/api/serializers.py index 8ab36ad2c..ad582fe8d 100644 --- a/app/api/api/serializers.py +++ b/app/api/api/serializers.py @@ -1,34 +1,33 @@ import csv -from collections import Counter +from collections import Counter, defaultdict from io import BytesIO, StringIO -from collections import defaultdict -from api.logger import logger - import openpyxl # type: ignore +from config.settings import DATA_UPLOAD_MAX_MEMORY_SIZE +from data.models import Concept from datasets.serializers import DatasetSerializer from django.contrib.auth.models import User from drf_dynamic_fields import DynamicFieldsMixin # type: ignore -from openpyxl.workbook.workbook import Workbook # type: ignore -from rest_framework import serializers -from rest_framework.exceptions import NotFound, ParseError, PermissionDenied -from data.models import Concept from mapping.models import ( Dataset, + MappingRecommendation, + MappingStatus, ScanReport, ScanReportConcept, ScanReportField, ScanReportTable, ScanReportValue, - VisibilityChoices, UploadStatus, - MappingStatus, - MappingRecommendation, + VisibilityChoices, ) -from users.serializers import UserSerializer from mapping.permissions import has_editorship, is_admin, is_az_function_user +from openpyxl.workbook.workbook import Workbook # type: ignore +from rest_framework import serializers +from rest_framework.exceptions import NotFound, ParseError, PermissionDenied from services.rules_export import analyse_concepts -from config.settings import DATA_UPLOAD_MAX_MEMORY_SIZE +from users.serializers import UserSerializer + +from api.logger import logger class ConceptSerializerV2(serializers.ModelSerializer): @@ -51,7 +50,6 @@ class Meta: class ConceptSerializerV3(DynamicFieldsMixin, serializers.ModelSerializer): - class Meta: model = Concept fields = [ @@ -430,8 +428,7 @@ def run_fast_consistency_checks(self, wb: Workbook): fo_only = set(expected_sheetnames).difference(actual_sheetnames) errors.append( ParseError( - "Tables in Field Overview sheet do not " - "match the sheets supplied." + "Tables in Field Overview sheet do not match the sheets supplied." ) ) if sheets_only: diff --git a/app/api/api/urls.py b/app/api/api/urls.py index 02dfe6bc7..161124483 100644 --- a/app/api/api/urls.py +++ b/app/api/api/urls.py @@ -1,12 +1,13 @@ -from api import views from django.urls import include, path -from files.views import FileDownloadView -from jobs.views import JobView from drf_spectacular.views import ( SpectacularAPIView, - SpectacularSwaggerView, SpectacularRedocView, + SpectacularSwaggerView, ) +from files.views import FileDownloadView +from jobs.views import JobView + +from api import views urlpatterns = [ path("v2/datasets/", include("datasets.urls")), diff --git a/app/api/api/views.py b/app/api/api/views.py index 0a0fb3f34..95965b6c2 100644 --- a/app/api/api/views.py +++ b/app/api/api/views.py @@ -2,27 +2,10 @@ import os import random import string +from importlib.metadata import version from typing import Any, Optional -from api.filters import ScanReportAccessFilter, ScanReportValueFilter -from api.mixins import ScanReportPermissionMixin -from api.paginations import CustomPagination -from api.serializers import ( - ConceptSerializerV2, - GetRulesAnalysis, - ScanReportConceptDetailSerializerV3, - ScanReportConceptSerializer, - ScanReportCreateSerializer, - ScanReportEditSerializer, - ScanReportFieldEditSerializer, - ScanReportFieldListSerializerV2, - ScanReportFilesSerializer, - ScanReportTableEditSerializer, - ScanReportTableListSerializerV2, - ScanReportValueViewSerializerV2, - ScanReportValueViewSerializerV3, - ScanReportViewSerializerV2, - UserSerializer, -) + +from data.models import Concept from datasets.serializers import DataPartnerSerializer from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType @@ -34,21 +17,8 @@ from django.views.decorators.cache import cache_page from django.views.decorators.vary import vary_on_cookie from django_filters.rest_framework import DjangoFilterBackend -from rest_framework import status, viewsets -from rest_framework.filters import OrderingFilter -from rest_framework.generics import GenericAPIView -from rest_framework.mixins import ( - CreateModelMixin, - DestroyModelMixin, - ListModelMixin, - RetrieveModelMixin, - UpdateModelMixin, -) -from rest_framework.parsers import FormParser, MultiPartParser -from rest_framework.permissions import IsAuthenticated, AllowAny -from rest_framework.response import Response -from rest_framework.views import APIView -from data.models import Concept +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import OpenApiParameter, extend_schema from jobs.models import Job, JobStage, StageStatus from mapping.models import ( DataDictionary, @@ -61,10 +31,21 @@ ScanReportTable, ScanReportValue, ) -from drf_spectacular.utils import extend_schema -from drf_spectacular.types import OpenApiTypes -from drf_spectacular.utils import OpenApiParameter from mapping.permissions import get_user_permissions_on_scan_report +from rest_framework import status, viewsets +from rest_framework.filters import OrderingFilter +from rest_framework.generics import GenericAPIView +from rest_framework.mixins import ( + CreateModelMixin, + DestroyModelMixin, + ListModelMixin, + RetrieveModelMixin, + UpdateModelMixin, +) +from rest_framework.parsers import FormParser, MultiPartParser +from rest_framework.permissions import AllowAny, IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView from services.rules import ( _find_destination_table, save_mapping_rules, @@ -76,7 +57,27 @@ ) from services.storage_service import StorageService from services.worker_service import get_worker_service -from importlib.metadata import version + +from api.filters import ScanReportAccessFilter, ScanReportValueFilter +from api.mixins import ScanReportPermissionMixin +from api.paginations import CustomPagination +from api.serializers import ( + ConceptSerializerV2, + GetRulesAnalysis, + ScanReportConceptDetailSerializerV3, + ScanReportConceptSerializer, + ScanReportCreateSerializer, + ScanReportEditSerializer, + ScanReportFieldEditSerializer, + ScanReportFieldListSerializerV2, + ScanReportFilesSerializer, + ScanReportTableEditSerializer, + ScanReportTableListSerializerV2, + ScanReportValueViewSerializerV2, + ScanReportValueViewSerializerV3, + ScanReportViewSerializerV2, + UserSerializer, +) storage_service = StorageService() worker_service = get_worker_service() diff --git a/app/api/authn/urls.py b/app/api/authn/urls.py index 82891f58e..4f45cfa0e 100644 --- a/app/api/authn/urls.py +++ b/app/api/authn/urls.py @@ -7,6 +7,7 @@ ) from django.urls import path from rest_framework_simplejwt.views import TokenVerifyView + from .views import PasswordResetView urlpatterns = [ diff --git a/app/api/authn/views.py b/app/api/authn/views.py index 05c803173..fad18807c 100644 --- a/app/api/authn/views.py +++ b/app/api/authn/views.py @@ -1,12 +1,12 @@ -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status -from drf_spectacular.utils import extend_schema +from django.contrib.auth.password_validation import validate_password +from django.core.exceptions import ValidationError as DjangoValidationError from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import extend_schema +from rest_framework import status from rest_framework.exceptions import ValidationError from rest_framework.permissions import IsAuthenticated -from django.contrib.auth.password_validation import validate_password -from django.core.exceptions import ValidationError as DjangoValidationError +from rest_framework.response import Response +from rest_framework.views import APIView class PasswordResetView(APIView): diff --git a/app/api/config/settings.py b/app/api/config/settings.py index b490f5e9b..fa37701ab 100644 --- a/app/api/config/settings.py +++ b/app/api/config/settings.py @@ -12,6 +12,7 @@ import os from datetime import timedelta + from dotenv import load_dotenv from services.enums import WorkerServiceType diff --git a/app/api/config/urls.py b/app/api/config/urls.py index 7b912a77e..25eb932d2 100644 --- a/app/api/config/urls.py +++ b/app/api/config/urls.py @@ -1,6 +1,6 @@ +from api.views import HealthCheckView from django.contrib import admin from django.urls import include, path -from api.views import HealthCheckView urlpatterns = [ path("api/", include("api.urls")), diff --git a/app/api/datasets/serializers.py b/app/api/datasets/serializers.py index 087ca2443..76a4ab41f 100644 --- a/app/api/datasets/serializers.py +++ b/app/api/datasets/serializers.py @@ -1,8 +1,8 @@ from drf_dynamic_fields import DynamicFieldsMixin # type: ignore -from rest_framework import serializers from mapping.models import DataPartner, Dataset from mapping.permissions import is_admin, is_az_function_user from projects.serializers import ProjectNameSerializer +from rest_framework import serializers from users.serializers import UserSerializer diff --git a/app/api/datasets/urls.py b/app/api/datasets/urls.py index b21256197..e0f794742 100644 --- a/app/api/datasets/urls.py +++ b/app/api/datasets/urls.py @@ -1,6 +1,7 @@ -from datasets import views from django.urls import path +from datasets import views + urlpatterns = [ path( r"", diff --git a/app/api/datasets/views.py b/app/api/datasets/views.py index b82c12e36..b9830aa94 100644 --- a/app/api/datasets/views.py +++ b/app/api/datasets/views.py @@ -1,14 +1,17 @@ import os from api.paginations import CustomPagination -from datasets.serializers import ( - DatasetAndDataPartnerViewSerializer, - DatasetEditSerializer, - DatasetViewSerializerV2, - DatasetCreateSerializerV2, -) from django.db.models.query_utils import Q from django_filters.rest_framework import DjangoFilterBackend +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import extend_schema +from mapping.models import Dataset, VisibilityChoices +from mapping.permissions import ( + CanAdmin, + CanEdit, + CanView, + get_user_permissions_on_dataset, +) from rest_framework import status from rest_framework.filters import OrderingFilter from rest_framework.generics import GenericAPIView @@ -21,14 +24,12 @@ ) from rest_framework.response import Response from rest_framework.views import APIView -from drf_spectacular.utils import extend_schema -from drf_spectacular.types import OpenApiTypes -from mapping.models import Dataset, VisibilityChoices -from mapping.permissions import ( - CanAdmin, - CanEdit, - CanView, - get_user_permissions_on_dataset, + +from datasets.serializers import ( + DatasetAndDataPartnerViewSerializer, + DatasetCreateSerializerV2, + DatasetEditSerializer, + DatasetViewSerializerV2, ) diff --git a/app/api/files/migrations/0001_initial.py b/app/api/files/migrations/0001_initial.py index e66da6ac8..f56a8184c 100644 --- a/app/api/files/migrations/0001_initial.py +++ b/app/api/files/migrations/0001_initial.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/app/api/files/views.py b/app/api/files/views.py index cb7e994e4..10bc67360 100644 --- a/app/api/files/views.py +++ b/app/api/files/views.py @@ -1,18 +1,20 @@ import json + from django.http import HttpResponse, JsonResponse from django.shortcuts import get_object_or_404 from django_filters.rest_framework import DjangoFilterBackend +from drf_spectacular.utils import extend_schema +from jobs.models import Job, JobStage, StageStatus +from mapping.models import ScanReport from rest_framework.filters import OrderingFilter from rest_framework.generics import GenericAPIView from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from rest_framework.permissions import IsAuthenticated -from files.paginations import CustomPagination -from jobs.models import Job, JobStage, StageStatus -from mapping.models import ScanReport -from drf_spectacular.utils import extend_schema from services.storage_service import StorageService from services.worker_service import get_worker_service +from files.paginations import CustomPagination + from .models import FileDownload from .serializers import FileDownloadSerializer @@ -133,7 +135,9 @@ def post(self, request, *args, **kwargs): file_type_description = ( "JSON V1" if file_type == "application/json_v1" - else "JSON V2" if file_type == "application/json_v2" else "CSV" + else "JSON V2" + if file_type == "application/json_v2" + else "CSV" ) Job.objects.create( scan_report=ScanReport.objects.get(id=scan_report_id), diff --git a/app/api/gunicorn.conf.py b/app/api/gunicorn.conf.py index 1932ae7d3..2bdf3da08 100644 --- a/app/api/gunicorn.conf.py +++ b/app/api/gunicorn.conf.py @@ -6,7 +6,6 @@ def on_starting(server): - load_dotenv() if os.getenv("APPLICATIONINSIGHTS_CONNECTION_STRING"): diff --git a/app/api/jobs/migrations/0001_initial.py b/app/api/jobs/migrations/0001_initial.py index be6dd8e9c..cc881d6df 100644 --- a/app/api/jobs/migrations/0001_initial.py +++ b/app/api/jobs/migrations/0001_initial.py @@ -1,8 +1,8 @@ # Generated by Django 4.2.15 on 2024-11-28 09:27 -from django.db import migrations, models import django.db.models.deletion from django.conf import settings +from django.db import migrations, models def seeding_objects(apps, schema_editor): @@ -71,7 +71,6 @@ def unseeding_objects(apps, schema_editor): class Migration(migrations.Migration): - initial = True dependencies = [ diff --git a/app/api/jobs/migrations/0002_remove_job_scan_report_id_and_more.py b/app/api/jobs/migrations/0002_remove_job_scan_report_id_and_more.py index 9924fd0ea..14f2f5630 100644 --- a/app/api/jobs/migrations/0002_remove_job_scan_report_id_and_more.py +++ b/app/api/jobs/migrations/0002_remove_job_scan_report_id_and_more.py @@ -1,11 +1,10 @@ # Generated by Django 4.2.15 on 2024-12-03 14:03 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): - dependencies = [ ("mapping", "0005_auto_20241015_0900"), ("jobs", "0001_initial"), diff --git a/app/api/jobs/migrations/0003_alter_job_details.py b/app/api/jobs/migrations/0003_alter_job_details.py index 6a87f56d1..49907d6da 100644 --- a/app/api/jobs/migrations/0003_alter_job_details.py +++ b/app/api/jobs/migrations/0003_alter_job_details.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("jobs", "0002_remove_job_scan_report_id_and_more"), ] diff --git a/app/api/jobs/serializers.py b/app/api/jobs/serializers.py index 57de730b1..b806aded1 100644 --- a/app/api/jobs/serializers.py +++ b/app/api/jobs/serializers.py @@ -1,5 +1,6 @@ from rest_framework import serializers -from .models import StageStatus, Job, JobStage + +from .models import Job, JobStage, StageStatus class StageStatusSerializer(serializers.ModelSerializer): @@ -15,7 +16,6 @@ class Meta: class JobSerializer(serializers.ModelSerializer): - stage = JobStageSerializer() status = StageStatusSerializer() diff --git a/app/api/manage.py b/app/api/manage.py index a6dec35b4..eadff94a0 100755 --- a/app/api/manage.py +++ b/app/api/manage.py @@ -1,5 +1,6 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" + import os import sys diff --git a/app/api/mapping/admin.py b/app/api/mapping/admin.py index 113fccb99..1087303fd 100644 --- a/app/api/mapping/admin.py +++ b/app/api/mapping/admin.py @@ -1,8 +1,10 @@ from django.contrib import admin + from mapping.models import ( DataDictionary, DataPartner, Dataset, + MappingRecommendation, MappingRule, OmopField, OmopTable, @@ -12,7 +14,6 @@ ScanReportField, ScanReportTable, ScanReportValue, - MappingRecommendation, ) diff --git a/app/api/mapping/management/commands/add_admins_to_datasets.py b/app/api/mapping/management/commands/add_admins_to_datasets.py index e85b6f05f..fc22d3e71 100644 --- a/app/api/mapping/management/commands/add_admins_to_datasets.py +++ b/app/api/mapping/management/commands/add_admins_to_datasets.py @@ -1,4 +1,5 @@ from django.core.management.base import BaseCommand + from mapping.models import Dataset diff --git a/app/api/mapping/management/commands/add_datasets_to_partner.py b/app/api/mapping/management/commands/add_datasets_to_partner.py index e538e0b63..4d763d54f 100644 --- a/app/api/mapping/management/commands/add_datasets_to_partner.py +++ b/app/api/mapping/management/commands/add_datasets_to_partner.py @@ -1,4 +1,5 @@ from django.core.management.base import BaseCommand + from mapping.models import DataPartner, Dataset diff --git a/app/api/mapping/management/commands/airflow_schema_creation.py b/app/api/mapping/management/commands/airflow_schema_creation.py index 21274a953..997f1bedc 100644 --- a/app/api/mapping/management/commands/airflow_schema_creation.py +++ b/app/api/mapping/management/commands/airflow_schema_creation.py @@ -1,11 +1,11 @@ import os + +from django.conf import settings from django.core.management.base import BaseCommand from django.db import connection -from django.conf import settings class Command(BaseCommand): - help = "Create Airflow schema in the database automatically" _SCHEMA_NAME = "airflow" @@ -58,7 +58,6 @@ def _create_schema(self): try: self.stdout.write(f"Creating Airflow schema '{self._SCHEMA_NAME}'...") with connection.cursor() as cursor: - # Create the schema in the PostgreSQL database cursor.execute(f"CREATE SCHEMA {self._SCHEMA_NAME};") db_user = settings.DATABASES["default"]["USER"] diff --git a/app/api/mapping/management/commands/automatic_queue_and_containers_creation.py b/app/api/mapping/management/commands/automatic_queue_and_containers_creation.py index 6ebaf0650..1af0d5dae 100644 --- a/app/api/mapping/management/commands/automatic_queue_and_containers_creation.py +++ b/app/api/mapping/management/commands/automatic_queue_and_containers_creation.py @@ -1,7 +1,8 @@ import os -from django.core.management.base import BaseCommand # type: ignore -from azure.storage.queue import QueueServiceClient # type: ignore + from azure.storage.blob import BlobServiceClient # type: ignore +from azure.storage.queue import QueueServiceClient # type: ignore +from django.core.management.base import BaseCommand # type: ignore from minio import Minio # type: ignore diff --git a/app/api/mapping/management/commands/automatic_seeding_data.py b/app/api/mapping/management/commands/automatic_seeding_data.py index 099240cc7..47b7efe6e 100644 --- a/app/api/mapping/management/commands/automatic_seeding_data.py +++ b/app/api/mapping/management/commands/automatic_seeding_data.py @@ -1,8 +1,9 @@ -from django.core.management.base import BaseCommand from django.core.management import call_command -from mapping.models import OmopTable +from django.core.management.base import BaseCommand from files.models import FileType +from mapping.models import OmopTable + class Command(BaseCommand): help = "Set automatic seeding of data if it is not already seeded." diff --git a/app/api/mapping/management/commands/default_super_user.py b/app/api/mapping/management/commands/default_super_user.py index e9080c606..c79e81a77 100644 --- a/app/api/mapping/management/commands/default_super_user.py +++ b/app/api/mapping/management/commands/default_super_user.py @@ -1,10 +1,11 @@ """Command to create a default superuser if no users exist""" -from django.contrib.auth import get_user_model -from django.core.management.base import BaseCommand +import os + import django import django.conf as dc -import os +from django.contrib.auth import get_user_model +from django.core.management.base import BaseCommand class Command(BaseCommand): diff --git a/app/api/mapping/management/commands/generate_rules_json.py b/app/api/mapping/management/commands/generate_rules_json.py index 7ddaa25e7..0b6144536 100644 --- a/app/api/mapping/management/commands/generate_rules_json.py +++ b/app/api/mapping/management/commands/generate_rules_json.py @@ -1,9 +1,10 @@ import json from django.core.management.base import BaseCommand -from mapping.models import MappingRule from services.rules_export import get_mapping_rules_json, make_dag +from mapping.models import MappingRule + class Command(BaseCommand): help = "generate_rules json" diff --git a/app/api/mapping/management/commands/unorphan_scanreports.py b/app/api/mapping/management/commands/unorphan_scanreports.py index 56b1537ca..5cca6401a 100644 --- a/app/api/mapping/management/commands/unorphan_scanreports.py +++ b/app/api/mapping/management/commands/unorphan_scanreports.py @@ -1,4 +1,5 @@ from django.core.management.base import BaseCommand + from mapping.models import Dataset, Project, ScanReport diff --git a/app/api/mapping/migrations/0004_mappingstatus_uploadstatus.py b/app/api/mapping/migrations/0004_mappingstatus_uploadstatus.py index 26dc8e7d9..c6ffee4c2 100644 --- a/app/api/mapping/migrations/0004_mappingstatus_uploadstatus.py +++ b/app/api/mapping/migrations/0004_mappingstatus_uploadstatus.py @@ -47,7 +47,6 @@ def remove_status_objects(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ("mapping", "0003_handmade_20220428_1503"), ] diff --git a/app/api/mapping/migrations/0005_auto_20241015_0900.py b/app/api/mapping/migrations/0005_auto_20241015_0900.py index 241b62046..7e5b08ee8 100644 --- a/app/api/mapping/migrations/0005_auto_20241015_0900.py +++ b/app/api/mapping/migrations/0005_auto_20241015_0900.py @@ -1,7 +1,7 @@ # Generated by Django 4.2.13 on 2024-10-15 09:00 -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models def migrate_status_data_forwards(apps, schema_editor): @@ -93,7 +93,6 @@ def migrate_status_data_backwards(apps, schema_editor): class Migration(migrations.Migration): - dependencies = [ ("mapping", "0004_mappingstatus_uploadstatus"), ] diff --git a/app/api/mapping/migrations/0006_scanreporttable_trigger_reuse.py b/app/api/mapping/migrations/0006_scanreporttable_trigger_reuse.py index b4f24ae25..f0c76dc72 100644 --- a/app/api/mapping/migrations/0006_scanreporttable_trigger_reuse.py +++ b/app/api/mapping/migrations/0006_scanreporttable_trigger_reuse.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("mapping", "0005_auto_20241015_0900"), ] diff --git a/app/api/mapping/migrations/0007_alter_dataset_hidden_alter_mappingstatus_value_and_more.py b/app/api/mapping/migrations/0007_alter_dataset_hidden_alter_mappingstatus_value_and_more.py index 0df0d39d3..1e9f790b7 100644 --- a/app/api/mapping/migrations/0007_alter_dataset_hidden_alter_mappingstatus_value_and_more.py +++ b/app/api/mapping/migrations/0007_alter_dataset_hidden_alter_mappingstatus_value_and_more.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ("contenttypes", "0002_remove_content_type_name"), ("data", "__first__"), diff --git a/app/api/mapping/migrations/0008_remove_scanreportfield_fraction_empty_and_more.py b/app/api/mapping/migrations/0008_remove_scanreportfield_fraction_empty_and_more.py index cac28b1a6..40246cb48 100644 --- a/app/api/mapping/migrations/0008_remove_scanreportfield_fraction_empty_and_more.py +++ b/app/api/mapping/migrations/0008_remove_scanreportfield_fraction_empty_and_more.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("mapping", "0007_alter_dataset_hidden_alter_mappingstatus_value_and_more"), ] diff --git a/app/api/mapping/migrations/0009_delete_classificationsystem_delete_nlpmodel_and_more.py b/app/api/mapping/migrations/0009_delete_classificationsystem_delete_nlpmodel_and_more.py index e32476470..0b9310e6b 100644 --- a/app/api/mapping/migrations/0009_delete_classificationsystem_delete_nlpmodel_and_more.py +++ b/app/api/mapping/migrations/0009_delete_classificationsystem_delete_nlpmodel_and_more.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("mapping", "0008_remove_scanreportfield_fraction_empty_and_more"), ] diff --git a/app/api/mapping/migrations/0010_scanreportconcept_confidence_and_more.py b/app/api/mapping/migrations/0010_scanreportconcept_confidence_and_more.py index faabb755f..986e9d16a 100644 --- a/app/api/mapping/migrations/0010_scanreportconcept_confidence_and_more.py +++ b/app/api/mapping/migrations/0010_scanreportconcept_confidence_and_more.py @@ -6,7 +6,6 @@ class Migration(migrations.Migration): - dependencies = [ ("mapping", "0009_delete_classificationsystem_delete_nlpmodel_and_more"), migrations.swappable_dependency(settings.AUTH_USER_MODEL), diff --git a/app/api/mapping/migrations/0011_scanreport_upload_status_details.py b/app/api/mapping/migrations/0011_scanreport_upload_status_details.py index 38c251ae7..4b554361a 100644 --- a/app/api/mapping/migrations/0011_scanreport_upload_status_details.py +++ b/app/api/mapping/migrations/0011_scanreport_upload_status_details.py @@ -4,7 +4,6 @@ class Migration(migrations.Migration): - dependencies = [ ("mapping", "0010_scanreportconcept_confidence_and_more"), ] diff --git a/app/api/mapping/migrations/0012_mappingrecommendation.py b/app/api/mapping/migrations/0012_mappingrecommendation.py index b17685f8c..ef07c23ef 100644 --- a/app/api/mapping/migrations/0012_mappingrecommendation.py +++ b/app/api/mapping/migrations/0012_mappingrecommendation.py @@ -5,7 +5,6 @@ class Migration(migrations.Migration): - dependencies = [ ("contenttypes", "0002_remove_content_type_name"), ("data", "__first__"), diff --git a/app/api/mapping/models.py b/app/api/mapping/models.py index ad55809b1..a686ca3f0 100644 --- a/app/api/mapping/models.py +++ b/app/api/mapping/models.py @@ -1,9 +1,9 @@ +from data.models import Concept from django.conf import settings from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models.constraints import UniqueConstraint -from data.models import Concept STATUS_LIVE = "LIVE" STATUS_ARCHIVED = "ARCHIVED" diff --git a/app/api/mapping/permissions.py b/app/api/mapping/permissions.py index 02a5bc026..f560cd233 100644 --- a/app/api/mapping/permissions.py +++ b/app/api/mapping/permissions.py @@ -5,6 +5,7 @@ from django.db.models.query_utils import Q from rest_framework import permissions from rest_framework.request import Request + from mapping.models import ( Dataset, ScanReport, diff --git a/app/api/projects/serializers.py b/app/api/projects/serializers.py index a43305d70..4f954b78a 100644 --- a/app/api/projects/serializers.py +++ b/app/api/projects/serializers.py @@ -1,6 +1,6 @@ from drf_dynamic_fields import DynamicFieldsMixin # type: ignore -from rest_framework import serializers from mapping.models import Project +from rest_framework import serializers from users.serializers import UserSerializer diff --git a/app/api/projects/urls.py b/app/api/projects/urls.py index edc859faa..44c2ccd74 100644 --- a/app/api/projects/urls.py +++ b/app/api/projects/urls.py @@ -1,4 +1,5 @@ from django.urls import path + from projects import views urlpatterns = [ diff --git a/app/api/projects/views.py b/app/api/projects/views.py index 6b64fcf8c..cf1a645d8 100644 --- a/app/api/projects/views.py +++ b/app/api/projects/views.py @@ -1,14 +1,15 @@ +from api.paginations import CustomPagination from django_filters.rest_framework import DjangoFilterBackend +from mapping.models import Project +from mapping.permissions import CanViewProject +from rest_framework.filters import OrderingFilter +from rest_framework.generics import ListAPIView, RetrieveAPIView +from rest_framework.permissions import IsAuthenticated + from projects.serializers import ( ProjectDatasetSerializer, ProjectSerializer, ) -from rest_framework.generics import ListAPIView, RetrieveAPIView -from rest_framework.permissions import IsAuthenticated -from mapping.models import Project -from mapping.permissions import CanViewProject -from api.paginations import CustomPagination -from rest_framework.filters import OrderingFilter class ProjectList(ListAPIView): diff --git a/app/api/services/field_vocab_mappings.py b/app/api/services/field_vocab_mappings.py index 81e532a8f..d342c4543 100644 --- a/app/api/services/field_vocab_mappings.py +++ b/app/api/services/field_vocab_mappings.py @@ -1,9 +1,10 @@ import logging from typing import List -from mapping.models import ScanReportTable, ScanReportField -from services.storage_service import StorageService +from mapping.models import ScanReportField, ScanReportTable + from services.models import VocabularyMapping +from services.storage_service import StorageService logger = logging.getLogger("test_table_mapping_logger") diff --git a/app/api/services/rules.py b/app/api/services/rules.py index ecc6f977a..e7400f5ce 100644 --- a/app/api/services/rules.py +++ b/app/api/services/rules.py @@ -1,7 +1,7 @@ from typing import List, Optional -from django.db import transaction from data.models import Concept +from django.db import transaction from mapping.models import ( MappingRule, OmopField, diff --git a/app/api/services/rules_export.py b/app/api/services/rules_export.py index c9b3800a0..2976a70eb 100644 --- a/app/api/services/rules_export.py +++ b/app/api/services/rules_export.py @@ -3,11 +3,11 @@ from datetime import date, datetime, timezone from typing import Any +from data.models import Concept, ConceptAncestor from django.contrib.contenttypes.models import ContentType from django.db.models import Q from django.db.models.query import QuerySet from graphviz import Digraph -from data.models import Concept, ConceptAncestor from mapping.models import ( MappingRule, OmopField, @@ -17,6 +17,7 @@ ScanReportTable, ScanReportValue, ) + from services.enums import ContentTypeModel diff --git a/app/api/services/storage_service.py b/app/api/services/storage_service.py index 78f306da1..bfd4e612c 100644 --- a/app/api/services/storage_service.py +++ b/app/api/services/storage_service.py @@ -1,14 +1,17 @@ import csv import logging import os +from enum import StrEnum from io import BytesIO from typing import IO, Any, AnyStr, Dict, Iterable, Optional, Tuple, Union -from enum import StrEnum import openpyxl # type: ignore -from azure.storage.blob import BlobServiceClient # type: ignore -from azure.storage.blob import ContentSettings +from azure.storage.blob import ( + BlobServiceClient, # type: ignore + ContentSettings, +) from minio import Minio + from services.utils import ( process_four_item_dict, process_three_item_dict, @@ -395,7 +398,6 @@ def upload_file( file_size = len(file_content) if self._minio_client: - try: self._minio_client.put_object( container, diff --git a/app/api/services/worker_service.py b/app/api/services/worker_service.py index 05abe40db..bbad26022 100644 --- a/app/api/services/worker_service.py +++ b/app/api/services/worker_service.py @@ -1,13 +1,15 @@ -from requests.auth import HTTPBasicAuth import logging +from abc import ABC, abstractmethod +from typing import Any, Dict, Optional from urllib.parse import urljoin + import requests -from typing import Optional, Dict, Any -from mapping.models import ScanReport, ScanReportTable -from services.field_vocab_mappings import get_field_vocab_mappings from django.conf import settings -from abc import ABC, abstractmethod +from mapping.models import ScanReport, ScanReportTable +from requests.auth import HTTPBasicAuth + from services.enums import WorkerServiceType +from services.field_vocab_mappings import get_field_vocab_mappings class WorkerService(ABC): diff --git a/app/api/test/conftest.py b/app/api/test/conftest.py index 20a5614ba..86160d0c4 100644 --- a/app/api/test/conftest.py +++ b/app/api/test/conftest.py @@ -2,12 +2,12 @@ import psycopg2 import pytest +from data.models import Concept, ConceptRelationship from django.conf import settings from django.core.management import call_command from django.db import connection from psycopg2.extensions import ISOLATION_LEVEL_AUTOCOMMIT from pytest_django import DjangoDbBlocker -from data.models import Concept, ConceptRelationship def run_sql(db: str, sql: str): diff --git a/app/api/test/test_permissions.py b/app/api/test/test_permissions.py index 466acce0c..83333d602 100644 --- a/app/api/test/test_permissions.py +++ b/app/api/test/test_permissions.py @@ -3,10 +3,6 @@ from django.contrib.auth import get_user_model from django.test import TestCase -from projects.views import ProjectDetail -from rest_framework.authtoken.models import Token -from rest_framework.generics import GenericAPIView -from rest_framework.test import APIRequestFactory, force_authenticate from mapping.models import ( DataPartner, Dataset, @@ -23,6 +19,10 @@ has_viewership, is_admin, ) +from projects.views import ProjectDetail +from rest_framework.authtoken.models import Token +from rest_framework.generics import GenericAPIView +from rest_framework.test import APIRequestFactory, force_authenticate class TestHasViewership(TestCase): diff --git a/app/api/test/test_serializers.py b/app/api/test/test_serializers.py index 462530145..26a0ef8ed 100644 --- a/app/api/test/test_serializers.py +++ b/app/api/test/test_serializers.py @@ -1,29 +1,26 @@ -from api.serializers import ScanReportEditSerializer +from api.serializers import ( + MappingRecommendationSerializerV3, + ScanReportEditSerializer, + ScanReportValueViewSerializerV3, +) +from data.models import Concept from datasets.serializers import DatasetEditSerializer from django.contrib.auth import get_user_model +from django.contrib.contenttypes.models import ContentType from django.test import TestCase -from rest_framework.serializers import ValidationError -from rest_framework.test import APIRequestFactory from mapping.models import ( DataPartner, Dataset, + MappingRecommendation, Project, ScanReport, - VisibilityChoices, -) -from django.contrib.contenttypes.models import ContentType -from data.models import Concept -from mapping.models import ( - ScanReport, - ScanReportTable, ScanReportField, + ScanReportTable, ScanReportValue, - MappingRecommendation, -) -from api.serializers import ( - MappingRecommendationSerializerV3, - ScanReportValueViewSerializerV3, + VisibilityChoices, ) +from rest_framework.serializers import ValidationError +from rest_framework.test import APIRequestFactory class TestScanReportEditSerializer(TestCase): diff --git a/app/api/test/test_services_rules.py b/app/api/test/test_services_rules.py index 1dcc65fc6..debc6abbe 100644 --- a/app/api/test/test_services_rules.py +++ b/app/api/test/test_services_rules.py @@ -1,8 +1,7 @@ +from data.models import Concept from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.test import TestCase -from rest_framework.authtoken.models import Token -from data.models import Concept from mapping.models import ( DataPartner, Dataset, @@ -16,6 +15,7 @@ ScanReportTable, ScanReportValue, ) +from rest_framework.authtoken.models import Token class TestMisalignedMappings(TestCase): diff --git a/app/api/test/test_setup.py b/app/api/test/test_setup.py index 5c227667d..bafa74970 100644 --- a/app/api/test/test_setup.py +++ b/app/api/test/test_setup.py @@ -1,5 +1,5 @@ -from django.test import TestCase import mapping.management.commands.default_super_user as dsu +from django.test import TestCase def mocks(command): diff --git a/app/api/test/test_views.py b/app/api/test/test_views.py index 1a02d5417..f53fe7dfb 100644 --- a/app/api/test/test_views.py +++ b/app/api/test/test_views.py @@ -7,12 +7,11 @@ from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.test import TestCase, TransactionTestCase -from rest_framework.authtoken.models import Token -from rest_framework.test import APIClient, APIRequestFactory, force_authenticate from mapping.models import ( Concept, DataPartner, Dataset, + MappingRecommendation, Project, ScanReport, ScanReportConcept, @@ -20,8 +19,9 @@ ScanReportTable, ScanReportValue, VisibilityChoices, - MappingRecommendation, ) +from rest_framework.authtoken.models import Token +from rest_framework.test import APIClient, APIRequestFactory, force_authenticate class TestDatasetListView(TestCase): diff --git a/pyproject.toml b/pyproject.toml index 181153e65..e06f5bde1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,12 +18,13 @@ test = [ ] dev = [ "pre-commit>=3.6.1,<4", - "isort>=5.13.2,<6", - "black>=24.2.0,<25", "mypy>=1.8.0,<2", "flake8>=7.0.0,<8", + "ruff>=0.13.0", ] + + [tool.uv] default-groups = [ "test", diff --git a/uv.lock b/uv.lock index 6f62f27bb..0d8f1c3b3 100644 --- a/uv.lock +++ b/uv.lock @@ -2,40 +2,6 @@ version = 1 revision = 2 requires-python = ">=3.9, <4" -[[package]] -name = "black" -version = "24.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "mypy-extensions" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "platformdirs" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/29/69/f3ab49cdb938b3eecb048fa64f86bdadb1fac26e92c435d287181d543b0a/black-24.2.0.tar.gz", hash = "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894", size = 631598, upload-time = "2024-02-12T20:21:26.969Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/75/25d478751e03a46663b3ae9ff961deb3c2ce1b24885f6fc010df5ba08b1d/black-24.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29", size = 1575905, upload-time = "2024-02-12T20:32:30.688Z" }, - { url = "https://files.pythonhosted.org/packages/88/12/59b6ed8eefa43a0b131a97db5820141b94f6722c627cb10346c5408cf7f3/black-24.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430", size = 1423215, upload-time = "2024-02-12T20:31:24.135Z" }, - { url = "https://files.pythonhosted.org/packages/46/b8/70a3cab340301d480f601d483452e6e68da61202abad881f1a91250c2d27/black-24.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f", size = 1730127, upload-time = "2024-02-12T20:23:44.397Z" }, - { url = "https://files.pythonhosted.org/packages/64/5f/74be45f22ac104b4759084c792798f6b5695a5b4b2064e31222fea7fd3c7/black-24.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a", size = 1355109, upload-time = "2024-02-12T20:25:09.905Z" }, - { url = "https://files.pythonhosted.org/packages/6c/de/938fd8c271ee903212f3599d7ece0d4a930af667c51135d4e6d1fac50455/black-24.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd", size = 1556863, upload-time = "2024-02-12T20:33:27.299Z" }, - { url = "https://files.pythonhosted.org/packages/11/15/4646143c19bc896a0cbc0c6663abf5751b465edefbc49491179454e2e2fd/black-24.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2", size = 1403471, upload-time = "2024-02-12T20:33:11.704Z" }, - { url = "https://files.pythonhosted.org/packages/1a/53/0b82f36f0a8beee5388f6bcc3a1591e094c87a275c64532ac6ce69fb491f/black-24.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92", size = 1710660, upload-time = "2024-02-12T20:24:29.273Z" }, - { url = "https://files.pythonhosted.org/packages/72/be/58ae1a41e1d174a05d074a7a10524f1cdf37df13be3ac314098ab9435550/black-24.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23", size = 1361881, upload-time = "2024-02-12T20:25:27.484Z" }, - { url = "https://files.pythonhosted.org/packages/43/1e/67c87a1fb39592aa944f35cc26892946ebe0a10aa324b87f9380b8753862/black-24.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b", size = 1585288, upload-time = "2024-02-12T20:37:13.8Z" }, - { url = "https://files.pythonhosted.org/packages/5e/62/6437212cf40e40b74dbc7e134700a21cb21a9ac7e46ade940b5d4826456f/black-24.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9", size = 1417360, upload-time = "2024-02-12T20:34:56.41Z" }, - { url = "https://files.pythonhosted.org/packages/36/8f/de0d339ae683422a8e15d6f74b8022d4947009c347d8c2178c303c68cc4d/black-24.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693", size = 1739406, upload-time = "2024-02-12T20:23:59.596Z" }, - { url = "https://files.pythonhosted.org/packages/3e/58/89e5f5a1c4c5b66dc74eabe6337623d53b4d1c27fbbbe16defee53397f60/black-24.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982", size = 1373310, upload-time = "2024-02-12T20:25:27.243Z" }, - { url = "https://files.pythonhosted.org/packages/30/e1/eb61bca6e1bb69529bc1295f17ca7a0ebadf56c480de37b2acb485dc04e2/black-24.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8", size = 1575445, upload-time = "2024-02-12T20:41:38.354Z" }, - { url = "https://files.pythonhosted.org/packages/1f/44/41f6766ad9007b98478547ba5cceada721383ae7a81793c1180cb7ad8b30/black-24.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8", size = 1422403, upload-time = "2024-02-12T20:37:16.005Z" }, - { url = "https://files.pythonhosted.org/packages/d4/76/0c4bbb734126cdbbd7e82f6df4b58e67e17742250b15f1bac6901f1e8d9b/black-24.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540", size = 1728526, upload-time = "2024-02-12T20:23:42.22Z" }, - { url = "https://files.pythonhosted.org/packages/5a/72/7441c2d961140a2273a4047429b4cd30b663d76c8c375d66f7fd57ae10f5/black-24.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31", size = 1354913, upload-time = "2024-02-12T20:24:53.441Z" }, - { url = "https://files.pythonhosted.org/packages/47/15/b3770bc3328685a53bc9c041136240146c5cd866a1f020c2cf47f2ff9683/black-24.2.0-py3-none-any.whl", hash = "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6", size = 200610, upload-time = "2024-02-12T20:21:17.657Z" }, -] - [[package]] name = "carrot-mapper" version = "4.2.2" @@ -43,11 +9,10 @@ source = { virtual = "." } [package.dev-dependencies] dev = [ - { name = "black" }, { name = "flake8" }, - { name = "isort" }, { name = "mypy" }, { name = "pre-commit" }, + { name = "ruff" }, ] test = [ { name = "coverage" }, @@ -59,11 +24,10 @@ test = [ [package.metadata.requires-dev] dev = [ - { name = "black", specifier = ">=24.2.0,<25" }, { name = "flake8", specifier = ">=7.0.0,<8" }, - { name = "isort", specifier = ">=5.13.2,<6" }, { name = "mypy", specifier = ">=1.8.0,<2" }, { name = "pre-commit", specifier = ">=3.6.1,<4" }, + { name = "ruff", specifier = ">=0.13.0" }, ] test = [ { name = "coverage", specifier = ">=7.3.0,<8" }, @@ -80,18 +44,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, ] -[[package]] -name = "click" -version = "8.1.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121, upload-time = "2023-08-17T17:29:11.868Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941, upload-time = "2023-08-17T17:29:10.08Z" }, -] - [[package]] name = "colorama" version = "0.4.6" @@ -214,15 +166,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892, upload-time = "2023-01-07T11:08:09.864Z" }, ] -[[package]] -name = "isort" -version = "5.13.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303, upload-time = "2023-12-13T20:37:26.124Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310, upload-time = "2023-12-13T20:37:23.244Z" }, -] - [[package]] name = "mccabe" version = "0.7.0" @@ -296,15 +239,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/1a/610693ac4ee14fcdf2d9bf3c493370e4f2ef7ae2e19217d7a237ff42367d/packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7", size = 53011, upload-time = "2023-10-01T13:50:03.745Z" }, ] -[[package]] -name = "pathspec" -version = "0.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, -] - [[package]] name = "platformdirs" version = "4.2.0" @@ -426,6 +360,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/84/4d/82704d1ab9290b03da94e6425f5e87396b999fd7eb8e08f3a92c158402bf/PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486", size = 152751, upload-time = "2023-07-18T00:00:19.939Z" }, ] +[[package]] +name = "ruff" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/1a/1f4b722862840295bcaba8c9e5261572347509548faaa99b2d57ee7bfe6a/ruff-0.13.0.tar.gz", hash = "sha256:5b4b1ee7eb35afae128ab94459b13b2baaed282b1fb0f472a73c82c996c8ae60", size = 5372863, upload-time = "2025-09-10T16:25:37.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/fe/6f87b419dbe166fd30a991390221f14c5b68946f389ea07913e1719741e0/ruff-0.13.0-py3-none-linux_armv6l.whl", hash = "sha256:137f3d65d58ee828ae136a12d1dc33d992773d8f7644bc6b82714570f31b2004", size = 12187826, upload-time = "2025-09-10T16:24:39.5Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/c92296b1fc36d2499e12b74a3fdb230f77af7bdf048fad7b0a62e94ed56a/ruff-0.13.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:21ae48151b66e71fd111b7d79f9ad358814ed58c339631450c66a4be33cc28b9", size = 12933428, upload-time = "2025-09-10T16:24:43.866Z" }, + { url = "https://files.pythonhosted.org/packages/44/cf/40bc7221a949470307d9c35b4ef5810c294e6cfa3caafb57d882731a9f42/ruff-0.13.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:64de45f4ca5441209e41742d527944635a05a6e7c05798904f39c85bafa819e3", size = 12095543, upload-time = "2025-09-10T16:24:46.638Z" }, + { url = "https://files.pythonhosted.org/packages/f1/03/8b5ff2a211efb68c63a1d03d157e924997ada87d01bebffbd13a0f3fcdeb/ruff-0.13.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b2c653ae9b9d46e0ef62fc6fbf5b979bda20a0b1d2b22f8f7eb0cde9f4963b8", size = 12312489, upload-time = "2025-09-10T16:24:49.556Z" }, + { url = "https://files.pythonhosted.org/packages/37/fc/2336ef6d5e9c8d8ea8305c5f91e767d795cd4fc171a6d97ef38a5302dadc/ruff-0.13.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4cec632534332062bc9eb5884a267b689085a1afea9801bf94e3ba7498a2d207", size = 11991631, upload-time = "2025-09-10T16:24:53.439Z" }, + { url = "https://files.pythonhosted.org/packages/39/7f/f6d574d100fca83d32637d7f5541bea2f5e473c40020bbc7fc4a4d5b7294/ruff-0.13.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd628101d9f7d122e120ac7c17e0a0f468b19bc925501dbe03c1cb7f5415b24", size = 13720602, upload-time = "2025-09-10T16:24:56.392Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c8/a8a5b81d8729b5d1f663348d11e2a9d65a7a9bd3c399763b1a51c72be1ce/ruff-0.13.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:afe37db8e1466acb173bb2a39ca92df00570e0fd7c94c72d87b51b21bb63efea", size = 14697751, upload-time = "2025-09-10T16:24:59.89Z" }, + { url = "https://files.pythonhosted.org/packages/57/f5/183ec292272ce7ec5e882aea74937f7288e88ecb500198b832c24debc6d3/ruff-0.13.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f96a8d90bb258d7d3358b372905fe7333aaacf6c39e2408b9f8ba181f4b6ef2", size = 14095317, upload-time = "2025-09-10T16:25:03.025Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8d/7f9771c971724701af7926c14dab31754e7b303d127b0d3f01116faef456/ruff-0.13.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b5e3d883e4f924c5298e3f2ee0f3085819c14f68d1e5b6715597681433f153", size = 13144418, upload-time = "2025-09-10T16:25:06.272Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a6/7985ad1778e60922d4bef546688cd8a25822c58873e9ff30189cfe5dc4ab/ruff-0.13.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03447f3d18479df3d24917a92d768a89f873a7181a064858ea90a804a7538991", size = 13370843, upload-time = "2025-09-10T16:25:09.965Z" }, + { url = "https://files.pythonhosted.org/packages/64/1c/bafdd5a7a05a50cc51d9f5711da704942d8dd62df3d8c70c311e98ce9f8a/ruff-0.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fbc6b1934eb1c0033da427c805e27d164bb713f8e273a024a7e86176d7f462cf", size = 13321891, upload-time = "2025-09-10T16:25:12.969Z" }, + { url = "https://files.pythonhosted.org/packages/bc/3e/7817f989cb9725ef7e8d2cee74186bf90555279e119de50c750c4b7a72fe/ruff-0.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8ab6a3e03665d39d4a25ee199d207a488724f022db0e1fe4002968abdb8001b", size = 12119119, upload-time = "2025-09-10T16:25:16.621Z" }, + { url = "https://files.pythonhosted.org/packages/58/07/9df080742e8d1080e60c426dce6e96a8faf9a371e2ce22eef662e3839c95/ruff-0.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2a5c62f8ccc6dd2fe259917482de7275cecc86141ee10432727c4816235bc41", size = 11961594, upload-time = "2025-09-10T16:25:19.49Z" }, + { url = "https://files.pythonhosted.org/packages/6a/f4/ae1185349197d26a2316840cb4d6c3fba61d4ac36ed728bf0228b222d71f/ruff-0.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b7b85ca27aeeb1ab421bc787009831cffe6048faae08ad80867edab9f2760945", size = 12933377, upload-time = "2025-09-10T16:25:22.371Z" }, + { url = "https://files.pythonhosted.org/packages/b6/39/e776c10a3b349fc8209a905bfb327831d7516f6058339a613a8d2aaecacd/ruff-0.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:79ea0c44a3032af768cabfd9616e44c24303af49d633b43e3a5096e009ebe823", size = 13418555, upload-time = "2025-09-10T16:25:25.681Z" }, + { url = "https://files.pythonhosted.org/packages/46/09/dca8df3d48e8b3f4202bf20b1658898e74b6442ac835bfe2c1816d926697/ruff-0.13.0-py3-none-win32.whl", hash = "sha256:4e473e8f0e6a04e4113f2e1de12a5039579892329ecc49958424e5568ef4f768", size = 12141613, upload-time = "2025-09-10T16:25:28.664Z" }, + { url = "https://files.pythonhosted.org/packages/61/21/0647eb71ed99b888ad50e44d8ec65d7148babc0e242d531a499a0bbcda5f/ruff-0.13.0-py3-none-win_amd64.whl", hash = "sha256:48e5c25c7a3713eea9ce755995767f4dcd1b0b9599b638b12946e892123d1efb", size = 13258250, upload-time = "2025-09-10T16:25:31.773Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a3/03216a6a86c706df54422612981fb0f9041dbb452c3401501d4a22b942c9/ruff-0.13.0-py3-none-win_arm64.whl", hash = "sha256:ab80525317b1e1d38614addec8ac954f1b3e662de9d59114ecbf771d00cf613e", size = 12312357, upload-time = "2025-09-10T16:25:35.595Z" }, +] + [[package]] name = "setuptools" version = "69.1.0"