From df980bb2965469323a44750f0156a897cfffea6b Mon Sep 17 00:00:00 2001 From: Haresh Kainth Date: Tue, 10 Dec 2024 00:07:28 +0000 Subject: [PATCH 01/12] refactor:restructure project architecture Reorganized application package structure to improve modularity and navigability. Moved files from `fbr` to `app` directory and renamed key modules to better reflect functionality, such as `celery_worker`. Additionally, updated all relevant imports and settings configurations to align with the new structure, including Django settings and task management. --- .copilot/image_build_run.sh | 2 +- .github/workflows/code_quality.yml | 2 +- Makefile | 22 +- Procfile | 5 +- {fbr/cache => app}/__init__.py | 0 {fbr/core => app/cache}/__init__.py | 0 .../cache/construction_legislation.py | 0 {fbr => app}/cache/legislation.py | 8 +- {fbr => app}/cache/manage_cache.py | 13 +- {fbr => app}/cache/public_gateway.py | 4 +- {fbr/core/models => app/core}/__init__.py | 0 {fbr => app}/core/admin.py | 0 {fbr => app}/core/apps.py | 2 +- {fbr => app}/core/cookies.py | 0 {fbr => app}/core/forms.py | 0 {fbr => app}/core/healthcheck.py | 0 .../core/models}/__init__.py | 0 .../templates/accessibility_statement.html | 0 {fbr => app}/core/templates/cookies.html | 0 {fbr => app}/core/templates/home.html | 0 .../core/templates/privacy_notice.html | 0 .../core/templatetags}/__init__.py | 0 {fbr => app}/core/templatetags/cookie_tags.py | 0 .../core/templatetags/service_problem_tags.py | 0 .../core/templatetags/url_replace_tags.py | 0 {fbr => app}/core/views.py | 0 .../migrations => app/search}/__init__.py | 0 {fbr => app}/search/apps.py | 2 +- {fbr => app}/search/config.py | 0 .../search/migrations/0001_initial.py | 0 .../search/migrations}/__init__.py | 0 {fbr => app}/search/models.py | 0 {fbr => app}/search/templates/django-fbr.html | 0 {fbr => app}/search/templates/document.html | 0 {fbr => app}/search/templates/intro.html | 0 {fbr => app}/search/templates/react-fbr.html | 0 .../utils => app/search/tests}/__init__.py | 0 {fbr => app}/search/tests/test_search.py | 0 app/search/utils/__init__.py | 0 {fbr => app}/search/utils/date.py | 0 {fbr => app}/search/utils/documents.py | 2 +- {fbr => app}/search/utils/paginate.py | 2 +- {fbr => app}/search/utils/search.py | 17 +- {fbr => app}/search/utils/terms.py | 0 app/search/views.py | 74 ++++ {fbr => app}/templates/base.html | 0 {fbr => app}/templates/cookie_banner.html | 0 {fbr => app}/templates/service_problem.html | 0 celery_worker/__init__.py | 0 {fbr/cache => celery_worker}/tasks.py | 13 +- docker-compose.yml | 55 +++ fbr/__init__.py | 6 +- fbr/{config => }/asgi.py | 0 fbr/config/settings/base.py | 1 + fbr/config/settings/local.py | 1 - fbr/{config => }/context_processors.py | 0 fbr/search/views.py | 150 ------- fbr/settings.py | 289 +++++++++++++ fbr/{config => }/urls.py | 112 ++--- fbr/{config => }/version.py | 0 fbr/{config => }/wsgi.py | 2 +- local.env.example | 2 +- local_deployment/Dockerfile | 2 +- local_deployment/entry.sh | 5 +- fbr/manage.py => manage.py | 3 +- poetry.lock | 394 +++++++++--------- pyproject.toml | 4 +- pytest.ini | 2 +- 68 files changed, 742 insertions(+), 454 deletions(-) rename {fbr/cache => app}/__init__.py (100%) rename {fbr/core => app/cache}/__init__.py (100%) rename {fbr => app}/cache/construction_legislation.py (100%) rename {fbr => app}/cache/legislation.py (97%) rename {fbr => app}/cache/manage_cache.py (63%) rename {fbr => app}/cache/public_gateway.py (96%) rename {fbr/core/models => app/core}/__init__.py (100%) rename {fbr => app}/core/admin.py (100%) rename {fbr => app}/core/apps.py (90%) rename {fbr => app}/core/cookies.py (100%) rename {fbr => app}/core/forms.py (100%) rename {fbr => app}/core/healthcheck.py (100%) rename {fbr/core/templatetags => app/core/models}/__init__.py (100%) rename {fbr => app}/core/templates/accessibility_statement.html (100%) rename {fbr => app}/core/templates/cookies.html (100%) rename {fbr => app}/core/templates/home.html (100%) rename {fbr => app}/core/templates/privacy_notice.html (100%) rename {fbr/search => app/core/templatetags}/__init__.py (100%) rename {fbr => app}/core/templatetags/cookie_tags.py (100%) rename {fbr => app}/core/templatetags/service_problem_tags.py (100%) rename {fbr => app}/core/templatetags/url_replace_tags.py (100%) rename {fbr => app}/core/views.py (100%) rename {fbr/search/migrations => app/search}/__init__.py (100%) rename {fbr => app}/search/apps.py (95%) rename {fbr => app}/search/config.py (100%) rename {fbr => app}/search/migrations/0001_initial.py (100%) rename {fbr/search/tests => app/search/migrations}/__init__.py (100%) rename {fbr => app}/search/models.py (100%) rename {fbr => app}/search/templates/django-fbr.html (100%) rename {fbr => app}/search/templates/document.html (100%) rename {fbr => app}/search/templates/intro.html (100%) rename {fbr => app}/search/templates/react-fbr.html (100%) rename {fbr/search/utils => app/search/tests}/__init__.py (100%) rename {fbr => app}/search/tests/test_search.py (100%) create mode 100644 app/search/utils/__init__.py rename {fbr => app}/search/utils/date.py (100%) rename {fbr => app}/search/utils/documents.py (98%) rename {fbr => app}/search/utils/paginate.py (98%) rename {fbr => app}/search/utils/search.py (94%) rename {fbr => app}/search/utils/terms.py (100%) create mode 100644 app/search/views.py rename {fbr => app}/templates/base.html (100%) rename {fbr => app}/templates/cookie_banner.html (100%) rename {fbr => app}/templates/service_problem.html (100%) create mode 100644 celery_worker/__init__.py rename {fbr/cache => celery_worker}/tasks.py (77%) rename fbr/{config => }/asgi.py (100%) rename fbr/{config => }/context_processors.py (100%) delete mode 100644 fbr/search/views.py create mode 100644 fbr/settings.py rename fbr/{config => }/urls.py (62%) rename fbr/{config => }/version.py (100%) rename fbr/{config => }/wsgi.py (82%) rename fbr/manage.py => manage.py (88%) diff --git a/.copilot/image_build_run.sh b/.copilot/image_build_run.sh index f089c13..aa55b50 100755 --- a/.copilot/image_build_run.sh +++ b/.copilot/image_build_run.sh @@ -6,6 +6,6 @@ set -e # Add commands below to run inside the container after all the other buildpacks have been applied export BUILD_STEP='True' export COPILOT_ENVIRONMENT_NAME='build' -export DJANGO_SETTINGS_MODULE="config.settings.base" +export DJANGO_SETTINGS_MODULE="fbr.settings.base" poetry run python fbr/manage.py collectstatic --noinput diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 4ad2462..19f0581 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -65,7 +65,7 @@ jobs: run: | npm install npm run build - DJANGO_SETTINGS_MODULE=config.settings.local poetry run fbr/manage.py collectstatic --noinput + DJANGO_SETTINGS_MODULE=fbr.settings poetry run fbr/manage.py collectstatic --noinput # poetry run fbr/manage.py makemigrations --check --dry-run diff --git a/Makefile b/Makefile index 09264c6..c02e002 100644 --- a/Makefile +++ b/Makefile @@ -38,10 +38,10 @@ build: # Build docker containers for local execution docker compose build collectstatic: # Run Django collectstatic - docker compose run --rm web poetry run python fbr/manage.py collectstatic --noinput + docker compose run --rm web poetry run python manage.py collectstatic --noinput admin: # Create a superuser - docker compose exec web poetry run python fbr/manage.py createsuperuser --username admin --email admin@localhost + docker compose exec web poetry run python manage.py createsuperuser --username admin --email admin@localhost first-use: # Initialise for local execution @echo "$(COLOUR_GREEN)Preparing for first use$(COLOUR_NONE)" @@ -90,27 +90,27 @@ logs: # View container logs docker compose logs -f -t test: # Run tests - pytest fbr/tests --cov-report term + pytest app/tests --cov-report term bdd: # Run BDD tests - HEADLESS_MODE=false SLOW_MO_MS=500 behave ./fbr/tests/bdd/features/ --tags=LOCAL + HEADLESS_MODE=false SLOW_MO_MS=500 behave ./app/tests/bdd/features/ --tags=LOCAL django-shell: # Run a Django shell (on container) - docker compose run web poetry run python fbr/manage.py shell + docker compose run web poetry run python manage.py shell django-shell-local: # Run a Django shell (local django instance) DATABASE_URL=postgres://postgres:postgres@localhost:5432/fbr \ DEBUG=True \ DJANGO_ADMIN=False \ DJANGO_SECRET_KEY=walls-have-ears \ - DJANGO_SETTINGS_MODULE=config.settings.local \ - poetry run python fbr/manage.py shell + DJANGO_SETTINGS_MODULE=fbr.settings \ + poetry run python manage.py shell migrate: # Run Django migrate - docker compose run --rm web poetry run python fbr/manage.py migrate --noinput + docker compose run --rm web poetry run python manage.py migrate --noinput migrations: # Run Django makemigrations - docker compose run --rm web poetry run python fbr/manage.py makemigrations --noinput + docker compose run --rm web poetry run python manage.py makemigrations --noinput lint: # Run all linting make black @@ -126,8 +126,8 @@ secrets-baseline: # Generate a new secrets baseline file poetry run detect-secrets scan > .secrets.baseline rebuild_cache: - export PYTHONPATH=./fbr && \ - export DJANGO_SETTINGS_MODULE='fbr.config.settings.local' && \ + export PYTHONPATH=. && \ + export DJANGO_SETTINGS_MODULE='fbr.settings' && \ export DATABASE_URL=postgres://postgres:postgres@localhost:5432/fbr && \ poetry install && \ poetry run rebuild-cache diff --git a/Procfile b/Procfile index 3ff34cd..801ed80 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,4 @@ web: bash paas_entrypoint.sh -celery-worker: celery --app fbr.config.celery worker --task-events --loglevel INFO -celery-beat: celery --app fbr.config.celery beat --loglevel INFO +celery-worker: celery --app fbr.celery_app worker --task-events --loglevel INFO +celery-beat: celery --app fbr.celery_app beat --loglevel INFO +check: python manage.py check diff --git a/fbr/cache/__init__.py b/app/__init__.py similarity index 100% rename from fbr/cache/__init__.py rename to app/__init__.py diff --git a/fbr/core/__init__.py b/app/cache/__init__.py similarity index 100% rename from fbr/core/__init__.py rename to app/cache/__init__.py diff --git a/fbr/cache/construction_legislation.py b/app/cache/construction_legislation.py similarity index 100% rename from fbr/cache/construction_legislation.py rename to app/cache/construction_legislation.py diff --git a/fbr/cache/legislation.py b/app/cache/legislation.py similarity index 97% rename from fbr/cache/legislation.py rename to app/cache/legislation.py index 949f2db..de2e19b 100644 --- a/fbr/cache/legislation.py +++ b/app/cache/legislation.py @@ -10,10 +10,10 @@ import requests # type: ignore -from fbr.cache.construction_legislation import construction_legislation_dataframe -from fbr.search.config import SearchDocumentConfig -from fbr.search.utils.date import convert_date_string_to_obj -from fbr.search.utils.documents import ( # noqa: E501 +from app.cache.construction_legislation import construction_legislation_dataframe +from app.search.config import SearchDocumentConfig +from app.search.utils.date import convert_date_string_to_obj +from app.search.utils.documents import ( # noqa: E501 generate_short_uuid, insert_or_update_document, ) diff --git a/fbr/cache/manage_cache.py b/app/cache/manage_cache.py similarity index 63% rename from fbr/cache/manage_cache.py rename to app/cache/manage_cache.py index 361b279..ece5463 100644 --- a/fbr/cache/manage_cache.py +++ b/app/cache/manage_cache.py @@ -3,16 +3,17 @@ import django -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") -django.setup() +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fbr.settings") +# Initialize Django setup +django.setup() import time -from fbr.cache.legislation import Legislation -from fbr.cache.public_gateway import PublicGateway -from fbr.search.config import SearchDocumentConfig -from fbr.search.utils.documents import clear_all_documents +from app.cache.legislation import Legislation +from app.cache.public_gateway import PublicGateway +from app.search.config import SearchDocumentConfig +from app.search.utils.documents import clear_all_documents def rebuild_cache(): diff --git a/fbr/cache/public_gateway.py b/app/cache/public_gateway.py similarity index 96% rename from fbr/cache/public_gateway.py rename to app/cache/public_gateway.py index 3a1e788..99483f6 100644 --- a/fbr/cache/public_gateway.py +++ b/app/cache/public_gateway.py @@ -4,8 +4,8 @@ import requests # type: ignore -from fbr.search.utils.date import convert_date_string_to_obj -from fbr.search.utils.documents import ( # noqa: E501 +from app.search.utils.date import convert_date_string_to_obj +from app.search.utils.documents import ( # noqa: E501 generate_short_uuid, insert_or_update_document, ) diff --git a/fbr/core/models/__init__.py b/app/core/__init__.py similarity index 100% rename from fbr/core/models/__init__.py rename to app/core/__init__.py diff --git a/fbr/core/admin.py b/app/core/admin.py similarity index 100% rename from fbr/core/admin.py rename to app/core/admin.py diff --git a/fbr/core/apps.py b/app/core/apps.py similarity index 90% rename from fbr/core/apps.py rename to app/core/apps.py index 5ef4a89..de290af 100644 --- a/fbr/core/apps.py +++ b/app/core/apps.py @@ -2,6 +2,6 @@ class CoreConfig(AppConfig): - name = "core" + name = "app.core" verbose_name = "Find business regulations core application functionality" default_auto_field = "django.db.models.BigAutoField" diff --git a/fbr/core/cookies.py b/app/core/cookies.py similarity index 100% rename from fbr/core/cookies.py rename to app/core/cookies.py diff --git a/fbr/core/forms.py b/app/core/forms.py similarity index 100% rename from fbr/core/forms.py rename to app/core/forms.py diff --git a/fbr/core/healthcheck.py b/app/core/healthcheck.py similarity index 100% rename from fbr/core/healthcheck.py rename to app/core/healthcheck.py diff --git a/fbr/core/templatetags/__init__.py b/app/core/models/__init__.py similarity index 100% rename from fbr/core/templatetags/__init__.py rename to app/core/models/__init__.py diff --git a/fbr/core/templates/accessibility_statement.html b/app/core/templates/accessibility_statement.html similarity index 100% rename from fbr/core/templates/accessibility_statement.html rename to app/core/templates/accessibility_statement.html diff --git a/fbr/core/templates/cookies.html b/app/core/templates/cookies.html similarity index 100% rename from fbr/core/templates/cookies.html rename to app/core/templates/cookies.html diff --git a/fbr/core/templates/home.html b/app/core/templates/home.html similarity index 100% rename from fbr/core/templates/home.html rename to app/core/templates/home.html diff --git a/fbr/core/templates/privacy_notice.html b/app/core/templates/privacy_notice.html similarity index 100% rename from fbr/core/templates/privacy_notice.html rename to app/core/templates/privacy_notice.html diff --git a/fbr/search/__init__.py b/app/core/templatetags/__init__.py similarity index 100% rename from fbr/search/__init__.py rename to app/core/templatetags/__init__.py diff --git a/fbr/core/templatetags/cookie_tags.py b/app/core/templatetags/cookie_tags.py similarity index 100% rename from fbr/core/templatetags/cookie_tags.py rename to app/core/templatetags/cookie_tags.py diff --git a/fbr/core/templatetags/service_problem_tags.py b/app/core/templatetags/service_problem_tags.py similarity index 100% rename from fbr/core/templatetags/service_problem_tags.py rename to app/core/templatetags/service_problem_tags.py diff --git a/fbr/core/templatetags/url_replace_tags.py b/app/core/templatetags/url_replace_tags.py similarity index 100% rename from fbr/core/templatetags/url_replace_tags.py rename to app/core/templatetags/url_replace_tags.py diff --git a/fbr/core/views.py b/app/core/views.py similarity index 100% rename from fbr/core/views.py rename to app/core/views.py diff --git a/fbr/search/migrations/__init__.py b/app/search/__init__.py similarity index 100% rename from fbr/search/migrations/__init__.py rename to app/search/__init__.py diff --git a/fbr/search/apps.py b/app/search/apps.py similarity index 95% rename from fbr/search/apps.py rename to app/search/apps.py index 608fabf..d2ff9c8 100644 --- a/fbr/search/apps.py +++ b/app/search/apps.py @@ -13,6 +13,6 @@ class SearchConfig(AppConfig): """ - name = "search" + name = "app.search" verbose_name = "Find business regulations application functionality" default_auto_field = "django.db.models.BigAutoField" diff --git a/fbr/search/config.py b/app/search/config.py similarity index 100% rename from fbr/search/config.py rename to app/search/config.py diff --git a/fbr/search/migrations/0001_initial.py b/app/search/migrations/0001_initial.py similarity index 100% rename from fbr/search/migrations/0001_initial.py rename to app/search/migrations/0001_initial.py diff --git a/fbr/search/tests/__init__.py b/app/search/migrations/__init__.py similarity index 100% rename from fbr/search/tests/__init__.py rename to app/search/migrations/__init__.py diff --git a/fbr/search/models.py b/app/search/models.py similarity index 100% rename from fbr/search/models.py rename to app/search/models.py diff --git a/fbr/search/templates/django-fbr.html b/app/search/templates/django-fbr.html similarity index 100% rename from fbr/search/templates/django-fbr.html rename to app/search/templates/django-fbr.html diff --git a/fbr/search/templates/document.html b/app/search/templates/document.html similarity index 100% rename from fbr/search/templates/document.html rename to app/search/templates/document.html diff --git a/fbr/search/templates/intro.html b/app/search/templates/intro.html similarity index 100% rename from fbr/search/templates/intro.html rename to app/search/templates/intro.html diff --git a/fbr/search/templates/react-fbr.html b/app/search/templates/react-fbr.html similarity index 100% rename from fbr/search/templates/react-fbr.html rename to app/search/templates/react-fbr.html diff --git a/fbr/search/utils/__init__.py b/app/search/tests/__init__.py similarity index 100% rename from fbr/search/utils/__init__.py rename to app/search/tests/__init__.py diff --git a/fbr/search/tests/test_search.py b/app/search/tests/test_search.py similarity index 100% rename from fbr/search/tests/test_search.py rename to app/search/tests/test_search.py diff --git a/app/search/utils/__init__.py b/app/search/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fbr/search/utils/date.py b/app/search/utils/date.py similarity index 100% rename from fbr/search/utils/date.py rename to app/search/utils/date.py diff --git a/fbr/search/utils/documents.py b/app/search/utils/documents.py similarity index 98% rename from fbr/search/utils/documents.py rename to app/search/utils/documents.py index 28207b6..8ee2161 100644 --- a/fbr/search/utils/documents.py +++ b/app/search/utils/documents.py @@ -6,7 +6,7 @@ from django.db.models import QuerySet -from search.models import DataResponseModel, logger +from app.search.models import DataResponseModel, logger def clear_all_documents(): diff --git a/fbr/search/utils/paginate.py b/app/search/utils/paginate.py similarity index 98% rename from fbr/search/utils/paginate.py rename to app/search/utils/paginate.py index 2a90bbd..649e53b 100644 --- a/fbr/search/utils/paginate.py +++ b/app/search/utils/paginate.py @@ -4,7 +4,7 @@ from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.db.models import QuerySet -from search.config import SearchDocumentConfig +from app.search.config import SearchDocumentConfig logger = logging.getLogger(__name__) diff --git a/fbr/search/utils/search.py b/app/search/utils/search.py similarity index 94% rename from fbr/search/utils/search.py rename to app/search/utils/search.py index a01f47e..3400a89 100644 --- a/fbr/search/utils/search.py +++ b/app/search/utils/search.py @@ -6,11 +6,11 @@ from django.db.models import F, Func, Q, QuerySet from django.http import HttpRequest -from search.config import SearchDocumentConfig -from search.models import DataResponseModel -from search.utils.documents import calculate_score -from search.utils.paginate import paginate -from search.utils.terms import sanitize_input +from app.search.config import SearchDocumentConfig +from app.search.models import DataResponseModel +from app.search.utils.documents import calculate_score +from app.search.utils.paginate import paginate +from app.search.utils.terms import sanitize_input logger = logging.getLogger(__name__) @@ -145,7 +145,9 @@ def search_database( return queryset -def search(context: dict, request: HttpRequest) -> dict: +def search( + context: dict, request: HttpRequest +) -> dict | QuerySet[DataResponseModel]: logger.debug("received search request: %s", request) start_time = time.time() @@ -174,6 +176,9 @@ def search(context: dict, request: HttpRequest) -> dict: # Search across specific fields results = search_database(config) + if config.limit == "*": + return results + # convert search_results into json pag_start_time = time.time() context = paginate(context, config, results) diff --git a/fbr/search/utils/terms.py b/app/search/utils/terms.py similarity index 100% rename from fbr/search/utils/terms.py rename to app/search/utils/terms.py diff --git a/app/search/views.py b/app/search/views.py new file mode 100644 index 0000000..5424f32 --- /dev/null +++ b/app/search/views.py @@ -0,0 +1,74 @@ +import logging + +from django.conf import settings +from django.http import HttpRequest, HttpResponse +from django.shortcuts import render +from django.views.decorators.http import require_http_methods + +from app.search.config import SearchDocumentConfig +from app.search.utils.search import search, search_database + +logger = logging.getLogger(__name__) + + +@require_http_methods(["GET"]) +def document(request: HttpRequest, id) -> HttpResponse: + """ + Document details view. + + Handles the GET request to fetch details based on the provided id. + """ + context = { + "service_name": settings.SERVICE_NAME_SEARCH, + } + + if not id: + context["error"] = "id parameter is required" + return render(request, template_name="document.html", context=context) + + # Create a search configuration object with the provided id + config = SearchDocumentConfig(search_query="", id=id) + + try: + queryset = search_database(config) + context["result"] = queryset + except Exception as e: + logger.error("error fetching details: %s", e) + context["error"] = f"error fetching details: {e}" + + return render(request, template_name="document.html", context=context) + + +@require_http_methods(["GET"]) +def search_django(request: HttpRequest): + """ + Search view. + + Renders the Django based search page. + """ + context = { + "service_name": settings.SERVICE_NAME_SEARCH, + } + + context = search(context, request) + return render(request, template_name="django-fbr.html", context=context) + + +@require_http_methods(["GET"]) +def search_react(request: HttpRequest) -> HttpResponse: + """ + Search view. + + Renders the React based search page. + """ + + context = { + "service_name": settings.SERVICE_NAME_SEARCH, + "document_types": { + "standard": "Standard", + "guidance": "Guidance", + "legislation": "Legislation", + }, + } + + return render(request, template_name="react-fbr.html", context=context) diff --git a/fbr/templates/base.html b/app/templates/base.html similarity index 100% rename from fbr/templates/base.html rename to app/templates/base.html diff --git a/fbr/templates/cookie_banner.html b/app/templates/cookie_banner.html similarity index 100% rename from fbr/templates/cookie_banner.html rename to app/templates/cookie_banner.html diff --git a/fbr/templates/service_problem.html b/app/templates/service_problem.html similarity index 100% rename from fbr/templates/service_problem.html rename to app/templates/service_problem.html diff --git a/celery_worker/__init__.py b/celery_worker/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fbr/cache/tasks.py b/celery_worker/tasks.py similarity index 77% rename from fbr/cache/tasks.py rename to celery_worker/tasks.py index 096783f..ecaf9d0 100644 --- a/fbr/cache/tasks.py +++ b/celery_worker/tasks.py @@ -1,13 +1,14 @@ import time -from config.celery import celery_app -from fbr.cache.legislation import Legislation -from fbr.cache.public_gateway import PublicGateway -from fbr.search.config import SearchDocumentConfig -from fbr.search.utils.documents import clear_all_documents +from celery import shared_task +from app.cache.legislation import Legislation +from app.cache.public_gateway import PublicGateway +from app.search.config import SearchDocumentConfig +from app.search.utils.documents import clear_all_documents -@celery_app.task(name="fbr.cache.tasks.rebuild_cache") + +@shared_task(bind=True) def rebuild_cache() -> None: """ Rebuilds the cache for search documents across various components by diff --git a/docker-compose.yml b/docker-compose.yml index 6519f5c..e141750 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,8 @@ volumes: postgres_data: driver: local + redis_data: + driver: local services: db: @@ -21,6 +23,7 @@ services: retries: 3 networks: - proxynet + web: build: context: . @@ -37,6 +40,58 @@ services: networks: - proxynet + redis: + image: redis + # Expose port so we can query it for debugging + ports: + - "6379:6379" + + celery-worker: + build: + context: . + dockerfile: local_deployment/Dockerfile + command: celery --app fbr.celery_app worker --task-events --loglevel INFO + entrypoint: '' + volumes: + - .:/app + healthcheck: + test: [ "CMD-SHELL", "python -m dbt_copilot_python.celery_health_check.healthcheck" ] + interval: 10s + timeout: 5s + retries: 2 + start_period: 5s + depends_on: + - redis + - db + environment: + REDIS_ENDPOINT: redis://redis:6379 + DEBUG: true + DJANGO_SETTINGS_MODULE: fbr.settings + RDS_POSTGRES_CREDENTIALS: '{"password":"postgres","dbname":"fbr","engine":"postgres","port":5432,"dbInstanceIdentifier":"xxx","host":"db","username":"postgres"}' + + celery-beats: + build: + context: . + dockerfile: local_deployment/Dockerfile + command: celery --app fbr.celery_app beat --loglevel INFO + entrypoint: '' + volumes: + - .:/app + healthcheck: + test: [ "CMD-SHELL", "python -m dbt_copilot_python.celery_health_check.healthcheck" ] + interval: 10s + timeout: 5s + retries: 2 + start_period: 5s + depends_on: + - redis + - db + environment: + REDIS_ENDPOINT: redis://redis:6379 + DEBUG: true + DJANGO_SETTINGS_MODULE: fbr.settings + RDS_POSTGRES_CREDENTIALS: '{"password":"postgres","dbname":"fbr","engine":"postgres","port":5432,"dbInstanceIdentifier":"xxx","host":"db","username":"postgres"}' + networks: proxynet: name: fbr_network diff --git a/fbr/__init__.py b/fbr/__init__.py index 0062f8d..23bb48f 100644 --- a/fbr/__init__.py +++ b/fbr/__init__.py @@ -1,7 +1,3 @@ -from __future__ import absolute_import, unicode_literals - -# This ensures the app is always imported when Django starts -# allowing shared_task to utilize this app. -from .config.celery import celery_app +from .celery_app import celery_app __all__ = ("celery_app",) diff --git a/fbr/config/asgi.py b/fbr/asgi.py similarity index 100% rename from fbr/config/asgi.py rename to fbr/asgi.py diff --git a/fbr/config/settings/base.py b/fbr/config/settings/base.py index 711de8b..7d20553 100644 --- a/fbr/config/settings/base.py +++ b/fbr/config/settings/base.py @@ -54,6 +54,7 @@ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "rest_framework", ] LOCAL_APPS = [ diff --git a/fbr/config/settings/local.py b/fbr/config/settings/local.py index e6d9ab5..840912c 100644 --- a/fbr/config/settings/local.py +++ b/fbr/config/settings/local.py @@ -5,7 +5,6 @@ # Applications that are required to load before DJANGO_APPS BASE_APPS = [ "whitenoise.runserver_nostatic", # Serve static files via WhiteNoise - "rest_framework", ] INSTALLED_APPS = BASE_APPS + INSTALLED_APPS # noqa diff --git a/fbr/config/context_processors.py b/fbr/context_processors.py similarity index 100% rename from fbr/config/context_processors.py rename to fbr/context_processors.py diff --git a/fbr/search/views.py b/fbr/search/views.py deleted file mode 100644 index 9a7280b..0000000 --- a/fbr/search/views.py +++ /dev/null @@ -1,150 +0,0 @@ -import csv -import logging - -import pandas as pd - -from django.conf import settings -from django.http import HttpRequest, HttpResponse -from django.shortcuts import render -from django.views.decorators.http import require_http_methods - -from search.config import SearchDocumentConfig -from search.models import DataResponseModel -from search.utils.search import search, search_database - -logger = logging.getLogger(__name__) - - -@require_http_methods(["GET"]) -def document(request: HttpRequest, id) -> HttpResponse: - """ - Document details view. - - Handles the GET request to fetch details based on the provided id. - """ - context = { - "service_name": settings.SERVICE_NAME_SEARCH, - } - - if not id: - context["error"] = "id parameter is required" - return render(request, template_name="document.html", context=context) - - # Create a search configuration object with the provided id - config = SearchDocumentConfig(search_query="", id=id) - - try: - queryset = search_database(config) - context["result"] = queryset - except Exception as e: - logger.error("error fetching details: %s", e) - context["error"] = f"error fetching details: {e}" - - return render(request, template_name="document.html", context=context) - - -@require_http_methods(["GET"]) -def download_search_csv(request: HttpRequest) -> HttpResponse: - """ - Handles the download of search results as a CSV file. - - This view function is restricted to the GET HTTP method. - It accepts several query - - parameters to configure the search: - - `search`: A string to search within the documents. - - `document_type`: - A list of document types to filter the search results. - - `publisher`: A list of publishers to filter the search results. - - `sort`: A field name to sort the search results. - - The function constructs a `SearchDocumentConfig` object using the - received query parameters and performs a search using this - configuration. `DataResponseModel` objects from the search results - are retrieved and compiled into a list of dictionaries, which is - then converted into a DataFrame for demonstration purposes. - Finally, the ataFrame is written into a CSV file and returned as - an HTTP response with the appropriate content type and file - attachment headers. - """ - search_query = request.GET.get("search", "") - document_types = request.GET.getlist("document_type", "") - publishers = request.GET.getlist("publisher", None) - sort_by = request.GET.get("sort", None) - config = SearchDocumentConfig( - search_query, - document_types, - publisher_names=publishers, - sort_by=sort_by, - ) - # Perform search - search(config) - - # Get DataResponseModel objects from the search results - documents = DataResponseModel.objects.all() - - search_results = [] - for result in documents: - search_results.append( - { - "id": result["id"], - "title": result["title"], - "publisher": result["publisher"], - "description": result["description"], - "type": result["type"], - "date_modified": result["date_modified"], - } - ) - - # Convert search_results JSON object to DataFrame - # (for demonstration purposes) - search_results_df = pd.DataFrame(search_results) - - response = HttpResponse(content_type="text/csv") - response["Content-Disposition"] = ( - 'attachment; filename="search_results.csv"' - ) - - # Write the DataFrame to the response - writer = csv.writer(response) - writer.writerow(search_results_df.columns) # Write the header - - for _, row in search_results_df.iterrows(): - writer.writerow(row) - - return response - - -@require_http_methods(["GET"]) -def search_django(request: HttpRequest): - """ - Search view. - - Renders the Django based search page. - """ - context = { - "service_name": settings.SERVICE_NAME_SEARCH, - } - - context = search(context, request) - return render(request, template_name="django-fbr.html", context=context) - - -@require_http_methods(["GET"]) -def search_react(request: HttpRequest) -> HttpResponse: - """ - Search view. - - Renders the React based search page. - """ - - context = { - "service_name": settings.SERVICE_NAME_SEARCH, - "document_types": { - "standard": "Standard", - "guidance": "Guidance", - "legislation": "Legislation", - }, - } - - return render(request, template_name="react-fbr.html", context=context) diff --git a/fbr/settings.py b/fbr/settings.py new file mode 100644 index 0000000..0c6db18 --- /dev/null +++ b/fbr/settings.py @@ -0,0 +1,289 @@ +"""Django base settings for Find business regulations project. + +Environment: +We use django-environ but do not read a `.env` file. Locally we provide +docker-compose an environment from `local.env` file in the project root, +using `env_file` field in `docker-compose.yml`. There is a `local.env.example` +in this repo. When deployed, the service retrieves these values from the +environment. + +NB: Some settings acquired using `env()` deliberately *do not* have defaults +as we want to get an `ImproperlyConfigured` exception. This highlights badly +configured deployments. +""" + +import os + +from pathlib import Path +from typing import Any + +import dj_database_url +import environ + +from django_log_formatter_asim import ASIMFormatter + +# Define the root directory (i.e. ) +root = environ.Path(__file__) - 4 # i.e. Repository root +SITE_ROOT = Path(root()) + +# Define the project base directory (i.e. /fbr) +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Get environment variables +env = environ.Env( + DEBUG=(bool, False), +) + +# Must be provided by the environment +SECRET_KEY = env( + "DJANGO_SECRET_KEY", default="find-business-regulations-secret-key" +) + +DEBUG = env("DEBUG", default=False) +DJANGO_ADMIN = env("DJANGO_ADMIN", default=False) +ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=["localhost"]) +ENVIRONMENT = env( + "COPILOT_ENVIRONMENT_NAME", default="local" +) # TODO: Change to APP_ENV, updates required in deploy repo + +# Application definition +DJANGO_APPS = [ + "django_celery_beat", + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "rest_framework", + "app", + "app.core", + "app.search", +] + +THIRD_PARTY_APPS: list = [ + "webpack_loader", +] + +INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "whitenoise.middleware.WhiteNoiseMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" + +ROOT_URLCONF = "fbr.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [ + os.path.join(BASE_DIR, "app", "search", "templates"), + os.path.join(BASE_DIR, "app", "templates"), + ], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + "fbr.context_processors.google_tag_manager", + ], + }, + }, +] + +WSGI_APPLICATION = "fbr.wsgi.application" + +DATABASES: dict = {} +if DATABASE_URL := env("DATABASE_URL", default=None): + DATABASES = { + "default": { + **dj_database_url.parse( + DATABASE_URL, + engine="postgresql", + ), + "ENGINE": "django.db.backends.postgresql", + } + } +else: + DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": SITE_ROOT / "db.sqlite3", + } + } + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", # noqa: E501 + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", # noqa: E501 + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", # noqa: E501 + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", # noqa: E501 + }, +] + +REDIS_ENDPOINT = env("REDIS_ENDPOINT", default="") + +# Celery +CELERY_BROKER_URL = env("REDIS_ENDPOINT", default="") +if CELERY_BROKER_URL and CELERY_BROKER_URL.startswith("rediss://"): + CELERY_BROKER_URL = f"{CELERY_BROKER_URL}?ssl_cert_reqs=CERT_REQUIRED" +CELERY_RESULT_BACKEND = CELERY_BROKER_URL +CELERY_ACCEPT_CONTENT = ["application/json"] +CELERY_RESULT_SERIALIZER = "json" +CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True +CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler" +CELERY_RESULT_EXTENDED = True +CELERY_TASK_TIME_LIMIT = ( + 450 # Maximum runtime for a task in seconds (e.g., 7.5 minutes) +) +CELERY_TASK_SOFT_TIME_LIMIT = ( + 270 # Optional: Grace period before forced termination +) +CELERY_TIMEZONE = "UTC" +CELERY_ENABLE_UTC = True +USE_DEPRECATED_PYTZ = True + +# Internationalisation +LANGUAGE_CODE = "en-gb" +TIME_ZONE = "Europe/London" +USE_I18N = True +USE_TZ = True + +# Static files +STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", +] +STATIC_ROOT = BASE_DIR / "static" +STATIC_URL = "static/" + +STORAGES = { + "staticfiles": { + "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + }, +} + +# Webpack + +STATICFILES_DIRS = [ + BASE_DIR / "front_end/", +] + +WEBPACK_LOADER = { + "DEFAULT": { + "CACHE": not DEBUG, + "BUNDLE_DIR_NAME": "webpack_bundles/", # must end with slash + "STATS_FILE": os.path.join(BASE_DIR, "webpack-stats.json"), + "POLL_INTERVAL": 0.1, + "TIMEOUT": None, + "LOADER_CLASS": "webpack_loader.loader.WebpackLoader", + } +} + + +# TODO: Use redis for cache? +CACHES = { + "default": { + "BACKEND": "django.core.cache.backends.locmem.LocMemCache", + } +} + +# Logging +LOGGING: dict[str, Any] = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "asim_formatter": { + "()": ASIMFormatter, + }, + "simple": { + "style": "{", + "format": "{asctime} {levelname} {message}", + }, + }, + "handlers": { + "asim": { + "class": "logging.StreamHandler", + "formatter": "asim_formatter", + }, + "console": { + "class": "logging.StreamHandler", + "formatter": "simple", + }, + }, + "root": { + "handlers": ["console"], + "level": "INFO", + }, + "loggers": { + "django": { + "handlers": ["console"], + "level": "INFO", + "propagate": False, + }, + }, +} + +# Django Log Formatter ASIM settings +# See https://github.com/uktrade/django-log-formatter-asim#settings +DLFA_TRACE_HEADERS = ("X-B3-TraceId", "X-B3-SpanId") + +# Set the correct handlers when running in DBT Platform +# console handler set as default as it's easier to read +LOGGING["root"]["handlers"] = ["asim"] +LOGGING["loggers"]["django"]["handlers"] = ["asim"] + +# ------------------------------------------------------------------------------ +# The Find business regulations zone - specific service settings. +# ------------------------------------------------------------------------------ + +# Service + +SERVICE_NAME: str = "Find business regulations" +SERVICE_NAME_SEARCH: str = "Find business regulations" +CONTACT_EMAIL: str = "findbusinessregulations@businessandtrade.gov.uk" + +# Cookies +ANALYTICS_CONSENT_NAME: str = "analytics_consent" + +# DBT Data API +# DBT_DATA_API_URL = env( +# "DBT_DATA_API_URL", +# default="https://data.api.trade.gov.uk/v1/datasets/market-barriers/versions/v1.0.10/data?format=json", # noqa: E501 +# ) + +# HOSTNAME +HOSTNAME_MAP = { + "local": "http://localhost:8081", + "dev": "https://dev.find-business-regulations.uktrade.digital/", + "staging": "https://staging.find-business-regulations.uktrade.digital/", + "prod": "https://find-business-regulations.uktrade.digital/", +} + +HOSTNAME = HOSTNAME_MAP.get(ENVIRONMENT.lower(), HOSTNAME_MAP["prod"]) + +# Google Analytics (GA) +# Note: please consult the performance team before changing these settings +COOKIE_PREFERENCES_SET_NAME: str = "cookie_preferences_set" +COOKIE_ACCEPTED_GA_NAME: str = "accepted_ga_cookies" +GOOGLE_ANALYTICS_TAG_MANAGER_ID = env( + "GOOGLE_ANALYTICS_TAG_MANAGER_ID", default=None +) diff --git a/fbr/config/urls.py b/fbr/urls.py similarity index 62% rename from fbr/config/urls.py rename to fbr/urls.py index 0ab1934..e48bce0 100644 --- a/fbr/config/urls.py +++ b/fbr/urls.py @@ -1,62 +1,27 @@ """Find business regulations URL configuration.""" +import csv import logging -from rest_framework import routers, serializers, status, viewsets +import pandas as pd + +from rest_framework import routers, status, viewsets from rest_framework.decorators import action from rest_framework.response import Response from django.conf import settings from django.contrib import admin +from django.http import HttpResponse from django.urls import include, path -import core.views as core_views -import search.views as search_views +import app.core.views as core_views +import app.search.views as search_views -from search.models import DataResponseModel -from search.utils.search import get_publisher_names, search +from app.search.utils.search import get_publisher_names, search urls_logger = logging.getLogger(__name__) -# Serializers define the API representation. -class DataResponseSerializer(serializers.HyperlinkedModelSerializer): - class Meta: - model = DataResponseModel - fields = [ - "id", - "title", - "link", - "publisher", - "language", - "format", - "description", - "date_issued", - "date_modified", - "date_valid", - "audience", - "coverage", - "subject", - "type", - "license", - "regulatory_topics", - "status", - "date_uploaded_to_orp", - "has_format", - "is_format_of", - "has_version", - "is_version_of", - "references", - "is_referenced_by", - "has_part", - "is_part_of", - "is_replaced_by", - "replaces", - "related_legislation", - "id", - ] - - class DataResponseViewSet(viewsets.ModelViewSet): @action(detail=False, methods=["get"], url_path="search") def search(self, request, *args, **kwargs): @@ -113,9 +78,65 @@ def publishers(self, request, *args, **kwargs): ) +class DownloadDataResponseViewSet(viewsets.ModelViewSet): + @action(detail=False, methods=["get"], url_path="download_csv") + def download_csv(self, request, *args, **kwargs): + context = { + "service_name": settings.SERVICE_NAME_SEARCH, + } + + urls_logger.debug(f"download_csv - request: {request}") + + try: + # set the limit to '*' to get all results + request.GET = request.GET.copy() + request.GET["limit"] = "*" + + response_data = search(context, request) + urls_logger.debug(f"response_data: {response_data}") + + search_results = [] + for result in response_data: + urls_logger.debug(f"result: {result}") + + search_results.append( + { + "title": result["title"], + "publisher": result["publisher"], + "description": result["description"], + "type": result["type"], + "date": result["date_valid"], + } + ) + + search_results_df = pd.DataFrame(search_results) + + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = ( + 'attachment; filename="search_results.csv"' + ) + + writer = csv.writer(response) + + # Write the DataFrame to the response + writer.writerow(search_results_df.columns) # Write the header + for _, row in search_results_df.iterrows(): + writer.writerow(row) + + return Response(response, status=status.HTTP_200_OK) + except Exception as e: + return Response( + data={"message": f"error building csv download response: {e}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + # Routers provide an easy way of automatically determining the URL conf. router = routers.DefaultRouter() router.register(r"v1", DataResponseViewSet, basename="search") +router.register( + r"v1/download_csv", DownloadDataResponseViewSet, basename="download_csv" +) router.register(r"v1/retrieve", PublishersViewSet, basename="publishers") urlpatterns = [ @@ -124,11 +145,6 @@ def publishers(self, request, *args, **kwargs): path("nojs/", search_views.search_django, name="search_django"), # If we choose to have a start page with green button, this is it: # path("", core_views.home, name="home"), - path( - "download_csv/", - search_views.download_search_csv, - name="download_csv", - ), path("document/", search_views.document, name="document"), path("healthcheck/", core_views.health_check, name="healthcheck"), path( diff --git a/fbr/config/version.py b/fbr/version.py similarity index 100% rename from fbr/config/version.py rename to fbr/version.py diff --git a/fbr/config/wsgi.py b/fbr/wsgi.py similarity index 82% rename from fbr/config/wsgi.py rename to fbr/wsgi.py index 5f3bd7e..1ff65b7 100644 --- a/fbr/config/wsgi.py +++ b/fbr/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fbr.settings") application = get_wsgi_application() diff --git a/local.env.example b/local.env.example index 40a5d87..303a204 100644 --- a/local.env.example +++ b/local.env.example @@ -6,7 +6,7 @@ ALLOWED_HOSTS=localhost DEBUG=True DJANGO_ADMIN=True -DJANGO_SETTINGS_MODULE=config.settings.local +DJANGO_SETTINGS_MODULE=fbr.settings DJANGO_SECRET_KEY=find-business-regulations-secret # Database Settings diff --git a/local_deployment/Dockerfile b/local_deployment/Dockerfile index e149339..0b529b0 100644 --- a/local_deployment/Dockerfile +++ b/local_deployment/Dockerfile @@ -25,7 +25,7 @@ ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ DEBUG=1 \ DJANGO_ADMIN=1 \ - DJANGO_SETTINGS_MODULE=config.settings.local + DJANGO_SETTINGS_MODULE=fbr.settings # Install nodejs RUN apt install -y curl && \ diff --git a/local_deployment/entry.sh b/local_deployment/entry.sh index b644ad4..4df984f 100755 --- a/local_deployment/entry.sh +++ b/local_deployment/entry.sh @@ -6,10 +6,11 @@ npm install echo "Bundling WebPack" npm run build +# Activate the Poetry virtual environment . "$(poetry env info --path)/bin/activate" echo "Collecting Static Files" -python fbr/manage.py collectstatic --noinput +python manage.py collectstatic --noinput echo "Starting server" -python fbr/manage.py runserver 0.0.0.0:8080 +python manage.py runserver 0.0.0.0:8080 diff --git a/fbr/manage.py b/manage.py similarity index 88% rename from fbr/manage.py rename to manage.py index 37003e1..58ea086 100755 --- a/fbr/manage.py +++ b/manage.py @@ -6,8 +6,7 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local") - + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fbr.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/poetry.lock b/poetry.lock index ae26841..eba32c1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. [[package]] name = "amqp" @@ -116,17 +116,17 @@ uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "boto3" -version = "1.35.71" +version = "1.35.77" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.35.71-py3-none-any.whl", hash = "sha256:e2969a246bb3208122b3c349c49cc6604c6fc3fc2b2f65d99d3e8ccd745b0c16"}, - {file = "boto3-1.35.71.tar.gz", hash = "sha256:3ed7172b3d4fceb6218bb0ec3668c4d40c03690939c2fca4f22bb875d741a07f"}, + {file = "boto3-1.35.77-py3-none-any.whl", hash = "sha256:a09871805f8e462349a1c33c23eb413668df0bf68424e61d53518e1a7d883b2f"}, + {file = "boto3-1.35.77.tar.gz", hash = "sha256:cc819cdbccbc2d0dc185f1dcfe74cf3809489c4cae63c2e5d6a557aa0c5ab928"}, ] [package.dependencies] -botocore = ">=1.35.71,<1.36.0" +botocore = ">=1.35.77,<1.36.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -135,13 +135,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.35.71" +version = "1.35.77" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.35.71-py3-none-any.whl", hash = "sha256:fc46e7ab1df3cef66dfba1633f4da77c75e07365b36f03bd64a3793634be8fc1"}, - {file = "botocore-1.35.71.tar.gz", hash = "sha256:f9fa058e0393660c3fe53c1e044751beb64b586def0bd2212448a7c328b0cbba"}, + {file = "botocore-1.35.77-py3-none-any.whl", hash = "sha256:3faa27d65841499762228902d7e215fa99a4c2fdc76c9113e1c3f339bdf685b8"}, + {file = "botocore-1.35.77.tar.gz", hash = "sha256:17b778016644e9342ca3ff2f430c1d1db0c6126e9b41a57cff52ac58e7a455e0"}, ] [package.dependencies] @@ -524,73 +524,73 @@ files = [ [[package]] name = "coverage" -version = "7.6.8" +version = "7.6.9" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" files = [ - {file = "coverage-7.6.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b39e6011cd06822eb964d038d5dff5da5d98652b81f5ecd439277b32361a3a50"}, - {file = "coverage-7.6.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:63c19702db10ad79151a059d2d6336fe0c470f2e18d0d4d1a57f7f9713875dcf"}, - {file = "coverage-7.6.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3985b9be361d8fb6b2d1adc9924d01dec575a1d7453a14cccd73225cb79243ee"}, - {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:644ec81edec0f4ad17d51c838a7d01e42811054543b76d4ba2c5d6af741ce2a6"}, - {file = "coverage-7.6.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f188a2402f8359cf0c4b1fe89eea40dc13b52e7b4fd4812450da9fcd210181d"}, - {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e19122296822deafce89a0c5e8685704c067ae65d45e79718c92df7b3ec3d331"}, - {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:13618bed0c38acc418896005732e565b317aa9e98d855a0e9f211a7ffc2d6638"}, - {file = "coverage-7.6.8-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:193e3bffca48ad74b8c764fb4492dd875038a2f9925530cb094db92bb5e47bed"}, - {file = "coverage-7.6.8-cp310-cp310-win32.whl", hash = "sha256:3988665ee376abce49613701336544041f2117de7b7fbfe91b93d8ff8b151c8e"}, - {file = "coverage-7.6.8-cp310-cp310-win_amd64.whl", hash = "sha256:f56f49b2553d7dd85fd86e029515a221e5c1f8cb3d9c38b470bc38bde7b8445a"}, - {file = "coverage-7.6.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:86cffe9c6dfcfe22e28027069725c7f57f4b868a3f86e81d1c62462764dc46d4"}, - {file = "coverage-7.6.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d82ab6816c3277dc962cfcdc85b1efa0e5f50fb2c449432deaf2398a2928ab94"}, - {file = "coverage-7.6.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13690e923a3932e4fad4c0ebfb9cb5988e03d9dcb4c5150b5fcbf58fd8bddfc4"}, - {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be32da0c3827ac9132bb488d331cb32e8d9638dd41a0557c5569d57cf22c9c1"}, - {file = "coverage-7.6.8-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e6c85bbdc809383b509d732b06419fb4544dca29ebe18480379633623baafb"}, - {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:768939f7c4353c0fac2f7c37897e10b1414b571fd85dd9fc49e6a87e37a2e0d8"}, - {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e44961e36cb13c495806d4cac67640ac2866cb99044e210895b506c26ee63d3a"}, - {file = "coverage-7.6.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ea8bb1ab9558374c0ab591783808511d135a833c3ca64a18ec927f20c4030f0"}, - {file = "coverage-7.6.8-cp311-cp311-win32.whl", hash = "sha256:629a1ba2115dce8bf75a5cce9f2486ae483cb89c0145795603d6554bdc83e801"}, - {file = "coverage-7.6.8-cp311-cp311-win_amd64.whl", hash = "sha256:fb9fc32399dca861584d96eccd6c980b69bbcd7c228d06fb74fe53e007aa8ef9"}, - {file = "coverage-7.6.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e683e6ecc587643f8cde8f5da6768e9d165cd31edf39ee90ed7034f9ca0eefee"}, - {file = "coverage-7.6.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1defe91d41ce1bd44b40fabf071e6a01a5aa14de4a31b986aa9dfd1b3e3e414a"}, - {file = "coverage-7.6.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7ad66e8e50225ebf4236368cc43c37f59d5e6728f15f6e258c8639fa0dd8e6d"}, - {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3fe47da3e4fda5f1abb5709c156eca207eacf8007304ce3019eb001e7a7204cb"}, - {file = "coverage-7.6.8-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:202a2d645c5a46b84992f55b0a3affe4f0ba6b4c611abec32ee88358db4bb649"}, - {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4674f0daa1823c295845b6a740d98a840d7a1c11df00d1fd62614545c1583787"}, - {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:74610105ebd6f33d7c10f8907afed696e79c59e3043c5f20eaa3a46fddf33b4c"}, - {file = "coverage-7.6.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37cda8712145917105e07aab96388ae76e787270ec04bcb9d5cc786d7cbb8443"}, - {file = "coverage-7.6.8-cp312-cp312-win32.whl", hash = "sha256:9e89d5c8509fbd6c03d0dd1972925b22f50db0792ce06324ba069f10787429ad"}, - {file = "coverage-7.6.8-cp312-cp312-win_amd64.whl", hash = "sha256:379c111d3558272a2cae3d8e57e6b6e6f4fe652905692d54bad5ea0ca37c5ad4"}, - {file = "coverage-7.6.8-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b0c69f4f724c64dfbfe79f5dfb503b42fe6127b8d479b2677f2b227478db2eb"}, - {file = "coverage-7.6.8-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c15b32a7aca8038ed7644f854bf17b663bc38e1671b5d6f43f9a2b2bd0c46f63"}, - {file = "coverage-7.6.8-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63068a11171e4276f6ece913bde059e77c713b48c3a848814a6537f35afb8365"}, - {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f4548c5ead23ad13fb7a2c8ea541357474ec13c2b736feb02e19a3085fac002"}, - {file = "coverage-7.6.8-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4b4299dd0d2c67caaaf286d58aef5e75b125b95615dda4542561a5a566a1e3"}, - {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9ebfb2507751f7196995142f057d1324afdab56db1d9743aab7f50289abd022"}, - {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c1b4474beee02ede1eef86c25ad4600a424fe36cff01a6103cb4533c6bf0169e"}, - {file = "coverage-7.6.8-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d9fd2547e6decdbf985d579cf3fc78e4c1d662b9b0ff7cc7862baaab71c9cc5b"}, - {file = "coverage-7.6.8-cp313-cp313-win32.whl", hash = "sha256:8aae5aea53cbfe024919715eca696b1a3201886ce83790537d1c3668459c7146"}, - {file = "coverage-7.6.8-cp313-cp313-win_amd64.whl", hash = "sha256:ae270e79f7e169ccfe23284ff5ea2d52a6f401dc01b337efb54b3783e2ce3f28"}, - {file = "coverage-7.6.8-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:de38add67a0af869b0d79c525d3e4588ac1ffa92f39116dbe0ed9753f26eba7d"}, - {file = "coverage-7.6.8-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b07c25d52b1c16ce5de088046cd2432b30f9ad5e224ff17c8f496d9cb7d1d451"}, - {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62a66ff235e4c2e37ed3b6104d8b478d767ff73838d1222132a7a026aa548764"}, - {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b9f848b28081e7b975a3626e9081574a7b9196cde26604540582da60235fdf"}, - {file = "coverage-7.6.8-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:093896e530c38c8e9c996901858ac63f3d4171268db2c9c8b373a228f459bbc5"}, - {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9a7b8ac36fd688c8361cbc7bf1cb5866977ece6e0b17c34aa0df58bda4fa18a4"}, - {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:38c51297b35b3ed91670e1e4efb702b790002e3245a28c76e627478aa3c10d83"}, - {file = "coverage-7.6.8-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2e4e0f60cb4bd7396108823548e82fdab72d4d8a65e58e2c19bbbc2f1e2bfa4b"}, - {file = "coverage-7.6.8-cp313-cp313t-win32.whl", hash = "sha256:6535d996f6537ecb298b4e287a855f37deaf64ff007162ec0afb9ab8ba3b8b71"}, - {file = "coverage-7.6.8-cp313-cp313t-win_amd64.whl", hash = "sha256:c79c0685f142ca53256722a384540832420dff4ab15fec1863d7e5bc8691bdcc"}, - {file = "coverage-7.6.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3ac47fa29d8d41059ea3df65bd3ade92f97ee4910ed638e87075b8e8ce69599e"}, - {file = "coverage-7.6.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:24eda3a24a38157eee639ca9afe45eefa8d2420d49468819ac5f88b10de84f4c"}, - {file = "coverage-7.6.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4c81ed2820b9023a9a90717020315e63b17b18c274a332e3b6437d7ff70abe0"}, - {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd55f8fc8fa494958772a2a7302b0354ab16e0b9272b3c3d83cdb5bec5bd1779"}, - {file = "coverage-7.6.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f39e2f3530ed1626c66e7493be7a8423b023ca852aacdc91fb30162c350d2a92"}, - {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:716a78a342679cd1177bc8c2fe957e0ab91405bd43a17094324845200b2fddf4"}, - {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:177f01eeaa3aee4a5ffb0d1439c5952b53d5010f86e9d2667963e632e30082cc"}, - {file = "coverage-7.6.8-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:912e95017ff51dc3d7b6e2be158dedc889d9a5cc3382445589ce554f1a34c0ea"}, - {file = "coverage-7.6.8-cp39-cp39-win32.whl", hash = "sha256:4db3ed6a907b555e57cc2e6f14dc3a4c2458cdad8919e40b5357ab9b6db6c43e"}, - {file = "coverage-7.6.8-cp39-cp39-win_amd64.whl", hash = "sha256:428ac484592f780e8cd7b6b14eb568f7c85460c92e2a37cb0c0e5186e1a0d076"}, - {file = "coverage-7.6.8-pp39.pp310-none-any.whl", hash = "sha256:5c52a036535d12590c32c49209e79cabaad9f9ad8aa4cbd875b68c4d67a9cbce"}, - {file = "coverage-7.6.8.tar.gz", hash = "sha256:8b2b8503edb06822c86d82fa64a4a5cb0760bb8f31f26e138ec743f422f37cfc"}, + {file = "coverage-7.6.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85d9636f72e8991a1706b2b55b06c27545448baf9f6dbf51c4004609aacd7dcb"}, + {file = "coverage-7.6.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:608a7fd78c67bee8936378299a6cb9f5149bb80238c7a566fc3e6717a4e68710"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96d636c77af18b5cb664ddf12dab9b15a0cfe9c0bde715da38698c8cea748bfa"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d75cded8a3cff93da9edc31446872d2997e327921d8eed86641efafd350e1df1"}, + {file = "coverage-7.6.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7b15f589593110ae767ce997775d645b47e5cbbf54fd322f8ebea6277466cec"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:44349150f6811b44b25574839b39ae35291f6496eb795b7366fef3bd3cf112d3"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d891c136b5b310d0e702e186d70cd16d1119ea8927347045124cb286b29297e5"}, + {file = "coverage-7.6.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:db1dab894cc139f67822a92910466531de5ea6034ddfd2b11c0d4c6257168073"}, + {file = "coverage-7.6.9-cp310-cp310-win32.whl", hash = "sha256:41ff7b0da5af71a51b53f501a3bac65fb0ec311ebed1632e58fc6107f03b9198"}, + {file = "coverage-7.6.9-cp310-cp310-win_amd64.whl", hash = "sha256:35371f8438028fdccfaf3570b31d98e8d9eda8bb1d6ab9473f5a390969e98717"}, + {file = "coverage-7.6.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:932fc826442132dde42ee52cf66d941f581c685a6313feebed358411238f60f9"}, + {file = "coverage-7.6.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:085161be5f3b30fd9b3e7b9a8c301f935c8313dcf928a07b116324abea2c1c2c"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc660a77e1c2bf24ddbce969af9447a9474790160cfb23de6be4fa88e3951c7"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c69e42c892c018cd3c8d90da61d845f50a8243062b19d228189b0224150018a9"}, + {file = "coverage-7.6.9-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0824a28ec542a0be22f60c6ac36d679e0e262e5353203bea81d44ee81fe9c6d4"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4401ae5fc52ad8d26d2a5d8a7428b0f0c72431683f8e63e42e70606374c311a1"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98caba4476a6c8d59ec1eb00c7dd862ba9beca34085642d46ed503cc2d440d4b"}, + {file = "coverage-7.6.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ee5defd1733fd6ec08b168bd4f5387d5b322f45ca9e0e6c817ea6c4cd36313e3"}, + {file = "coverage-7.6.9-cp311-cp311-win32.whl", hash = "sha256:f2d1ec60d6d256bdf298cb86b78dd715980828f50c46701abc3b0a2b3f8a0dc0"}, + {file = "coverage-7.6.9-cp311-cp311-win_amd64.whl", hash = "sha256:0d59fd927b1f04de57a2ba0137166d31c1a6dd9e764ad4af552912d70428c92b"}, + {file = "coverage-7.6.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:99e266ae0b5d15f1ca8d278a668df6f51cc4b854513daab5cae695ed7b721cf8"}, + {file = "coverage-7.6.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9901d36492009a0a9b94b20e52ebfc8453bf49bb2b27bca2c9706f8b4f5a554a"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abd3e72dd5b97e3af4246cdada7738ef0e608168de952b837b8dd7e90341f015"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff74026a461eb0660366fb01c650c1d00f833a086b336bdad7ab00cc952072b3"}, + {file = "coverage-7.6.9-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65dad5a248823a4996724a88eb51d4b31587aa7aa428562dbe459c684e5787ae"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22be16571504c9ccea919fcedb459d5ab20d41172056206eb2994e2ff06118a4"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f957943bc718b87144ecaee70762bc2bc3f1a7a53c7b861103546d3a403f0a6"}, + {file = "coverage-7.6.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ae1387db4aecb1f485fb70a6c0148c6cdaebb6038f1d40089b1fc84a5db556f"}, + {file = "coverage-7.6.9-cp312-cp312-win32.whl", hash = "sha256:1a330812d9cc7ac2182586f6d41b4d0fadf9be9049f350e0efb275c8ee8eb692"}, + {file = "coverage-7.6.9-cp312-cp312-win_amd64.whl", hash = "sha256:b12c6b18269ca471eedd41c1b6a1065b2f7827508edb9a7ed5555e9a56dcfc97"}, + {file = "coverage-7.6.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:899b8cd4781c400454f2f64f7776a5d87bbd7b3e7f7bda0cb18f857bb1334664"}, + {file = "coverage-7.6.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:61f70dc68bd36810972e55bbbe83674ea073dd1dcc121040a08cdf3416c5349c"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a289d23d4c46f1a82d5db4abeb40b9b5be91731ee19a379d15790e53031c014"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e216d8044a356fc0337c7a2a0536d6de07888d7bcda76febcb8adc50bdbbd00"}, + {file = "coverage-7.6.9-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c026eb44f744acaa2bda7493dad903aa5bf5fc4f2554293a798d5606710055d"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e77363e8425325384f9d49272c54045bbed2f478e9dd698dbc65dbc37860eb0a"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:777abfab476cf83b5177b84d7486497e034eb9eaea0d746ce0c1268c71652077"}, + {file = "coverage-7.6.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:447af20e25fdbe16f26e84eb714ba21d98868705cb138252d28bc400381f6ffb"}, + {file = "coverage-7.6.9-cp313-cp313-win32.whl", hash = "sha256:d872ec5aeb086cbea771c573600d47944eea2dcba8be5f3ee649bfe3cb8dc9ba"}, + {file = "coverage-7.6.9-cp313-cp313-win_amd64.whl", hash = "sha256:fd1213c86e48dfdc5a0cc676551db467495a95a662d2396ecd58e719191446e1"}, + {file = "coverage-7.6.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:ba9e7484d286cd5a43744e5f47b0b3fb457865baf07bafc6bee91896364e1419"}, + {file = "coverage-7.6.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1cf0872ee455c03e5674b5bca5e3e68e159379c1af0903e89f5eba9ccc3a"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d10e07aa2b91835d6abec555ec8b2733347956991901eea6ffac295f83a30e4"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13a9e2d3ee855db3dd6ea1ba5203316a1b1fd8eaeffc37c5b54987e61e4194ae"}, + {file = "coverage-7.6.9-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c38bf15a40ccf5619fa2fe8f26106c7e8e080d7760aeccb3722664c8656b030"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d5275455b3e4627c8e7154feaf7ee0743c2e7af82f6e3b561967b1cca755a0be"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8f8770dfc6e2c6a2d4569f411015c8d751c980d17a14b0530da2d7f27ffdd88e"}, + {file = "coverage-7.6.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8d2dfa71665a29b153a9681edb1c8d9c1ea50dfc2375fb4dac99ea7e21a0bcd9"}, + {file = "coverage-7.6.9-cp313-cp313t-win32.whl", hash = "sha256:5e6b86b5847a016d0fbd31ffe1001b63355ed309651851295315031ea7eb5a9b"}, + {file = "coverage-7.6.9-cp313-cp313t-win_amd64.whl", hash = "sha256:97ddc94d46088304772d21b060041c97fc16bdda13c6c7f9d8fcd8d5ae0d8611"}, + {file = "coverage-7.6.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adb697c0bd35100dc690de83154627fbab1f4f3c0386df266dded865fc50a902"}, + {file = "coverage-7.6.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be57b6d56e49c2739cdf776839a92330e933dd5e5d929966fbbd380c77f060be"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1592791f8204ae9166de22ba7e6705fa4ebd02936c09436a1bb85aabca3e599"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e12ae8cc979cf83d258acb5e1f1cf2f3f83524d1564a49d20b8bec14b637f08"}, + {file = "coverage-7.6.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb5555cff66c4d3d6213a296b360f9e1a8e323e74e0426b6c10ed7f4d021e464"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b9389a429e0e5142e69d5bf4a435dd688c14478a19bb901735cdf75e57b13845"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:592ac539812e9b46046620341498caf09ca21023c41c893e1eb9dbda00a70cbf"}, + {file = "coverage-7.6.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a27801adef24cc30871da98a105f77995e13a25a505a0161911f6aafbd66e678"}, + {file = "coverage-7.6.9-cp39-cp39-win32.whl", hash = "sha256:8e3c3e38930cfb729cb8137d7f055e5a473ddaf1217966aa6238c88bd9fd50e6"}, + {file = "coverage-7.6.9-cp39-cp39-win_amd64.whl", hash = "sha256:e28bf44afa2b187cc9f41749138a64435bf340adfcacb5b2290c070ce99839d4"}, + {file = "coverage-7.6.9-pp39.pp310-none-any.whl", hash = "sha256:f3ca78518bc6bc92828cd11867b121891d75cae4ea9e908d72030609b996db1b"}, + {file = "coverage-7.6.9.tar.gz", hash = "sha256:4a8d8977b0c6ef5aeadcb644da9e69ae0dcfe66ec7f368c89c72e058bd71164d"}, ] [package.extras] @@ -694,13 +694,13 @@ typing_extensions = ">=3.10.0.0" [[package]] name = "django" -version = "4.2.16" +version = "4.2.17" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" files = [ - {file = "Django-4.2.16-py3-none-any.whl", hash = "sha256:1ddc333a16fc139fd253035a1606bb24261951bbc3a6ca256717fa06cc41a898"}, - {file = "Django-4.2.16.tar.gz", hash = "sha256:6f1616c2786c408ce86ab7e10f792b8f15742f7b7b7460243929cb371e7f1dad"}, + {file = "Django-4.2.17-py3-none-any.whl", hash = "sha256:3a93350214ba25f178d4045c0786c61573e7dbfa3c509b3551374f1e11ba8de0"}, + {file = "Django-4.2.17.tar.gz", hash = "sha256:6b56d834cc94c8b21a8f4e775064896be3b4a4ca387f2612d4406a5927cd2fdc"}, ] [package.dependencies] @@ -938,70 +938,70 @@ test = ["objgraph", "psutil"] [[package]] name = "grpcio" -version = "1.68.0" +version = "1.68.1" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.68.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:619b5d0f29f4f5351440e9343224c3e19912c21aeda44e0c49d0d147a8d01544"}, - {file = "grpcio-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:a59f5822f9459bed098ffbceb2713abbf7c6fd13f2b9243461da5c338d0cd6c3"}, - {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:c03d89df516128febc5a7e760d675b478ba25802447624edf7aa13b1e7b11e2a"}, - {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44bcbebb24363d587472089b89e2ea0ab2e2b4df0e4856ba4c0b087c82412121"}, - {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79f81b7fbfb136247b70465bd836fa1733043fdee539cd6031cb499e9608a110"}, - {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88fb2925789cfe6daa20900260ef0a1d0a61283dfb2d2fffe6194396a354c618"}, - {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:99f06232b5c9138593ae6f2e355054318717d32a9c09cdc5a2885540835067a1"}, - {file = "grpcio-1.68.0-cp310-cp310-win32.whl", hash = "sha256:a6213d2f7a22c3c30a479fb5e249b6b7e648e17f364598ff64d08a5136fe488b"}, - {file = "grpcio-1.68.0-cp310-cp310-win_amd64.whl", hash = "sha256:15327ab81131ef9b94cb9f45b5bd98803a179c7c61205c8c0ac9aff9d6c4e82a"}, - {file = "grpcio-1.68.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:3b2b559beb2d433129441783e5f42e3be40a9e1a89ec906efabf26591c5cd415"}, - {file = "grpcio-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e46541de8425a4d6829ac6c5d9b16c03c292105fe9ebf78cb1c31e8d242f9155"}, - {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c1245651f3c9ea92a2db4f95d37b7597db6b246d5892bca6ee8c0e90d76fb73c"}, - {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f1931c7aa85be0fa6cea6af388e576f3bf6baee9e5d481c586980c774debcb4"}, - {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ff09c81e3aded7a183bc6473639b46b6caa9c1901d6f5e2cba24b95e59e30"}, - {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8c73f9fbbaee1a132487e31585aa83987ddf626426d703ebcb9a528cf231c9b1"}, - {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6b2f98165ea2790ea159393a2246b56f580d24d7da0d0342c18a085299c40a75"}, - {file = "grpcio-1.68.0-cp311-cp311-win32.whl", hash = "sha256:e1e7ed311afb351ff0d0e583a66fcb39675be112d61e7cfd6c8269884a98afbc"}, - {file = "grpcio-1.68.0-cp311-cp311-win_amd64.whl", hash = "sha256:e0d2f68eaa0a755edd9a47d40e50dba6df2bceda66960dee1218da81a2834d27"}, - {file = "grpcio-1.68.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8af6137cc4ae8e421690d276e7627cfc726d4293f6607acf9ea7260bd8fc3d7d"}, - {file = "grpcio-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4028b8e9a3bff6f377698587d642e24bd221810c06579a18420a17688e421af7"}, - {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f60fa2adf281fd73ae3a50677572521edca34ba373a45b457b5ebe87c2d01e1d"}, - {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e18589e747c1e70b60fab6767ff99b2d0c359ea1db8a2cb524477f93cdbedf5b"}, - {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d30f3fee9372796f54d3100b31ee70972eaadcc87314be369360248a3dcffe"}, - {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7e0a3e72c0e9a1acab77bef14a73a416630b7fd2cbd893c0a873edc47c42c8cd"}, - {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a831dcc343440969aaa812004685ed322cdb526cd197112d0db303b0da1e8659"}, - {file = "grpcio-1.68.0-cp312-cp312-win32.whl", hash = "sha256:5a180328e92b9a0050958ced34dddcb86fec5a8b332f5a229e353dafc16cd332"}, - {file = "grpcio-1.68.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bddd04a790b69f7a7385f6a112f46ea0b34c4746f361ebafe9ca0be567c78e9"}, - {file = "grpcio-1.68.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:fc05759ffbd7875e0ff2bd877be1438dfe97c9312bbc558c8284a9afa1d0f40e"}, - {file = "grpcio-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:15fa1fe25d365a13bc6d52fcac0e3ee1f9baebdde2c9b3b2425f8a4979fccea1"}, - {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:32a9cb4686eb2e89d97022ecb9e1606d132f85c444354c17a7dbde4a455e4a3b"}, - {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dba037ff8d284c8e7ea9a510c8ae0f5b016004f13c3648f72411c464b67ff2fb"}, - {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0efbbd849867e0e569af09e165363ade75cf84f5229b2698d53cf22c7a4f9e21"}, - {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:4e300e6978df0b65cc2d100c54e097c10dfc7018b9bd890bbbf08022d47f766d"}, - {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:6f9c7ad1a23e1047f827385f4713b5b8c6c7d325705be1dd3e31fb00dcb2f665"}, - {file = "grpcio-1.68.0-cp313-cp313-win32.whl", hash = "sha256:3ac7f10850fd0487fcce169c3c55509101c3bde2a3b454869639df2176b60a03"}, - {file = "grpcio-1.68.0-cp313-cp313-win_amd64.whl", hash = "sha256:afbf45a62ba85a720491bfe9b2642f8761ff348006f5ef67e4622621f116b04a"}, - {file = "grpcio-1.68.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:f8f695d9576ce836eab27ba7401c60acaf9ef6cf2f70dfe5462055ba3df02cc3"}, - {file = "grpcio-1.68.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9fe1b141cda52f2ca73e17d2d3c6a9f3f3a0c255c216b50ce616e9dca7e3441d"}, - {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:4df81d78fd1646bf94ced4fb4cd0a7fe2e91608089c522ef17bc7db26e64effd"}, - {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46a2d74d4dd8993151c6cd585594c082abe74112c8e4175ddda4106f2ceb022f"}, - {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17278d977746472698460c63abf333e1d806bd41f2224f90dbe9460101c9796"}, - {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:15377bce516b1c861c35e18eaa1c280692bf563264836cece693c0f169b48829"}, - {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cc5f0a4f5904b8c25729a0498886b797feb817d1fd3812554ffa39551112c161"}, - {file = "grpcio-1.68.0-cp38-cp38-win32.whl", hash = "sha256:def1a60a111d24376e4b753db39705adbe9483ef4ca4761f825639d884d5da78"}, - {file = "grpcio-1.68.0-cp38-cp38-win_amd64.whl", hash = "sha256:55d3b52fd41ec5772a953612db4e70ae741a6d6ed640c4c89a64f017a1ac02b5"}, - {file = "grpcio-1.68.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:0d230852ba97654453d290e98d6aa61cb48fa5fafb474fb4c4298d8721809354"}, - {file = "grpcio-1.68.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:50992f214264e207e07222703c17d9cfdcc2c46ed5a1ea86843d440148ebbe10"}, - {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:14331e5c27ed3545360464a139ed279aa09db088f6e9502e95ad4bfa852bb116"}, - {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f84890b205692ea813653ece4ac9afa2139eae136e419231b0eec7c39fdbe4c2"}, - {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0cf343c6f4f6aa44863e13ec9ddfe299e0be68f87d68e777328bff785897b05"}, - {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fd2c2d47969daa0e27eadaf15c13b5e92605c5e5953d23c06d0b5239a2f176d3"}, - {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:18668e36e7f4045820f069997834e94e8275910b1f03e078a6020bd464cb2363"}, - {file = "grpcio-1.68.0-cp39-cp39-win32.whl", hash = "sha256:2af76ab7c427aaa26aa9187c3e3c42f38d3771f91a20f99657d992afada2294a"}, - {file = "grpcio-1.68.0-cp39-cp39-win_amd64.whl", hash = "sha256:e694b5928b7b33ca2d3b4d5f9bf8b5888906f181daff6b406f4938f3a997a490"}, - {file = "grpcio-1.68.0.tar.gz", hash = "sha256:7e7483d39b4a4fddb9906671e9ea21aaad4f031cdfc349fec76bdfa1e404543a"}, + {file = "grpcio-1.68.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:d35740e3f45f60f3c37b1e6f2f4702c23867b9ce21c6410254c9c682237da68d"}, + {file = "grpcio-1.68.1-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:d99abcd61760ebb34bdff37e5a3ba333c5cc09feda8c1ad42547bea0416ada78"}, + {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:f8261fa2a5f679abeb2a0a93ad056d765cdca1c47745eda3f2d87f874ff4b8c9"}, + {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0feb02205a27caca128627bd1df4ee7212db051019a9afa76f4bb6a1a80ca95e"}, + {file = "grpcio-1.68.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:919d7f18f63bcad3a0f81146188e90274fde800a94e35d42ffe9eadf6a9a6330"}, + {file = "grpcio-1.68.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:963cc8d7d79b12c56008aabd8b457f400952dbea8997dd185f155e2f228db079"}, + {file = "grpcio-1.68.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ccf2ebd2de2d6661e2520dae293298a3803a98ebfc099275f113ce1f6c2a80f1"}, + {file = "grpcio-1.68.1-cp310-cp310-win32.whl", hash = "sha256:2cc1fd04af8399971bcd4f43bd98c22d01029ea2e56e69c34daf2bf8470e47f5"}, + {file = "grpcio-1.68.1-cp310-cp310-win_amd64.whl", hash = "sha256:ee2e743e51cb964b4975de572aa8fb95b633f496f9fcb5e257893df3be854746"}, + {file = "grpcio-1.68.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:55857c71641064f01ff0541a1776bfe04a59db5558e82897d35a7793e525774c"}, + {file = "grpcio-1.68.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4b177f5547f1b995826ef529d2eef89cca2f830dd8b2c99ffd5fde4da734ba73"}, + {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:3522c77d7e6606d6665ec8d50e867f13f946a4e00c7df46768f1c85089eae515"}, + {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d1fae6bbf0816415b81db1e82fb3bf56f7857273c84dcbe68cbe046e58e1ccd"}, + {file = "grpcio-1.68.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:298ee7f80e26f9483f0b6f94cc0a046caf54400a11b644713bb5b3d8eb387600"}, + {file = "grpcio-1.68.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cbb5780e2e740b6b4f2d208e90453591036ff80c02cc605fea1af8e6fc6b1bbe"}, + {file = "grpcio-1.68.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ddda1aa22495d8acd9dfbafff2866438d12faec4d024ebc2e656784d96328ad0"}, + {file = "grpcio-1.68.1-cp311-cp311-win32.whl", hash = "sha256:b33bd114fa5a83f03ec6b7b262ef9f5cac549d4126f1dc702078767b10c46ed9"}, + {file = "grpcio-1.68.1-cp311-cp311-win_amd64.whl", hash = "sha256:7f20ebec257af55694d8f993e162ddf0d36bd82d4e57f74b31c67b3c6d63d8b2"}, + {file = "grpcio-1.68.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:8829924fffb25386995a31998ccbbeaa7367223e647e0122043dfc485a87c666"}, + {file = "grpcio-1.68.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:3aed6544e4d523cd6b3119b0916cef3d15ef2da51e088211e4d1eb91a6c7f4f1"}, + {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:4efac5481c696d5cb124ff1c119a78bddbfdd13fc499e3bc0ca81e95fc573684"}, + {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ab2d912ca39c51f46baf2a0d92aa265aa96b2443266fc50d234fa88bf877d8e"}, + {file = "grpcio-1.68.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95c87ce2a97434dffe7327a4071839ab8e8bffd0054cc74cbe971fba98aedd60"}, + {file = "grpcio-1.68.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e4842e4872ae4ae0f5497bf60a0498fa778c192cc7a9e87877abd2814aca9475"}, + {file = "grpcio-1.68.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:255b1635b0ed81e9f91da4fcc8d43b7ea5520090b9a9ad9340d147066d1d3613"}, + {file = "grpcio-1.68.1-cp312-cp312-win32.whl", hash = "sha256:7dfc914cc31c906297b30463dde0b9be48e36939575eaf2a0a22a8096e69afe5"}, + {file = "grpcio-1.68.1-cp312-cp312-win_amd64.whl", hash = "sha256:a0c8ddabef9c8f41617f213e527254c41e8b96ea9d387c632af878d05db9229c"}, + {file = "grpcio-1.68.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:a47faedc9ea2e7a3b6569795c040aae5895a19dde0c728a48d3c5d7995fda385"}, + {file = "grpcio-1.68.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:390eee4225a661c5cd133c09f5da1ee3c84498dc265fd292a6912b65c421c78c"}, + {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:66a24f3d45c33550703f0abb8b656515b0ab777970fa275693a2f6dc8e35f1c1"}, + {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c08079b4934b0bf0a8847f42c197b1d12cba6495a3d43febd7e99ecd1cdc8d54"}, + {file = "grpcio-1.68.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8720c25cd9ac25dd04ee02b69256d0ce35bf8a0f29e20577427355272230965a"}, + {file = "grpcio-1.68.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:04cfd68bf4f38f5bb959ee2361a7546916bd9a50f78617a346b3aeb2b42e2161"}, + {file = "grpcio-1.68.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c28848761a6520c5c6071d2904a18d339a796ebe6b800adc8b3f474c5ce3c3ad"}, + {file = "grpcio-1.68.1-cp313-cp313-win32.whl", hash = "sha256:77d65165fc35cff6e954e7fd4229e05ec76102d4406d4576528d3a3635fc6172"}, + {file = "grpcio-1.68.1-cp313-cp313-win_amd64.whl", hash = "sha256:a8040f85dcb9830d8bbb033ae66d272614cec6faceee88d37a88a9bd1a7a704e"}, + {file = "grpcio-1.68.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:eeb38ff04ab6e5756a2aef6ad8d94e89bb4a51ef96e20f45c44ba190fa0bcaad"}, + {file = "grpcio-1.68.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8a3869a6661ec8f81d93f4597da50336718bde9eb13267a699ac7e0a1d6d0bea"}, + {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:2c4cec6177bf325eb6faa6bd834d2ff6aa8bb3b29012cceb4937b86f8b74323c"}, + {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12941d533f3cd45d46f202e3667be8ebf6bcb3573629c7ec12c3e211d99cfccf"}, + {file = "grpcio-1.68.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80af6f1e69c5e68a2be529990684abdd31ed6622e988bf18850075c81bb1ad6e"}, + {file = "grpcio-1.68.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e8dbe3e00771bfe3d04feed8210fc6617006d06d9a2679b74605b9fed3e8362c"}, + {file = "grpcio-1.68.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:83bbf5807dc3ee94ce1de2dfe8a356e1d74101e4b9d7aa8c720cc4818a34aded"}, + {file = "grpcio-1.68.1-cp38-cp38-win32.whl", hash = "sha256:8cb620037a2fd9eeee97b4531880e439ebfcd6d7d78f2e7dcc3726428ab5ef63"}, + {file = "grpcio-1.68.1-cp38-cp38-win_amd64.whl", hash = "sha256:52fbf85aa71263380d330f4fce9f013c0798242e31ede05fcee7fbe40ccfc20d"}, + {file = "grpcio-1.68.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:cb400138e73969eb5e0535d1d06cae6a6f7a15f2cc74add320e2130b8179211a"}, + {file = "grpcio-1.68.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a1b988b40f2fd9de5c820f3a701a43339d8dcf2cb2f1ca137e2c02671cc83ac1"}, + {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:96f473cdacfdd506008a5d7579c9f6a7ff245a9ade92c3c0265eb76cc591914f"}, + {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:37ea3be171f3cf3e7b7e412a98b77685eba9d4fd67421f4a34686a63a65d99f9"}, + {file = "grpcio-1.68.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ceb56c4285754e33bb3c2fa777d055e96e6932351a3082ce3559be47f8024f0"}, + {file = "grpcio-1.68.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dffd29a2961f3263a16d73945b57cd44a8fd0b235740cb14056f0612329b345e"}, + {file = "grpcio-1.68.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:025f790c056815b3bf53da850dd70ebb849fd755a4b1ac822cb65cd631e37d43"}, + {file = "grpcio-1.68.1-cp39-cp39-win32.whl", hash = "sha256:1098f03dedc3b9810810568060dea4ac0822b4062f537b0f53aa015269be0a76"}, + {file = "grpcio-1.68.1-cp39-cp39-win_amd64.whl", hash = "sha256:334ab917792904245a028f10e803fcd5b6f36a7b2173a820c0b5b076555825e1"}, + {file = "grpcio-1.68.1.tar.gz", hash = "sha256:44a8502dd5de653ae6a73e2de50a401d84184f0331d0ac3daeb044e66d5c5054"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.68.0)"] +protobuf = ["grpcio-tools (>=1.68.1)"] [[package]] name = "gunicorn" @@ -1309,66 +1309,66 @@ files = [ [[package]] name = "numpy" -version = "2.1.3" +version = "2.2.0" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" files = [ - {file = "numpy-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff"}, - {file = "numpy-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5"}, - {file = "numpy-2.1.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1"}, - {file = "numpy-2.1.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd"}, - {file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3"}, - {file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098"}, - {file = "numpy-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c"}, - {file = "numpy-2.1.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4"}, - {file = "numpy-2.1.3-cp310-cp310-win32.whl", hash = "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23"}, - {file = "numpy-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0"}, - {file = "numpy-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d"}, - {file = "numpy-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41"}, - {file = "numpy-2.1.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9"}, - {file = "numpy-2.1.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09"}, - {file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a"}, - {file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b"}, - {file = "numpy-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee"}, - {file = "numpy-2.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0"}, - {file = "numpy-2.1.3-cp311-cp311-win32.whl", hash = "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9"}, - {file = "numpy-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2"}, - {file = "numpy-2.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e"}, - {file = "numpy-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958"}, - {file = "numpy-2.1.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8"}, - {file = "numpy-2.1.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564"}, - {file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512"}, - {file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b"}, - {file = "numpy-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc"}, - {file = "numpy-2.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0"}, - {file = "numpy-2.1.3-cp312-cp312-win32.whl", hash = "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9"}, - {file = "numpy-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a"}, - {file = "numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f"}, - {file = "numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598"}, - {file = "numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57"}, - {file = "numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe"}, - {file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43"}, - {file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56"}, - {file = "numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a"}, - {file = "numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef"}, - {file = "numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f"}, - {file = "numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed"}, - {file = "numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f"}, - {file = "numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4"}, - {file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e"}, - {file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0"}, - {file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408"}, - {file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6"}, - {file = "numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f"}, - {file = "numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17"}, - {file = "numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48"}, - {file = "numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4"}, - {file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f"}, - {file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4"}, - {file = "numpy-2.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d"}, - {file = "numpy-2.1.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb"}, - {file = "numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761"}, + {file = "numpy-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e25507d85da11ff5066269d0bd25d06e0a0f2e908415534f3e603d2a78e4ffa"}, + {file = "numpy-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a62eb442011776e4036af5c8b1a00b706c5bc02dc15eb5344b0c750428c94219"}, + {file = "numpy-2.2.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:b606b1aaf802e6468c2608c65ff7ece53eae1a6874b3765f69b8ceb20c5fa78e"}, + {file = "numpy-2.2.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:36b2b43146f646642b425dd2027730f99bac962618ec2052932157e213a040e9"}, + {file = "numpy-2.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fe8f3583e0607ad4e43a954e35c1748b553bfe9fdac8635c02058023277d1b3"}, + {file = "numpy-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:122fd2fcfafdefc889c64ad99c228d5a1f9692c3a83f56c292618a59aa60ae83"}, + {file = "numpy-2.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3f2f5cddeaa4424a0a118924b988746db6ffa8565e5829b1841a8a3bd73eb59a"}, + {file = "numpy-2.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fe4bb0695fe986a9e4deec3b6857003b4cfe5c5e4aac0b95f6a658c14635e31"}, + {file = "numpy-2.2.0-cp310-cp310-win32.whl", hash = "sha256:b30042fe92dbd79f1ba7f6898fada10bdaad1847c44f2dff9a16147e00a93661"}, + {file = "numpy-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dc1d6d66f8d37843ed281773c7174f03bf7ad826523f73435deb88ba60d2d4"}, + {file = "numpy-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9874bc2ff574c40ab7a5cbb7464bf9b045d617e36754a7bc93f933d52bd9ffc6"}, + {file = "numpy-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0da8495970f6b101ddd0c38ace92edea30e7e12b9a926b57f5fabb1ecc25bb90"}, + {file = "numpy-2.2.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0557eebc699c1c34cccdd8c3778c9294e8196df27d713706895edc6f57d29608"}, + {file = "numpy-2.2.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:3579eaeb5e07f3ded59298ce22b65f877a86ba8e9fe701f5576c99bb17c283da"}, + {file = "numpy-2.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40deb10198bbaa531509aad0cd2f9fadb26c8b94070831e2208e7df543562b74"}, + {file = "numpy-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2aed8fcf8abc3020d6a9ccb31dbc9e7d7819c56a348cc88fd44be269b37427e"}, + {file = "numpy-2.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a222d764352c773aa5ebde02dd84dba3279c81c6db2e482d62a3fa54e5ece69b"}, + {file = "numpy-2.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4e58666988605e251d42c2818c7d3d8991555381be26399303053b58a5bbf30d"}, + {file = "numpy-2.2.0-cp311-cp311-win32.whl", hash = "sha256:4723a50e1523e1de4fccd1b9a6dcea750c2102461e9a02b2ac55ffeae09a4410"}, + {file = "numpy-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:16757cf28621e43e252c560d25b15f18a2f11da94fea344bf26c599b9cf54b73"}, + {file = "numpy-2.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cff210198bb4cae3f3c100444c5eaa573a823f05c253e7188e1362a5555235b3"}, + {file = "numpy-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b92a5828bd4d9aa0952492b7de803135038de47343b2aa3cc23f3b71a3dc4e"}, + {file = "numpy-2.2.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ebe5e59545401fbb1b24da76f006ab19734ae71e703cdb4a8b347e84a0cece67"}, + {file = "numpy-2.2.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e2b8cd48a9942ed3f85b95ca4105c45758438c7ed28fff1e4ce3e57c3b589d8e"}, + {file = "numpy-2.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57fcc997ffc0bef234b8875a54d4058afa92b0b0c4223fc1f62f24b3b5e86038"}, + {file = "numpy-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ad7d11b309bd132d74397fcf2920933c9d1dc865487128f5c03d580f2c3d03"}, + {file = "numpy-2.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cb24cca1968b21355cc6f3da1a20cd1cebd8a023e3c5b09b432444617949085a"}, + {file = "numpy-2.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0798b138c291d792f8ea40fe3768610f3c7dd2574389e37c3f26573757c8f7ef"}, + {file = "numpy-2.2.0-cp312-cp312-win32.whl", hash = "sha256:afe8fb968743d40435c3827632fd36c5fbde633b0423da7692e426529b1759b1"}, + {file = "numpy-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:3a4199f519e57d517ebd48cb76b36c82da0360781c6a0353e64c0cac30ecaad3"}, + {file = "numpy-2.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f8c8b141ef9699ae777c6278b52c706b653bf15d135d302754f6b2e90eb30367"}, + {file = "numpy-2.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f0986e917aca18f7a567b812ef7ca9391288e2acb7a4308aa9d265bd724bdae"}, + {file = "numpy-2.2.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:1c92113619f7b272838b8d6702a7f8ebe5edea0df48166c47929611d0b4dea69"}, + {file = "numpy-2.2.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5a145e956b374e72ad1dff82779177d4a3c62bc8248f41b80cb5122e68f22d13"}, + {file = "numpy-2.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18142b497d70a34b01642b9feabb70156311b326fdddd875a9981f34a369b671"}, + {file = "numpy-2.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7d41d1612c1a82b64697e894b75db6758d4f21c3ec069d841e60ebe54b5b571"}, + {file = "numpy-2.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a98f6f20465e7618c83252c02041517bd2f7ea29be5378f09667a8f654a5918d"}, + {file = "numpy-2.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e09d40edfdb4e260cb1567d8ae770ccf3b8b7e9f0d9b5c2a9992696b30ce2742"}, + {file = "numpy-2.2.0-cp313-cp313-win32.whl", hash = "sha256:3905a5fffcc23e597ee4d9fb3fcd209bd658c352657548db7316e810ca80458e"}, + {file = "numpy-2.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a184288538e6ad699cbe6b24859206e38ce5fba28f3bcfa51c90d0502c1582b2"}, + {file = "numpy-2.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7832f9e8eb00be32f15fdfb9a981d6955ea9adc8574c521d48710171b6c55e95"}, + {file = "numpy-2.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0dd071b95bbca244f4cb7f70b77d2ff3aaaba7fa16dc41f58d14854a6204e6c"}, + {file = "numpy-2.2.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0b227dcff8cdc3efbce66d4e50891f04d0a387cce282fe1e66199146a6a8fca"}, + {file = "numpy-2.2.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ab153263a7c5ccaf6dfe7e53447b74f77789f28ecb278c3b5d49db7ece10d6d"}, + {file = "numpy-2.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e500aba968a48e9019e42c0c199b7ec0696a97fa69037bea163b55398e390529"}, + {file = "numpy-2.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:440cfb3db4c5029775803794f8638fbdbf71ec702caf32735f53b008e1eaece3"}, + {file = "numpy-2.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a55dc7a7f0b6198b07ec0cd445fbb98b05234e8b00c5ac4874a63372ba98d4ab"}, + {file = "numpy-2.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4bddbaa30d78c86329b26bd6aaaea06b1e47444da99eddac7bf1e2fab717bd72"}, + {file = "numpy-2.2.0-cp313-cp313t-win32.whl", hash = "sha256:30bf971c12e4365153afb31fc73f441d4da157153f3400b82db32d04de1e4066"}, + {file = "numpy-2.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d35717333b39d1b6bb8433fa758a55f1081543de527171543a2b710551d40881"}, + {file = "numpy-2.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e12c6c1ce84628c52d6367863773f7c8c8241be554e8b79686e91a43f1733773"}, + {file = "numpy-2.2.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:b6207dc8fb3c8cb5668e885cef9ec7f70189bec4e276f0ff70d5aa078d32c88e"}, + {file = "numpy-2.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a50aeff71d0f97b6450d33940c7181b08be1441c6c193e678211bff11aa725e7"}, + {file = "numpy-2.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:df12a1f99b99f569a7c2ae59aa2d31724e8d835fc7f33e14f4792e3071d11221"}, + {file = "numpy-2.2.0.tar.gz", hash = "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0"}, ] [[package]] @@ -1988,13 +1988,13 @@ files = [ [[package]] name = "pytest" -version = "8.3.3" +version = "8.3.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, - {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, ] [package.dependencies] @@ -2229,13 +2229,13 @@ crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] [[package]] name = "sentry-sdk" -version = "2.19.0" +version = "2.19.2" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = ">=3.6" files = [ - {file = "sentry_sdk-2.19.0-py2.py3-none-any.whl", hash = "sha256:7b0b3b709dee051337244a09a30dbf6e95afe0d34a1f8b430d45e0982a7c125b"}, - {file = "sentry_sdk-2.19.0.tar.gz", hash = "sha256:ee4a4d2ae8bfe3cac012dcf3e4607975904c137e1738116549fc3dbbb6ff0e36"}, + {file = "sentry_sdk-2.19.2-py2.py3-none-any.whl", hash = "sha256:ebdc08228b4d131128e568d696c210d846e5b9d70aa0327dec6b1272d9d40b84"}, + {file = "sentry_sdk-2.19.2.tar.gz", hash = "sha256:467df6e126ba242d39952375dd816fbee0f217d119bf454a8ce74cf1e7909e8d"}, ] [package.dependencies] @@ -2303,13 +2303,13 @@ type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (>=1.12 [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] diff --git a/pyproject.toml b/pyproject.toml index b573fdb..de4ac10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ playwright = "^1.45.0" behave = "^1.2.6" [tool.pytest.ini_options] -DJANGO_SETTINGS_MODULE = "config.settings.local" +DJANGO_SETTINGS_MODULE = "fbr.settings" xfail_strict=true addopts = """ -rsxXw @@ -105,4 +105,4 @@ max-line-length = 79 max-complexity = 10 [tool.poetry.scripts] -rebuild-cache = "fbr.cache.manage_cache:rebuild_cache" +rebuild-cache = "app.cache.manage_cache:rebuild_cache" diff --git a/pytest.ini b/pytest.ini index 7b775ce..67c6085 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] -DJANGO_SETTINGS_MODULE = config.settings.local +DJANGO_SETTINGS_MODULE = fbr.settings DJANGO_FIND_PROJECT = false" From ef0935af78b3a55ae76f0200bd226a4009b2d8e3 Mon Sep 17 00:00:00 2001 From: Haresh Kainth Date: Tue, 10 Dec 2024 00:07:28 +0000 Subject: [PATCH 02/12] refactor:restructure project architecture Reorganized application package structure to improve modularity and navigability. Moved files from `fbr` to `app` directory and renamed key modules to better reflect functionality, such as `celery_worker`. Additionally, updated all relevant imports and settings configurations to align with the new structure, including Django settings and task management. --- fbr/celery_app.py | 24 ++++++++++++++++++++++++ fbr/config/celery.py | 34 ---------------------------------- 2 files changed, 24 insertions(+), 34 deletions(-) create mode 100644 fbr/celery_app.py delete mode 100644 fbr/config/celery.py diff --git a/fbr/celery_app.py b/fbr/celery_app.py new file mode 100644 index 0000000..6e33420 --- /dev/null +++ b/fbr/celery_app.py @@ -0,0 +1,24 @@ +# isort:skip_file +# flake8: noqa + +import os + +from celery import Celery +from celery.schedules import crontab + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fbr.settings") + +from dbt_copilot_python.celery_health_check import healthcheck + +celery_app = Celery("fbr_celery") +celery_app.config_from_object("django.conf:settings", namespace="CELERY") +celery_app.autodiscover_tasks() + +celery_app = healthcheck.setup(celery_app) + +celery_app.conf.beat_schedule = { + "schedule-fbr-cache-task": { + "task": "celery_worker.tasks.rebuild_cache", + "schedule": crontab(hour="1", minute="0"), # Runs daily at 1:00 AM + }, +} diff --git a/fbr/config/celery.py b/fbr/config/celery.py deleted file mode 100644 index cd0473b..0000000 --- a/fbr/config/celery.py +++ /dev/null @@ -1,34 +0,0 @@ -# isort:skip_file - -from __future__ import absolute_import, unicode_literals - -import os - -from celery import Celery -from celery.schedules import crontab -from dbt_copilot_python.celery_health_check import healthcheck - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "fbr.config.settings.local") - -celery_app = Celery("fbr_celery") - -celery_app.config_from_object("django.conf:settings", namespace="CELERY") - -celery_app.autodiscover_tasks() - -celery_app = healthcheck.setup(celery_app) - - -@celery_app.on_after_configure.connect -def setup_periodic_tasks(sender, **kwargs): - schedule = get_cache_beat_schedule() - celery_app.conf.beat_schedule = schedule - - -def get_cache_beat_schedule(): - return { - "schedule-fbr-cache-task": { - "task": "fbr.cache.tasks.rebuild_cache", - "schedule": crontab(hour="1", minute="0"), # Runs daily at 1:00 AM - }, - } From 9c27d1793fd8517bb2addb4d5e14812002159cb7 Mon Sep 17 00:00:00 2001 From: Haresh Kainth Date: Tue, 10 Dec 2024 01:47:50 +0000 Subject: [PATCH 03/12] refactor: Docker setup to streamline build and deployment Moved Dockerfile and entry script out of local_deployment for a cleaner structure. Updated docker-compose and Makefile to reflect these changes, using a unified image to leverage caching. Removed Poetry setup in favor of using pip with requirements.txt for dependency management. --- local_deployment/Dockerfile => Dockerfile | 17 +++-- Makefile | 6 +- docker-compose.yml | 12 ++- local_deployment/entry.sh => entry.sh | 3 - requirements.txt | 89 +++++++++++++++++++++++ 5 files changed, 113 insertions(+), 14 deletions(-) rename local_deployment/Dockerfile => Dockerfile (81%) rename local_deployment/entry.sh => entry.sh (73%) create mode 100644 requirements.txt diff --git a/local_deployment/Dockerfile b/Dockerfile similarity index 81% rename from local_deployment/Dockerfile rename to Dockerfile index 0b529b0..0627c9d 100644 --- a/local_deployment/Dockerfile +++ b/Dockerfile @@ -33,10 +33,17 @@ RUN apt install -y curl && \ apt install -y nodejs WORKDIR /app -COPY . /app -# Install poetry and project dependencies -RUN pip install poetry==1.8.3 && \ - poetry install --without dev +RUN pip install poetry + +# Copy only the requirements.txt into the container +COPY requirements.txt /app/ + +# Install the dependencies specified in requirements.txt +RUN pip install --no-cache-dir -r requirements.txt + +COPY . /app -CMD ["local_deployment/entry.sh"] +COPY entry.sh /entry.sh +RUN chmod +x /entry.sh +ENTRYPOINT ["/entry.sh"] diff --git a/Makefile b/Makefile index c02e002..98db6ba 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ drop-database: # Delete project's postgres database fi build: # Build docker containers for local execution - docker build --no-cache -f local_deployment/Dockerfile -t local_deployment . + docker build --no-cache -f Dockerfile -t local_deployment . docker compose build collectstatic: # Run Django collectstatic @@ -136,6 +136,6 @@ setup_local: # Set up the local environment @echo "$(COLOUR_GREEN)Running initial setup for local environment...$(COLOUR_NONE)" $(MAKE) first-use $(MAKE) start - $(MAKE) migrate - $(MAKE) rebuild_cache + #$(MAKE) migrate + #$(MAKE) rebuild_cache @echo "$(COLOUR_GREEN)Local setup complete.$(COLOUR_NONE)" diff --git a/docker-compose.yml b/docker-compose.yml index e141750..58b908b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,7 +27,9 @@ services: web: build: context: . - dockerfile: local_deployment/Dockerfile + cache_from: + - fbr/application:latest + image: fbr/application:latest env_file: - local.env ports: @@ -49,7 +51,9 @@ services: celery-worker: build: context: . - dockerfile: local_deployment/Dockerfile + cache_from: + - fbr/application:latest + image: fbr/application:latest command: celery --app fbr.celery_app worker --task-events --loglevel INFO entrypoint: '' volumes: @@ -72,7 +76,9 @@ services: celery-beats: build: context: . - dockerfile: local_deployment/Dockerfile + cache_from: + - fbr/application:latest + image: fbr/application:latest command: celery --app fbr.celery_app beat --loglevel INFO entrypoint: '' volumes: diff --git a/local_deployment/entry.sh b/entry.sh similarity index 73% rename from local_deployment/entry.sh rename to entry.sh index 4df984f..2c5b594 100755 --- a/local_deployment/entry.sh +++ b/entry.sh @@ -6,9 +6,6 @@ npm install echo "Bundling WebPack" npm run build -# Activate the Poetry virtual environment -. "$(poetry env info --path)/bin/activate" - echo "Collecting Static Files" python manage.py collectstatic --noinput diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..339f00c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,89 @@ +amqp==5.3.1 ; python_version >= "3.12" and python_version < "4.0" +asgiref==3.8.1 ; python_version >= "3.12" and python_version < "4.0" +backoff==2.2.1 ; python_version >= "3.12" and python_version < "4.0" +billiard==4.2.1 ; python_version >= "3.12" and python_version < "4.0" +boto3==1.35.77 ; python_version >= "3.12" and python_version < "4.0" +botocore==1.35.77 ; python_version >= "3.12" and python_version < "4.0" +brotli==1.1.0 ; python_version >= "3.12" and python_version < "4.0" +celery==5.4.0 ; python_version >= "3.12" and python_version < "4.0" +celery[redis]==5.4.0 ; python_version >= "3.12" and python_version < "4.0" +certifi==2024.8.30 ; python_version >= "3.12" and python_version < "4.0" +cfgv==3.4.0 ; python_version >= "3.12" and python_version < "4" +charset-normalizer==3.4.0 ; python_version >= "3.12" and python_version < "4.0" +click-didyoumean==0.3.1 ; python_version >= "3.12" and python_version < "4.0" +click-plugins==1.1.1 ; python_version >= "3.12" and python_version < "4.0" +click-repl==0.3.0 ; python_version >= "3.12" and python_version < "4.0" +click==8.1.7 ; python_version >= "3.12" and python_version < "4.0" +colorama==0.4.6 ; python_version >= "3.12" and python_version < "4.0" and platform_system == "Windows" +config==0.5.1 ; python_version >= "3.12" and python_version < "4.0" +cron-descriptor==1.4.5 ; python_version >= "3.12" and python_version < "4.0" +dbt-copilot-python==0.2.2 ; python_version >= "3.12" and python_version < "4.0" +deprecated==1.2.15 ; python_version >= "3.12" and python_version < "4.0" +distlib==0.3.9 ; python_version >= "3.12" and python_version < "4" +dj-database-url==2.3.0 ; python_version >= "3.12" and python_version < "4.0" +django-celery-beat==2.7.0 ; python_version >= "3.12" and python_version < "4.0" +django-environ==0.11.2 ; python_version >= "3.12" and python_version < "4" +django-log-formatter-asim==0.0.4 ; python_version >= "3.12" and python_version < "4" +django-timezone-field==7.0 ; python_version >= "3.12" and python_version < "4.0" +django-webpack-loader==3.1.1 ; python_version >= "3.12" and python_version < "4.0" +django==4.2.17 ; python_version >= "3.12" and python_version < "4.0" +djangorestframework==3.15.2 ; python_version >= "3.12" and python_version < "4.0" +filelock==3.16.1 ; python_version >= "3.12" and python_version < "4" +googleapis-common-protos==1.66.0 ; python_version >= "3.12" and python_version < "4.0" +grpcio==1.68.1 ; python_version >= "3.12" and python_version < "4.0" +gunicorn==22.0.0 ; python_version >= "3.12" and python_version < "4.0" +identify==2.6.3 ; python_version >= "3.12" and python_version < "4" +idna==3.10 ; python_version >= "3.12" and python_version < "4.0" +importlib-metadata==6.11.0 ; python_version >= "3.12" and python_version < "4.0" +jinja2==3.1.4 ; python_version >= "3.12" and python_version < "4.0" +jmespath==1.0.1 ; python_version >= "3.12" and python_version < "4.0" +kombu==5.4.2 ; python_version >= "3.12" and python_version < "4.0" +kombu[redis]==5.4.2 ; python_version >= "3.12" and python_version < "4.0" +markupsafe==3.0.2 ; python_version >= "3.12" and python_version < "4.0" +nodeenv==1.9.1 ; python_version >= "3.12" and python_version < "4" +numpy==2.2.0 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-api==1.22.0 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-distro==0.43b0 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-exporter-otlp-proto-common==1.22.0 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-exporter-otlp-proto-grpc==1.22.0 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-exporter-otlp-proto-http==1.22.0 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-exporter-otlp==1.22.0 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-instrumentation-wsgi==0.43b0 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-instrumentation==0.43b0 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-propagator-aws-xray==1.0.2 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-proto==1.22.0 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-sdk-extension-aws==2.0.2 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-sdk==1.22.0 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-semantic-conventions==0.43b0 ; python_version >= "3.12" and python_version < "4.0" +opentelemetry-util-http==0.43b0 ; python_version >= "3.12" and python_version < "4.0" +packaging==24.2 ; python_version >= "3.12" and python_version < "4.0" +pandas==2.2.3 ; python_version >= "3.12" and python_version < "4.0" +platformdirs==4.3.6 ; python_version >= "3.12" and python_version < "4" +pre-commit==3.8.0 ; python_version >= "3.12" and python_version < "4" +prompt-toolkit==3.0.48 ; python_version >= "3.12" and python_version < "4.0" +protobuf==4.25.5 ; python_version >= "3.12" and python_version < "4.0" +psycopg-c==3.2.3 ; python_version >= "3.12" and python_version < "4.0" +psycopg2-binary==2.9.10 ; python_version >= "3.12" and python_version < "4.0" +psycopg[c]==3.2.3 ; python_version >= "3.12" and python_version < "4.0" +python-crontab==3.2.0 ; python_version >= "3.12" and python_version < "4.0" +python-dateutil==2.9.0.post0 ; python_version >= "3.12" and python_version < "4.0" +python-environ==0.4.54 ; python_version >= "3.12" and python_version < "4.0" +pytz==2024.2 ; python_version >= "3.12" and python_version < "4.0" +pyyaml==6.0.2 ; python_version >= "3.12" and python_version < "4" +redis==5.2.1 ; python_version >= "3.12" and python_version < "4.0" +requests==2.32.3 ; python_version >= "3.12" and python_version < "4.0" +s3transfer==0.10.4 ; python_version >= "3.12" and python_version < "4.0" +sentry-sdk==2.19.2 ; python_version >= "3.12" and python_version < "4.0" +setuptools==75.6.0 ; python_version >= "3.12" and python_version < "4.0" +six==1.17.0 ; python_version >= "3.12" and python_version < "4.0" +sqlparse==0.5.2 ; python_version >= "3.12" and python_version < "4.0" +typing-extensions==4.12.2 ; python_version >= "3.12" and python_version < "4.0" +tzdata==2024.2 ; python_version >= "3.12" and python_version < "4.0" +urllib3==2.2.3 ; python_version >= "3.12" and python_version < "4.0" +utils==1.0.2 ; python_version >= "3.12" and python_version < "4.0" +vine==5.1.0 ; python_version >= "3.12" and python_version < "4.0" +virtualenv==20.28.0 ; python_version >= "3.12" and python_version < "4" +wcwidth==0.2.13 ; python_version >= "3.12" and python_version < "4.0" +whitenoise[brotli]==6.8.2 ; python_version >= "3.12" and python_version < "4.0" +wrapt==1.17.0 ; python_version >= "3.12" and python_version < "4.0" +zipp==3.21.0 ; python_version >= "3.12" and python_version < "4.0" From 3522a937ea2a6d0f5be560c3140e534d25cdb853 Mon Sep 17 00:00:00 2001 From: Haresh Kainth Date: Tue, 10 Dec 2024 03:52:59 +0000 Subject: [PATCH 04/12] chore:Add DATABASE_URL and refactor tasks and migration Introduce DATABASE_URL environment variable for local PostgreSQL setup in Docker and Makefile. Update `rebuild_cache` task with a specific name and adjust its schedule. Refactor Django settings and Makefile commands for improved database management consistency. --- .secrets.baseline | 4 ++-- Makefile | 10 +++++++--- celery_worker/tasks.py | 4 ++-- docker-compose.yml | 1 + fbr/celery_app.py | 2 +- fbr/settings.py | 3 ++- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/.secrets.baseline b/.secrets.baseline index 316ee80..9dafacc 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -133,9 +133,9 @@ "filename": "Makefile", "hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3", "is_verified": false, - "line_number": 102 + "line_number": 65 } ] }, - "generated_at": "2024-09-09T12:08:54Z" + "generated_at": "2024-12-10T03:50:08Z" } diff --git a/Makefile b/Makefile index 98db6ba..fe56b1b 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,7 @@ first-use: # Initialise for local execution @echo "$(COLOUR_GREEN)Destroy containers with 'make down'$(COLOUR_NONE)" up: # Build, (re)create and start containers + export DATABASE_URL=postgres://postgres:postgres@localhost:5432/fbr docker compose up -d @echo "$(COLOUR_GREEN)Services are up - use 'make logs' to view service logs$(COLOUR_NONE)" @@ -107,10 +108,12 @@ django-shell-local: # Run a Django shell (local django instance) poetry run python manage.py shell migrate: # Run Django migrate - docker compose run --rm web poetry run python manage.py migrate --noinput + export DATABASE_URL=postgres://postgres:postgres@localhost:5432/fbr && \ + python manage.py migrate migrations: # Run Django makemigrations - docker compose run --rm web poetry run python manage.py makemigrations --noinput + export DATABASE_URL=postgres://postgres:postgres@localhost:5432/fbr && \ + python manage.py makemigrations lint: # Run all linting make black @@ -136,6 +139,7 @@ setup_local: # Set up the local environment @echo "$(COLOUR_GREEN)Running initial setup for local environment...$(COLOUR_NONE)" $(MAKE) first-use $(MAKE) start - #$(MAKE) migrate + #$(MAKE) migrations + $(MAKE) migrate #$(MAKE) rebuild_cache @echo "$(COLOUR_GREEN)Local setup complete.$(COLOUR_NONE)" diff --git a/celery_worker/tasks.py b/celery_worker/tasks.py index ecaf9d0..28d22c9 100644 --- a/celery_worker/tasks.py +++ b/celery_worker/tasks.py @@ -8,8 +8,8 @@ from app.search.utils.documents import clear_all_documents -@shared_task(bind=True) -def rebuild_cache() -> None: +@shared_task(name="celery_worker.tasks.rebuild_cache") +def rebuild_cache(): """ Rebuilds the cache for search documents across various components by clearing all existing documents. The process is timed, and the diff --git a/docker-compose.yml b/docker-compose.yml index 58b908b..4ab610b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -72,6 +72,7 @@ services: DEBUG: true DJANGO_SETTINGS_MODULE: fbr.settings RDS_POSTGRES_CREDENTIALS: '{"password":"postgres","dbname":"fbr","engine":"postgres","port":5432,"dbInstanceIdentifier":"xxx","host":"db","username":"postgres"}' + DATABASE_URL: postgres://postgres:postgres@host.docker.internal:5432/fbr # pragma: allowlist secret celery-beats: build: diff --git a/fbr/celery_app.py b/fbr/celery_app.py index 6e33420..e7e4fad 100644 --- a/fbr/celery_app.py +++ b/fbr/celery_app.py @@ -19,6 +19,6 @@ celery_app.conf.beat_schedule = { "schedule-fbr-cache-task": { "task": "celery_worker.tasks.rebuild_cache", - "schedule": crontab(hour="1", minute="0"), # Runs daily at 1:00 AM + "schedule": crontab(hour="3", minute="46"), # Runs daily at 1:00 AM }, } diff --git a/fbr/settings.py b/fbr/settings.py index 0c6db18..9a82355 100644 --- a/fbr/settings.py +++ b/fbr/settings.py @@ -60,6 +60,7 @@ "app", "app.core", "app.search", + "celery_worker", ] THIRD_PARTY_APPS: list = [ @@ -120,7 +121,7 @@ DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": SITE_ROOT / "db.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", } } From 1b7a50c53fac910fdc42a30719366dc63c7fc86b Mon Sep 17 00:00:00 2001 From: Haresh Kainth Date: Tue, 10 Dec 2024 03:57:39 +0000 Subject: [PATCH 05/12] chore:Adjust cache rebuild task timing to 1:00 AM daily. The schedule for the cache rebuild task was incorrectly documented as running at a different time than configured. This change aligns both the actual schedule and the comment to correctly indicate the task runs at 1:00 AM daily. --- fbr/celery_app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fbr/celery_app.py b/fbr/celery_app.py index e7e4fad..6e33420 100644 --- a/fbr/celery_app.py +++ b/fbr/celery_app.py @@ -19,6 +19,6 @@ celery_app.conf.beat_schedule = { "schedule-fbr-cache-task": { "task": "celery_worker.tasks.rebuild_cache", - "schedule": crontab(hour="3", minute="46"), # Runs daily at 1:00 AM + "schedule": crontab(hour="1", minute="0"), # Runs daily at 1:00 AM }, } From 5428858d9c9f27a845c0a8164da59db3836400eb Mon Sep 17 00:00:00 2001 From: Haresh Kainth Date: Tue, 10 Dec 2024 09:39:59 +0000 Subject: [PATCH 06/12] chore:Add setup_local_force_rebuild target to Makefile Introduce a new make target to allow local setup with forced cache rebuild. This ensures that the local environment is set up and caches are manually rebuilt without relying on Celery tasks. --- Makefile | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index fe56b1b..2588f53 100644 --- a/Makefile +++ b/Makefile @@ -139,7 +139,12 @@ setup_local: # Set up the local environment @echo "$(COLOUR_GREEN)Running initial setup for local environment...$(COLOUR_NONE)" $(MAKE) first-use $(MAKE) start - #$(MAKE) migrations $(MAKE) migrate - #$(MAKE) rebuild_cache @echo "$(COLOUR_GREEN)Local setup complete.$(COLOUR_NONE)" + +setup_local_force_rebuild: + @echo "$(COLOUR_GREEN)Will run initial setup, followed by cache rebuild for local environment...$(COLOUR_NONE)" + $(MAKE) setup_local + @echo "$(COLOUR_GREEN)Manual cache rebuild (not using Celery task)...$(COLOUR_NONE)" + $(MAKE) rebuild_cache + @echo "$(COLOUR_GREEN)Cache rebuilt complete.$(COLOUR_NONE)" From 36aae1fc366d07d39a1f80ab373bb44ae8738b0f Mon Sep 17 00:00:00 2001 From: Haresh Kainth Date: Tue, 10 Dec 2024 09:51:06 +0000 Subject: [PATCH 07/12] chore:simplify manage.py path across scripts. Unified the manage.py path by removing 'fbr/' prefix in scripts and configuration files, ensuring consistency across the project. Adjusted DJANGO_SETTINGS_MODULE in image_build_run.sh for a streamlined setup. These changes improve maintainability and reduce potential errors related to incorrect file paths. --- .copilot/image_build_run.sh | 4 ++-- .github/workflows/code_quality.yml | 2 +- pyproject.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.copilot/image_build_run.sh b/.copilot/image_build_run.sh index aa55b50..59f28bf 100755 --- a/.copilot/image_build_run.sh +++ b/.copilot/image_build_run.sh @@ -6,6 +6,6 @@ set -e # Add commands below to run inside the container after all the other buildpacks have been applied export BUILD_STEP='True' export COPILOT_ENVIRONMENT_NAME='build' -export DJANGO_SETTINGS_MODULE="fbr.settings.base" +export DJANGO_SETTINGS_MODULE="fbr.settings" -poetry run python fbr/manage.py collectstatic --noinput +poetry run python manage.py collectstatic --noinput diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 19f0581..059afab 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -67,7 +67,7 @@ jobs: npm run build DJANGO_SETTINGS_MODULE=fbr.settings poetry run fbr/manage.py collectstatic --noinput - # poetry run fbr/manage.py makemigrations --check --dry-run + # poetry run manage.py makemigrations --check --dry-run # - name: Run tests # run: poetry run pytest fbr/tests diff --git a/pyproject.toml b/pyproject.toml index de4ac10..ef8f1b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ filterwarnings = [ [tool.coverage.run] omit = [ - "fbr/manage.py", + "manage.py", "fbr/config/*sgi.py", "fbr/config/urls.py", "fbr/config/settings/*.py", From a98148638712e018cc95c1af13d5812d66db2e0e Mon Sep 17 00:00:00 2001 From: Haresh Kainth Date: Tue, 10 Dec 2024 10:00:18 +0000 Subject: [PATCH 08/12] chore:Update import paths in search test module Modify import paths for `create_search_query` and `SearchQuery` in `test_search.py` to reflect the correct module structure under `app/search/utils/search.py`. This ensures that test cases reference the correct location and maintain consistency with the application structure. --- app/search/tests/test_search.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/app/search/tests/test_search.py b/app/search/tests/test_search.py index fcc1161..f81d997 100644 --- a/app/search/tests/test_search.py +++ b/app/search/tests/test_search.py @@ -2,18 +2,18 @@ from unittest.mock import MagicMock, call, patch -from search.utils.search import create_search_query +from app.search.utils.search import create_search_query class TestCreateSearchQuery(unittest.TestCase): - @patch("search.utils.search.SearchQuery", autospec=True) + @patch("app.search.utils.search.SearchQuery", autospec=True) def test_single_word_query(self, mock_search_query): result = create_search_query("test") mock_search_query.assert_called_with("test", search_type="plain") self.assertEqual(result, mock_search_query.return_value) - @patch("search.utils.search.SearchQuery", autospec=True) + @patch("app.search.utils.search.SearchQuery", autospec=True) def test_implicit_and_search_operator_query(self, mock_search_query): # Mock SearchQuery instances mock_query1 = MagicMock(name="MockQuery1") @@ -35,7 +35,7 @@ def test_implicit_and_search_operator_query(self, mock_search_query): # Assert the AND operation was applied mock_query1.__and__.assert_called_once_with(mock_query2) - @patch("search.utils.search.SearchQuery", autospec=True) + @patch("app.search.utils.search.SearchQuery", autospec=True) def test_multiple_implicit_and_search_operator_query( self, mock_search_query ): @@ -61,7 +61,7 @@ def test_multiple_implicit_and_search_operator_query( # Assert the AND operation was applied mock_query1.__and__.assert_called_with(mock_query3) - @patch("search.utils.search.SearchQuery", autospec=True) + @patch("app.search.utils.search.SearchQuery", autospec=True) def test_and_search_operator_query(self, mock_search_query): # Mock SearchQuery instances mock_query1 = MagicMock(name="MockQuery1") @@ -83,7 +83,7 @@ def test_and_search_operator_query(self, mock_search_query): # Assert the AND operation was applied mock_query1.__and__.assert_called_once_with(mock_query2) - @patch("search.utils.search.SearchQuery", autospec=True) + @patch("app.search.utils.search.SearchQuery", autospec=True) def test_multiple_and_search_operator_query(self, mock_search_query): # Mock SearchQuery instances mock_query1 = MagicMock(name="MockQuery1") @@ -107,7 +107,7 @@ def test_multiple_and_search_operator_query(self, mock_search_query): # Assert the AND operation was applied mock_query1.__and__.assert_called_with(mock_query3) - @patch("search.utils.search.SearchQuery", autospec=True) + @patch("app.search.utils.search.SearchQuery", autospec=True) def test_or_search_operator_query(self, mock_search_query): # Mock SearchQuery instances mock_query1 = MagicMock(name="MockQuery1") @@ -129,7 +129,7 @@ def test_or_search_operator_query(self, mock_search_query): # Assert the AND operation was applied mock_query1.__or__.assert_called_once_with(mock_query2) - @patch("search.utils.search.SearchQuery", autospec=True) + @patch("app.search.utils.search.SearchQuery", autospec=True) def test_multple_or_search_operator_query(self, mock_search_query): # Mock SearchQuery instances mock_query1 = MagicMock(name="MockQuery1") @@ -153,7 +153,7 @@ def test_multple_or_search_operator_query(self, mock_search_query): # Assert the AND operation was applied mock_query1.__or__.assert_called_with(mock_query3) - @patch("search.utils.search.SearchQuery", autospec=True) + @patch("app.search.utils.search.SearchQuery", autospec=True) def test_multiple_or_search_operator_query(self, mock_search_query): # Mock SearchQuery instances mock_query1 = MagicMock(name="MockQuery1") @@ -177,7 +177,7 @@ def test_multiple_or_search_operator_query(self, mock_search_query): # Assert the AND operation was applied mock_query1.__or__.assert_called_with(mock_query3) - @patch("search.utils.search.SearchQuery", autospec=True) + @patch("app.search.utils.search.SearchQuery", autospec=True) def test_phrase_search_query(self, mock_search_query): result = create_search_query('"test trial"') mock_search_query.assert_called_with( @@ -185,7 +185,7 @@ def test_phrase_search_query(self, mock_search_query): ) self.assertEqual(result, mock_search_query.return_value) - @patch("search.utils.search.SearchQuery", autospec=True) + @patch("app.search.utils.search.SearchQuery", autospec=True) def test_and_multiple_single_single_phrase_search_query( self, mock_search_query ): @@ -221,7 +221,7 @@ def test_and_multiple_single_single_phrase_search_query( # Assert the AND operation was applied mock_query1.__and__.assert_called_with(mock_query5) - @patch("search.utils.search.SearchQuery", autospec=True) + @patch("app.search.utils.search.SearchQuery", autospec=True) def test_single_or_and_search_operator_query(self, mock_search_query): # Mock SearchQuery instances mock_query1 = MagicMock(name="MockQuery1") From bedff82dd94354be415034baf62c0ca0669a0310 Mon Sep 17 00:00:00 2001 From: Haresh Kainth Date: Tue, 10 Dec 2024 10:15:46 +0000 Subject: [PATCH 09/12] fix:manage.py path in collectstatic command Updated the path to `manage.py` in the `collectstatic` command within the code quality GitHub workflow to correct the file path. This ensures the command executes successfully without errors related to incorrect paths. --- .github/workflows/code_quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index 059afab..a8a701e 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -65,7 +65,7 @@ jobs: run: | npm install npm run build - DJANGO_SETTINGS_MODULE=fbr.settings poetry run fbr/manage.py collectstatic --noinput + DJANGO_SETTINGS_MODULE=fbr.settings poetry run manage.py collectstatic --noinput # poetry run manage.py makemigrations --check --dry-run From 90245197af6227b6c1312cd56151e96178ffad1e Mon Sep 17 00:00:00 2001 From: Haresh Kainth Date: Tue, 10 Dec 2024 10:29:00 +0000 Subject: [PATCH 10/12] fix:collectstatic command in CI workflow Correct the syntax for the collectstatic command by explicitly using python with manage.py. This ensures compatibility and prevents potential execution issues in the CI environment. --- .github/workflows/code_quality.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_quality.yml b/.github/workflows/code_quality.yml index a8a701e..28b5c5b 100644 --- a/.github/workflows/code_quality.yml +++ b/.github/workflows/code_quality.yml @@ -65,7 +65,7 @@ jobs: run: | npm install npm run build - DJANGO_SETTINGS_MODULE=fbr.settings poetry run manage.py collectstatic --noinput + DJANGO_SETTINGS_MODULE=fbr.settings poetry run python manage.py collectstatic --noinput # poetry run manage.py makemigrations --check --dry-run From d6050a3c9d26ba177d3233b22bd7300ac78f94a7 Mon Sep 17 00:00:00 2001 From: Haresh Kainth Date: Tue, 10 Dec 2024 10:34:58 +0000 Subject: [PATCH 11/12] chore:Handle failed URL fetches in legislation cache building Add handling for failed URL fetches by logging errors and storing them in a list instead of raising exceptions. This ensures that the process can continue attempting to build the cache while logging a comprehensive list of any failed sources. --- app/cache/legislation.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/cache/legislation.py b/app/cache/legislation.py index de2e19b..152c2ac 100644 --- a/app/cache/legislation.py +++ b/app/cache/legislation.py @@ -4,6 +4,7 @@ import logging import re +import time import xml.etree.ElementTree as ET # nosec BXXX from typing import Optional @@ -123,6 +124,8 @@ def build_cache(self, config: SearchDocumentConfig): logger.info("building legislation cache...") dataset = construction_legislation_dataframe() + failed_url_fetches = [] + # For each row, get the URL from the column named # 'URI to Extract XML Data' # and store the XML data in a list @@ -185,10 +188,15 @@ def build_cache(self, config: SearchDocumentConfig): # Insert or update the document insert_or_update_document(document_json) + + # # Sleep for a short time to avoid rate limiting + # time.sleep(0.5) except Exception as e: logger.error(f"error fetching data from {url}: {e}") - raise e + failed_url_fetches.append(url) + if failed_url_fetches: + logger.warning(f"failed to fetch data {len(failed_url_fetches)} legislation sources: {failed_url_fetches}") def _to_json( self, description, From a907c81e4ee319fb792d8e27c6c6c5781ee50042 Mon Sep 17 00:00:00 2001 From: Haresh Kainth Date: Tue, 10 Dec 2024 10:47:47 +0000 Subject: [PATCH 12/12] chore:Reduce cache timeout from 20s to 3s. Shortening the timeout improves the speed of clearing all documents and building the cache. This adjustment aims to enhance the system's responsiveness and efficiency during cache management operations. --- app/cache/manage_cache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cache/manage_cache.py b/app/cache/manage_cache.py index ece5463..2225175 100644 --- a/app/cache/manage_cache.py +++ b/app/cache/manage_cache.py @@ -20,7 +20,7 @@ def rebuild_cache(): try: start = time.time() clear_all_documents() - config = SearchDocumentConfig(search_query="", timeout=20) + config = SearchDocumentConfig(search_query="", timeout=3) Legislation().build_cache(config) PublicGateway().build_cache(config) end = time.time()