From b11c5bb9c04386cdb6c69ed9e2af267829a02748 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Tue, 27 Aug 2024 15:48:15 -0500 Subject: [PATCH 01/30] add transfer repositories --- .../commands/schedule_repository_deletions.py | 132 ++++- breathecode/assignments/models.py | 5 +- .../tests_schedule_repository_deletions.py | 450 +++++++++++++++++- breathecode/services/github.py | 44 +- 4 files changed, 613 insertions(+), 18 deletions(-) diff --git a/breathecode/assignments/management/commands/schedule_repository_deletions.py b/breathecode/assignments/management/commands/schedule_repository_deletions.py index 3ab07ed6c..080a772e5 100644 --- a/breathecode/assignments/management/commands/schedule_repository_deletions.py +++ b/breathecode/assignments/management/commands/schedule_repository_deletions.py @@ -48,8 +48,53 @@ def github(self): last_check = last.created_at self.schedule_github_deletions(settings.github_username, last_check) + self.collect_transferred_orders() + self.transfer_ownership() self.delete_github_repositories() + def check_path(self, obj: dict, *indexes: str) -> bool: + try: + value = obj + for index in indexes: + value = value[index] + return True + except KeyError: + return False + + def get_username(self, owner: str, repo: str) -> Optional[str]: + repo = repo.lower() + for events in self.github_client.get_repo_events(owner, repo): + for event in events: + if self.check_path(event, "type") is False: + continue + + if ( + event["type"] == "watchEvent" + and self.check_path(event, "actor", "login") + and event["actor"]["login"].replace("-", "").lower() in repo + ): + return event["actor"]["login"] + + if ( + event["type"] == "MemberEvent" + and self.check_path(event, "payload", "member", "login") + and event["payload"]["member"]["login"].replace("-", "").lower() in repo + ): + return event["payload"]["member"]["login"] + + if ( + event["type"] == "IssuesEvent" + and self.check_path(event, "payload", "assignee", "login") + and event["payload"]["assignee"]["login"].replace("-", "").lower() in repo + ): + return event["payload"]["assignee"]["login"] + + if ( + self.check_path(event, "actor", "login") + and event["actor"]["login"].replace("-", "").lower() in repo + ): + return event["actor"]["login"] + def purge_deletion_orders(self): page = 0 @@ -76,7 +121,7 @@ def delete_github_repositories(self): while True: qs = RepositoryDeletionOrder.objects.filter( provider=RepositoryDeletionOrder.Provider.GITHUB, - status=RepositoryDeletionOrder.Status.PENDING, + status__in=[RepositoryDeletionOrder.Status.PENDING, RepositoryDeletionOrder.Status.TRANSFERRING], created_at__lte=timezone.now() - relativedelta(months=2), )[:100] @@ -85,11 +130,23 @@ def delete_github_repositories(self): for deletion_order in qs: try: - self.github_client.delete_org_repo( + if self.github_client.repo_exists( owner=deletion_order.repository_user, repo=deletion_order.repository_name - ) - deletion_order.status = RepositoryDeletionOrder.Status.DELETED - deletion_order.save() + ): + self.github_client.delete_org_repo( + owner=deletion_order.repository_user, repo=deletion_order.repository_name + ) + deletion_order.status = RepositoryDeletionOrder.Status.DELETED + deletion_order.save() + + elif deletion_order.status == RepositoryDeletionOrder.Status.TRANSFERRING: + deletion_order.status = RepositoryDeletionOrder.Status.TRANSFERRED + deletion_order.save() + + else: + raise Exception( + f"Repository does not exist: {deletion_order.repository_user}/{deletion_order.repository_name}" + ) except Exception as e: deletion_order.status = RepositoryDeletionOrder.Status.ERROR @@ -175,3 +232,68 @@ def schedule_github_deletion(self, provider: str, user: str, repo_name: str): RepositoryDeletionOrder.objects.get_or_create( provider=provider, repository_user=user, repository_name=repo_name ) + + def collect_transferred_orders(self): + + ids = [] + + while True: + qs = RepositoryDeletionOrder.objects.filter( + provider=RepositoryDeletionOrder.Provider.GITHUB, + status=RepositoryDeletionOrder.Status.TRANSFERRING, + created_at__gt=timezone.now(), + ).exclude(id__in=ids)[:100] + + if qs.count() == 0: + break + + for deletion_order in qs: + try: + ids.append(deletion_order.id) + if ( + self.github_client.repo_exists( + owner=deletion_order.repository_user, repo=deletion_order.repository_name + ) + is False + ): + deletion_order.status = RepositoryDeletionOrder.Status.TRANSFERRED + deletion_order.save() + + except Exception as e: + deletion_order.status = RepositoryDeletionOrder.Status.ERROR + deletion_order.status_text = str(e) + deletion_order.save() + + def transfer_ownership(self): + ids = [] + + while True: + qs = RepositoryDeletionOrder.objects.filter( + provider=RepositoryDeletionOrder.Provider.GITHUB, + status=RepositoryDeletionOrder.Status.PENDING, + created_at__gt=timezone.now(), + ).exclude(id__in=ids)[:100] + + if qs.count() == 0: + break + + for deletion_order in qs: + try: + if self.github_client.repo_exists( + owner=deletion_order.repository_user, repo=deletion_order.repository_name + ): + new_owner = self.get_username(deletion_order.repository_user, deletion_order.repository_name) + if new_owner: + continue + + res = self.github_client.transfer_repo(repo=deletion_order.repository_name, new_owner=new_owner) + if res["status"] == 202: + deletion_order.status = RepositoryDeletionOrder.Status.TRANSFERRING + deletion_order.save() + + except Exception as e: + deletion_order.status = RepositoryDeletionOrder.Status.ERROR + deletion_order.status_text = str(e) + deletion_order.save() + + ids.append(deletion_order.id) diff --git a/breathecode/assignments/models.py b/breathecode/assignments/models.py index f5159a5f8..ae7b0e6af 100644 --- a/breathecode/assignments/models.py +++ b/breathecode/assignments/models.py @@ -242,6 +242,8 @@ class Status(models.TextChoices): PENDING = "PENDING", "Pending" ERROR = "ERROR", "Error" DELETED = "DELETED", "Deleted" + TRANSFERRED = "TRANSFERRED", "Transferred" + TRANSFERRING = "TRANSFERRING", "Transferring" CANCELLED = "CANCELLED", "Cancelled" provider = models.CharField(max_length=15, choices=Provider, default=Provider.GITHUB) @@ -258,9 +260,6 @@ def save(self, *args, **kwargs): self.full_clean() super().save(*args, **kwargs) - # def __str__(self): - # return f'Learnpack event {self.event} {self.status} => Student: {self.student.id}' - class RepositoryWhiteList(models.Model): Provider = Provider diff --git a/breathecode/assignments/tests/management/commands/tests_schedule_repository_deletions.py b/breathecode/assignments/tests/management/commands/tests_schedule_repository_deletions.py index 6f8fa6657..384277339 100644 --- a/breathecode/assignments/tests/management/commands/tests_schedule_repository_deletions.py +++ b/breathecode/assignments/tests/management/commands/tests_schedule_repository_deletions.py @@ -2,6 +2,7 @@ Test /answer """ +from typing import Any from unittest.mock import MagicMock import pytest @@ -19,6 +20,41 @@ def setup(db): yield +# https://api.github.com/repos/{org}/{repo}/events +class Event: + + @staticmethod + def push(login: str) -> dict[str, Any]: + return { + "type": "PushEvent", + "actor": { + "login": login, + }, + } + + @staticmethod + def member(login: str) -> dict[str, Any]: + return { + "type": "MemberEvent", + "payload": { + "member": { + "login": login, + }, + }, + } + + @staticmethod + def watch(login: str) -> dict[str, Any]: + return ( + { + "type": "WatchEvent", + "actor": { + "login": login, + }, + }, + ) + + class ResponseMock: def __init__(self, data, status=200, headers={}): @@ -137,7 +173,6 @@ def test_two_repos(database: capyc.Database, patch_get): def test_two_repos__deleting_repositories(database: capyc.Database, patch_get, set_datetime, utc_now): - from django.utils import timezone delta = relativedelta(months=2, hours=1) model = database.create( @@ -205,6 +240,20 @@ def test_two_repos__deleting_repositories(database: capyc.Database, patch_get, s "code": 204, "headers": {}, }, + { + "method": "HEAD", + "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", + "expected": None, + "code": 200, + "headers": {}, + }, + { + "method": "HEAD", + "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", + "expected": None, + "code": 200, + "headers": {}, + }, ] ) command = Command() @@ -231,8 +280,373 @@ def test_two_repos__deleting_repositories(database: capyc.Database, patch_get, s assert database.list_of("assignments.RepositoryWhiteList") == [] +def test_two_repos__repository_transferred(database: capyc.Database, patch_get, set_datetime, utc_now): + + delta = relativedelta(months=2, hours=1) + model = database.create( + academy_auth_settings=1, + city=1, + country=1, + user=1, + credentials_github=1, + repository_deletion_order=[ + { + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": "breatheco-de", + "status": "TRANSFERRING", + "status_text": None, + }, + { + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": "4GeeksAcademy", + "status": "TRANSFERRING", + "status_text": None, + }, + ], + ) + set_datetime(utc_now + delta) + + patch_get( + [ + { + "method": "GET", + "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/repos?page=1&type=forks&per_page=30&sort=created&direction=desc", + "expected": [ + { + "private": False, + "html_url": "https://github.com/breatheco-de/curso-nodejs-4geeks", + "fork": True, + "created_at": "2024-04-05T19:22:39Z", + "is_template": False, + "allow_forking": True, + }, + { + "private": False, + "html_url": "https://github.com/4GeeksAcademy/curso-nodejs-4geeks", + "fork": True, + "created_at": "2024-04-05T19:22:39Z", + "is_template": False, + "allow_forking": True, + }, + ], + "code": 200, + "headers": {}, + }, + { + "method": "DELETE", + "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", + "expected": None, + "code": 204, + "headers": {}, + }, + { + "method": "DELETE", + "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", + "expected": None, + "code": 204, + "headers": {}, + }, + { + "method": "HEAD", + "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", + "expected": None, + "code": 404, + "headers": {}, + }, + { + "method": "HEAD", + "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", + "expected": None, + "code": 404, + "headers": {}, + }, + { + "method": "POST", + "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks/transfer", + "expected": None, + "code": 202, + "headers": {}, + }, + { + "method": "POST", + "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks/transfer", + "expected": None, + "code": 202, + "headers": {}, + }, + ] + ) + command = Command() + command.handle() + + assert database.list_of("assignments.RepositoryDeletionOrder") == [ + { + "id": 1, + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": "breatheco-de", + "status": "TRANSFERRED", + "status_text": None, + }, + { + "id": 2, + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": "4GeeksAcademy", + "status": "TRANSFERRED", + "status_text": None, + }, + ] + assert database.list_of("assignments.RepositoryWhiteList") == [] + + +def test_two_repos__repository_does_not_exists(database: capyc.Database, patch_get, set_datetime, utc_now): + + delta = relativedelta(months=2, hours=1) + model = database.create( + academy_auth_settings=1, + city=1, + country=1, + user=1, + credentials_github=1, + repository_deletion_order=[ + { + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": "breatheco-de", + "status": "PENDING", + "status_text": None, + }, + { + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": "4GeeksAcademy", + "status": "PENDING", + "status_text": None, + }, + ], + ) + set_datetime(utc_now + delta) + + patch_get( + [ + { + "method": "GET", + "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/repos?page=1&type=forks&per_page=30&sort=created&direction=desc", + "expected": [ + { + "private": False, + "html_url": "https://github.com/breatheco-de/curso-nodejs-4geeks", + "fork": True, + "created_at": "2024-04-05T19:22:39Z", + "is_template": False, + "allow_forking": True, + }, + { + "private": False, + "html_url": "https://github.com/4GeeksAcademy/curso-nodejs-4geeks", + "fork": True, + "created_at": "2024-04-05T19:22:39Z", + "is_template": False, + "allow_forking": True, + }, + ], + "code": 200, + "headers": {}, + }, + { + "method": "DELETE", + "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", + "expected": None, + "code": 204, + "headers": {}, + }, + { + "method": "DELETE", + "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", + "expected": None, + "code": 204, + "headers": {}, + }, + { + "method": "HEAD", + "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", + "expected": None, + "code": 404, + "headers": {}, + }, + { + "method": "HEAD", + "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", + "expected": None, + "code": 404, + "headers": {}, + }, + { + "method": "POST", + "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks/transfer", + "expected": None, + "code": 202, + "headers": {}, + }, + { + "method": "POST", + "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks/transfer", + "expected": None, + "code": 202, + "headers": {}, + }, + ] + ) + command = Command() + command.handle() + + assert database.list_of("assignments.RepositoryDeletionOrder") == [ + { + "id": 1, + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": "breatheco-de", + "status": "ERROR", + "status_text": "Repository does not exist: breatheco-de/curso-nodejs-4geeks", + }, + { + "id": 2, + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": "4GeeksAcademy", + "status": "ERROR", + "status_text": "Repository does not exist: 4GeeksAcademy/curso-nodejs-4geeks", + }, + ] + assert database.list_of("assignments.RepositoryWhiteList") == [] + + +def test_two_repos__repository_does_not_exists____(database: capyc.Database, patch_get, set_datetime, utc_now): + + delta = relativedelta(months=2, hours=1) + model = database.create( + academy_auth_settings=1, + city=1, + country=1, + user=1, + credentials_github=1, + repository_deletion_order=[ + { + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": "breatheco-de", + "status": "PENDING", + "status_text": None, + }, + { + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": "4GeeksAcademy", + "status": "PENDING", + "status_text": None, + }, + ], + ) + set_datetime(utc_now - delta) + + patch_get( + [ + { + "method": "GET", + "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/repos?page=1&type=forks&per_page=30&sort=created&direction=desc", + "expected": [ + { + "private": False, + "html_url": "https://github.com/breatheco-de/curso-nodejs-4geeks", + "fork": True, + "created_at": "2024-04-05T19:22:39Z", + "is_template": False, + "allow_forking": True, + }, + { + "private": False, + "html_url": "https://github.com/4GeeksAcademy/curso-nodejs-4geeks", + "fork": True, + "created_at": "2024-04-05T19:22:39Z", + "is_template": False, + "allow_forking": True, + }, + ], + "code": 200, + "headers": {}, + }, + { + "method": "DELETE", + "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", + "expected": None, + "code": 204, + "headers": {}, + }, + { + "method": "DELETE", + "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", + "expected": None, + "code": 204, + "headers": {}, + }, + { + "method": "HEAD", + "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", + "expected": None, + "code": 200, + "headers": {}, + }, + { + "method": "HEAD", + "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", + "expected": None, + "code": 200, + "headers": {}, + }, + { + "method": "POST", + "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks/transfer", + "expected": None, + "code": 202, + "headers": {}, + }, + { + "method": "POST", + "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks/transfer", + "expected": None, + "code": 202, + "headers": {}, + }, + ] + ) + command = Command() + command.handle() + + assert database.list_of("assignments.RepositoryDeletionOrder") == [ + { + "id": 1, + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": "breatheco-de", + "status": "ERROR", + "status_text": "Repository does not exist: breatheco-de/curso-nodejs-4geeks", + }, + { + "id": 2, + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": "4GeeksAcademy", + "status": "ERROR", + "status_text": "Repository does not exist: 4GeeksAcademy/curso-nodejs-4geeks", + }, + ] + assert database.list_of("assignments.RepositoryWhiteList") == [] + + def test_two_repos__deleting_repositories__got_an_error(database: capyc.Database, patch_get, set_datetime, utc_now): - from django.utils import timezone delta = relativedelta(months=2, hours=1) model = database.create( @@ -286,6 +700,20 @@ def test_two_repos__deleting_repositories__got_an_error(database: capyc.Database "code": 200, "headers": {}, }, + { + "method": "HEAD", + "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", + "expected": None, + "code": 404, + "headers": {}, + }, + { + "method": "HEAD", + "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", + "expected": None, + "code": 404, + "headers": {}, + }, ] ) command = Command() @@ -298,7 +726,7 @@ def test_two_repos__deleting_repositories__got_an_error(database: capyc.Database "repository_name": "curso-nodejs-4geeks", "repository_user": "breatheco-de", "status": "ERROR", - "status_text": "Unable to communicate with Github API for /repos/breatheco-de/curso-nodejs-4geeks, error: 404", + "status_text": "Repository does not exist: breatheco-de/curso-nodejs-4geeks", }, { "id": 2, @@ -306,7 +734,7 @@ def test_two_repos__deleting_repositories__got_an_error(database: capyc.Database "repository_name": "curso-nodejs-4geeks", "repository_user": "4GeeksAcademy", "status": "ERROR", - "status_text": "Unable to communicate with Github API for /repos/4GeeksAcademy/curso-nodejs-4geeks, error: 404", + "status_text": "Repository does not exist: 4GeeksAcademy/curso-nodejs-4geeks", }, ] assert database.list_of("assignments.RepositoryWhiteList") == [] @@ -792,6 +1220,20 @@ def test_two_repos_scheduled_and_in_this_execution_was_added_to_the_assets( "code": 200, "headers": {}, }, + { + "method": "GET", + "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks/events?page=1&per_page=30", + "expected": [], + "code": 200, + "headers": {}, + }, + { + "method": "GET", + "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks/events?page=2&per_page=30", + "expected": [], + "code": 200, + "headers": {}, + }, ] ) command = Command() diff --git a/breathecode/services/github.py b/breathecode/services/github.py index 10adbff2e..baffc0d8f 100644 --- a/breathecode/services/github.py +++ b/breathecode/services/github.py @@ -1,6 +1,6 @@ import logging import os -from typing import Generator +from typing import Generator, Optional import requests @@ -85,7 +85,9 @@ def _call(self, method_name, action_name, params=None, json=None): except Exception: pass - raise Exception(f"Unable to communicate with Github API for {action_name}, error: {error_message}") + raise Exception( + f"Unable to communicate with Github API for {method_name} {action_name}, error: {error_message}" + ) def get_machines_types(self, repo_name): return self.get(f"/repos/{self.org}/{repo_name}/codespaces/machines") @@ -163,10 +165,6 @@ def get_org_repos( page = 0 while True: page += 1 - - if page == 2: - break - res = self.get( f"/orgs/{organization}/repos?page={page}&type={type}&per_page={per_page}&sort={sort}&direction={direction}" ) @@ -179,3 +177,37 @@ def get_org_repos( def delete_org_repo(self, owner: str, repo: str): res = self.delete(f"/repos/{owner}/{repo}") return res + + def transfer_repo(self, repo: str, new_owner: str, new_name: Optional[str] = None): + if new_name is None: + new_name = repo + return self.post( + f"/repos/{self.org}/{repo}/transfer", + request_data={"new_owner": new_owner, "new_name": new_name}, + ) + + def repo_exists(self, owner: str, repo: str): + try: + response = self.head(f"/repos/{owner}/{repo}") + return response.status_code == 200 + except Exception: + return False + + def get_repo_events( + self, + owner: str, + repo: str, + per_page: int = 30, + ) -> Generator[list[dict], None, None]: + if per_page > 100: + raise Exception("per_page cannot be greater than 100") + + page = 0 + while True: + page += 1 + res = self.get(f"/orgs/{owner}/{repo}/events?page={page}&per_page={per_page}") + + if len(res) == 0: + break + + yield res From b46a962c3462c7f7de7f3fee25ab376892ba94e3 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Wed, 28 Aug 2024 21:37:33 -0500 Subject: [PATCH 02/30] add tests --- .../commands/schedule_repository_deletions.py | 44 +- .../tests_schedule_repository_deletions.py | 428 ++++++++++++------ breathecode/services/github.py | 11 +- 3 files changed, 327 insertions(+), 156 deletions(-) diff --git a/breathecode/assignments/management/commands/schedule_repository_deletions.py b/breathecode/assignments/management/commands/schedule_repository_deletions.py index 080a772e5..2b0e33bd3 100644 --- a/breathecode/assignments/management/commands/schedule_repository_deletions.py +++ b/breathecode/assignments/management/commands/schedule_repository_deletions.py @@ -1,6 +1,6 @@ import re from datetime import datetime -from typing import Optional +from typing import Any, Optional from dateutil import parser from dateutil.relativedelta import relativedelta @@ -58,16 +58,42 @@ def check_path(self, obj: dict, *indexes: str) -> bool: for index in indexes: value = value[index] return True - except KeyError: + except Exception: return False + def how_many_added_members(self, events: list[dict[str, Any]]) -> int: + return len( + [ + event + for event in events + if self.check_path(event, "type") + and self.check_path(event, "payload", "action") + and event["type"] == "MemberEvent" + and event["payload"]["action"] == "added" + ] + ) + def get_username(self, owner: str, repo: str) -> Optional[str]: + r = repo repo = repo.lower() - for events in self.github_client.get_repo_events(owner, repo): + index = -1 + for events in self.github_client.get_repo_events(owner, r): + index += 1 for event in events: if self.check_path(event, "type") is False: continue + if ( + index == 0 + and event["type"] == "MemberEvent" + and len(events) < 30 + and self.check_path(event, "payload", "action") + and self.how_many_added_members(events) == 1 + and self.check_path(event, "payload", "member", "login") + and event["payload"]["action"] == "added" + ): + return event["payload"]["member"]["login"] + if ( event["type"] == "watchEvent" and self.check_path(event, "actor", "login") @@ -278,22 +304,20 @@ def transfer_ownership(self): break for deletion_order in qs: + ids.append(deletion_order.id) try: if self.github_client.repo_exists( owner=deletion_order.repository_user, repo=deletion_order.repository_name ): new_owner = self.get_username(deletion_order.repository_user, deletion_order.repository_name) - if new_owner: + if not new_owner: continue - res = self.github_client.transfer_repo(repo=deletion_order.repository_name, new_owner=new_owner) - if res["status"] == 202: - deletion_order.status = RepositoryDeletionOrder.Status.TRANSFERRING - deletion_order.save() + self.github_client.transfer_repo(repo=deletion_order.repository_name, new_owner=new_owner) + deletion_order.status = RepositoryDeletionOrder.Status.TRANSFERRING + deletion_order.save() except Exception as e: deletion_order.status = RepositoryDeletionOrder.Status.ERROR deletion_order.status_text = str(e) deletion_order.save() - - ids.append(deletion_order.id) diff --git a/breathecode/assignments/tests/management/commands/tests_schedule_repository_deletions.py b/breathecode/assignments/tests/management/commands/tests_schedule_repository_deletions.py index 384277339..729945d4f 100644 --- a/breathecode/assignments/tests/management/commands/tests_schedule_repository_deletions.py +++ b/breathecode/assignments/tests/management/commands/tests_schedule_repository_deletions.py @@ -33,26 +33,25 @@ def push(login: str) -> dict[str, Any]: } @staticmethod - def member(login: str) -> dict[str, Any]: + def member(login: str, action: str = "added") -> dict[str, Any]: return { "type": "MemberEvent", "payload": { "member": { "login": login, }, + "action": action, }, } @staticmethod def watch(login: str) -> dict[str, Any]: - return ( - { - "type": "WatchEvent", - "actor": { - "login": login, - }, + return { + "type": "WatchEvent", + "actor": { + "login": login, }, - ) + } class ResponseMock: @@ -334,48 +333,6 @@ def test_two_repos__repository_transferred(database: capyc.Database, patch_get, "code": 200, "headers": {}, }, - { - "method": "DELETE", - "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", - "expected": None, - "code": 204, - "headers": {}, - }, - { - "method": "DELETE", - "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", - "expected": None, - "code": 204, - "headers": {}, - }, - { - "method": "HEAD", - "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", - "expected": None, - "code": 404, - "headers": {}, - }, - { - "method": "HEAD", - "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", - "expected": None, - "code": 404, - "headers": {}, - }, - { - "method": "POST", - "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks/transfer", - "expected": None, - "code": 202, - "headers": {}, - }, - { - "method": "POST", - "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks/transfer", - "expected": None, - "code": 202, - "headers": {}, - }, ] ) command = Command() @@ -456,48 +413,6 @@ def test_two_repos__repository_does_not_exists(database: capyc.Database, patch_g "code": 200, "headers": {}, }, - { - "method": "DELETE", - "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", - "expected": None, - "code": 204, - "headers": {}, - }, - { - "method": "DELETE", - "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", - "expected": None, - "code": 204, - "headers": {}, - }, - { - "method": "HEAD", - "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", - "expected": None, - "code": 404, - "headers": {}, - }, - { - "method": "HEAD", - "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", - "expected": None, - "code": 404, - "headers": {}, - }, - { - "method": "POST", - "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks/transfer", - "expected": None, - "code": 202, - "headers": {}, - }, - { - "method": "POST", - "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks/transfer", - "expected": None, - "code": 202, - "headers": {}, - }, ] ) command = Command() @@ -524,11 +439,12 @@ def test_two_repos__repository_does_not_exists(database: capyc.Database, patch_g assert database.list_of("assignments.RepositoryWhiteList") == [] -def test_two_repos__repository_does_not_exists____(database: capyc.Database, patch_get, set_datetime, utc_now): +def test_one_repo__pending__user_not_found(database: capyc.Database, patch_get, set_datetime, utc_now, fake): delta = relativedelta(months=2, hours=1) + github_username = fake.slug() model = database.create( - academy_auth_settings=1, + academy_auth_settings={"github_username": github_username}, city=1, country=1, user=1, @@ -537,14 +453,7 @@ def test_two_repos__repository_does_not_exists____(database: capyc.Database, pat { "provider": "GITHUB", "repository_name": "curso-nodejs-4geeks", - "repository_user": "breatheco-de", - "status": "PENDING", - "status_text": None, - }, - { - "provider": "GITHUB", - "repository_name": "curso-nodejs-4geeks", - "repository_user": "4GeeksAcademy", + "repository_user": github_username, "status": "PENDING", "status_text": None, }, @@ -560,15 +469,81 @@ def test_two_repos__repository_does_not_exists____(database: capyc.Database, pat "expected": [ { "private": False, - "html_url": "https://github.com/breatheco-de/curso-nodejs-4geeks", + "html_url": f"https://github.com/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks", "fork": True, "created_at": "2024-04-05T19:22:39Z", "is_template": False, "allow_forking": True, }, + ], + "code": 200, + "headers": {}, + }, + { + "method": "GET", + "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks/events?page=1&per_page=30", + "expected": [], + "code": 200, + "headers": {}, + }, + ] + ) + command = Command() + command.handle() + + assert database.list_of("assignments.RepositoryDeletionOrder") == [ + { + "id": 1, + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": github_username, + "status": "PENDING", + "status_text": None, + }, + ] + assert database.list_of("assignments.RepositoryWhiteList") == [] + + +@pytest.mark.parametrize( + "event", + [ + Event.push("my-user"), + Event.member("my-user"), + Event.watch("my-user"), + ], +) +def test_one_repo__pending__user_found(database: capyc.Database, patch_get, set_datetime, utc_now, event): + + delta = relativedelta(months=2, hours=1) + github_username = "my-user" + parsed_name = github_username.replace("-", "") + model = database.create( + academy_auth_settings={"github_username": github_username}, + city=1, + country=1, + user=1, + credentials_github=1, + repository_deletion_order=[ + { + "provider": "GITHUB", + "repository_name": f"curso-nodejs-4geeks-{parsed_name}", + "repository_user": github_username, + "status": "PENDING", + "status_text": None, + }, + ], + ) + set_datetime(utc_now - delta) + + patch_get( + [ + { + "method": "GET", + "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/repos?page=1&type=forks&per_page=30&sort=created&direction=desc", + "expected": [ { "private": False, - "html_url": "https://github.com/4GeeksAcademy/curso-nodejs-4geeks", + "html_url": f"https://github.com/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks-{parsed_name}", "fork": True, "created_at": "2024-04-05T19:22:39Z", "is_template": False, @@ -579,45 +554,172 @@ def test_two_repos__repository_does_not_exists____(database: capyc.Database, pat "headers": {}, }, { - "method": "DELETE", - "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", + "method": "HEAD", + "url": f"https://api.github.com/repos/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks-{parsed_name}", "expected": None, - "code": 204, + "code": 200, "headers": {}, }, { - "method": "DELETE", - "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", - "expected": None, - "code": 204, + "method": "POST", + "url": f"https://api.github.com/repos/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks-{parsed_name}/transfer", + "expected": {}, + "code": 202, "headers": {}, }, { - "method": "HEAD", - "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks", - "expected": None, + "method": "GET", + "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks-{parsed_name}/events?page=1&per_page=30", + "expected": [event], + "code": 200, + "headers": {}, + }, + ] + ) + command = Command() + command.handle() + + assert database.list_of("assignments.RepositoryDeletionOrder") == [ + { + "id": 1, + "provider": "GITHUB", + "repository_name": f"curso-nodejs-4geeks-{parsed_name}", + "repository_user": github_username, + "status": "TRANSFERRING", + "status_text": None, + }, + ] + assert database.list_of("assignments.RepositoryWhiteList") == [] + + +def test_one_repo__pending__user_found__inferred(database: capyc.Database, patch_get, set_datetime, utc_now): + event = Event.member("my-user") + + delta = relativedelta(months=2, hours=1) + github_username = "my-user" + parsed_name = github_username.replace("-", "") + model = database.create( + academy_auth_settings={"github_username": github_username}, + city=1, + country=1, + user=1, + credentials_github=1, + repository_deletion_order=[ + { + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": github_username, + "status": "PENDING", + "status_text": None, + }, + ], + ) + set_datetime(utc_now - delta) + + patch_get( + [ + { + "method": "GET", + "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/repos?page=1&type=forks&per_page=30&sort=created&direction=desc", + "expected": [ + { + "private": False, + "html_url": f"https://github.com/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks", + "fork": True, + "created_at": "2024-04-05T19:22:39Z", + "is_template": False, + "allow_forking": True, + }, + ], "code": 200, "headers": {}, }, { "method": "HEAD", - "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks", + "url": f"https://api.github.com/repos/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks", "expected": None, "code": 200, "headers": {}, }, { "method": "POST", - "url": "https://api.github.com/repos/breatheco-de/curso-nodejs-4geeks/transfer", - "expected": None, + "url": f"https://api.github.com/repos/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks/transfer", + "expected": {}, "code": 202, "headers": {}, }, { - "method": "POST", - "url": "https://api.github.com/repos/4GeeksAcademy/curso-nodejs-4geeks/transfer", + "method": "GET", + "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks/events?page=1&per_page=30", + "expected": [event], + "code": 200, + "headers": {}, + }, + ] + ) + command = Command() + command.handle() + + assert database.list_of("assignments.RepositoryDeletionOrder") == [ + { + "id": 1, + "provider": "GITHUB", + "repository_name": f"curso-nodejs-4geeks", + "repository_user": github_username, + "status": "TRANSFERRING", + "status_text": None, + }, + ] + assert database.list_of("assignments.RepositoryWhiteList") == [] + + +def test_one_repo__transferring__repo_found(database: capyc.Database, patch_get, set_datetime, utc_now): + event = Event.member("my-user") + + delta = relativedelta(months=2, hours=1) + github_username = "my-user" + parsed_name = github_username.replace("-", "") + model = database.create( + academy_auth_settings={"github_username": github_username}, + city=1, + country=1, + user=1, + credentials_github=1, + repository_deletion_order=[ + { + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": github_username, + "status": "TRANSFERRING", + "status_text": None, + }, + ], + ) + set_datetime(utc_now - delta) + + patch_get( + [ + { + "method": "GET", + "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/repos?page=1&type=forks&per_page=30&sort=created&direction=desc", + "expected": [ + { + "private": False, + "html_url": f"https://github.com/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks", + "fork": True, + "created_at": "2024-04-05T19:22:39Z", + "is_template": False, + "allow_forking": True, + }, + ], + "code": 200, + "headers": {}, + }, + { + "method": "HEAD", + "url": f"https://api.github.com/repos/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks", "expected": None, - "code": 202, + "code": 200, "headers": {}, }, ] @@ -630,17 +732,75 @@ def test_two_repos__repository_does_not_exists____(database: capyc.Database, pat "id": 1, "provider": "GITHUB", "repository_name": "curso-nodejs-4geeks", - "repository_user": "breatheco-de", - "status": "ERROR", - "status_text": "Repository does not exist: breatheco-de/curso-nodejs-4geeks", + "repository_user": github_username, + "status": "TRANSFERRING", + "status_text": None, }, + ] + assert database.list_of("assignments.RepositoryWhiteList") == [] + + +def test_one_repo__transferring__repo_not_found(database: capyc.Database, patch_get, set_datetime, utc_now): + event = Event.member("my-user") + + delta = relativedelta(months=2, hours=1) + github_username = "my-user" + model = database.create( + academy_auth_settings={"github_username": github_username}, + city=1, + country=1, + user=1, + credentials_github=1, + repository_deletion_order=[ + { + "provider": "GITHUB", + "repository_name": "curso-nodejs-4geeks", + "repository_user": github_username, + "status": "TRANSFERRING", + "status_text": None, + }, + ], + ) + set_datetime(utc_now - delta) + + patch_get( + [ + { + "method": "GET", + "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/repos?page=1&type=forks&per_page=30&sort=created&direction=desc", + "expected": [ + { + "private": False, + "html_url": f"https://github.com/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks", + "fork": True, + "created_at": "2024-04-05T19:22:39Z", + "is_template": False, + "allow_forking": True, + }, + ], + "code": 200, + "headers": {}, + }, + { + "method": "HEAD", + "url": f"https://api.github.com/repos/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks", + "expected": None, + "code": 404, + "headers": {}, + }, + ] + ) + command = Command() + command.handle() + + assert database.list_of("assignments.RepositoryDeletionOrder") == [ { - "id": 2, + "id": 1, "provider": "GITHUB", "repository_name": "curso-nodejs-4geeks", - "repository_user": "4GeeksAcademy", - "status": "ERROR", - "status_text": "Repository does not exist: 4GeeksAcademy/curso-nodejs-4geeks", + "repository_user": github_username, + "status": "TRANSFERRED", + "status_text": None, }, ] assert database.list_of("assignments.RepositoryWhiteList") == [] @@ -1220,20 +1380,6 @@ def test_two_repos_scheduled_and_in_this_execution_was_added_to_the_assets( "code": 200, "headers": {}, }, - { - "method": "GET", - "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks/events?page=1&per_page=30", - "expected": [], - "code": 200, - "headers": {}, - }, - { - "method": "GET", - "url": f"https://api.github.com/orgs/{model.academy_auth_settings.github_username}/curso-nodejs-4geeks/events?page=2&per_page=30", - "expected": [], - "code": 200, - "headers": {}, - }, ] ) command = Command() diff --git a/breathecode/services/github.py b/breathecode/services/github.py index baffc0d8f..0f6e6199f 100644 --- a/breathecode/services/github.py +++ b/breathecode/services/github.py @@ -57,11 +57,6 @@ def _call(self, method_name, action_name, params=None, json=None): "Authorization": "Bearer " + self.token, "Content-type": "application/json", } - if method_name in ["GET", "DELETE"]: - params = { - # 'token': self.token, - **params, - } url = self.HOST + action_name resp = requests.request(method=method_name, url=url, headers=self.headers, params=params, json=json, timeout=2) @@ -174,6 +169,9 @@ def get_org_repos( yield res + if len(res) < per_page: + break + def delete_org_repo(self, owner: str, repo: str): res = self.delete(f"/repos/{owner}/{repo}") return res @@ -211,3 +209,6 @@ def get_repo_events( break yield res + + if len(res) < per_page: + break From 970f8cc55c660eeb70604c9862e53d9c114d3117 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Sat, 7 Sep 2024 19:37:56 -0500 Subject: [PATCH 03/30] schedule deletions for student tasks --- breathecode/admissions/receivers.py | 28 +++++++ .../tests_schedule_repository_deletion.py | 80 +++++++++++++++++++ .../commands/schedule_repository_deletions.py | 28 +++++-- breathecode/assignments/models.py | 63 +++++++++------ .../tasks/tests_student_task_notification.py | 20 ++--- .../tasks/tests_sync_cohort_user_tasks.py | 16 ++++ .../tasks/tests_teacher_task_notification.py | 10 +-- breathecode/middlewares.py | 23 ++++++ breathecode/settings.py | 1 + 9 files changed, 224 insertions(+), 45 deletions(-) create mode 100644 breathecode/admissions/tests/receivers/tests_schedule_repository_deletion.py diff --git a/breathecode/admissions/receivers.py b/breathecode/admissions/receivers.py index c36398c0b..1c81977a0 100644 --- a/breathecode/admissions/receivers.py +++ b/breathecode/admissions/receivers.py @@ -1,5 +1,6 @@ # from django.db.models.signals import post_save import logging +import re from typing import Any, Type from django.dispatch import receiver @@ -15,6 +16,7 @@ # add your receives here logger = logging.getLogger(__name__) +GITHUB_URL_PATTERN = re.compile(r"https?:\/\/github\.com\/(?P[^\/]+)\/(?P[^\/\s]+)\/?") @receiver(cohort_log_saved, sender=Cohort) @@ -43,6 +45,32 @@ async def new_cohort_user(sender: Type[Cohort], instance: Cohort, **kwargs: Any) ) +@receiver(revision_status_updated, sender=Task, weak=False) +def schedule_repository_deletion(sender: Type[Task], instance: Task, **kwargs: Any): + logger.info("Scheduling repository deletion for task: " + str(instance.id)) + + if instance.revision_status != Task.RevisionStatus.PENDING and instance.github_url: + match = GITHUB_URL_PATTERN.match(instance.github_url) + if match: + user = match.group("user") + repo = match.group("repo") + from breathecode.assignments.models import RepositoryDeletionOrder + + order, created = RepositoryDeletionOrder.objects.get_or_create( + provider=RepositoryDeletionOrder.Provider.GITHUB, + repository_user=user, + repository_name=repo, + defaults={"status": RepositoryDeletionOrder.Status.PENDING}, + ) + + if not created and order.status in [ + RepositoryDeletionOrder.Status.NO_STARTED, + RepositoryDeletionOrder.Status.ERROR, + ]: + order.status = RepositoryDeletionOrder.Status.PENDING + order.save() + + @receiver(revision_status_updated, sender=Task, weak=False) def mark_saas_student_as_graduated(sender: Type[Task], instance: Task, **kwargs: Any): logger.info("Processing available as saas student's tasks and marking as GRADUATED if it is") diff --git a/breathecode/admissions/tests/receivers/tests_schedule_repository_deletion.py b/breathecode/admissions/tests/receivers/tests_schedule_repository_deletion.py new file mode 100644 index 000000000..4e877e1c0 --- /dev/null +++ b/breathecode/admissions/tests/receivers/tests_schedule_repository_deletion.py @@ -0,0 +1,80 @@ +import random + +import pytest + +from breathecode.tests.mixins.breathecode_mixin.breathecode import Breathecode +import capyc.pytest as capy + + +@pytest.mark.parametrize( + "revision_status, github_url", + [ + ("PENDING", None), + ("PENDING", ""), + ("PENDING", "https://github.com/breathecode-test/test"), + ("REJECTED", None), + ("REJECTED", ""), + ("IGNORED", None), + ("IGNORED", ""), + ("APPROVED", None), + ("APPROVED", ""), + ], +) +def test_nothing_happens( + database: capy.Database, format: capy.Format, signals: capy.Signals, revision_status: str, github_url: str +): + signals.enable("breathecode.assignments.signals.revision_status_updated") + + model = database.create(task={"revision_status": "PENDING", "github_url": github_url}) + + if revision_status != "PENDING": + model.task.revision_status = revision_status + model.task.save() + + assert database.list_of("assignments.Task") == [ + format.to_obj_repr(model.task), + ] + + assert database.list_of("assignments.RepositoryDeletionOrder") == [] + + +@pytest.mark.parametrize("revision_status", ["REJECTED", "IGNORED", "APPROVED"]) +@pytest.mark.parametrize( + "github_url, username, repo", + [ + ("https://github.com/user1/repo1", "user1", "repo1"), + ("https://github.com/user2/repo2", "user2", "repo2"), + ("https://github.com/user3/repo3", "user3", "repo3"), + ], +) +def test_schedule_repository_deletion( + database: capy.Database, + format: capy.Format, + signals: capy.Signals, + revision_status: str, + github_url: str, + username: str, + repo: str, +): + signals.enable("breathecode.assignments.signals.revision_status_updated") + + model = database.create(task={"revision_status": "PENDING", "github_url": github_url}) + + if revision_status != "PENDING": + model.task.revision_status = revision_status + model.task.save() + + assert database.list_of("assignments.Task") == [ + format.to_obj_repr(model.task), + ] + + assert database.list_of("assignments.RepositoryDeletionOrder") == [ + { + "id": 1, + "provider": "GITHUB", + "repository_name": repo, + "repository_user": username, + "status": "PENDING", + "status_text": None, + }, + ] diff --git a/breathecode/assignments/management/commands/schedule_repository_deletions.py b/breathecode/assignments/management/commands/schedule_repository_deletions.py index 2b0e33bd3..bc9586107 100644 --- a/breathecode/assignments/management/commands/schedule_repository_deletions.py +++ b/breathecode/assignments/management/commands/schedule_repository_deletions.py @@ -7,7 +7,7 @@ from django.core.management.base import BaseCommand from django.utils import timezone -from breathecode.assignments.models import RepositoryDeletionOrder, RepositoryWhiteList +from breathecode.assignments.models import RepositoryDeletionOrder, RepositoryWhiteList, Task from breathecode.authenticate.models import AcademyAuthSettings from breathecode.monitoring.models import RepositorySubscription from breathecode.registry.models import Asset @@ -16,7 +16,7 @@ class Command(BaseCommand): help = "Clean data from marketing module" - github_url_pattern = re.compile(r"https:\/\/github\.com\/(?P[^\/]+)\/(?P[^\/\s]+)\/?") + github_url_pattern = re.compile(r"https?:\/\/github\.com\/(?P[^\/]+)\/(?P[^\/\s]+)\/?") def handle(self, *args, **options): self.fill_whitelist() @@ -124,6 +124,7 @@ def get_username(self, owner: str, repo: str) -> Optional[str]: def purge_deletion_orders(self): page = 0 + to_delete = [] while True: qs = RepositoryDeletionOrder.objects.filter( status=RepositoryDeletionOrder.Status.PENDING, @@ -138,10 +139,12 @@ def purge_deletion_orders(self): repository_user__iexact=deletion_order.repository_user, repository_name__iexact=deletion_order.repository_name, ).exists(): - deletion_order.delete() + to_delete.append(deletion_order.id) page += 1 + RepositoryDeletionOrder.objects.filter(id__in=to_delete).delete() + def delete_github_repositories(self): while True: @@ -255,10 +258,25 @@ def schedule_github_deletion(self, provider: str, user: str, repo_name: str): ).exists(): return - RepositoryDeletionOrder.objects.get_or_create( - provider=provider, repository_user=user, repository_name=repo_name + status = RepositoryDeletionOrder.Status.PENDING + if ( + Task.objects.filter(github_url__icontains=f"github.com/{user}/{repo_name}") + .exclude(revision_status=Task.RevisionStatus.PENDING) + .exists() + ): + status = RepositoryDeletionOrder.Status.NO_STARTED + + order, _ = RepositoryDeletionOrder.objects.get_or_create( + provider=provider, + repository_user=user, + repository_name=repo_name, + defaults={"status": status}, ) + if order.status != status: + order.status = status + order.save() + def collect_transferred_orders(self): ids = [] diff --git a/breathecode/assignments/models.py b/breathecode/assignments/models.py index ae7b0e6af..531eda733 100644 --- a/breathecode/assignments/models.py +++ b/breathecode/assignments/models.py @@ -39,20 +39,7 @@ class AssignmentTelemetry(models.Model): PENDING = "PENDING" DONE = "DONE" -TASK_STATUS = ( - (PENDING, "Pending"), - (DONE, "Done"), -) -APPROVED = "APPROVED" -REJECTED = "REJECTED" -IGNORED = "IGNORED" -REVISION_STATUS = ( - (PENDING, "Pending"), - (APPROVED, "Approved"), - (REJECTED, "Rejected"), - (IGNORED, "Ignored"), -) PROJECT = "PROJECT" QUIZ = "QUIZ" @@ -66,8 +53,29 @@ class AssignmentTelemetry(models.Model): ) +class TaskStatus(models.TextChoices): + PENDING = "PENDING", "Pending" + DONE = "DONE", "Done" + + +class RevisionStatus(models.TextChoices): + PENDING = "PENDING", "Pending" + APPROVED = "APPROVED", "Approved" + REJECTED = "REJECTED", "Rejected" + IGNORED = "IGNORED", "Ignored" + + # Create your models here. class Task(models.Model): + TaskStatus = TaskStatus + RevisionStatus = RevisionStatus + + class TaskType(models.TextChoices): + PROJECT = "PROJECT", "project" + QUIZ = "QUIZ", "quiz" + LESSON = "LESSON", "lesson" + EXERCISE = "EXERCISE", "Exercise" + _current_task_status = None _current_revision_status = None @@ -87,9 +95,11 @@ class Task(models.Model): rigobot_repository_id = models.IntegerField(null=True, blank=True, default=None, db_index=True) - task_status = models.CharField(max_length=15, choices=TASK_STATUS, default=PENDING, db_index=True) - revision_status = models.CharField(max_length=15, choices=REVISION_STATUS, default=PENDING, db_index=True) - task_type = models.CharField(max_length=15, choices=TASK_TYPE, db_index=True) + task_status = models.CharField(max_length=15, choices=TaskStatus, default=TaskStatus.PENDING, db_index=True) + revision_status = models.CharField( + max_length=15, choices=RevisionStatus, default=RevisionStatus.PENDING, db_index=True + ) + task_type = models.CharField(max_length=15, choices=TaskType, db_index=True) github_url = models.CharField(max_length=150, blank=True, default=None, null=True) live_url = models.CharField(max_length=150, blank=True, default=None, null=True) description = models.TextField(max_length=450, blank=True) @@ -125,16 +135,15 @@ def save(self, *args, **kwargs): creating = not self.pk super().save(*args, **kwargs) - if not creating and self.task_status != self._current_task_status: - signals.assignment_status_updated.send_robust(instance=self, sender=self.__class__) + signals.assignment_status_updated.delay(instance=self, sender=self.__class__) if not creating and self.revision_status != self._current_revision_status: - signals.revision_status_updated.send_robust(instance=self, sender=self.__class__) + signals.revision_status_updated.delay(instance=self, sender=self.__class__) # only validate this on creation if creating: - signals.assignment_created.send_robust(instance=self, sender=self.__class__) + signals.assignment_created.delay(instance=self, sender=self.__class__) self._current_task_status = self.task_status self._current_revision_status = self.revision_status @@ -163,6 +172,8 @@ class Meta: class FinalProject(models.Model): + TaskStatus = TaskStatus + repo_owner = models.ForeignKey( User, on_delete=models.SET_NULL, blank=True, null=True, related_name="projects_owned" ) @@ -173,12 +184,15 @@ class FinalProject(models.Model): members = models.ManyToManyField(User, related_name="final_projects") project_status = models.CharField( - max_length=15, choices=TASK_STATUS, default=PENDING, help_text="Done projects will be reviewed for publication" + max_length=15, + choices=TaskStatus, + default=TaskStatus.PENDING, + help_text="Done projects will be reviewed for publication", ) revision_status = models.CharField( max_length=15, - choices=REVISION_STATUS, - default=PENDING, + choices=RevisionStatus, + default=RevisionStatus.PENDING, help_text="Only approved projects will display on the feature projects list", ) revision_message = models.TextField(null=True, blank=True, default=None) @@ -203,8 +217,6 @@ class FinalProject(models.Model): updated_at = models.DateTimeField(auto_now=True, editable=False) -# PENDING = 'PENDING' -# DONE = 'DONE' ERROR = "ERROR" LEARNPACK_WEBHOOK_STATUS = ( (PENDING, "Pending"), @@ -243,6 +255,7 @@ class Status(models.TextChoices): ERROR = "ERROR", "Error" DELETED = "DELETED", "Deleted" TRANSFERRED = "TRANSFERRED", "Transferred" + NO_STARTED = "NO_STARTED", "No started" TRANSFERRING = "TRANSFERRING", "Transferring" CANCELLED = "CANCELLED", "Cancelled" diff --git a/breathecode/assignments/tests/tasks/tests_student_task_notification.py b/breathecode/assignments/tests/tasks/tests_student_task_notification.py index 8b6b6ef58..5ec80286a 100644 --- a/breathecode/assignments/tests/tasks/tests_student_task_notification.py +++ b/breathecode/assignments/tests/tasks/tests_student_task_notification.py @@ -32,7 +32,7 @@ def test_student_task_notification__without_tasks(self): self.assertEqual(send_email_message.call_args_list, []) self.assertEqual(Logger.info.call_args_list, [call("Starting student_task_notification")]) self.assertEqual(Logger.error.call_args_list, [call("Task not found")]) - self.assertEqual(signals.assignment_created.send_robust.call_args_list, []) + self.assertEqual(signals.assignment_created.delay.call_args_list, []) """ 🔽🔽🔽 With Task and Cohort revision_status PENDING @@ -74,7 +74,7 @@ def test_student_task_notification__pending__with_task__with_cohort(self): self.assertEqual(Logger.info.call_args_list, [call("Starting student_task_notification")]) self.assertEqual(Logger.error.call_args_list, []) self.assertEqual( - signals.assignment_created.send_robust.call_args_list, + signals.assignment_created.delay.call_args_list, [call(instance=model.task, sender=model.task.__class__)], ) @@ -114,7 +114,7 @@ def test_student_task_notification__with_task__pending__with_cohort__url_ends_wi self.assertEqual(Logger.info.call_args_list, [call("Starting student_task_notification")]) self.assertEqual(Logger.error.call_args_list, []) self.assertEqual( - signals.assignment_created.send_robust.call_args_list, + signals.assignment_created.delay.call_args_list, [call(instance=model.task, sender=model.task.__class__)], ) @@ -155,7 +155,7 @@ def test_student_task_notification__with_task__pending__with_cohort__lang_es(sel self.assertEqual(Logger.info.call_args_list, [call("Starting student_task_notification")]) self.assertEqual(Logger.error.call_args_list, []) self.assertEqual( - signals.assignment_created.send_robust.call_args_list, + signals.assignment_created.delay.call_args_list, [call(instance=model.task, sender=model.task.__class__)], ) @@ -199,7 +199,7 @@ def test_student_task_notification__approved__with_task__with_cohort(self): self.assertEqual(Logger.info.call_args_list, [call("Starting student_task_notification")]) self.assertEqual(Logger.error.call_args_list, []) self.assertEqual( - signals.assignment_created.send_robust.call_args_list, + signals.assignment_created.delay.call_args_list, [call(instance=model.task, sender=model.task.__class__)], ) @@ -239,7 +239,7 @@ def test_student_task_notification__with_task__approved__with_cohort__url_ends_w self.assertEqual(Logger.info.call_args_list, [call("Starting student_task_notification")]) self.assertEqual(Logger.error.call_args_list, []) self.assertEqual( - signals.assignment_created.send_robust.call_args_list, + signals.assignment_created.delay.call_args_list, [call(instance=model.task, sender=model.task.__class__)], ) @@ -280,7 +280,7 @@ def test_student_task_notification__with_task__approved__with_cohort__lang_es(se self.assertEqual(Logger.info.call_args_list, [call("Starting student_task_notification")]) self.assertEqual(Logger.error.call_args_list, []) self.assertEqual( - signals.assignment_created.send_robust.call_args_list, + signals.assignment_created.delay.call_args_list, [call(instance=model.task, sender=model.task.__class__)], ) @@ -324,7 +324,7 @@ def test_student_task_notification__rejected__with_task__with_cohort(self): self.assertEqual(Logger.info.call_args_list, [call("Starting student_task_notification")]) self.assertEqual(Logger.error.call_args_list, []) self.assertEqual( - signals.assignment_created.send_robust.call_args_list, + signals.assignment_created.delay.call_args_list, [call(instance=model.task, sender=model.task.__class__)], ) @@ -364,7 +364,7 @@ def test_student_task_notification__with_task__rejected__with_cohort__url_ends_w self.assertEqual(str(Logger.info.call_args_list), str([call("Starting student_task_notification")])) self.assertEqual(Logger.error.call_args_list, []) self.assertEqual( - signals.assignment_created.send_robust.call_args_list, + signals.assignment_created.delay.call_args_list, [call(instance=model.task, sender=model.task.__class__)], ) @@ -405,6 +405,6 @@ def test_student_task_notification__with_task__rejected__with_cohort__lang_es(se self.assertEqual(Logger.info.call_args_list, [call("Starting student_task_notification")]) self.assertEqual(Logger.error.call_args_list, []) self.assertEqual( - signals.assignment_created.send_robust.call_args_list, + signals.assignment_created.delay.call_args_list, [call(instance=model.task, sender=model.task.__class__)], ) diff --git a/breathecode/assignments/tests/tasks/tests_sync_cohort_user_tasks.py b/breathecode/assignments/tests/tasks/tests_sync_cohort_user_tasks.py index 81bb08132..5be0a15f5 100644 --- a/breathecode/assignments/tests/tasks/tests_sync_cohort_user_tasks.py +++ b/breathecode/assignments/tests/tasks/tests_sync_cohort_user_tasks.py @@ -2,11 +2,18 @@ Test sync_cohort_user_tasks """ +import pytest from unittest.mock import MagicMock, call, patch from ...tasks import sync_cohort_user_tasks from ..mixins import AssignmentsTestCase +from breathecode.assignments.signals import assignment_created + + +@pytest.fixture(autouse=True) +def setup(db, monkeypatch: pytest.MonkeyPatch): + monkeypatch.setattr(assignment_created, "delay", MagicMock()) def serialize_task(data={}): @@ -48,6 +55,7 @@ def test__sync_cohort_user_not_found(self): self.assertEqual(self.bc.database.list_of("assignments.Task"), []) self.assertEqual(Logger.info.call_args_list, [call("Executing sync_cohort_user_tasks for cohort user 1")]) self.assertEqual(Logger.error.call_args_list, [call("Cohort user not found")]) + assert assignment_created.delay.call_args_list == [] @patch("logging.Logger.info", MagicMock()) def test__sync_cohort_user(self): @@ -111,6 +119,10 @@ def test__sync_cohort_user(self): call("Cohort User 1 synced successfully"), ], ) + assert assignment_created.delay.call_args_list == [ + call(instance=task, sender=task.__class__) + for task in self.bc.database.list_of("assignments.Task", dict=False) + ] @patch("logging.Logger.info", MagicMock()) def test__sync_cohort_user_with_previous_tasks(self): @@ -178,3 +190,7 @@ def test__sync_cohort_user_with_previous_tasks(self): call("Cohort User 1 synced successfully"), ], ) + assert assignment_created.delay.call_args_list == [ + call(instance=task, sender=task.__class__) + for task in self.bc.database.list_of("assignments.Task", dict=False) + ] diff --git a/breathecode/assignments/tests/tasks/tests_teacher_task_notification.py b/breathecode/assignments/tests/tasks/tests_teacher_task_notification.py index aeda89747..fb1b2052b 100644 --- a/breathecode/assignments/tests/tasks/tests_teacher_task_notification.py +++ b/breathecode/assignments/tests/tasks/tests_teacher_task_notification.py @@ -35,7 +35,7 @@ def test_teacher_task_notification__without_env(self): self.assertEqual(os.getenv.call_args_list, [call("TEACHER_URL")]) self.assertEqual(Logger.info.call_args_list, [call("Starting teacher_task_notification")]) self.assertEqual(Logger.error.call_args_list, [call("TEACHER_URL is not set as environment variable")]) - self.assertEqual(signals.assignment_created.send_robust.call_args_list, []) + self.assertEqual(signals.assignment_created.delay.call_args_list, []) """ 🔽🔽🔽 Without Task @@ -61,7 +61,7 @@ def test_teacher_task_notification__without_tasks(self): self.assertEqual(os.getenv.call_args_list, [call("TEACHER_URL")]) self.assertEqual(Logger.info.call_args_list, [call("Starting teacher_task_notification")]) self.assertEqual(Logger.error.call_args_list, [call("Task not found")]) - self.assertEqual(signals.assignment_created.send_robust.call_args_list, []) + self.assertEqual(signals.assignment_created.delay.call_args_list, []) """ 🔽🔽🔽 With Task and Cohort @@ -112,7 +112,7 @@ def test_teacher_task_notification__with_task__with_cohort(self): ) self.assertEqual(Logger.error.call_args_list, []) self.assertEqual( - signals.assignment_created.send_robust.call_args_list, + signals.assignment_created.delay.call_args_list, [call(instance=model.task, sender=model.task.__class__)], ) @@ -168,7 +168,7 @@ def test_teacher_task_notification__with_task__with_cohort__lang_es(self): self.assertEqual(Logger.info.call_args_list, [call("Starting teacher_task_notification")]) self.assertEqual(Logger.error.call_args_list, []) self.assertEqual( - signals.assignment_created.send_robust.call_args_list, + signals.assignment_created.delay.call_args_list, [call(instance=model.task, sender=model.task.__class__)], ) @@ -223,6 +223,6 @@ def test_teacher_task_notification__with_task__with_cohort__ends_with_slash(self self.assertEqual(Logger.info.call_args_list, [call("Starting teacher_task_notification")]) self.assertEqual(Logger.error.call_args_list, []) self.assertEqual( - signals.assignment_created.send_robust.call_args_list, + signals.assignment_created.delay.call_args_list, [call(instance=model.task, sender=model.task.__class__)], ) diff --git a/breathecode/middlewares.py b/breathecode/middlewares.py index 33c77bf76..c1e8309b5 100644 --- a/breathecode/middlewares.py +++ b/breathecode/middlewares.py @@ -125,3 +125,26 @@ def middleware(request): return response return middleware + + +@sync_and_async_middleware +def set_service_header_middleware(get_response): + def set_header(response): + if "Service" not in response.headers: + response.headers["Service"] = "BreatheCode" + + if iscoroutinefunction(get_response): + + async def middleware(request): + response = await get_response(request) + set_header(response) + return response + + else: + + def middleware(request): + response = get_response(request) + set_header(response) + return response + + return middleware diff --git a/breathecode/settings.py b/breathecode/settings.py index 6e02a9999..4e5f02bda 100644 --- a/breathecode/settings.py +++ b/breathecode/settings.py @@ -118,6 +118,7 @@ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "breathecode.middlewares.static_redirect_middleware", + "breathecode.middlewares.set_service_header_middleware", "django.contrib.sessions.middleware.SessionMiddleware", "corsheaders.middleware.CorsMiddleware", # Cache From 5dfc99129da3cade7e6d781ef30d0daba4b6f9e1 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Sat, 7 Sep 2024 19:40:06 -0500 Subject: [PATCH 04/30] add migration --- .../0018_repositorydeletionorder_repositorywhitelist.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/breathecode/assignments/migrations/0018_repositorydeletionorder_repositorywhitelist.py b/breathecode/assignments/migrations/0018_repositorydeletionorder_repositorywhitelist.py index 25e09d159..b67001874 100644 --- a/breathecode/assignments/migrations/0018_repositorydeletionorder_repositorywhitelist.py +++ b/breathecode/assignments/migrations/0018_repositorydeletionorder_repositorywhitelist.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.6 on 2024-07-02 08:25 +# Generated by Django 5.1.1 on 2024-09-08 00:40 from django.db import migrations, models @@ -22,6 +22,9 @@ class Migration(migrations.Migration): ("PENDING", "Pending"), ("ERROR", "Error"), ("DELETED", "Deleted"), + ("TRANSFERRED", "Transferred"), + ("NO_STARTED", "No started"), + ("TRANSFERRING", "Transferring"), ("CANCELLED", "Cancelled"), ], default="PENDING", From 73f5cfdf6db310e7461206d77fa894c6419dd424 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Tue, 10 Sep 2024 11:41:09 -0500 Subject: [PATCH 05/30] test google auth --- Pipfile | 4 + Pipfile.lock | 1198 +++++++++++------ .../tests/urls/tests_google_callback.py | 176 +++ .../tests/urls/tests_google_token.py | 88 ++ breathecode/authenticate/views.py | 28 +- conftest.py | 1 + docs/infrastructure/journal.md | 6 + scripts/dyno/web.sh | 4 +- staging/core/__init__.py | 0 staging/core/managers.py | 44 + staging/pytest/__init__.py | 1 + staging/pytest/core/__init__.py | 1 + staging/pytest/core/fixtures/__init__.py | 1 + staging/pytest/core/fixtures/http.py | 23 + 14 files changed, 1177 insertions(+), 398 deletions(-) create mode 100644 breathecode/authenticate/tests/urls/tests_google_callback.py create mode 100644 breathecode/authenticate/tests/urls/tests_google_token.py create mode 100644 staging/core/__init__.py create mode 100644 staging/core/managers.py create mode 100644 staging/pytest/__init__.py create mode 100644 staging/pytest/core/__init__.py create mode 100644 staging/pytest/core/fixtures/__init__.py create mode 100644 staging/pytest/core/fixtures/http.py diff --git a/Pipfile b/Pipfile index 48a28bda7..5ab420910 100644 --- a/Pipfile +++ b/Pipfile @@ -57,6 +57,8 @@ google-auth-httplib2 = "*" google-auth-oauthlib = "*" black = "*" capy-core = {extras = ["pytest"], version = "*"} +aioresponses = "*" +requests-mock = "*" [packages] django = "*" @@ -147,3 +149,5 @@ google-auth-httplib2 = "*" google-auth-oauthlib = "*" capy-core = {extras = ["django"], version = "*"} google-api-python-client = "*" +python-dotenv = "*" +uvicorn-worker = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 7fd43d681..3f7ea01db 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "5ab16307b216465a63886089f3095dcb4fba23930609a2496bde8904e46838a0" + "sha256": "4c728c6cf7d320b7bb6f8447c1711c37d8251b36d0ebae50a55448c5613396c1" }, "pipfile-spec": 6, "requires": {}, @@ -379,11 +379,11 @@ "django" ], "hashes": [ - "sha256:3b644975414d8d6380f4456b8abae3884fa3d57c3552609f7ac7afe6361af10c", - "sha256:bc254beabfc787d8d49e590e7a55b21c6a9420c86de6756d7aa00e977c0219d2" + "sha256:6fd5b17cda53adf9ec3829ada084e91091ea293cad93d2295c2a6ddde8bacaa1", + "sha256:ea70dd183a3a999f329ab7ff52e3b65f442f3a418b8acec81367f57f36a58012" ], "markers": "python_version >= '3.11'", - "version": "==1.0.1" + "version": "==1.0.2" }, "celery": { "hashes": [ @@ -1084,12 +1084,12 @@ }, "google-api-python-client": { "hashes": [ - "sha256:6a75441f9078e6e2fcdf4946a153fda1e2cc81b5e9c8d6e8c0750c85c7f8a566", - "sha256:d5654134522b9b574b82234e96f7e0aeeabcbf33643fbabcd449ef0068e3a476" + "sha256:f9c333ac4454a012adca90c297f9a22611a8953f3aae5481f90b3a56b9bdd413", + "sha256:fe00851b257157bca600e1692ed8a54762c4a5c7d9eb7f6d4822059424b0d0a9" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.143.0" + "version": "==2.144.0" }, "google-apps-meet": { "hashes": [ @@ -1343,7 +1343,7 @@ "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da", "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33" ], - "markers": "python_version >= '3' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", + "markers": "python_version >= '3.7'", "version": "==3.0.3" }, "grpcio": { @@ -2041,11 +2041,11 @@ }, "more-itertools": { "hashes": [ - "sha256:0f7d9f83a0a8dcfa8a2694a770590d98a67ea943e3d9f5298309a484758c4e27", - "sha256:fe0e63c4ab068eac62410ab05cccca2dc71ec44ba8ef29916a0090df061cf923" + "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef", + "sha256:5482bfef7849c25dc3c6dd53a6173ae4795da2a41a80faea6700d9f5846c5da6" ], "markers": "python_version >= '3.8'", - "version": "==10.4.0" + "version": "==10.5.0" }, "msgpack": { "hashes": [ @@ -2111,99 +2111,101 @@ }, "multidict": { "hashes": [ - "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556", - "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c", - "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29", - "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b", - "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8", - "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7", - "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd", - "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40", - "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6", - "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3", - "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c", - "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9", - "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5", - "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae", - "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442", - "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9", - "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc", - "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c", - "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea", - "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5", - "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50", - "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182", - "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453", - "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e", - "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600", - "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733", - "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda", - "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241", - "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461", - "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e", - "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e", - "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b", - "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e", - "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7", - "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386", - "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd", - "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9", - "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf", - "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee", - "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5", - "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a", - "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271", - "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54", - "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4", - "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496", - "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb", - "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319", - "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3", - "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f", - "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527", - "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed", - "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604", - "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef", - "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8", - "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5", - "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5", - "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626", - "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c", - "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d", - "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c", - "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc", - "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc", - "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b", - "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38", - "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450", - "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1", - "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f", - "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3", - "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755", - "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226", - "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a", - "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046", - "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf", - "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479", - "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e", - "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1", - "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a", - "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83", - "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929", - "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93", - "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a", - "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c", - "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44", - "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89", - "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba", - "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e", - "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da", - "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24", - "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423", - "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef" + "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", + "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056", + "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", + "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", + "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", + "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", + "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748", + "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", + "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f", + "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", + "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6", + "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada", + "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", + "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2", + "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d", + "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", + "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef", + "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", + "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", + "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60", + "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6", + "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", + "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478", + "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", + "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7", + "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56", + "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", + "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", + "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30", + "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", + "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", + "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0", + "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", + "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c", + "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", + "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", + "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", + "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", + "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", + "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2", + "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", + "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", + "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", + "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", + "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657", + "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581", + "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492", + "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43", + "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", + "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", + "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", + "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057", + "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc", + "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", + "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255", + "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1", + "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972", + "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53", + "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1", + "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", + "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a", + "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160", + "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c", + "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd", + "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", + "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5", + "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", + "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", + "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", + "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", + "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4", + "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", + "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", + "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28", + "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d", + "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a", + "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", + "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", + "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429", + "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", + "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", + "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", + "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392", + "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167", + "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c", + "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", + "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", + "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76", + "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875", + "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd", + "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", + "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db" ], - "markers": "python_version >= '3.7'", - "version": "==6.0.5" + "markers": "python_version >= '3.8'", + "version": "==6.1.0" }, "nbclient": { "hashes": [ @@ -2336,12 +2338,12 @@ }, "openai": { "hashes": [ - "sha256:1a748c2728edd3a738a72a0212ba866f4fdbe39c9ae03813508b267d45104abe", - "sha256:e607aff9fc3e28eade107e5edd8ca95a910a4b12589336d3cbb6bfe2ac306b3c" + "sha256:07e2c2758d1c94151c740b14dab638ba0d04bcb41a2e397045c90e7661cdf741", + "sha256:e0ffdab601118329ea7529e684b606a72c6c9d4f05be9ee1116255fcf5593874" ], "index": "pypi", "markers": "python_full_version >= '3.7.1'", - "version": "==1.43.0" + "version": "==1.44.1" }, "packaging": { "hashes": [ @@ -2397,10 +2399,10 @@ }, "phonenumberslite": { "hashes": [ - "sha256:63d650f2fe464602c8b01b7e0cda6b7057811a2410cbd1d21d90374642abdc51", - "sha256:ab6aa6e5fa115c89ea63e3c316c764ff449f8ecb826499cfc925f6a39f29e174" + "sha256:d0028e76664e4c702db7e08352863f903042563bb0b97db9fce9291ed2c5e5d9", + "sha256:f393d3a90a800ae1796c4c9a844a433bdefc18b97a5fb8c10976c973517e37b8" ], - "version": "==8.13.44" + "version": "==8.13.45" }, "pillow": { "hashes": [ @@ -2500,11 +2502,11 @@ }, "platformdirs": { "hashes": [ - "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", - "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3" + "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c", + "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617" ], "markers": "python_version >= '3.8'", - "version": "==4.2.2" + "version": "==4.3.2" }, "pluggy": { "hashes": [ @@ -2788,106 +2790,106 @@ }, "pydantic": { "hashes": [ - "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a", - "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8" + "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2", + "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612" ], "markers": "python_version >= '3.8'", - "version": "==2.8.2" + "version": "==2.9.1" }, "pydantic-core": { "hashes": [ - "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d", - "sha256:04024d270cf63f586ad41fff13fde4311c4fc13ea74676962c876d9577bcc78f", - "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686", - "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482", - "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006", - "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83", - "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6", - "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88", - "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86", - "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a", - "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6", - "sha256:225b67a1f6d602de0ce7f6c1c3ae89a4aa25d3de9be857999e9124f15dab486a", - "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6", - "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6", - "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43", - "sha256:26ab812fa0c845df815e506be30337e2df27e88399b985d0bb4e3ecfe72df31c", - "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4", - "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e", - "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203", - "sha256:2aafc5a503855ea5885559eae883978c9b6d8c8993d67766ee73d82e841300dd", - "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1", - "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24", - "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc", - "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc", - "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3", - "sha256:3c5ebac750d9d5f2706654c638c041635c385596caf68f81342011ddfa1e5598", - "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98", - "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331", - "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2", - "sha256:41e81317dd6a0127cabce83c0c9c3fbecceae981c8391e6f1dec88a77c8a569a", - "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6", - "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688", - "sha256:4745f4ac52cc6686390c40eaa01d48b18997cb130833154801a442323cc78f91", - "sha256:4868f6bd7c9d98904b748a2653031fc9c2f85b6237009d475b1008bfaeb0a5aa", - "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b", - "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0", - "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840", - "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c", - "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd", - "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3", - "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231", - "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1", - "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953", - "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250", - "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a", - "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2", - "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20", - "sha256:6b507132dcfc0dea440cce23ee2182c0ce7aba7054576efc65634f080dbe9434", - "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab", - "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703", - "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a", - "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2", - "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac", - "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611", - "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121", - "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e", - "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b", - "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09", - "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906", - "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9", - "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7", - "sha256:a8ad4c766d3f33ba8fd692f9aa297c9058970530a32c728a2c4bfd2616d3358b", - "sha256:aa2f457b4af386254372dfa78a2eda2563680d982422641a85f271c859df1987", - "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c", - "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b", - "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e", - "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237", - "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1", - "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19", - "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b", - "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad", - "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0", - "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94", - "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312", - "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f", - "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669", - "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1", - "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe", - "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99", - "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a", - "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a", - "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52", - "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c", - "sha256:eaad4ff2de1c3823fddf82f41121bdf453d922e9a238642b1dedb33c4e4f98ad", - "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1", - "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a", - "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f", - "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a", - "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27" + "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801", + "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec", + "sha256:03795b9e8a5d7fda05f3873efc3f59105e2dcff14231680296b87b80bb327295", + "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba", + "sha256:04b07490bc2f6f2717b10c3969e1b830f5720b632f8ae2f3b8b1542394c47a8e", + "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e", + "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4", + "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211", + "sha256:13dd45ba2561603681a2676ca56006d6dee94493f03d5cadc055d2055615c3ea", + "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c", + "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835", + "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d", + "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c", + "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c", + "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61", + "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83", + "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb", + "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1", + "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5", + "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690", + "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b", + "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7", + "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70", + "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a", + "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8", + "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd", + "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee", + "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1", + "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab", + "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958", + "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5", + "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b", + "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961", + "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c", + "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25", + "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4", + "sha256:6cb968da9a0746a0cf521b2b5ef25fc5a0bee9b9a1a8214e0a1cfaea5be7e8a4", + "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f", + "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326", + "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab", + "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8", + "sha256:7e6f33503c5495059148cc486867e1d24ca35df5fc064686e631e314d959ad5b", + "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6", + "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8", + "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01", + "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc", + "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d", + "sha256:8b2682038e255e94baf2c473dca914a7460069171ff5cdd4080be18ab8a7fd6e", + "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b", + "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855", + "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700", + "sha256:94f85614f2cba13f62c3c6481716e4adeae48e1eaa7e8bac379b9d177d93947a", + "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa", + "sha256:a0d90e08b2727c5d01af1b5ef4121d2f0c99fbee692c762f4d9d0409c9da6541", + "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791", + "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162", + "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611", + "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef", + "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe", + "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5", + "sha256:c483dab0f14b8d3f0df0c6c18d70b21b086f74c87ab03c59250dbf6d3c89baba", + "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28", + "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa", + "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27", + "sha256:cbaaf2ef20d282659093913da9d402108203f7cb5955020bd8d1ae5a2325d1c4", + "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b", + "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2", + "sha256:d063c6b9fed7d992bcbebfc9133f4c24b7a7f215d6b102f3e082b1117cddb72c", + "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8", + "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb", + "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c", + "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e", + "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305", + "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8", + "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4", + "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433", + "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45", + "sha256:edbefe079a520c5984e30e1f1f29325054b59534729c25b874a16a5048028d16", + "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed", + "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0", + "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d", + "sha256:f4a57db8966b3a1d1a350012839c6a0099f0898c56512dfade8a1fe5fb278710", + "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48", + "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423", + "sha256:fb539d7e5dc4aac345846f290cf504d2fd3c1be26ac4e8b5e4c2b688069ff4cf", + "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9", + "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63", + "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5", + "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb" ], "markers": "python_version >= '3.8'", - "version": "==2.20.1" + "version": "==2.23.3" }, "pyfcm": { "hashes": [ @@ -2990,12 +2992,12 @@ }, "pytest-env": { "hashes": [ - "sha256:aada77e6d09fcfb04540a6e462c58533c37df35fa853da78707b17ec04d17dfc", - "sha256:fcd7dc23bb71efd3d35632bde1bbe5ee8c8dc4489d6617fb010674880d96216b" + "sha256:86653658da8f11c6844975db955746c458a9c09f1e64957603161e2ff93f5133", + "sha256:a4212056d4d440febef311a98fdca56c31256d58fb453d103cba4e8a532b721d" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.1.3" + "version": "==1.1.4" }, "python-dateutil": { "hashes": [ @@ -3005,6 +3007,15 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.9.0.post0" }, + "python-dotenv": { + "hashes": [ + "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", + "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.0.1" + }, "python-frontmatter": { "hashes": [ "sha256:335465556358d9d0e6c98bbeb69b1c969f2a4a21360587b9873bfc3b213407c1", @@ -3391,11 +3402,11 @@ }, "setuptools": { "hashes": [ - "sha256:2353af060c06388be1cecbf5953dcdb1f38362f87a2356c480b6b4d5fcfc8847", - "sha256:fc91b5f89e392ef5b77fe143b17e32f65d3024744fba66dc3afe07201684d766" + "sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308", + "sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6" ], "markers": "python_version >= '3.8'", - "version": "==74.1.1" + "version": "==74.1.2" }, "six": { "hashes": [ @@ -3423,54 +3434,54 @@ }, "sqlalchemy": { "hashes": [ - "sha256:0465b8a68f8f4de754c1966c45b187ac784ad97bc9747736f913130f0e1adea0", - "sha256:07ba54f09033d387ae9df8d62cbe211ed7304e0bfbece1f8c55e21db9fae5c11", - "sha256:122d7b5722df1a24402c6748bbb04687ef981493bb559d0cc0beffe722e0e6ed", - "sha256:13fc34b35d8ddb3fbe3f8fcfdf6c2546e676187f0fb20f5774da362ddaf8fa2d", - "sha256:16bb9fa4d00b4581b14d9f0e2224dc7745b854aa4687738279af0f48f7056c98", - "sha256:197065b91456574d70b6459bfa62bc0b52a4960a29ef923c375ec427274a3e05", - "sha256:1a38834b4c183c33daf58544281395aad2e985f0b47cca1e88ea5ada88344e63", - "sha256:1a96aa8d425047551676b0e178ddb0683421e78eda879ab55775128b2e612cae", - "sha256:2774c24c405136c3ef472e2352bdca7330659d481fbf2283f996c0ef9eb90f22", - "sha256:421306c4b936b0271a3ce2dc074928d5ece4a36f9c482daa5770f44ecfc3a883", - "sha256:437592b341a3229dd0443c9c803b0bf0a466f8f539014fef6cdb9c06b7edb7f9", - "sha256:4604d42b2abccba266d3f5bbe883684b5df93e74054024c70d3fbb5eea45e530", - "sha256:4e10ac36f0b994235c13388b39598bf27219ec8bdea5be99bdac612b01cbe525", - "sha256:4fe5168d0249c23f537950b6d75935ff2709365a113e29938a979aec36668ecf", - "sha256:5e6ab710c4c064755fd92d1a417bef360228a19bdf0eee32b03aa0f5f8e9fe0d", - "sha256:5f67b9e9dcac3241781e96575468d55a42332157dee04bdbf781df573dff5f85", - "sha256:616492f5315128a847f293a7c552f3561ac7e996d2aa5dc46bef4fb0d3781f1d", - "sha256:626be971ff89541cfd3e70b54be00b57a7f8557204decb6223ce0428fec058f3", - "sha256:670c7769bf5dcae9aff331247b5d82fe635c63731088a46ce68ba2ba519ef36e", - "sha256:68a614765197b3d13a730d631a78c3bb9b3b72ba58ed7ab295d58d517464e315", - "sha256:6dd06572872ca13ef5a90306a3e5af787498ddaa17fb00109b1243642646cd69", - "sha256:784272ceb5eb71421fea9568749bcbe8bd019261a0e2e710a7efa76057af2499", - "sha256:83a9c3514ff19d9d30d8a8d378b24cd1dfa5528d20891481cb5f196117db6a48", - "sha256:86b11640251f9a9789fd96cd6e5d176b1c230230c70ad40299bcbcc568451b4c", - "sha256:89d8ac4158ef68eea8bb0f6dd0583127d9aa8720606964ba8eee20b254f9c83a", - "sha256:8b8608d162d3bd29d807aab32c3fb6e2f8e225a43d1c54c917fed38513785380", - "sha256:93e90aa3e3b2f8e8cbae4d5509f8e0cf82972378d323c740a8df1c1e9f484172", - "sha256:95123f3a1e0e8020848fd32ba751db889a01a44e4e4fef7e58c87ddd0b2fca59", - "sha256:991e42fdfec561ebc6a4fae7161a86d129d6069fa14210b96b8dd752afa7059c", - "sha256:9d7368df54d3ed45a18955f6cec38ebe075290594ac0d5c87a8ddaff7e10de27", - "sha256:a8c2f2a0b2c4e3b86eb58c9b6bb98548205eea2fba9dae4edfd29dc6aebbe95a", - "sha256:a9d4d132198844bd6828047135ce7b887687c92925049a2468a605fc775c7a1a", - "sha256:b61ac5457d91b5629a3dea2b258deb4cdd35ac8f6fa2031d2b9b2fff5b3396da", - "sha256:bc8be4df55e8fde3006d9cb1f6b3df2ba26db613855dc4df2c0fcd5ec15cb3b7", - "sha256:c05fe05941424c2f3747a8952381b7725e24cba2ca00141380e54789d5b616b6", - "sha256:c0cf8c0af9563892c6632f7343bc393dfce6eeef8e4d10c5fadba9c0390520bd", - "sha256:c15d1f1fcf1f9bec0499ae1d9132b950fcc7730f2d26d10484c8808b4e077816", - "sha256:c58e011e9e6373b3a091d83f20601fb335a3b4bace80bfcb914ac168aad3b70d", - "sha256:cd534c716f86bdf95b7b984a34ee278c91d1b1d7d183e7e5ff878600b1696046", - "sha256:d021699b9007deb7aa715629078830c99a5fec2753d9bdd5ff33290d363ef755", - "sha256:d13d4dfbc6e52363886b47cf02cf68c5d2a37c468626694dc210d7e97d4ad330", - "sha256:eaaeedbceb4dfd688fff2faf25a9a87a391f548811494f7bff7fa701b639abc3", - "sha256:edf094a20a386ff2ec73de65ef18014b250259cb860edc61741e240ca22d6981", - "sha256:fb8e15dfa47f5de11ab073e12aadd6b502cfb7ac4bafd18bd18cfd1c7d13dbbc" + "sha256:02d2ecb9508f16ab9c5af466dfe5a88e26adf2e1a8d1c56eb616396ccae2c186", + "sha256:0b76bbb1cbae618d10679be8966f6d66c94f301cfc15cb49e2f2382563fb6efb", + "sha256:0de620f978ca273ce027769dc8db7e6ee72631796187adc8471b3c76091b809e", + "sha256:1183599e25fa38a1a322294b949da02b4f0da13dbc2688ef9dbe746df573f8a6", + "sha256:12bc0141b245918b80d9d17eca94663dbd3f5266ac77a0be60750f36102bbb0f", + "sha256:1390ca2d301a2708fd4425c6d75528d22f26b8f5cbc9faba1ddca136671432bc", + "sha256:13e91d6892b5fcb94a36ba061fb7a1f03d0185ed9d8a77c84ba389e5bb05e936", + "sha256:14b3f4783275339170984cadda66e3ec011cce87b405968dc8d51cf0f9997b0d", + "sha256:1576fba3616f79496e2f067262200dbf4aab1bb727cd7e4e006076686413c80c", + "sha256:1990d5a6a5dc358a0894c8ca02043fb9a5ad9538422001fb2826e91c50f1d539", + "sha256:1d83cd1cc03c22d922ec94d0d5f7b7c96b1332f5e122e81b1a61fb22da77879a", + "sha256:1e8c1b9ecaf9f2590337d5622189aeb2f0dbc54ba0232fa0856cf390957584a9", + "sha256:26e78444bc77d089e62874dc74df05a5c71f01ac598010a327881a48408d0064", + "sha256:2b37931eac4b837c45e2522066bda221ac6d80e78922fb77c75eb12e4dbcdee5", + "sha256:3112de9e11ff1957148c6de1df2bc5cc1440ee36783412e5eedc6f53638a577d", + "sha256:394b0135900b62dbf63e4809cdc8ac923182af2816d06ea61cd6763943c2cc05", + "sha256:3f01c2629a7d6b30d8afe0326b8c649b74825a0e1ebdcb01e8ffd1c920deb07d", + "sha256:41cffc63c7c83dfc30c4cab5b4308ba74440a9633c4509c51a0c52431fb0f8ab", + "sha256:4470fbed088c35dc20b78a39aaf4ae54fe81790c783b3264872a0224f437c31a", + "sha256:5ed3576675c187e3baa80b02c4c9d0edfab78eff4e89dd9da736b921333a2432", + "sha256:6b24364150738ce488333b3fb48bfa14c189a66de41cd632796fbcacb26b4585", + "sha256:6da60fb24577f989535b8fc8b2ddc4212204aaf02e53c4c7ac94ac364150ed08", + "sha256:76c2ba7b5a09863d0a8166fbc753af96d561818c572dbaf697c52095938e7be4", + "sha256:954816850777ac234a4e32b8c88ac1f7847088a6e90cfb8f0e127a1bf3feddff", + "sha256:9c24dd161c06992ed16c5e528a75878edbaeced5660c3db88c820f1f0d3fe1f4", + "sha256:a01bc25eb7a5688656c8770f931d5cb4a44c7de1b3cec69b84cc9745d1e4cc10", + "sha256:a19f816f4702d7b1951d7576026c7124b9bfb64a9543e571774cf517b7a50b29", + "sha256:a41611835010ed4ea4c7aed1da5b58aac78ee7e70932a91ed2705a7b38e40f52", + "sha256:a49730afb716f3f675755afec109895cab95bc9875db7ffe2e42c1b1c6279482", + "sha256:a86b0e4be775902a5496af4fb1b60d8a2a457d78f531458d294360b8637bb014", + "sha256:a8a72259a1652f192c68377be7011eac3c463e9892ef2948828c7d58e4829988", + "sha256:af00236fe21c4d4f4c227b6ccc19b44c594160cc3ff28d104cdce85855369277", + "sha256:b05e0626ec1c391432eabb47a8abd3bf199fb74bfde7cc44a26d2b1b352c2c6e", + "sha256:b5933c45d11cbd9694b1540aa9076816cc7406964c7b16a380fd84d3a5fe3241", + "sha256:b5e0d47d619c739bdc636bbe007da4519fc953393304a5943e0b5aec96c9877c", + "sha256:b67589f7955924865344e6eacfdcf70675e64f36800a576aa5e961f0008cde2a", + "sha256:c5a2530400a6e7e68fd1552a55515de6a4559122e495f73554a51cedafc11669", + "sha256:cafe0ba3a96d0845121433cffa2b9232844a2609fce694fcc02f3f31214ece28", + "sha256:cdb2886c0be2c6c54d0651d5a61c29ef347e8eec81fd83afebbf7b59b80b7393", + "sha256:d0cf7076c8578b3de4e43a046cc7a1af8466e1c3f5e64167189fe8958a4f9c02", + "sha256:f1e1b92ee4ee9ffc68624ace218b89ca5ca667607ccee4541a90cc44999b9aea", + "sha256:f941aaf15f47f316123e1933f9ea91a6efda73a161a6ab6046d1cde37be62c88", + "sha256:fb59a11689ff3c58e7652260127f9e34f7f45478a2f3ef831ab6db7bcd72108f", + "sha256:fc9ffd9a38e21fad3e8c5a88926d57f94a32546e937e0be46142b2702003eba7" ], "index": "pypi", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.4.53" + "version": "==1.4.54" }, "sqlalchemy-bigquery": { "extras": [ @@ -3493,12 +3504,12 @@ }, "stripe": { "hashes": [ - "sha256:5d35c06f6995cba6181d7b18e6ce3ef532321cd4dd3fc7ee9b0f0b0d9641819d", - "sha256:7dfdf84e6734e3afd541ec6c26fef8f5f15ba9d8722bcff0613c0a4d2990ea64" + "sha256:36ab5f75e4af790dd6888450d812f502357b3e699a93cd2f1a8fd0014a4b4fca", + "sha256:82351f9e1055c2161dc38c08a9bbf5c4b6c7f1caffcf911e999a7012369415e2" ], "index": "pypi", "markers": "python_version >= '3.6'", - "version": "==10.9.0" + "version": "==10.10.0" }, "text-unidecode": { "hashes": [ @@ -3557,12 +3568,12 @@ }, "twilio": { "hashes": [ - "sha256:454b7d075c6bee3b64c81c39151be1f9105c695df6dbb0021b0c43e2930263e7", - "sha256:490da2518c0da370d738d436f9086b2463902707a811cd306ec8dcc8ce831758" + "sha256:01e6cbc6d7eaf02918250918140cc764866d62b048a5732c097b12ab5ed4560e", + "sha256:538b260ab464cdfd8e7dc890337ef581fd59ceac92de9f58a678db93474323f4" ], "index": "pypi", "markers": "python_full_version >= '3.7.0'", - "version": "==9.2.4" + "version": "==9.3.0" }, "twisted": { "extras": [ @@ -3626,6 +3637,15 @@ "markers": "python_version >= '3.8'", "version": "==0.30.6" }, + "uvicorn-worker": { + "hashes": [ + "sha256:65dcef25ab80a62e0919640f9582216ee05b3bb1dc2f0e58b354ca0511c398fb", + "sha256:f6894544391796be6eeed37d48cae9d7739e5a105f7e37061eccef2eac5a0295" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.2.0" + }, "vine": { "hashes": [ "sha256:40fdf3c48b2cfe1c38a49e9ae2da6fda88e4794c810050a728bd7413811fb1dc", @@ -3744,111 +3764,103 @@ }, "yarl": { "hashes": [ - "sha256:05c0720f01f94c8d792ed8653a8bde7de143e5d6917ea86dcf51de9d3ba0e3ed", - "sha256:07354c6cf182fe130ae0392f54d76f864a105134bef88f71f98365d323be7115", - "sha256:075261df1a9b28753d8af133b4fef97931a6f85515615241e76d73a655a0fdae", - "sha256:0aa36416f7d2b984adff86f3e087ee402053c2a050ae232ff103177dff0f517e", - "sha256:0efb49c6637197749fa5f7cfb21d8922b82c61d063a7f88227fb5c81c5ea35e7", - "sha256:0fbafb51e453c325660e7f4eb9e827b268bace2d5a924bcff0247c7d10ddd8d3", - "sha256:1045aca1d48c9f614cdcebce5345cd4e8be0b856b608aaa2fcb67ea3c816ff9a", - "sha256:116c1fa600f0afabdb312bf1b95767098c03d5550bae5c648b63aa2a95a84f6c", - "sha256:160d0a21abcd776d8589eb291bfe4c66f5aa754bda43f9346c561519aedb7f90", - "sha256:17745e92cc05aa28284e62a1938fd8e83ece4cddd0be44bf8b69bd0445b1d6ac", - "sha256:197b01e0dab3beb349a7f7be2cfb0f69979fab128f4fbef0fb655b898c22bc71", - "sha256:1ba0926703e64a8125054bbbba5d9dd8399465f298cd48338497506684221569", - "sha256:1f2b93e00243d8d0cedf82c0eaaa5cde27e0e4040013387085a0362ea8068f8d", - "sha256:251d8e6ede8e1f1b6bad8da23d685ab8f79ee8647aabf7b0dc4619c6eb6d433b", - "sha256:2920c39ddf5f710b6f70d9b6fe178dcd4c3bd0aeb7265d3fcdd97d55b944d664", - "sha256:2a24180a47b0aacdd51403a22b2149627edc5f01cc851fe0dac7a279b6ce123d", - "sha256:2aecbd90de8abfee55d67f7ae665452696f290ef74008dd505abeb40ba2be03d", - "sha256:2bb09fbd303586c96110cac521d6236e12b97d555f0cf5b086d59eaf643c7d75", - "sha256:30c9956ea776067dff46aa988899f1218b217469ec6a447d9d0021bd4653ce28", - "sha256:30f7b462b498fc1b109d319b77abb63cf74f71546e54785c92a62cfa296148de", - "sha256:31cc725911654fe77b4a21b79c4ba08d71bd9074a4cf1f8fcc1aca6b9ece5b29", - "sha256:338931f1358501d70d99ed1185b126f627cdce29862a868690ebaa0a1056f6c9", - "sha256:400bb873f7e1697830ef276a21d53cd63761b36a726fb9753cd00e7e00547449", - "sha256:410657b3267d5b95a84b1bbdfae03a58fb18b144f3f8e17505bf5df3b7440953", - "sha256:411df3ba2b58097e472468dd8a8a45696bb2fb4d8d98249d7ace6571c05a91c3", - "sha256:437e969848de7bbb2e34abc93db6ccf307ba4acec708e40b710b17f15e691d30", - "sha256:453784dbe38495749ea3ac00eb96c358d52a360fd54a8e8a901f38b22227c302", - "sha256:47ab8355ab667b908cf2a2bdd4edc8b27770fe327f915eafc440c5ea68a3df9c", - "sha256:49cf15f65b86b48fd1403d14452d2f213475e763e5b4a67ece1e114f8b5c89ad", - "sha256:4a638b73d4037b4f9e52514fec402ca97c98ef7af16909dd5c06254f23d2018e", - "sha256:4a80685a0355d12f2cc73fa07f070496802518f0a3d935754d2fecf4ecce9c3b", - "sha256:4f0da5a07def830dacd610675f1821702f252f6b3f8d5a1d5a282bc874497f4c", - "sha256:4f749ae3f9c3d4fbe42fe24bcd7ce81e785683fd144415540598a6f3769c8f52", - "sha256:562ea9c21d2563af2cf062ceaf4530f6e8ce58e89a243df2eeae933781ea6864", - "sha256:57021bc580ef6c8ef4eb9a7d4a26687da1a74cdce858696fb54bacadf4bc6ca8", - "sha256:592cb002e7b4a52313d6bcd8196b1f8b9e0b86e3bcac374e3c95375a59cf9456", - "sha256:5bb04a89b7efdb9fb215e007b3dca30819a23757633f0ef24da19729a278d168", - "sha256:5be05d6c8980e6ef818a7a789b49a4fd2253068cfd6dff8291030ea1b6002922", - "sha256:604f8d0e045f7788011b0b44e19f04ce62a3329e6af08a3382b8ec8070798aef", - "sha256:6367b8f2e885e0357dd8449a94f6d67480d1b2b7265eb0e54f2b226258f13b4c", - "sha256:642c387a51b99a18c99a7b781cd8c0ceaca0c6f956e2a01ccc1d624386021d86", - "sha256:6812f3b67111561226016af7edc5cbd340d455176edbc30e7c9d2c6b70eaae24", - "sha256:6ceba7b6ee24db0db6db329d5fe1ed1b8e95225e0343bf2390e66042c661a3c6", - "sha256:742651d2e53e535ef93cfd338a9d3faaeb5cecb4e216904d4b08ef2b7971f9ac", - "sha256:77832ed01e7587ebd56fc160a88ba677b9d706d368f78c73404aa04b9f69cbbb", - "sha256:78f1cc410fe6862726bef485a34f16aec24c6a4e1e1342ed2e3718cd3fe7c9ea", - "sha256:7b33cdb0b98daf968b02f9ed0d729af480d2117c4f8ad307893dc4777ffd8ff8", - "sha256:7d5ffbb8e6ba4cd7c5f9237f3543e2a3878eb2b45cb51579a0f83b3affcdd52d", - "sha256:7e7190a70a7067da2af6dc325032f07544bfc9fbca4ae471e76e8ace18cf408c", - "sha256:7f6871d7f3ed5410274cad801f1669ee4e66cd647bd53dbde7151deabe19f4ed", - "sha256:8318c8ec736dde091e02079ac82a4e3a9d67f6dfd23074dcc1f09b0517ed7660", - "sha256:83bb7b9d1d02442b0d6218f6cf4fc748aca6f1a077f1ffef1f73d8adf44f359a", - "sha256:892b37b4abaed283c01240a732a384fc239e875b39f3e9c774482e50451142eb", - "sha256:8a490845e0ded093ec503a90e236ea2cbd4e5873ec345a616f30f357a87e266d", - "sha256:8a5bd28e12bb85f2d8480097c4670bc5cf5f72c23613b0edbea8534d3b007c00", - "sha256:8aafa5ee1970027c11aee1c99999142e76de769bcca8f5364e06eee739d05e5b", - "sha256:8b6f7a9e88643d1ccfe7c51eb904c5cf357b85dbe5de6c562724533e4794c45f", - "sha256:8dd3be4068df64d952dc4488511fcda0809af99596639b5e4cd8f5b797d7f8ce", - "sha256:99cbaf5b5072293de38432260eaddc46c84a1008eb48e05366c703680d67c32b", - "sha256:9ebf1191e47e31405e210e0926d44b110237104f8deca54735b40cb5a2b5f348", - "sha256:a0f8e00f618d9e217c080ec87220da519c817a03a3138fe4cead992ec9b5d98b", - "sha256:a8ab1274030afb9f802fad41ce143a0971788509e47d187fad4560873938d308", - "sha256:a9d3826d3294cffb30598ecbf7660a45b42add21588a3cbb297f26141cd3ba41", - "sha256:b37f1400f0e6d86cadcfc701c149c6cd8aa8c851cab38813d3d0cb9e1f8e5014", - "sha256:b43880f6d6fad721786c62d74da7738d4c373d1dd321d9c2984a351b070a1d70", - "sha256:b7979df841db760b2ea957a7a05cbcbb29132adafdba4c4e713f7bac9f6c6e1c", - "sha256:b7a83d465037cae9a578dbc73b271aaa772e5b18412b1034a4187e3cd3246930", - "sha256:b9384298eba7f7c411c8289311f80075f6a2221690cbd57347239d45bb54f149", - "sha256:baa990d9bcdaf96160347852833bbf5b738ae1d21ce4d676af540984ebdc2b9a", - "sha256:bc1ef802088a9a5351e3f00bdd2c504dda5c65d515b8ad72497d6802d6273b71", - "sha256:bed9ff4834fed52ad8d4716ab1846b2445ed3341f5d376905ea02bb5d3d861df", - "sha256:bf4104176a918df0abab01044cb243096b68c707c8da83c57d137530c4289b7e", - "sha256:c1142ad33527aac79209dd9ebf53f2536e01efc728b883605e66bf7688a6b74a", - "sha256:c34c0b6f43f5b3107bd2e881e622585101c53c2c53e694ab9948e5252369628c", - "sha256:c47923aada4a2cd85daa810c241f1ba05454f386054946244131ab801920f468", - "sha256:c48beaecf7edd647e6688ad08bf0b89c88cfe18ea3d9077399e8fc08c9796276", - "sha256:c494cd77b080312ad881bf42ef90d27130a1cfc4d37dadbc2a8aaf03dd026443", - "sha256:ca7c4ef061b3e47514476ea40970df13a3db8c94db876adc95e3609767ed53b5", - "sha256:cb52a9d5286c300c34630f58d4403412bda1cebc4a7881b462a23f8cc5d2476e", - "sha256:cba3210cb4116b184d717db123484181bdfa07bccfc2dfd24718bfcafaee3509", - "sha256:d56c0498efc6bd13e9f25a0e67fee5544ae70f776983ac7c01a39694695eb0e2", - "sha256:d6366324f622c81b7c806f0a93372fe68e85828fe70e5728be2fe2e1f618d498", - "sha256:dccfee37fb7cda974627de852e88770a5618b4975a269a2ca076b860990b4a8c", - "sha256:df831cca0bb7af9494f7e9008a8099c7dda35bb1e540a00fd695889458cde945", - "sha256:ec055bf02fe03b6bb06966b80b79ddfc26501a8bcf7213d2c8a4c1a09d6ad6a3", - "sha256:ec2c1eafd7b5f869fd40831509edebc85489678d8949c61e967f4688630098e1", - "sha256:ecb9486e7d1c3a27318ac43aa0b364c5066c08858622fb054dde74e357f279a4", - "sha256:ee2758c6db892ea27675e6b394a215d7bc88346e4a0952e6f2f789c71ab2d467", - "sha256:f3a76bd93dfb1fffc48123ed266fa085fdfce5766ff61231da3f07c0e3367056", - "sha256:f40c88d185300b6d177e08f4b8359354e1ba10b564b2bcd9a14ac3fdb679e2f9", - "sha256:f56bd2bdeeb2416cc45236ce10898dc19932d4cc21fd37aa2da0e281112cde16", - "sha256:fe487b0399a850c8adf4158972331345541961b8aa17a35f050d6c3707c95965" + "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49", + "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867", + "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520", + "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a", + "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14", + "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a", + "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93", + "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05", + "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937", + "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74", + "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b", + "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420", + "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639", + "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089", + "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53", + "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e", + "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c", + "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e", + "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe", + "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a", + "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366", + "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63", + "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9", + "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145", + "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf", + "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc", + "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5", + "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff", + "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d", + "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b", + "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00", + "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad", + "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92", + "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998", + "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91", + "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b", + "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a", + "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5", + "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff", + "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367", + "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa", + "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413", + "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4", + "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45", + "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6", + "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5", + "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df", + "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c", + "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318", + "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591", + "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38", + "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8", + "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e", + "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804", + "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec", + "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6", + "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870", + "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83", + "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d", + "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f", + "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909", + "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269", + "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26", + "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b", + "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2", + "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7", + "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd", + "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68", + "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0", + "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786", + "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da", + "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc", + "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447", + "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239", + "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0", + "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84", + "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e", + "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef", + "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e", + "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82", + "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675", + "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26", + "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979", + "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46", + "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4", + "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff", + "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27", + "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c", + "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7", + "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265", + "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79", + "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd" ], "markers": "python_version >= '3.8'", - "version": "==1.9.10" - }, - "zope.event": { - "hashes": [ - "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", - "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd" - ], - "markers": "python_version >= '3.7'", - "version": "==5.0" + "version": "==1.11.1" }, - "zope.interface": { + "zope-interface": { "hashes": [ "sha256:01e6e58078ad2799130c14a1d34ec89044ada0e1495329d72ee0407b9ae5100d", "sha256:064ade95cb54c840647205987c7b557f75d2b2f7d1a84bfab4cf81822ef6e7d1", @@ -3888,6 +3900,14 @@ "markers": "python_version >= '3.8'", "version": "==7.0.3" }, + "zope.event": { + "hashes": [ + "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", + "sha256:bac440d8d9891b4068e2b5a2c5e2c9765a9df762944bda6955f96bb9b91e67cd" + ], + "markers": "python_version >= '3.7'", + "version": "==5.0" + }, "zstandard": { "hashes": [ "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", @@ -3994,6 +4014,130 @@ } }, "develop": { + "aiohappyeyeballs": { + "hashes": [ + "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2", + "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd" + ], + "markers": "python_version >= '3.8'", + "version": "==2.4.0" + }, + "aiohttp": { + "extras": [ + "speedups" + ], + "hashes": [ + "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277", + "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1", + "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe", + "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb", + "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca", + "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91", + "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972", + "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a", + "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3", + "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa", + "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77", + "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b", + "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8", + "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599", + "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc", + "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf", + "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511", + "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699", + "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487", + "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987", + "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff", + "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db", + "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022", + "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce", + "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a", + "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5", + "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7", + "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820", + "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf", + "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e", + "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf", + "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5", + "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6", + "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6", + "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91", + "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3", + "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a", + "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d", + "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088", + "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc", + "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f", + "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75", + "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471", + "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e", + "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697", + "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092", + "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69", + "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3", + "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32", + "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589", + "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178", + "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92", + "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2", + "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e", + "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058", + "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857", + "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1", + "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6", + "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22", + "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0", + "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b", + "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57", + "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f", + "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e", + "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16", + "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1", + "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f", + "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6", + "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04", + "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae", + "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d", + "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b", + "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f", + "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862", + "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689", + "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c", + "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683", + "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef", + "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f", + "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12", + "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73", + "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061", + "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072", + "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11", + "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691", + "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77", + "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385", + "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172", + "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569", + "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f", + "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5" + ], + "markers": "python_version >= '3.8'", + "version": "==3.10.5" + }, + "aioresponses": { + "hashes": [ + "sha256:d2c26defbb9b440ea2685ec132e90700907fd10bcca3e85ec2f157219f0d26f7", + "sha256:f795d9dbda2d61774840e7e32f5366f45752d1adc1b74c9362afd017296c7ee1" + ], + "index": "pypi", + "version": "==0.7.6" + }, + "aiosignal": { + "hashes": [ + "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", + "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.1" + }, "attrs": { "hashes": [ "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", @@ -4053,11 +4197,11 @@ "django" ], "hashes": [ - "sha256:3b644975414d8d6380f4456b8abae3884fa3d57c3552609f7ac7afe6361af10c", - "sha256:bc254beabfc787d8d49e590e7a55b21c6a9420c86de6756d7aa00e977c0219d2" + "sha256:6fd5b17cda53adf9ec3829ada084e91091ea293cad93d2295c2a6ddde8bacaa1", + "sha256:ea70dd183a3a999f329ab7ff52e3b65f442f3a418b8acec81367f57f36a58012" ], "markers": "python_version >= '3.11'", - "version": "==1.0.1" + "version": "==1.0.2" }, "certifi": { "hashes": [ @@ -4302,11 +4446,11 @@ }, "filelock": { "hashes": [ - "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb", - "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7" + "sha256:81de9eb8453c769b63369f87f11131a7ab04e367f8d97ad39dc230daa07e3bec", + "sha256:f6ed4c963184f4c84dd5557ce8fece759a3724b37b80c6c4f20a2f63a4dc6609" ], "markers": "python_version >= '3.8'", - "version": "==3.15.4" + "version": "==3.16.0" }, "flake8": { "hashes": [ @@ -4335,6 +4479,89 @@ "markers": "python_version >= '3.7'", "version": "==1.7.0" }, + "frozenlist": { + "hashes": [ + "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", + "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98", + "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad", + "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5", + "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae", + "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e", + "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", + "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701", + "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d", + "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6", + "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6", + "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", + "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75", + "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868", + "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a", + "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0", + "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1", + "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826", + "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", + "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6", + "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950", + "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", + "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0", + "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8", + "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a", + "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09", + "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86", + "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c", + "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5", + "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b", + "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b", + "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d", + "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", + "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea", + "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776", + "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a", + "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897", + "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7", + "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09", + "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9", + "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe", + "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd", + "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742", + "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09", + "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", + "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932", + "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1", + "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a", + "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", + "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d", + "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7", + "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480", + "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89", + "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e", + "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", + "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", + "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb", + "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", + "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8", + "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b", + "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb", + "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", + "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11", + "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b", + "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc", + "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0", + "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497", + "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17", + "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0", + "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", + "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439", + "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5", + "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac", + "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825", + "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887", + "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", + "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74" + ], + "markers": "python_version >= '3.8'", + "version": "==1.4.1" + }, "gevent": { "hashes": [ "sha256:03aa5879acd6b7076f6a2a307410fb1e0d288b84b03cdfd8c74db8b4bc882fc5", @@ -4504,7 +4731,7 @@ "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da", "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33" ], - "markers": "python_version >= '3' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", + "markers": "python_version >= '3.7'", "version": "==3.0.3" }, "griffe": { @@ -4749,12 +4976,12 @@ }, "mkdocstrings": { "hashes": [ - "sha256:1aa227fe94f88e80737d37514523aacd473fc4b50a7f6852ce41447ab23f2654", - "sha256:ff9d0de28c8fa877ed9b29a42fe407cfe6736d70a1c48177aa84fcc3dc8518cd" + "sha256:29738bfb72b4608e8e55cc50fb8a54f325dc7ebd2014e4e3881a49892d5983cf", + "sha256:bb8b8854d6713d5348ad05b069a09f3b79edbc6a0f33a34c6821141adb03fe33" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==0.26.0" + "version": "==0.26.1" }, "mkdocstrings-python": { "hashes": [ @@ -4765,6 +4992,104 @@ "markers": "python_version >= '3.8'", "version": "==1.11.1" }, + "multidict": { + "hashes": [ + "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", + "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056", + "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", + "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", + "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", + "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", + "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748", + "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", + "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f", + "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", + "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6", + "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada", + "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", + "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2", + "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d", + "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", + "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef", + "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", + "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", + "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60", + "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6", + "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", + "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478", + "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", + "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7", + "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56", + "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", + "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", + "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30", + "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", + "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", + "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0", + "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", + "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c", + "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", + "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", + "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", + "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", + "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", + "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2", + "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", + "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", + "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", + "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", + "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657", + "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581", + "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492", + "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43", + "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", + "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", + "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", + "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057", + "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc", + "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", + "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255", + "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1", + "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972", + "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53", + "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1", + "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", + "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a", + "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160", + "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c", + "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd", + "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", + "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5", + "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", + "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", + "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", + "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", + "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4", + "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", + "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", + "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28", + "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d", + "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a", + "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", + "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", + "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429", + "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", + "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", + "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", + "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392", + "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167", + "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c", + "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", + "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", + "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76", + "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875", + "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd", + "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", + "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db" + ], + "markers": "python_version >= '3.8'", + "version": "==6.1.0" + }, "mypy-extensions": { "hashes": [ "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", @@ -4970,11 +5295,11 @@ }, "platformdirs": { "hashes": [ - "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", - "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3" + "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c", + "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617" ], "markers": "python_version >= '3.8'", - "version": "==4.2.2" + "version": "==4.3.2" }, "pluggy": { "hashes": [ @@ -5304,6 +5629,15 @@ "markers": "python_version >= '3.8'", "version": "==2.32.3" }, + "requests-mock": { + "hashes": [ + "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563", + "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401" + ], + "index": "pypi", + "markers": "python_version >= '3.5'", + "version": "==1.12.1" + }, "requests-oauthlib": { "hashes": [ "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", @@ -5322,11 +5656,11 @@ }, "setuptools": { "hashes": [ - "sha256:2353af060c06388be1cecbf5953dcdb1f38362f87a2356c480b6b4d5fcfc8847", - "sha256:fc91b5f89e392ef5b77fe143b17e32f65d3024744fba66dc3afe07201684d766" + "sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308", + "sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6" ], "markers": "python_version >= '3.8'", - "version": "==74.1.1" + "version": "==74.1.2" }, "six": { "hashes": [ @@ -5353,11 +5687,11 @@ }, "virtualenv": { "hashes": [ - "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a", - "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589" + "sha256:48f2695d9809277003f30776d155615ffc11328e6a0a8c1f0ec80188d7874a55", + "sha256:c17f4e0f3e6036e9f26700446f85c76ab11df65ff6d8a9cbfad9f71aabfcf23c" ], "markers": "python_version >= '3.7'", - "version": "==20.26.3" + "version": "==20.26.4" }, "watchdog": { "hashes": [ @@ -5395,6 +5729,104 @@ "markers": "python_version >= '3.9'", "version": "==5.0.2" }, + "yarl": { + "hashes": [ + "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49", + "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867", + "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520", + "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a", + "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14", + "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a", + "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93", + "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05", + "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937", + "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74", + "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b", + "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420", + "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639", + "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089", + "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53", + "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e", + "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c", + "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e", + "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe", + "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a", + "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366", + "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63", + "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9", + "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145", + "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf", + "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc", + "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5", + "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff", + "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d", + "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b", + "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00", + "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad", + "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92", + "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998", + "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91", + "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b", + "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a", + "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5", + "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff", + "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367", + "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa", + "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413", + "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4", + "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45", + "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6", + "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5", + "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df", + "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c", + "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318", + "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591", + "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38", + "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8", + "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e", + "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804", + "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec", + "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6", + "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870", + "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83", + "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d", + "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f", + "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909", + "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269", + "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26", + "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b", + "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2", + "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7", + "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd", + "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68", + "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0", + "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786", + "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da", + "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc", + "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447", + "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239", + "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0", + "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84", + "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e", + "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef", + "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e", + "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82", + "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675", + "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26", + "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979", + "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46", + "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4", + "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff", + "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27", + "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c", + "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7", + "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265", + "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79", + "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd" + ], + "markers": "python_version >= '3.8'", + "version": "==1.11.1" + }, "zope.event": { "hashes": [ "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", diff --git a/breathecode/authenticate/tests/urls/tests_google_callback.py b/breathecode/authenticate/tests/urls/tests_google_callback.py new file mode 100644 index 000000000..4cd3ff39a --- /dev/null +++ b/breathecode/authenticate/tests/urls/tests_google_callback.py @@ -0,0 +1,176 @@ +""" +Test /v1/auth/subscribe +""" + +from datetime import datetime, timedelta +import random +from unittest.mock import call + +import pytest +from django.urls.base import reverse_lazy +from rest_framework import status + +import capyc.pytest as capy +import staging.pytest as staging +from urllib.parse import quote + + +@pytest.fixture(autouse=True) +def setup(monkeypatch: pytest.MonkeyPatch, db): + monkeypatch.setenv("GOOGLE_CLIENT_ID", "123456.apps.googleusercontent.com") + monkeypatch.setenv("GOOGLE_SECRET", "123456") + monkeypatch.setenv("GOOGLE_REDIRECT_URL", "https://breathecode.herokuapp.com/v1/auth/google/callback") + + yield + + +@pytest.fixture +def validation_res(patch_request): + validation_res = { + "quality_score": (random.random() * 0.4) + 0.6, + "email_quality": (random.random() * 0.4) + 0.6, + "is_valid_format": { + "value": True, + }, + "is_mx_found": { + "value": True, + }, + "is_smtp_valid": { + "value": True, + }, + "is_catchall_email": { + "value": True, + }, + "is_role_email": { + "value": True, + }, + "is_disposable_email": { + "value": False, + }, + "is_free_email": { + "value": True, + }, + } + patch_request( + [ + ( + call( + "get", + "https://emailvalidation.abstractapi.com/v1/?api_key=None&email=pokemon@potato.io", + params=None, + timeout=10, + ), + validation_res, + ), + ] + ) + return validation_res + + +def test_no_token(database: capy.Database, client: capy.Client): + url = reverse_lazy("authenticate:google_callback") + + response = client.get(url, format="json") + + json = response.json() + expected = {"detail": "no-callback-url", "status_code": 400} + + assert json == expected + assert response.status_code == status.HTTP_400_BAD_REQUEST + + assert database.list_of("authenticate.Token") == [] + assert database.list_of("authenticate.CredentialsGoogle") == [] + + +def test_no_url(database: capy.Database, client: capy.Client): + url = reverse_lazy("authenticate:google_callback") + "?state=url%3Dhttps://4geeks.com" + + response = client.get(url, format="json") + + json = response.json() + expected = {"detail": "no-user-token", "status_code": 400} + + assert json == expected + assert response.status_code == status.HTTP_400_BAD_REQUEST + + assert database.list_of("authenticate.Token") == [] + assert database.list_of("authenticate.CredentialsGoogle") == [] + + +def test_no_code(database: capy.Database, client: capy.Client): + url = reverse_lazy("authenticate:google_callback") + "?state=token%3Dabc123%26url%3Dhttps://4geeks.com" + + response = client.get(url, format="json") + + json = response.json() + expected = {"detail": "no-code", "status_code": 400} + + assert json == expected + assert response.status_code == status.HTTP_400_BAD_REQUEST + + assert database.list_of("authenticate.Token") == [] + assert database.list_of("authenticate.CredentialsGoogle") == [] + + +def test_token_not_found(database: capy.Database, client: capy.Client): + url = ( + reverse_lazy("authenticate:google_callback") + + "?state=token%3Dabc123%26url%3Dhttps://4geeks.com&code=12345&scope=https://www.googleapis.com/auth/calendar.events" + ) + + response = client.get(url, format="json") + + json = response.json() + expected = {"detail": "token-not-found", "status_code": 404} + + assert json == expected + assert response.status_code == status.HTTP_404_NOT_FOUND + + assert database.list_of("authenticate.Token") == [] + assert database.list_of("authenticate.CredentialsGoogle") == [] + + +def test_token( + database: capy.Database, client: capy.Client, format: capy.Format, utc_now: datetime, requests: staging.Requests +): + model = database.create(token={"token_type": "temporal"}) + url = ( + reverse_lazy("authenticate:google_callback") + + f"?state=token%3D{model.token.key}%26url%3Dhttps://4geeks.com&code=12345&scope=https://www.googleapis.com/auth/calendar.events" + ) + + requests.post( + "https://oauth2.googleapis.com/token", + json={"access_token": "test_access_token", "expires_in": 3600, "refresh_token": "test_refresh_token"}, + status_code=200, + ) + + response = client.get(url, format="json") + + assert response.status_code == status.HTTP_302_FOUND + assert response.url == f"https://4geeks.com?token={quote(model.token.key)}" + + assert requests.call_count == 1 + last_request = requests.last_request + + assert last_request.method == "POST" + assert last_request.url == "https://oauth2.googleapis.com/token" + assert last_request.qs == {} + assert last_request.json() == { + "client_id": "123456.apps.googleusercontent.com", + "client_secret": "123456", + "redirect_uri": "https://breathecode.herokuapp.com/v1/auth/google/callback", + "grant_type": "authorization_code", + "code": "12345", + } + + assert database.list_of("authenticate.Token") == [format.to_obj_repr(model.token)] + assert database.list_of("authenticate.CredentialsGoogle") == [ + { + "expires_at": utc_now + timedelta(seconds=3600), + "id": 1, + "refresh_token": "test_refresh_token", + "token": "test_access_token", + "user_id": 1, + }, + ] diff --git a/breathecode/authenticate/tests/urls/tests_google_token.py b/breathecode/authenticate/tests/urls/tests_google_token.py new file mode 100644 index 000000000..d00ee2050 --- /dev/null +++ b/breathecode/authenticate/tests/urls/tests_google_token.py @@ -0,0 +1,88 @@ +""" +Test /v1/auth/subscribe +""" + +from typing import Any +from urllib.parse import urlencode + +import pytest +from django.urls.base import reverse_lazy +from django.utils import timezone +from rest_framework import status +from rest_framework.test import APIClient + +from breathecode.tests.mixins.breathecode_mixin.breathecode import Breathecode +import capyc.pytest as capy + +now = timezone.now() + + +@pytest.fixture(autouse=True) +def setup(monkeypatch: pytest.MonkeyPatch, db): + monkeypatch.setenv("GOOGLE_CLIENT_ID", "123456.apps.googleusercontent.com") + monkeypatch.setenv("GOOGLE_SECRET", "123456") + monkeypatch.setenv("GOOGLE_REDIRECT_URL", "https://breathecode.herokuapp.com/v1/auth/google/callback") + + yield + + +def test_no_url(bc: Breathecode, client: APIClient): + url = reverse_lazy("authenticate:google_token", kwargs={"token": "78c9c2defd3be7f3f5b3ddd542ade55a2d35281b"}) + response = client.get(url, format="json") + + json = response.json() + expected = {"detail": "no-callback-url", "status_code": 400} + + assert json == expected + assert response.status_code == status.HTTP_400_BAD_REQUEST + + +@pytest.mark.parametrize( + "token", + [ + 0, + {"token_type": "one_time"}, + {"token_type": "permanent"}, + {"token_type": "login"}, + ], +) +def test_invalid_token(database: capy.Database, client: capy.Client, token: Any): + key = "78c9c2defd3be7f3f5b3ddd542ade55a2d35281b" + model = database.create(token=token) + if "token" in model: + key = model.token.key + + url = reverse_lazy("authenticate:google_token", kwargs={"token": key}) + "?url=https://4geeks.com" + response = client.get(url, format="json") + + json = response.json() + expected = {"detail": "invalid-token", "status_code": 403} + + assert json == expected + assert response.status_code == status.HTTP_403_FORBIDDEN + + +@pytest.mark.parametrize( + "token", + [ + {"token_type": "temporal"}, + ], +) +def test_redirect(database: capy.Database, client: capy.Client, token: Any): + model = database.create(token=token) + callback_url = "https://4geeks.com/" + + url = reverse_lazy("authenticate:google_token", kwargs={"token": model.token.key}) + f"?url={callback_url}" + response = client.get(url, format="json") + + assert response.status_code == status.HTTP_302_FOUND + params = { + "response_type": "code", + "client_id": "123456.apps.googleusercontent.com", + "redirect_uri": "https://breathecode.herokuapp.com/v1/auth/google/callback", + "access_type": "offline", + "scope": "https://www.googleapis.com/auth/calendar.events", + "state": f"token={model.token.key}&url={callback_url}", + } + + assert response.url == f"https://accounts.google.com/o/oauth2/v2/auth?{urlencode(params)}" diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index d26c1eb61..e785994f1 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2017,8 +2017,9 @@ def get_google_token(request, token=None): except Exception: pass - token = Token.get_valid(token) # IMPORTANT!! you can only connect to google with temporal short lasting tokens - if token is None or token.token_type != "temporal": + # you can only connect to google with temporal short lasting tokens + token = Token.get_valid(token, token_type="temporal") + if token is None: raise ValidationException("Invalid or inactive token", code=403, slug="invalid-token") params = { @@ -2058,15 +2059,23 @@ def save_google_token(request): state = parse_qs(request.query_params.get("state", None)) - if state["url"] == None: + if state.get("url") == None: raise ValidationException("No callback URL specified", slug="no-callback-url") - if state["token"] == None: + + if state.get("token") == None: raise ValidationException("No user token specified", slug="no-user-token") code = request.query_params.get("code", None) if code == None: raise ValidationException("No google code specified", slug="no-code") + token = Token.get_valid(state["token"][0]) + if not token: + logger.debug(f'Token {state["token"][0]} not found or is expired') + raise ValidationException( + "Token was not found or is expired, please use a different token", code=404, slug="token-not-found" + ) + payload = { "client_id": os.getenv("GOOGLE_CLIENT_ID", ""), "client_secret": os.getenv("GOOGLE_SECRET", ""), @@ -2075,9 +2084,8 @@ def save_google_token(request): "code": code, } headers = {"Accept": "application/json"} - resp = requests.post("https://oauth2.googleapis.com/token", data=payload, headers=headers, timeout=2) + resp = requests.post("https://oauth2.googleapis.com/token", json=payload, headers=headers) if resp.status_code == 200: - logger.debug("Google responded with 200") body = resp.json() @@ -2086,19 +2094,13 @@ def save_google_token(request): logger.debug(body) - token = Token.get_valid(state["token"][0]) - if not token: - logger.debug(f'Token {state["token"][0]} not found or is expired') - raise ValidationException( - "Token was not found or is expired, please use a different token", code=404, slug="token-not-found" - ) - user = token.user refresh = "" if "refresh_token" in body: refresh = body["refresh_token"] CredentialsGoogle.objects.filter(user__id=user.id).delete() + google_credentials = CredentialsGoogle( user=user, token=body["access_token"], diff --git a/conftest.py b/conftest.py index 20fc337ea..c3f98ddec 100644 --- a/conftest.py +++ b/conftest.py @@ -18,6 +18,7 @@ os.environ["DATABASE_URL"] = "sqlite:///:memory:" pytest_plugins = ( + "staging.pytest.core", "capyc.pytest.core", "capyc.pytest.newrelic", "capyc.pytest.django", diff --git a/docs/infrastructure/journal.md b/docs/infrastructure/journal.md index 356e84eb2..d18d6abda 100644 --- a/docs/infrastructure/journal.md +++ b/docs/infrastructure/journal.md @@ -61,3 +61,9 @@ Side effects: Reasons for the change: - Web worker was reaching 841 MB ram. + +## -9/09/2024 + +- `[all]` `GOOGLE_SECRET` setted. +- `[dev]` `GOOGLE_CLIENT_ID` setted. +- `[all]` `GOOGLE_REDIRECT_URL` setted. diff --git a/scripts/dyno/web.sh b/scripts/dyno/web.sh index 38332e840..6572ff3aa 100755 --- a/scripts/dyno/web.sh +++ b/scripts/dyno/web.sh @@ -1,7 +1,7 @@ #!/bin/env bash WEB_WORKER_CONNECTION=${WEB_WORKER_CONNECTION:-200} -WEB_WORKER_CLASS=${WEB_WORKER_CLASS:-uvicorn.workers.UvicornWorker} +WEB_WORKER_CLASS=${WEB_WORKER_CLASS:-uvicorn_worker.UvicornWorker} CELERY_POOL=${CELERY_POOL:-prefork} WEB_WORKERS=${WEB_WORKERS:-2} WEB_TIMEOUT=${WEB_TIMEOUT:-29} @@ -20,7 +20,7 @@ else GUNICORN_PARAMS="" fi -if [ "$WEB_WORKER_CLASS" = "uvicorn.workers.UvicornWorker" ]; then +if [ "$WEB_WORKER_CLASS" = "uvicorn_worker.UvicornWorker" ]; then export SERVER_TYPE=asgi; else export SERVER_TYPE=wsgi; diff --git a/staging/core/__init__.py b/staging/core/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/staging/core/managers.py b/staging/core/managers.py new file mode 100644 index 000000000..6bc249c9e --- /dev/null +++ b/staging/core/managers.py @@ -0,0 +1,44 @@ +import os +from typing import Optional +from dotenv import dotenv_values + + +class EnvLoader: + def __init_subclass__(self) -> None: + self._load_flags() + + @classmethod + def _load_flags(cls) -> None: + values = dotenv_values(".env.flags") + for key, value in values.items(): + os.environ[f"FLAGS_{key}"] = value + + +class FlagEnv: + @classmethod + def get(cls, key: str, default: Optional[str] = None) -> Optional[str]: + if not key.startswith("FLAGS_"): + key = f"FLAGS_{key}" + + return os.environ.get(key, default) + + @classmethod + def set(cls, key: str, value: str) -> None: + if not key.startswith("FLAGS_"): + key = f"FLAGS_{key}" + + os.environ[key] = value + + @classmethod + def delete(cls, key: str) -> None: + if not key.startswith("FLAGS_"): + key = f"FLAGS_{key}" + + del os.environ[key] + + @classmethod + def set_default(cls, key: str, value: str) -> None: + if not key.startswith("FLAGS_"): + key = f"FLAGS_{key}" + + os.environ.setdefault(key, value) diff --git a/staging/pytest/__init__.py b/staging/pytest/__init__.py new file mode 100644 index 000000000..c162fe991 --- /dev/null +++ b/staging/pytest/__init__.py @@ -0,0 +1 @@ +from .core import * # noqa: F401 diff --git a/staging/pytest/core/__init__.py b/staging/pytest/core/__init__.py new file mode 100644 index 000000000..648cbbac4 --- /dev/null +++ b/staging/pytest/core/__init__.py @@ -0,0 +1 @@ +from .fixtures import * # noqa: F401 diff --git a/staging/pytest/core/fixtures/__init__.py b/staging/pytest/core/fixtures/__init__.py new file mode 100644 index 000000000..5ae6ae6f4 --- /dev/null +++ b/staging/pytest/core/fixtures/__init__.py @@ -0,0 +1 @@ +from .http import * # noqa: F401 diff --git a/staging/pytest/core/fixtures/http.py b/staging/pytest/core/fixtures/http.py new file mode 100644 index 000000000..b8d71e9fc --- /dev/null +++ b/staging/pytest/core/fixtures/http.py @@ -0,0 +1,23 @@ +import pytest +from typing import Generator +from aioresponses import aioresponses +import requests_mock + +__all__ = ["aiohttp", "requests", "AIOHTTP", "Requests"] + +AIOHTTP = aioresponses +Requests = requests_mock.Mocker + + +# Fixture for mocking aiohttp requests +@pytest.fixture +def aiohttp() -> Generator[aioresponses, None, None]: + with aioresponses() as m: + yield m + + +# Fixture for mocking requests +@pytest.fixture +def requests() -> Generator[requests_mock.Mocker, None, None]: + with requests_mock.Mocker() as m: + yield m From 9bc4675cca8109de5e873d7c099734836e7347c6 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Tue, 10 Sep 2024 21:01:11 -0500 Subject: [PATCH 06/30] update google auth callback to be async --- breathecode/authenticate/models.py | 18 +- .../tests/urls/tests_google_callback.py | 32 ++- .../tests/urls/tests_google_token.py | 2 +- breathecode/authenticate/views.py | 62 ++--- staging/pytest/core/fixtures/http.py | 214 ++++++++++++++++-- 5 files changed, 260 insertions(+), 68 deletions(-) diff --git a/breathecode/authenticate/models.py b/breathecode/authenticate/models.py index df8f21657..f98993a2e 100644 --- a/breathecode/authenticate/models.py +++ b/breathecode/authenticate/models.py @@ -22,6 +22,7 @@ TryToGetOrCreateAOneTimeToken, ) from breathecode.utils.validators import validate_language_code +from asgiref.sync import sync_to_async from .signals import academy_invite_accepted @@ -632,16 +633,21 @@ def get_or_create(cls, user, token_type: str, **kwargs: Unpack[TokenGetOrCreateA return token, created @classmethod - def get_valid(cls, token: str, **kwargs: Unpack[TokenFilterArgs]) -> "Token | None": + def get_valid(cls, token: str, async_mode: bool = False, **kwargs: Unpack[TokenFilterArgs]) -> "Token | None": utc_now = timezone.now() cls.delete_expired_tokens() + qs = Token.objects.filter(Q(expires_at__gt=utc_now) | Q(expires_at__isnull=True), key=token, **kwargs) + if async_mode: + qs = qs.prefetch_related("user") + # find among any non-expired token - return ( - Token.objects.filter(key=token, **kwargs) - .filter(Q(expires_at__gt=utc_now) | Q(expires_at__isnull=True)) - .first() - ) + return qs.first() + + @classmethod + @sync_to_async + def aget_valid(cls, token: str, **kwargs: Unpack[TokenFilterArgs]) -> "Token | None": + return cls.get_valid(token, async_mode=True, **kwargs) @classmethod def validate_and_destroy(cls, hash: str) -> User: diff --git a/breathecode/authenticate/tests/urls/tests_google_callback.py b/breathecode/authenticate/tests/urls/tests_google_callback.py index 4cd3ff39a..042642885 100644 --- a/breathecode/authenticate/tests/urls/tests_google_callback.py +++ b/breathecode/authenticate/tests/urls/tests_google_callback.py @@ -131,7 +131,7 @@ def test_token_not_found(database: capy.Database, client: capy.Client): def test_token( - database: capy.Database, client: capy.Client, format: capy.Format, utc_now: datetime, requests: staging.Requests + database: capy.Database, client: capy.Client, format: capy.Format, utc_now: datetime, http: staging.HTTP ): model = database.create(token={"token_type": "temporal"}) url = ( @@ -139,10 +139,20 @@ def test_token( + f"?state=token%3D{model.token.key}%26url%3Dhttps://4geeks.com&code=12345&scope=https://www.googleapis.com/auth/calendar.events" ) - requests.post( + payload = { + "client_id": "123456.apps.googleusercontent.com", + "client_secret": "123456", + "redirect_uri": "https://breathecode.herokuapp.com/v1/auth/google/callback", + "grant_type": "authorization_code", + "code": "12345", + } + + http.post( "https://oauth2.googleapis.com/token", - json={"access_token": "test_access_token", "expires_in": 3600, "refresh_token": "test_refresh_token"}, - status_code=200, + json=payload, + headers={"Accept": "application/json"}, + ).response( + {"access_token": "test_access_token", "expires_in": 3600, "refresh_token": "test_refresh_token"}, status=200 ) response = client.get(url, format="json") @@ -150,19 +160,7 @@ def test_token( assert response.status_code == status.HTTP_302_FOUND assert response.url == f"https://4geeks.com?token={quote(model.token.key)}" - assert requests.call_count == 1 - last_request = requests.last_request - - assert last_request.method == "POST" - assert last_request.url == "https://oauth2.googleapis.com/token" - assert last_request.qs == {} - assert last_request.json() == { - "client_id": "123456.apps.googleusercontent.com", - "client_secret": "123456", - "redirect_uri": "https://breathecode.herokuapp.com/v1/auth/google/callback", - "grant_type": "authorization_code", - "code": "12345", - } + http.call_count == 1 assert database.list_of("authenticate.Token") == [format.to_obj_repr(model.token)] assert database.list_of("authenticate.CredentialsGoogle") == [ diff --git a/breathecode/authenticate/tests/urls/tests_google_token.py b/breathecode/authenticate/tests/urls/tests_google_token.py index d00ee2050..a37657804 100644 --- a/breathecode/authenticate/tests/urls/tests_google_token.py +++ b/breathecode/authenticate/tests/urls/tests_google_token.py @@ -41,7 +41,6 @@ def test_no_url(bc: Breathecode, client: APIClient): "token", [ 0, - {"token_type": "one_time"}, {"token_type": "permanent"}, {"token_type": "login"}, ], @@ -55,6 +54,7 @@ def test_invalid_token(database: capy.Database, client: capy.Client, token: Any) url = reverse_lazy("authenticate:google_token", kwargs={"token": key}) + "?url=https://4geeks.com" response = client.get(url, format="json") + print(response.content) json = response.json() expected = {"detail": "invalid-token", "status_code": 403} diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index e785994f1..a6eb32e8a 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2018,8 +2018,8 @@ def get_google_token(request, token=None): pass # you can only connect to google with temporal short lasting tokens - token = Token.get_valid(token, token_type="temporal") - if token is None: + token = Token.get_valid(token) + if token is None or token.token_type not in ["temporal", "one_time"]: raise ValidationException("Invalid or inactive token", code=403, slug="invalid-token") params = { @@ -2045,10 +2045,12 @@ def get_google_token(request, token=None): # Create your views here. +import aiohttp + + @api_view(["GET"]) @permission_classes([AllowAny]) -def save_google_token(request): - +async def save_google_token(request): logger.debug("Google callback just landed") logger.debug(request.query_params) @@ -2069,8 +2071,8 @@ def save_google_token(request): if code == None: raise ValidationException("No google code specified", slug="no-code") - token = Token.get_valid(state["token"][0]) - if not token: + token = await Token.aget_valid(state["token"][0]) + if not token or token.token_type not in ["temporal", "one_time"]: logger.debug(f'Token {state["token"][0]} not found or is expired') raise ValidationException( "Token was not found or is expired, please use a different token", code=404, slug="token-not-found" @@ -2084,36 +2086,38 @@ def save_google_token(request): "code": code, } headers = {"Accept": "application/json"} - resp = requests.post("https://oauth2.googleapis.com/token", json=payload, headers=headers) - if resp.status_code == 200: - logger.debug("Google responded with 200") - body = resp.json() - if "access_token" not in body: - raise APIException(body["error_description"]) + async with aiohttp.ClientSession() as session: + async with session.post("https://oauth2.googleapis.com/token", json=payload, headers=headers) as resp: + if resp.status == 200: + logger.debug("Google responded with 200") - logger.debug(body) + body = await resp.json() + if "access_token" not in body: + raise APIException(body["error_description"]) - user = token.user - refresh = "" - if "refresh_token" in body: - refresh = body["refresh_token"] + logger.debug(body) - CredentialsGoogle.objects.filter(user__id=user.id).delete() + user = token.user + refresh = "" + if "refresh_token" in body: + refresh = body["refresh_token"] - google_credentials = CredentialsGoogle( - user=user, - token=body["access_token"], - refresh_token=refresh, - expires_at=timezone.now() + timedelta(seconds=body["expires_in"]), - ) - google_credentials.save() + await CredentialsGoogle.objects.filter(user__id=user.id).adelete() - return HttpResponseRedirect(redirect_to=state["url"][0] + "?token=" + token.key) + google_credentials = CredentialsGoogle( + user=user, + token=body["access_token"], + refresh_token=refresh, + expires_at=timezone.now() + timedelta(seconds=body["expires_in"]), + ) + await google_credentials.asave() - else: - logger.error(resp.json()) - raise APIException("Error from google credentials") + return HttpResponseRedirect(redirect_to=state["url"][0] + "?token=" + token.key) + + else: + logger.error(await resp.json()) + raise APIException("Error from google credentials") class GithubUserView(APIView, GenerateLookupsMixin): diff --git a/staging/pytest/core/fixtures/http.py b/staging/pytest/core/fixtures/http.py index b8d71e9fc..dd6957e66 100644 --- a/staging/pytest/core/fixtures/http.py +++ b/staging/pytest/core/fixtures/http.py @@ -1,23 +1,207 @@ +from aiohttp_retry import Optional import pytest -from typing import Generator -from aioresponses import aioresponses -import requests_mock +from typing import Generator, Self -__all__ = ["aiohttp", "requests", "AIOHTTP", "Requests"] +# not implemented yet +supported_http_clients = [] -AIOHTTP = aioresponses -Requests = requests_mock.Mocker +try: + import requests + supported_http_clients.append("requests") +except ImportError: + ... -# Fixture for mocking aiohttp requests -@pytest.fixture -def aiohttp() -> Generator[aioresponses, None, None]: - with aioresponses() as m: - yield m +try: + import aiohttp + + supported_http_clients.append("aiohttp") +except ImportError: + ... + + +__all__ = ["http", "HTTP"] + + +class ResponseGenerator: + def __init__(self, method: str, is_request: bool, endpoints: list[tuple[str, dict]]): + self._method = method + self._is_request = is_request + self._endpoints = endpoints + + def request(self, url: str, **kwargs) -> Self: + self._url = url + self._kwargs = kwargs + return self + + def response(self, data, status: int = 200, headers: Optional[dict] = None) -> None: + if headers is None: + headers = {} + + self._endpoints.append((self._method, self._url, self._kwargs, data, status, headers)) + + +class AsyncResponseMock: + + def __init__(self, data=None, status=200, headers=None, json=None): + if json is not None: + data = json + + if headers is None: + headers = {} + + self.content = data + self.status = status + self.headers = headers + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + pass + + async def json(self): + return self.content + + +class ResponseMock: + + def __init__(self, data=None, json=None, status=200, headers=None): + if json is not None: + data = json + + if headers is None: + headers = {} + + self.content = data + self.status = status + self.headers = headers + + def json(self): + return self.content + + +class HTTP: + def __init__(self, monkeypatch: pytest.MonkeyPatch): + self._monkeypatch = monkeypatch + + self.call_count = 0 + + self._get = [] + self._post = [] + self._put = [] + self._delete = [] + self._head = [] + self._request = [] + + def _match(self, method: str, url: str, **kwargs): + + return self._get.get(url, {}).get(method, {}).get("kwargs", {}) == kwargs + + def _amatch(self, method: str, url: str, **kwargs): + return self._get.get(url, {}).get(method, {}).get("kwargs", {}) == kwargs + + def enable(self): + + def get_endpoint(is_request, method, url, **kwargs): + if is_request: + endpoints = handlers["request"][0] + else: + key = method.lower() + endpoints = handlers[key][0] + + key = (method, url, kwargs) + for endpoint in endpoints: + if key == (endpoint[0], endpoint[1], endpoint[2]): + return endpoint[3:] + + return None + + def match(is_request, method, url, kwargs): + x = get_endpoint(is_request, method, url, **kwargs) + if x is None: + return ResponseMock({"error": "not found"}, status=404) + + self.call_count += 1 + + return ResponseMock(data=x[0], status=x[1], headers=x[2]) + + def amatch(is_request, method, url, kwargs): + x = get_endpoint(is_request, method, url, **kwargs) + if x is None: + return AsyncResponseMock({"error": "not found"}, status=404) + + self.call_count += 1 + + return AsyncResponseMock(x[0], x[1], x[2]) + + handlers = { + "get": ( + self._get, + lambda url, **kwargs: match(is_request=False, method="GET", url=url, kwargs=kwargs), + lambda url, **kwargs: amatch(is_request=False, method="GET", url=url, kwargs=kwargs), + ), + "post": ( + self._post, + lambda url, **kwargs: match(is_request=False, method="POST", url=url, kwargs=kwargs), + lambda self, url, **kwargs: amatch(is_request=False, method="POST", url=url, kwargs=kwargs), + ), + "put": ( + self._put, + lambda url, **kwargs: match(is_request=False, method="PUT", url=url, kwargs=kwargs), + lambda url, **kwargs: amatch(is_request=False, method="PUT", url=url, kwargs=kwargs), + ), + "delete": ( + self._delete, + lambda url, **kwargs: match(is_request=False, method="DELETE", url=url, kwargs=kwargs), + lambda url, **kwargs: amatch(is_request=False, method="DELETE", url=url, kwargs=kwargs), + ), + "head": ( + self._head, + lambda url, **kwargs: match(is_request=False, method="HEAD", url=url, kwargs=kwargs), + lambda url, **kwargs: amatch(is_request=False, method="HEAD", url=url, kwargs=kwargs), + ), + "request": ( + self._request, + lambda method, url, **kwargs: match(is_request=True, method=method, url=url, kwargs=kwargs), + lambda self, method, url, **kwargs: amatch(is_request=True, method=method, url=url, kwargs=kwargs), + ), + } + + for method, (_, handler, ahandler) in handlers.items(): + + self._monkeypatch.setattr(aiohttp.ClientSession, method, ahandler) + self._monkeypatch.setattr(requests, method, handler) + + def disable(self): ... + + def get(self, url: str, **kwargs) -> ResponseGenerator: + return ResponseGenerator(method="GET", is_request=False, endpoints=self._get).request(url, **kwargs) + + def post(self, url: str, **kwargs) -> ResponseGenerator: + return ResponseGenerator(method="POST", is_request=False, endpoints=self._post).request(url, **kwargs) + + def put(self, url: str, **kwargs) -> ResponseGenerator: + return ResponseGenerator(method="PUT", is_request=False, endpoints=self._put).request(url, **kwargs) + + def delete(self, url: str, **kwargs) -> ResponseGenerator: + return ResponseGenerator(method="DELETE", is_request=False, endpoints=self._delete).request(url, **kwargs) + + def head(self, url: str, **kwargs) -> ResponseGenerator: + return ResponseGenerator(method="HEAD", is_request=False, endpoints=self._head).request(url, **kwargs) + + def _request_key(self, method: str, url: str): + return (method, url) + + def request(self, method: str, url: str, **kwargs) -> ResponseGenerator: + method = method.upper() + return ResponseGenerator(method=method, is_request=True, endpoints=self._request).request(url, **kwargs) -# Fixture for mocking requests @pytest.fixture -def requests() -> Generator[requests_mock.Mocker, None, None]: - with requests_mock.Mocker() as m: - yield m +def http(monkeypatch: pytest.MonkeyPatch) -> Generator[HTTP, None, None]: + http = HTTP(monkeypatch) + + http.enable() + yield http + http.disable() From 3021af3bf62952335f706197bdc4f9dcb64eaa70 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Tue, 10 Sep 2024 21:05:46 -0500 Subject: [PATCH 07/30] uninstall packages --- Pipfile.lock | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 3f7ea01db..c610f94b1 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -3860,46 +3860,6 @@ "markers": "python_version >= '3.8'", "version": "==1.11.1" }, - "zope-interface": { - "hashes": [ - "sha256:01e6e58078ad2799130c14a1d34ec89044ada0e1495329d72ee0407b9ae5100d", - "sha256:064ade95cb54c840647205987c7b557f75d2b2f7d1a84bfab4cf81822ef6e7d1", - "sha256:11fa1382c3efb34abf16becff8cb214b0b2e3144057c90611621f2d186b7e1b7", - "sha256:1bee1b722077d08721005e8da493ef3adf0b7908e0cd85cc7dc836ac117d6f32", - "sha256:1eeeb92cb7d95c45e726e3c1afe7707919370addae7ed14f614e22217a536958", - "sha256:21a207c6b2c58def5011768140861a73f5240f4f39800625072ba84e76c9da0b", - "sha256:2545d6d7aac425d528cd9bf0d9e55fcd47ab7fd15f41a64b1c4bf4c6b24946dc", - "sha256:2c4316a30e216f51acbd9fb318aa5af2e362b716596d82cbb92f9101c8f8d2e7", - "sha256:35062d93bc49bd9b191331c897a96155ffdad10744ab812485b6bad5b588d7e4", - "sha256:382d31d1e68877061daaa6499468e9eb38eb7625d4369b1615ac08d3860fe896", - "sha256:3aa8fcbb0d3c2be1bfd013a0f0acd636f6ed570c287743ae2bbd467ee967154d", - "sha256:3d4b91821305c8d8f6e6207639abcbdaf186db682e521af7855d0bea3047c8ca", - "sha256:3de1d553ce72868b77a7e9d598c9bff6d3816ad2b4cc81c04f9d8914603814f3", - "sha256:3fcdc76d0cde1c09c37b7c6b0f8beba2d857d8417b055d4f47df9c34ec518bdd", - "sha256:5112c530fa8aa2108a3196b9c2f078f5738c1c37cfc716970edc0df0414acda8", - "sha256:53d678bb1c3b784edbfb0adeebfeea6bf479f54da082854406a8f295d36f8386", - "sha256:6195c3c03fef9f87c0dbee0b3b6451df6e056322463cf35bca9a088e564a3c58", - "sha256:6d04b11ea47c9c369d66340dbe51e9031df2a0de97d68f442305ed7625ad6493", - "sha256:6dd647fcd765030638577fe6984284e0ebba1a1008244c8a38824be096e37fe3", - "sha256:799ef7a444aebbad5a145c3b34bff012b54453cddbde3332d47ca07225792ea4", - "sha256:7d92920416f31786bc1b2f34cc4fc4263a35a407425319572cbf96b51e835cd3", - "sha256:7e0c151a6c204f3830237c59ee4770cc346868a7a1af6925e5e38650141a7f05", - "sha256:84f8794bd59ca7d09d8fce43ae1b571be22f52748169d01a13d3ece8394d8b5b", - "sha256:95e5913ec718010dc0e7c215d79a9683b4990e7026828eedfda5268e74e73e11", - "sha256:9b9369671a20b8d039b8e5a1a33abd12e089e319a3383b4cc0bf5c67bd05fe7b", - "sha256:ab985c566a99cc5f73bc2741d93f1ed24a2cc9da3890144d37b9582965aff996", - "sha256:af94e429f9d57b36e71ef4e6865182090648aada0cb2d397ae2b3f7fc478493a", - "sha256:c96b3e6b0d4f6ddfec4e947130ec30bd2c7b19db6aa633777e46c8eecf1d6afd", - "sha256:cd2690d4b08ec9eaf47a85914fe513062b20da78d10d6d789a792c0b20307fb1", - "sha256:d3b7ce6d46fb0e60897d62d1ff370790ce50a57d40a651db91a3dde74f73b738", - "sha256:d976fa7b5faf5396eb18ce6c132c98e05504b52b60784e3401f4ef0b2e66709b", - "sha256:db6237e8fa91ea4f34d7e2d16d74741187e9105a63bbb5686c61fea04cdbacca", - "sha256:ecd32f30f40bfd8511b17666895831a51b532e93fc106bfa97f366589d3e4e0e", - "sha256:f418c88f09c3ba159b95a9d1cfcdbe58f208443abb1f3109f4b9b12fd60b187c" - ], - "markers": "python_version >= '3.8'", - "version": "==7.0.3" - }, "zope.event": { "hashes": [ "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", From f298038beb77fc3916ccb7ba6a3e4959fc18dab2 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Tue, 10 Sep 2024 21:07:05 -0500 Subject: [PATCH 08/30] remove print --- breathecode/authenticate/tests/urls/tests_google_token.py | 1 - 1 file changed, 1 deletion(-) diff --git a/breathecode/authenticate/tests/urls/tests_google_token.py b/breathecode/authenticate/tests/urls/tests_google_token.py index a37657804..0ac67b6aa 100644 --- a/breathecode/authenticate/tests/urls/tests_google_token.py +++ b/breathecode/authenticate/tests/urls/tests_google_token.py @@ -54,7 +54,6 @@ def test_invalid_token(database: capy.Database, client: capy.Client, token: Any) url = reverse_lazy("authenticate:google_token", kwargs={"token": key}) + "?url=https://4geeks.com" response = client.get(url, format="json") - print(response.content) json = response.json() expected = {"detail": "invalid-token", "status_code": 403} From 7183a7d7e94f54fdf48fd3c2606d8edb4e3a458d Mon Sep 17 00:00:00 2001 From: jefer94 Date: Wed, 11 Sep 2024 09:41:10 -0500 Subject: [PATCH 09/30] update deps --- Pipfile | 2 - Pipfile.lock | 898 ++++++++++++++++----------------------------------- 2 files changed, 273 insertions(+), 627 deletions(-) diff --git a/Pipfile b/Pipfile index 5ab420910..9ee2547a7 100644 --- a/Pipfile +++ b/Pipfile @@ -57,8 +57,6 @@ google-auth-httplib2 = "*" google-auth-oauthlib = "*" black = "*" capy-core = {extras = ["pytest"], version = "*"} -aioresponses = "*" -requests-mock = "*" [packages] django = "*" diff --git a/Pipfile.lock b/Pipfile.lock index c610f94b1..a41f3b51d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4c728c6cf7d320b7bb6f8447c1711c37d8251b36d0ebae50a55448c5613396c1" + "sha256": "f681c499e9ae8747c094000da7eb08d96edeb75e8d83e8bc15c5507a3a19f743" }, "pipfile-spec": 6, "requires": {}, @@ -1084,12 +1084,12 @@ }, "google-api-python-client": { "hashes": [ - "sha256:f9c333ac4454a012adca90c297f9a22611a8953f3aae5481f90b3a56b9bdd413", - "sha256:fe00851b257157bca600e1692ed8a54762c4a5c7d9eb7f6d4822059424b0d0a9" + "sha256:8b84dde11aaccadc127e4846f5cd932331d804ea324e353131595e3f25376e97", + "sha256:d74da1358f3f2d63daf3c6f26bd96d89652051183bc87cf10a56ceb2a70beb50" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2.144.0" + "version": "==2.145.0" }, "google-apps-meet": { "hashes": [ @@ -1284,67 +1284,75 @@ }, "greenlet": { "hashes": [ - "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67", - "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6", - "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257", - "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4", - "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676", - "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61", - "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc", - "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca", - "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7", - "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728", - "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305", - "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6", - "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379", - "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414", - "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04", - "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a", - "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf", - "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491", - "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559", - "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e", - "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274", - "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb", - "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b", - "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9", - "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b", - "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be", - "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506", - "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405", - "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113", - "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f", - "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5", - "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230", - "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d", - "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f", - "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a", - "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e", - "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61", - "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6", - "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d", - "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71", - "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22", - "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2", - "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3", - "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067", - "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc", - "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881", - "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3", - "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e", - "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac", - "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53", - "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0", - "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b", - "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83", - "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41", - "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c", - "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf", - "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da", - "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33" - ], - "markers": "python_version >= '3.7'", - "version": "==3.0.3" + "sha256:01059afb9b178606b4b6e92c3e710ea1635597c3537e44da69f4531e111dd5e9", + "sha256:037d9ac99540ace9424cb9ea89f0accfaff4316f149520b4ae293eebc5bded17", + "sha256:0e49a65d25d7350cca2da15aac31b6f67a43d867448babf997fe83c7505f57bc", + "sha256:13ff8c8e54a10472ce3b2a2da007f915175192f18e6495bad50486e87c7f6637", + "sha256:1544b8dd090b494c55e60c4ff46e238be44fdc472d2589e943c241e0169bcea2", + "sha256:184258372ae9e1e9bddce6f187967f2e08ecd16906557c4320e3ba88a93438c3", + "sha256:1ddc7bcedeb47187be74208bc652d63d6b20cb24f4e596bd356092d8000da6d6", + "sha256:221169d31cada333a0c7fd087b957c8f431c1dba202c3a58cf5a3583ed973e9b", + "sha256:243a223c96a4246f8a30ea470c440fe9db1f5e444941ee3c3cd79df119b8eebf", + "sha256:24fc216ec7c8be9becba8b64a98a78f9cd057fd2dc75ae952ca94ed8a893bf27", + "sha256:2651dfb006f391bcb240635079a68a261b227a10a08af6349cba834a2141efa1", + "sha256:26811df4dc81271033a7836bc20d12cd30938e6bd2e9437f56fa03da81b0f8fc", + "sha256:26d9c1c4f1748ccac0bae1dbb465fb1a795a75aba8af8ca871503019f4285e2a", + "sha256:28fe80a3eb673b2d5cc3b12eea468a5e5f4603c26aa34d88bf61bba82ceb2f9b", + "sha256:2cd8518eade968bc52262d8c46727cfc0826ff4d552cf0430b8d65aaf50bb91d", + "sha256:2d004db911ed7b6218ec5c5bfe4cf70ae8aa2223dffbb5b3c69e342bb253cb28", + "sha256:3d07c28b85b350564bdff9f51c1c5007dfb2f389385d1bc23288de51134ca303", + "sha256:3e7e6ef1737a819819b1163116ad4b48d06cfdd40352d813bb14436024fcda99", + "sha256:44151d7b81b9391ed759a2f2865bbe623ef00d648fed59363be2bbbd5154656f", + "sha256:44cd313629ded43bb3b98737bba2f3e2c2c8679b55ea29ed73daea6b755fe8e7", + "sha256:4a3dae7492d16e85ea6045fd11cb8e782b63eac8c8d520c3a92c02ac4573b0a6", + "sha256:4b5ea3664eed571779403858d7cd0a9b0ebf50d57d2cdeafc7748e09ef8cd81a", + "sha256:4c3446937be153718250fe421da548f973124189f18fe4575a0510b5c928f0cc", + "sha256:5415b9494ff6240b09af06b91a375731febe0090218e2898d2b85f9b92abcda0", + "sha256:5fd6e94593f6f9714dbad1aaba734b5ec04593374fa6638df61592055868f8b8", + "sha256:619935a44f414274a2c08c9e74611965650b730eb4efe4b2270f91df5e4adf9a", + "sha256:655b21ffd37a96b1e78cc48bf254f5ea4b5b85efaf9e9e2a526b3c9309d660ca", + "sha256:665b21e95bc0fce5cab03b2e1d90ba9c66c510f1bb5fdc864f3a377d0f553f6b", + "sha256:6a4bf607f690f7987ab3291406e012cd8591a4f77aa54f29b890f9c331e84989", + "sha256:6cea1cca3be76c9483282dc7760ea1cc08a6ecec1f0b6ca0a94ea0d17432da19", + "sha256:713d450cf8e61854de9420fb7eea8ad228df4e27e7d4ed465de98c955d2b3fa6", + "sha256:726377bd60081172685c0ff46afbc600d064f01053190e4450857483c4d44484", + "sha256:76b3e3976d2a452cba7aa9e453498ac72240d43030fdc6d538a72b87eaff52fd", + "sha256:76dc19e660baea5c38e949455c1181bc018893f25372d10ffe24b3ed7341fb25", + "sha256:76e5064fd8e94c3f74d9fd69b02d99e3cdb8fc286ed49a1f10b256e59d0d3a0b", + "sha256:7f346d24d74c00b6730440f5eb8ec3fe5774ca8d1c9574e8e57c8671bb51b910", + "sha256:81eeec4403a7d7684b5812a8aaa626fa23b7d0848edb3a28d2eb3220daddcbd0", + "sha256:90b5bbf05fe3d3ef697103850c2ce3374558f6fe40fd57c9fac1bf14903f50a5", + "sha256:9730929375021ec90f6447bff4f7f5508faef1c02f399a1953870cdb78e0c345", + "sha256:9eb4a1d7399b9f3c7ac68ae6baa6be5f9195d1d08c9ddc45ad559aa6b556bce6", + "sha256:a0409bc18a9f85321399c29baf93545152d74a49d92f2f55302f122007cfda00", + "sha256:a22f4e26400f7f48faef2d69c20dc055a1f3043d330923f9abe08ea0aecc44df", + "sha256:a53dfe8f82b715319e9953330fa5c8708b610d48b5c59f1316337302af5c0811", + "sha256:a771dc64fa44ebe58d65768d869fcfb9060169d203446c1d446e844b62bdfdca", + "sha256:a814dc3100e8a046ff48faeaa909e80cdb358411a3d6dd5293158425c684eda8", + "sha256:a8870983af660798dc1b529e1fd6f1cefd94e45135a32e58bd70edd694540f33", + "sha256:ac0adfdb3a21dc2a24ed728b61e72440d297d0fd3a577389df566651fcd08f97", + "sha256:b395121e9bbe8d02a750886f108d540abe66075e61e22f7353d9acb0b81be0f0", + "sha256:b9505a0c8579899057cbefd4ec34d865ab99852baf1ff33a9481eb3924e2da0b", + "sha256:c0a5b1c22c82831f56f2f7ad9bbe4948879762fe0d59833a4a71f16e5fa0f682", + "sha256:c3967dcc1cd2ea61b08b0b276659242cbce5caca39e7cbc02408222fb9e6ff39", + "sha256:c6f4c2027689093775fd58ca2388d58789009116844432d920e9147f91acbe64", + "sha256:c9d86401550b09a55410f32ceb5fe7efcd998bd2dad9e82521713cb148a4a15f", + "sha256:cd468ec62257bb4544989402b19d795d2305eccb06cde5da0eb739b63dc04665", + "sha256:cfcfb73aed40f550a57ea904629bdaf2e562c68fa1164fa4588e752af6efdc3f", + "sha256:d0dd943282231480aad5f50f89bdf26690c995e8ff555f26d8a5b9887b559bcc", + "sha256:d3c59a06c2c28a81a026ff11fbf012081ea34fb9b7052f2ed0366e14896f0a1d", + "sha256:d45b75b0f3fd8d99f62eb7908cfa6d727b7ed190737dec7fe46d993da550b81a", + "sha256:d46d5069e2eeda111d6f71970e341f4bd9aeeee92074e649ae263b834286ecc0", + "sha256:d58ec349e0c2c0bc6669bf2cd4982d2f93bf067860d23a0ea1fe677b0f0b1e09", + "sha256:db1b3ccb93488328c74e97ff888604a8b95ae4f35f4f56677ca57a4fc3a4220b", + "sha256:dd65695a8df1233309b701dec2539cc4b11e97d4fcc0f4185b4a12ce54db0491", + "sha256:f9482c2ed414781c0af0b35d9d575226da6b728bd1a720668fa05837184965b7", + "sha256:f9671e7282d8c6fcabc32c0fb8d7c0ea8894ae85cee89c9aadc2d7129e1a9954", + "sha256:fad7a051e07f64e297e6e8399b4d6a3bdcad3d7297409e9a06ef8cbccff4f501", + "sha256:ffb08f2a1e59d38c7b8b9ac8083c9c8b9875f0955b1e9b9b9a965607a51f8e54" + ], + "markers": "python_version >= '3.11' and platform_python_implementation == 'CPython'", + "version": "==3.1.0" }, "grpcio": { "hashes": [ @@ -1734,12 +1742,12 @@ }, "kombu": { "hashes": [ - "sha256:ad200a8dbdaaa2bbc5f26d2ee7d707d9a1fded353a0f4bd751ce8c7d9f449c60", - "sha256:c8dd99820467610b4febbc7a9e8a0d3d7da2d35116b67184418b51cc520ea6b6" + "sha256:1c05178826dab811f8cab5b0a154d42a7a33d8bcdde9fa3d7b4582e43c3c03db", + "sha256:621d365f234e4c089596f3a2510f1ade07026efc28caca426161d8f458786cab" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==5.4.0" + "version": "==5.4.1" }, "launchdarkly-eventsource": { "hashes": [ @@ -2049,65 +2057,73 @@ }, "msgpack": { "hashes": [ - "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982", - "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3", - "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40", - "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee", - "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693", - "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950", - "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151", - "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24", - "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305", - "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b", - "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c", - "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659", - "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d", - "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18", - "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746", - "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868", - "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2", - "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba", - "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228", - "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2", - "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273", - "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c", - "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653", - "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a", - "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596", - "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd", - "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8", - "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa", - "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85", - "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc", - "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836", - "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3", - "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58", - "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128", - "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db", - "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f", - "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77", - "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad", - "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13", - "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8", - "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b", - "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a", - "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543", - "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b", - "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce", - "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d", - "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a", - "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c", - "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f", - "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e", - "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011", - "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04", - "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480", - "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a", - "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d", - "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d" + "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b", + "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf", + "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca", + "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330", + "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f", + "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f", + "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39", + "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247", + "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b", + "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c", + "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7", + "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044", + "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6", + "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b", + "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0", + "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2", + "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468", + "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7", + "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734", + "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434", + "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325", + "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1", + "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846", + "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88", + "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420", + "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e", + "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2", + "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59", + "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb", + "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68", + "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915", + "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f", + "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701", + "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b", + "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d", + "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa", + "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d", + "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd", + "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc", + "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48", + "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb", + "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74", + "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b", + "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346", + "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e", + "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6", + "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5", + "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f", + "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5", + "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b", + "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c", + "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f", + "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec", + "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8", + "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5", + "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d", + "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e", + "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e", + "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870", + "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f", + "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96", + "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c", + "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd", + "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788" ], "markers": "python_version >= '3.8'", - "version": "==1.0.8" + "version": "==1.1.0" }, "multidict": { "hashes": [ @@ -2709,19 +2725,17 @@ }, "pyasn1": { "hashes": [ - "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c", - "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473" + "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034" ], "markers": "python_version >= '3.8'", - "version": "==0.6.0" + "version": "==0.6.1" }, "pyasn1-modules": { "hashes": [ - "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6", - "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b" + "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c" ], "markers": "python_version >= '3.8'", - "version": "==0.4.0" + "version": "==0.4.1" }, "pycares": { "hashes": [ @@ -2975,11 +2989,11 @@ }, "pytest": { "hashes": [ - "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5", - "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce" + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" ], "markers": "python_version >= '3.8'", - "version": "==8.3.2" + "version": "==8.3.3" }, "pytest-django": { "hashes": [ @@ -3035,11 +3049,11 @@ }, "pytz": { "hashes": [ - "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", - "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319" + "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", + "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725" ], "index": "pypi", - "version": "==2024.1" + "version": "==2024.2" }, "pyyaml": { "hashes": [ @@ -3868,6 +3882,46 @@ "markers": "python_version >= '3.7'", "version": "==5.0" }, + "zope.interface": { + "hashes": [ + "sha256:01e6e58078ad2799130c14a1d34ec89044ada0e1495329d72ee0407b9ae5100d", + "sha256:064ade95cb54c840647205987c7b557f75d2b2f7d1a84bfab4cf81822ef6e7d1", + "sha256:11fa1382c3efb34abf16becff8cb214b0b2e3144057c90611621f2d186b7e1b7", + "sha256:1bee1b722077d08721005e8da493ef3adf0b7908e0cd85cc7dc836ac117d6f32", + "sha256:1eeeb92cb7d95c45e726e3c1afe7707919370addae7ed14f614e22217a536958", + "sha256:21a207c6b2c58def5011768140861a73f5240f4f39800625072ba84e76c9da0b", + "sha256:2545d6d7aac425d528cd9bf0d9e55fcd47ab7fd15f41a64b1c4bf4c6b24946dc", + "sha256:2c4316a30e216f51acbd9fb318aa5af2e362b716596d82cbb92f9101c8f8d2e7", + "sha256:35062d93bc49bd9b191331c897a96155ffdad10744ab812485b6bad5b588d7e4", + "sha256:382d31d1e68877061daaa6499468e9eb38eb7625d4369b1615ac08d3860fe896", + "sha256:3aa8fcbb0d3c2be1bfd013a0f0acd636f6ed570c287743ae2bbd467ee967154d", + "sha256:3d4b91821305c8d8f6e6207639abcbdaf186db682e521af7855d0bea3047c8ca", + "sha256:3de1d553ce72868b77a7e9d598c9bff6d3816ad2b4cc81c04f9d8914603814f3", + "sha256:3fcdc76d0cde1c09c37b7c6b0f8beba2d857d8417b055d4f47df9c34ec518bdd", + "sha256:5112c530fa8aa2108a3196b9c2f078f5738c1c37cfc716970edc0df0414acda8", + "sha256:53d678bb1c3b784edbfb0adeebfeea6bf479f54da082854406a8f295d36f8386", + "sha256:6195c3c03fef9f87c0dbee0b3b6451df6e056322463cf35bca9a088e564a3c58", + "sha256:6d04b11ea47c9c369d66340dbe51e9031df2a0de97d68f442305ed7625ad6493", + "sha256:6dd647fcd765030638577fe6984284e0ebba1a1008244c8a38824be096e37fe3", + "sha256:799ef7a444aebbad5a145c3b34bff012b54453cddbde3332d47ca07225792ea4", + "sha256:7d92920416f31786bc1b2f34cc4fc4263a35a407425319572cbf96b51e835cd3", + "sha256:7e0c151a6c204f3830237c59ee4770cc346868a7a1af6925e5e38650141a7f05", + "sha256:84f8794bd59ca7d09d8fce43ae1b571be22f52748169d01a13d3ece8394d8b5b", + "sha256:95e5913ec718010dc0e7c215d79a9683b4990e7026828eedfda5268e74e73e11", + "sha256:9b9369671a20b8d039b8e5a1a33abd12e089e319a3383b4cc0bf5c67bd05fe7b", + "sha256:ab985c566a99cc5f73bc2741d93f1ed24a2cc9da3890144d37b9582965aff996", + "sha256:af94e429f9d57b36e71ef4e6865182090648aada0cb2d397ae2b3f7fc478493a", + "sha256:c96b3e6b0d4f6ddfec4e947130ec30bd2c7b19db6aa633777e46c8eecf1d6afd", + "sha256:cd2690d4b08ec9eaf47a85914fe513062b20da78d10d6d789a792c0b20307fb1", + "sha256:d3b7ce6d46fb0e60897d62d1ff370790ce50a57d40a651db91a3dde74f73b738", + "sha256:d976fa7b5faf5396eb18ce6c132c98e05504b52b60784e3401f4ef0b2e66709b", + "sha256:db6237e8fa91ea4f34d7e2d16d74741187e9105a63bbb5686c61fea04cdbacca", + "sha256:ecd32f30f40bfd8511b17666895831a51b532e93fc106bfa97f366589d3e4e0e", + "sha256:f418c88f09c3ba159b95a9d1cfcdbe58f208443abb1f3109f4b9b12fd60b187c" + ], + "markers": "python_version >= '3.8'", + "version": "==7.0.3" + }, "zstandard": { "hashes": [ "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", @@ -3974,130 +4028,6 @@ } }, "develop": { - "aiohappyeyeballs": { - "hashes": [ - "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2", - "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd" - ], - "markers": "python_version >= '3.8'", - "version": "==2.4.0" - }, - "aiohttp": { - "extras": [ - "speedups" - ], - "hashes": [ - "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277", - "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1", - "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe", - "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb", - "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca", - "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91", - "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972", - "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a", - "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3", - "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa", - "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77", - "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b", - "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8", - "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599", - "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc", - "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf", - "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511", - "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699", - "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487", - "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987", - "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff", - "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db", - "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022", - "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce", - "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a", - "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5", - "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7", - "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820", - "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf", - "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e", - "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf", - "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5", - "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6", - "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6", - "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91", - "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3", - "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a", - "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d", - "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088", - "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc", - "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f", - "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75", - "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471", - "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e", - "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697", - "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092", - "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69", - "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3", - "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32", - "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589", - "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178", - "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92", - "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2", - "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e", - "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058", - "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857", - "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1", - "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6", - "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22", - "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0", - "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b", - "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57", - "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f", - "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e", - "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16", - "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1", - "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f", - "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6", - "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04", - "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae", - "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d", - "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b", - "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f", - "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862", - "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689", - "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c", - "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683", - "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef", - "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f", - "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12", - "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73", - "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061", - "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072", - "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11", - "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691", - "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77", - "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385", - "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172", - "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569", - "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f", - "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5" - ], - "markers": "python_version >= '3.8'", - "version": "==3.10.5" - }, - "aioresponses": { - "hashes": [ - "sha256:d2c26defbb9b440ea2685ec132e90700907fd10bcca3e85ec2f157219f0d26f7", - "sha256:f795d9dbda2d61774840e7e32f5366f45752d1adc1b74c9362afd017296c7ee1" - ], - "index": "pypi", - "version": "==0.7.6" - }, - "aiosignal": { - "hashes": [ - "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", - "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17" - ], - "markers": "python_version >= '3.7'", - "version": "==1.3.1" - }, "attrs": { "hashes": [ "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", @@ -4439,89 +4369,6 @@ "markers": "python_version >= '3.7'", "version": "==1.7.0" }, - "frozenlist": { - "hashes": [ - "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", - "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98", - "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad", - "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5", - "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae", - "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e", - "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", - "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701", - "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d", - "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6", - "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6", - "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", - "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75", - "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868", - "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a", - "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0", - "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1", - "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826", - "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", - "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6", - "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950", - "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", - "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0", - "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8", - "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a", - "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09", - "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86", - "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c", - "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5", - "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b", - "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b", - "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d", - "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", - "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea", - "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776", - "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a", - "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897", - "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7", - "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09", - "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9", - "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe", - "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd", - "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742", - "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09", - "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", - "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932", - "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1", - "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a", - "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", - "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d", - "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7", - "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480", - "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89", - "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e", - "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", - "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", - "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb", - "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", - "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8", - "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b", - "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb", - "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", - "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11", - "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b", - "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc", - "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0", - "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497", - "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17", - "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0", - "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", - "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439", - "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5", - "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac", - "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825", - "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887", - "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", - "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74" - ], - "markers": "python_version >= '3.8'", - "version": "==1.4.1" - }, "gevent": { "hashes": [ "sha256:03aa5879acd6b7076f6a2a307410fb1e0d288b84b03cdfd8c74db8b4bc882fc5", @@ -4632,75 +4479,83 @@ }, "greenlet": { "hashes": [ - "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67", - "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6", - "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257", - "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4", - "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676", - "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61", - "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc", - "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca", - "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7", - "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728", - "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305", - "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6", - "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379", - "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414", - "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04", - "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a", - "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf", - "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491", - "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559", - "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e", - "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274", - "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb", - "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b", - "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9", - "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b", - "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be", - "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506", - "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405", - "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113", - "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f", - "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5", - "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230", - "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d", - "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f", - "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a", - "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e", - "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61", - "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6", - "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d", - "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71", - "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22", - "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2", - "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3", - "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067", - "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc", - "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881", - "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3", - "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e", - "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac", - "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53", - "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0", - "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b", - "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83", - "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41", - "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c", - "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf", - "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da", - "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33" - ], - "markers": "python_version >= '3.7'", - "version": "==3.0.3" + "sha256:01059afb9b178606b4b6e92c3e710ea1635597c3537e44da69f4531e111dd5e9", + "sha256:037d9ac99540ace9424cb9ea89f0accfaff4316f149520b4ae293eebc5bded17", + "sha256:0e49a65d25d7350cca2da15aac31b6f67a43d867448babf997fe83c7505f57bc", + "sha256:13ff8c8e54a10472ce3b2a2da007f915175192f18e6495bad50486e87c7f6637", + "sha256:1544b8dd090b494c55e60c4ff46e238be44fdc472d2589e943c241e0169bcea2", + "sha256:184258372ae9e1e9bddce6f187967f2e08ecd16906557c4320e3ba88a93438c3", + "sha256:1ddc7bcedeb47187be74208bc652d63d6b20cb24f4e596bd356092d8000da6d6", + "sha256:221169d31cada333a0c7fd087b957c8f431c1dba202c3a58cf5a3583ed973e9b", + "sha256:243a223c96a4246f8a30ea470c440fe9db1f5e444941ee3c3cd79df119b8eebf", + "sha256:24fc216ec7c8be9becba8b64a98a78f9cd057fd2dc75ae952ca94ed8a893bf27", + "sha256:2651dfb006f391bcb240635079a68a261b227a10a08af6349cba834a2141efa1", + "sha256:26811df4dc81271033a7836bc20d12cd30938e6bd2e9437f56fa03da81b0f8fc", + "sha256:26d9c1c4f1748ccac0bae1dbb465fb1a795a75aba8af8ca871503019f4285e2a", + "sha256:28fe80a3eb673b2d5cc3b12eea468a5e5f4603c26aa34d88bf61bba82ceb2f9b", + "sha256:2cd8518eade968bc52262d8c46727cfc0826ff4d552cf0430b8d65aaf50bb91d", + "sha256:2d004db911ed7b6218ec5c5bfe4cf70ae8aa2223dffbb5b3c69e342bb253cb28", + "sha256:3d07c28b85b350564bdff9f51c1c5007dfb2f389385d1bc23288de51134ca303", + "sha256:3e7e6ef1737a819819b1163116ad4b48d06cfdd40352d813bb14436024fcda99", + "sha256:44151d7b81b9391ed759a2f2865bbe623ef00d648fed59363be2bbbd5154656f", + "sha256:44cd313629ded43bb3b98737bba2f3e2c2c8679b55ea29ed73daea6b755fe8e7", + "sha256:4a3dae7492d16e85ea6045fd11cb8e782b63eac8c8d520c3a92c02ac4573b0a6", + "sha256:4b5ea3664eed571779403858d7cd0a9b0ebf50d57d2cdeafc7748e09ef8cd81a", + "sha256:4c3446937be153718250fe421da548f973124189f18fe4575a0510b5c928f0cc", + "sha256:5415b9494ff6240b09af06b91a375731febe0090218e2898d2b85f9b92abcda0", + "sha256:5fd6e94593f6f9714dbad1aaba734b5ec04593374fa6638df61592055868f8b8", + "sha256:619935a44f414274a2c08c9e74611965650b730eb4efe4b2270f91df5e4adf9a", + "sha256:655b21ffd37a96b1e78cc48bf254f5ea4b5b85efaf9e9e2a526b3c9309d660ca", + "sha256:665b21e95bc0fce5cab03b2e1d90ba9c66c510f1bb5fdc864f3a377d0f553f6b", + "sha256:6a4bf607f690f7987ab3291406e012cd8591a4f77aa54f29b890f9c331e84989", + "sha256:6cea1cca3be76c9483282dc7760ea1cc08a6ecec1f0b6ca0a94ea0d17432da19", + "sha256:713d450cf8e61854de9420fb7eea8ad228df4e27e7d4ed465de98c955d2b3fa6", + "sha256:726377bd60081172685c0ff46afbc600d064f01053190e4450857483c4d44484", + "sha256:76b3e3976d2a452cba7aa9e453498ac72240d43030fdc6d538a72b87eaff52fd", + "sha256:76dc19e660baea5c38e949455c1181bc018893f25372d10ffe24b3ed7341fb25", + "sha256:76e5064fd8e94c3f74d9fd69b02d99e3cdb8fc286ed49a1f10b256e59d0d3a0b", + "sha256:7f346d24d74c00b6730440f5eb8ec3fe5774ca8d1c9574e8e57c8671bb51b910", + "sha256:81eeec4403a7d7684b5812a8aaa626fa23b7d0848edb3a28d2eb3220daddcbd0", + "sha256:90b5bbf05fe3d3ef697103850c2ce3374558f6fe40fd57c9fac1bf14903f50a5", + "sha256:9730929375021ec90f6447bff4f7f5508faef1c02f399a1953870cdb78e0c345", + "sha256:9eb4a1d7399b9f3c7ac68ae6baa6be5f9195d1d08c9ddc45ad559aa6b556bce6", + "sha256:a0409bc18a9f85321399c29baf93545152d74a49d92f2f55302f122007cfda00", + "sha256:a22f4e26400f7f48faef2d69c20dc055a1f3043d330923f9abe08ea0aecc44df", + "sha256:a53dfe8f82b715319e9953330fa5c8708b610d48b5c59f1316337302af5c0811", + "sha256:a771dc64fa44ebe58d65768d869fcfb9060169d203446c1d446e844b62bdfdca", + "sha256:a814dc3100e8a046ff48faeaa909e80cdb358411a3d6dd5293158425c684eda8", + "sha256:a8870983af660798dc1b529e1fd6f1cefd94e45135a32e58bd70edd694540f33", + "sha256:ac0adfdb3a21dc2a24ed728b61e72440d297d0fd3a577389df566651fcd08f97", + "sha256:b395121e9bbe8d02a750886f108d540abe66075e61e22f7353d9acb0b81be0f0", + "sha256:b9505a0c8579899057cbefd4ec34d865ab99852baf1ff33a9481eb3924e2da0b", + "sha256:c0a5b1c22c82831f56f2f7ad9bbe4948879762fe0d59833a4a71f16e5fa0f682", + "sha256:c3967dcc1cd2ea61b08b0b276659242cbce5caca39e7cbc02408222fb9e6ff39", + "sha256:c6f4c2027689093775fd58ca2388d58789009116844432d920e9147f91acbe64", + "sha256:c9d86401550b09a55410f32ceb5fe7efcd998bd2dad9e82521713cb148a4a15f", + "sha256:cd468ec62257bb4544989402b19d795d2305eccb06cde5da0eb739b63dc04665", + "sha256:cfcfb73aed40f550a57ea904629bdaf2e562c68fa1164fa4588e752af6efdc3f", + "sha256:d0dd943282231480aad5f50f89bdf26690c995e8ff555f26d8a5b9887b559bcc", + "sha256:d3c59a06c2c28a81a026ff11fbf012081ea34fb9b7052f2ed0366e14896f0a1d", + "sha256:d45b75b0f3fd8d99f62eb7908cfa6d727b7ed190737dec7fe46d993da550b81a", + "sha256:d46d5069e2eeda111d6f71970e341f4bd9aeeee92074e649ae263b834286ecc0", + "sha256:d58ec349e0c2c0bc6669bf2cd4982d2f93bf067860d23a0ea1fe677b0f0b1e09", + "sha256:db1b3ccb93488328c74e97ff888604a8b95ae4f35f4f56677ca57a4fc3a4220b", + "sha256:dd65695a8df1233309b701dec2539cc4b11e97d4fcc0f4185b4a12ce54db0491", + "sha256:f9482c2ed414781c0af0b35d9d575226da6b728bd1a720668fa05837184965b7", + "sha256:f9671e7282d8c6fcabc32c0fb8d7c0ea8894ae85cee89c9aadc2d7129e1a9954", + "sha256:fad7a051e07f64e297e6e8399b4d6a3bdcad3d7297409e9a06ef8cbccff4f501", + "sha256:ffb08f2a1e59d38c7b8b9ac8083c9c8b9875f0955b1e9b9b9a965607a51f8e54" + ], + "markers": "python_version >= '3.11' and platform_python_implementation == 'CPython'", + "version": "==3.1.0" }, "griffe": { "hashes": [ - "sha256:1c9f6ef7455930f3f9b0c4145a961c90385d1e2cbc496f7796fbff560ec60d31", - "sha256:a8b2fcb1ecdc5a412e646b0b4375eb20a5d2eac3a11dd8c10c56967a4097663c" + "sha256:3c85b5704136379bed767ef9c1d7776cac50886e341b61b71c6983dfe04d7cb2", + "sha256:878cd99709b833fab7c41a6545188bcdbc1fcb3b441374449d34b69cb864de69" ], "markers": "python_version >= '3.8'", - "version": "==1.2.0" + "version": "==1.3.0" }, "grpcio": { "hashes": [ @@ -4952,104 +4807,6 @@ "markers": "python_version >= '3.8'", "version": "==1.11.1" }, - "multidict": { - "hashes": [ - "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", - "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056", - "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", - "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", - "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b", - "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", - "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748", - "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", - "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f", - "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", - "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6", - "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada", - "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", - "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2", - "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d", - "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", - "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef", - "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", - "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb", - "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60", - "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6", - "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", - "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478", - "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", - "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7", - "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56", - "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", - "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6", - "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30", - "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb", - "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", - "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0", - "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", - "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c", - "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", - "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", - "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", - "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", - "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", - "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2", - "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", - "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3", - "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", - "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", - "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657", - "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581", - "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492", - "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43", - "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", - "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", - "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926", - "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057", - "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc", - "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80", - "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255", - "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1", - "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972", - "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53", - "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1", - "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423", - "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a", - "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160", - "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c", - "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd", - "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", - "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5", - "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", - "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", - "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", - "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", - "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4", - "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156", - "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753", - "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28", - "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d", - "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a", - "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304", - "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", - "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429", - "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72", - "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399", - "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3", - "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392", - "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167", - "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c", - "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", - "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351", - "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76", - "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875", - "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd", - "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", - "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db" - ], - "markers": "python_version >= '3.8'", - "version": "==6.1.0" - }, "mypy-extensions": { "hashes": [ "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", @@ -5305,19 +5062,17 @@ }, "pyasn1": { "hashes": [ - "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c", - "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473" + "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034" ], "markers": "python_version >= '3.8'", - "version": "==0.6.0" + "version": "==0.6.1" }, "pyasn1-modules": { "hashes": [ - "sha256:831dbcea1b177b28c9baddf4c6d1013c24c3accd14a1873fffaa6a2e905f17b6", - "sha256:be04f15b66c206eed667e0bb5ab27e2b1855ea54a842e5037738099e8ca4ae0b" + "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c" ], "markers": "python_version >= '3.8'", - "version": "==0.4.0" + "version": "==0.4.1" }, "pycodestyle": { "hashes": [ @@ -5369,11 +5124,11 @@ }, "pytest": { "hashes": [ - "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5", - "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce" + "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", + "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2" ], "markers": "python_version >= '3.8'", - "version": "==8.3.2" + "version": "==8.3.3" }, "pytest-asyncio": { "hashes": [ @@ -5421,11 +5176,11 @@ }, "pytz": { "hashes": [ - "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812", - "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319" + "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", + "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725" ], "index": "pypi", - "version": "==2024.1" + "version": "==2024.2" }, "pyyaml": { "hashes": [ @@ -5589,15 +5344,6 @@ "markers": "python_version >= '3.8'", "version": "==2.32.3" }, - "requests-mock": { - "hashes": [ - "sha256:b1e37054004cdd5e56c84454cc7df12b25f90f382159087f4b6915aaeef39563", - "sha256:e9e12e333b525156e82a3c852f22016b9158220d2f47454de9cae8a77d371401" - ], - "index": "pypi", - "markers": "python_version >= '3.5'", - "version": "==1.12.1" - }, "requests-oauthlib": { "hashes": [ "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", @@ -5689,104 +5435,6 @@ "markers": "python_version >= '3.9'", "version": "==5.0.2" }, - "yarl": { - "hashes": [ - "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49", - "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867", - "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520", - "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a", - "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14", - "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a", - "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93", - "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05", - "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937", - "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74", - "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b", - "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420", - "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639", - "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089", - "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53", - "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e", - "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c", - "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e", - "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe", - "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a", - "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366", - "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63", - "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9", - "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145", - "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf", - "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc", - "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5", - "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff", - "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d", - "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b", - "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00", - "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad", - "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92", - "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998", - "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91", - "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b", - "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a", - "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5", - "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff", - "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367", - "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa", - "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413", - "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4", - "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45", - "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6", - "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5", - "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df", - "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c", - "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318", - "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591", - "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38", - "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8", - "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e", - "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804", - "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec", - "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6", - "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870", - "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83", - "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d", - "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f", - "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909", - "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269", - "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26", - "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b", - "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2", - "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7", - "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd", - "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68", - "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0", - "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786", - "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da", - "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc", - "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447", - "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239", - "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0", - "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84", - "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e", - "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef", - "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e", - "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82", - "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675", - "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26", - "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979", - "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46", - "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4", - "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff", - "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27", - "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c", - "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7", - "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265", - "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79", - "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd" - ], - "markers": "python_version >= '3.8'", - "version": "==1.11.1" - }, "zope.event": { "hashes": [ "sha256:2832e95014f4db26c47a13fdaef84cef2f4df37e66b59d8f1f4a8f319a632c26", From edffa12373a6f6e9196b08232a5efd032ece8993 Mon Sep 17 00:00:00 2001 From: Jeferson De Freitas Pinto Date: Wed, 11 Sep 2024 10:08:59 -0500 Subject: [PATCH 10/30] Update web.sh --- scripts/dyno/web.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/dyno/web.sh b/scripts/dyno/web.sh index 6572ff3aa..296170bbb 100755 --- a/scripts/dyno/web.sh +++ b/scripts/dyno/web.sh @@ -1,7 +1,9 @@ #!/bin/env bash WEB_WORKER_CONNECTION=${WEB_WORKER_CONNECTION:-200} -WEB_WORKER_CLASS=${WEB_WORKER_CLASS:-uvicorn_worker.UvicornWorker} +# uvicorn_worker.UvicornWorker is incompatible with new relic +uvicorn.workers.UvicornWorker +WEB_WORKER_CLASS=${WEB_WORKER_CLASS:-uvicorn.workers.UvicornWorker} CELERY_POOL=${CELERY_POOL:-prefork} WEB_WORKERS=${WEB_WORKERS:-2} WEB_TIMEOUT=${WEB_TIMEOUT:-29} @@ -20,7 +22,7 @@ else GUNICORN_PARAMS="" fi -if [ "$WEB_WORKER_CLASS" = "uvicorn_worker.UvicornWorker" ]; then +if [ "$WEB_WORKER_CLASS" = "uvicorn.workers.UvicornWorker" ]; then export SERVER_TYPE=asgi; else export SERVER_TYPE=wsgi; From dbcfaa283ec85a5f00d1302efb037d99a730aeec Mon Sep 17 00:00:00 2001 From: jefer94 Date: Wed, 11 Sep 2024 10:22:50 -0500 Subject: [PATCH 11/30] add prints --- breathecode/authenticate/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index a6eb32e8a..f9320dbb6 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2008,6 +2008,10 @@ def get_google_token(request, token=None): if token == None: raise ValidationException("No session token has been specified", slug="no-session-token") + print("request.query_params", request.query_params) + print("request.GET", request.GET) + print('request.query_params.get("url", None)', request.query_params.get("url", None)) + url = request.query_params.get("url", None) if url == None: raise ValidationException("No callback URL specified", slug="no-callback-url") From d9ece2e2cb08964a0a3fbc43059b9b51a79ecc88 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Wed, 11 Sep 2024 10:42:16 -0500 Subject: [PATCH 12/30] add parse --- breathecode/authenticate/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index f9320dbb6..996ee8519 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2012,7 +2012,10 @@ def get_google_token(request, token=None): print("request.GET", request.GET) print('request.query_params.get("url", None)', request.query_params.get("url", None)) - url = request.query_params.get("url", None) + state = parse_qs(request.query_params.get("state", None)) + print("state", state) + + url = state.get("url", None) if url == None: raise ValidationException("No callback URL specified", slug="no-callback-url") From 914e386ac34b3972d64be570609bf22885f645e8 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Wed, 11 Sep 2024 11:00:22 -0500 Subject: [PATCH 13/30] add prints --- breathecode/authenticate/views.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index 996ee8519..d22fcc768 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2008,14 +2008,7 @@ def get_google_token(request, token=None): if token == None: raise ValidationException("No session token has been specified", slug="no-session-token") - print("request.query_params", request.query_params) - print("request.GET", request.GET) - print('request.query_params.get("url", None)', request.query_params.get("url", None)) - - state = parse_qs(request.query_params.get("state", None)) - print("state", state) - - url = state.get("url", None) + url = request.query_params.get("url", None) if url == None: raise ValidationException("No callback URL specified", slug="no-callback-url") @@ -2066,7 +2059,12 @@ async def save_google_token(request): if error: raise APIException("Google OAuth: " + error_description) + print("request.query_params", request.query_params) + print("request.GET", request.GET) + print('request.query_params.get("url", None)', request.query_params.get("url", None)) + state = parse_qs(request.query_params.get("state", None)) + print("state", state) if state.get("url") == None: raise ValidationException("No callback URL specified", slug="no-callback-url") From 1a38364fb61e21638db5a04ae0a5b39b8158cb6c Mon Sep 17 00:00:00 2001 From: jefer94 Date: Wed, 11 Sep 2024 11:19:36 -0500 Subject: [PATCH 14/30] add logger --- breathecode/authenticate/views.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index d22fcc768..21a2b243c 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2060,11 +2060,19 @@ async def save_google_token(request): raise APIException("Google OAuth: " + error_description) print("request.query_params", request.query_params) + logger.info("request.query_params") + logger.info(request.query_params) print("request.GET", request.GET) + logger.info("request.GET") + logger.info(request.GET) print('request.query_params.get("url", None)', request.query_params.get("url", None)) + logger.info('request.query_params.get("url", None)') + logger.info(request.query_params.get("url", None)) state = parse_qs(request.query_params.get("state", None)) print("state", state) + logger.info("state") + logger.info(state) if state.get("url") == None: raise ValidationException("No callback URL specified", slug="no-callback-url") From 757dca1479bb6bfc6cca8788096e974a2c506c38 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Wed, 11 Sep 2024 11:32:26 -0500 Subject: [PATCH 15/30] change prints --- breathecode/authenticate/views.py | 25 ++++++++++++++ breathecode/events/actions.py | 56 ------------------------------- 2 files changed, 25 insertions(+), 56 deletions(-) diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index 21a2b243c..284654ef3 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2054,8 +2054,33 @@ async def save_google_token(request): logger.debug("Google callback just landed") logger.debug(request.query_params) + print(111111111111111111111) + print(111111111111111111111) + print(111111111111111111111) + print(111111111111111111111) + print(111111111111111111111) + print(111111111111111111111) + print(111111111111111111111) + print(111111111111111111111) + print(111111111111111111111) + print(111111111111111111111) + logger.info(111111111111111111111) + logger.info(111111111111111111111) + logger.info(111111111111111111111) + logger.info(111111111111111111111) + logger.info(111111111111111111111) + logger.info(111111111111111111111) + logger.info(111111111111111111111) + logger.info(111111111111111111111) + logger.info(111111111111111111111) + logger.info(111111111111111111111) + error = request.query_params.get("error", False) + print(error) + logger.info(error) error_description = request.query_params.get("error_description", "") + print(error_description) + logger.info(error_description) if error: raise APIException("Google OAuth: " + error_description) diff --git a/breathecode/events/actions.py b/breathecode/events/actions.py index 12d096b4d..6e029cd4d 100644 --- a/breathecode/events/actions.py +++ b/breathecode/events/actions.py @@ -48,17 +48,14 @@ def get_related_resources(): def process_i_owe_you(i_owe_them: QuerySet[AbstractIOweYou]): for i_owe_you in i_owe_them: - print("get in", i_owe_you) if ( i_owe_you.selected_cohort_set and i_owe_you.selected_cohort_set.cohorts.first().academy and i_owe_you.selected_cohort_set.cohorts.first().academy not in academies ): - print(4, i_owe_you.selected_cohort_set.cohorts.first().academy) academies.append(i_owe_you.selected_cohort_set.cohorts.first().academy) if i_owe_you.selected_cohort_set and i_owe_you.selected_cohort_set.cohorts.first() not in cohorts: - print(5, i_owe_you.selected_cohort_set.cohorts.first()) cohorts.append(i_owe_you.selected_cohort_set.cohorts.first()) if ( @@ -66,11 +63,6 @@ def process_i_owe_you(i_owe_them: QuerySet[AbstractIOweYou]): and i_owe_you.selected_cohort_set.cohorts.first().syllabus_version and i_owe_you.selected_cohort_set.cohorts.first().syllabus_version.syllabus not in syllabus ): - print( - 6, - i_owe_you.selected_cohort_set.cohorts.first().syllabus_version.syllabus, - i_owe_you.selected_cohort_set.cohorts.first().academy, - ) syllabus.append( { "syllabus": i_owe_you.selected_cohort_set.cohorts.first().syllabus_version.syllabus, @@ -79,20 +71,17 @@ def process_i_owe_you(i_owe_them: QuerySet[AbstractIOweYou]): ) if i_owe_you.selected_event_type_set and i_owe_you.selected_event_type_set.academy not in academies: - print(7, i_owe_you.selected_event_type_set.academy) academies.append(i_owe_you.selected_event_type_set.academy) if ( i_owe_you.selected_mentorship_service_set and i_owe_you.selected_mentorship_service_set.academy not in academies ): - print(8, i_owe_you.selected_mentorship_service_set.academy) academies.append(i_owe_you.selected_mentorship_service_set.academy) if i_owe_you.selected_event_type_set: for event_type in i_owe_you.selected_event_type_set.event_types.all(): if event_type.id not in ids: - print(9, event_type.id) ids.append(event_type.id) syllabus = [] @@ -142,51 +131,6 @@ def process_i_owe_you(i_owe_them: QuerySet[AbstractIOweYou]): process_i_owe_you(subscriptions) process_i_owe_you(plan_financings) - print( - -11, - [ - { - "id": x.id, - "slug": x.slug, - "name": x.name, - } - for x in academies - ], - ) - - print( - -22, - [ - { - "id": x.id, - "slug": x.slug, - "name": x.name, - } - for x in cohorts - ], - ) - - print( - -33, - [ - { - "syllabus": { - "id": x["syllabus"].id, - "slug": x["syllabus"].slug, - "name": x["syllabus"].name, - }, - "academy": { - "id": x["academy"].id, - "slug": x["academy"].slug, - "name": x["academy"].name, - }, - } - for x in syllabus - ], - ) - print(-44, subscriptions) - print(-55, plan_financings) - return academies, cohorts, syllabus, ids def my_events(): From b9a937ac60ba44ea0da52c6460e2ea43d19ea263 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Wed, 11 Sep 2024 12:47:36 -0500 Subject: [PATCH 16/30] fix urls --- breathecode/authenticate/urls.py | 4 ++-- breathecode/authenticate/views.py | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/breathecode/authenticate/urls.py b/breathecode/authenticate/urls.py index d97dcd0e1..1cc5166a2 100644 --- a/breathecode/authenticate/urls.py +++ b/breathecode/authenticate/urls.py @@ -133,8 +133,8 @@ path("password/", pick_password, name="password_token"), path("github/", get_github_token, name="github"), path("github/me", GithubMeView.as_view(), name="github_me"), - path("github/", get_github_token, name="github_token"), path("github/callback/", save_github_token, name="github_callback"), + path("github/", get_github_token, name="github_token"), path("slack/", get_slack_token, name="slack"), path("slack/callback/", save_slack_token, name="slack_callback"), path("facebook/", get_facebook_token, name="facebook"), @@ -144,8 +144,8 @@ path("user/me/invite/", MeInviteView.as_view(), name="user_me_invite_status"), path("academy/settings", AcademyAuthSettingsView.as_view(), name="academy_me_settings"), # google authentication oath2.0 + path("google/callback", save_google_token, name="google_callback"), path("google/", get_google_token, name="google_token"), - path("google/callback/", save_google_token, name="google_callback"), path("gitpod/sync", sync_gitpod_users_view, name="sync_gitpod_users"), # sync with gitHUB path("academy/github/user", GithubUserView.as_view(), name="github_user"), diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index 284654ef3..564ba0832 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -6,6 +6,7 @@ import urllib.parse from datetime import timedelta from urllib.parse import parse_qs, urlencode +import aiohttp import requests from adrf.decorators import api_view @@ -2004,7 +2005,6 @@ def login_html_view(request): @api_view(["GET"]) @permission_classes([AllowAny]) def get_google_token(request, token=None): - if token == None: raise ValidationException("No session token has been specified", slug="no-session-token") @@ -2042,12 +2042,6 @@ def get_google_token(request, token=None): return HttpResponseRedirect(redirect_to=redirect) -# Create your views here. - - -import aiohttp - - @api_view(["GET"]) @permission_classes([AllowAny]) async def save_google_token(request): From ca32e47a0a476599152c83100dbc32f3c1ca5bfe Mon Sep 17 00:00:00 2001 From: jefer94 Date: Wed, 11 Sep 2024 12:59:33 -0500 Subject: [PATCH 17/30] remove prints --- breathecode/authenticate/views.py | 38 ------------------------------- 1 file changed, 38 deletions(-) diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index 564ba0832..24f4e82de 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2048,50 +2048,12 @@ async def save_google_token(request): logger.debug("Google callback just landed") logger.debug(request.query_params) - print(111111111111111111111) - print(111111111111111111111) - print(111111111111111111111) - print(111111111111111111111) - print(111111111111111111111) - print(111111111111111111111) - print(111111111111111111111) - print(111111111111111111111) - print(111111111111111111111) - print(111111111111111111111) - logger.info(111111111111111111111) - logger.info(111111111111111111111) - logger.info(111111111111111111111) - logger.info(111111111111111111111) - logger.info(111111111111111111111) - logger.info(111111111111111111111) - logger.info(111111111111111111111) - logger.info(111111111111111111111) - logger.info(111111111111111111111) - logger.info(111111111111111111111) - error = request.query_params.get("error", False) - print(error) - logger.info(error) error_description = request.query_params.get("error_description", "") - print(error_description) - logger.info(error_description) if error: raise APIException("Google OAuth: " + error_description) - print("request.query_params", request.query_params) - logger.info("request.query_params") - logger.info(request.query_params) - print("request.GET", request.GET) - logger.info("request.GET") - logger.info(request.GET) - print('request.query_params.get("url", None)', request.query_params.get("url", None)) - logger.info('request.query_params.get("url", None)') - logger.info(request.query_params.get("url", None)) - state = parse_qs(request.query_params.get("state", None)) - print("state", state) - logger.info("state") - logger.info(state) if state.get("url") == None: raise ValidationException("No callback URL specified", slug="no-callback-url") From 551b2053bd5d03361be249fdde301f6e7a25cd58 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Wed, 11 Sep 2024 19:50:53 -0500 Subject: [PATCH 18/30] update google meet to generate the client secret file --- breathecode/services/google_meet/google_meet.py | 15 ++++++++++----- docs/infrastructure/journal.md | 8 ++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/breathecode/services/google_meet/google_meet.py b/breathecode/services/google_meet/google_meet.py index 5e9ca0b67..75c717898 100644 --- a/breathecode/services/google_meet/google_meet.py +++ b/breathecode/services/google_meet/google_meet.py @@ -84,14 +84,15 @@ class ListParticipantsRequest(TypedDict): # Scopes for Google Calendar API (used for creating Google Meet links) SCOPES = ["https://www.googleapis.com/auth/calendar"] -CREDENTIAL_FILE_NAME = "google_cloud_oauth_token.pickle" +TOKEN_FILE_NAME = "google_cloud_oauth_token.pickle" +GOOGLE_CLIENT_SECRET = "client_secret.json" def get_credentials(): creds = None # Check if google_meet_oauth_token.pickle exists (to reuse the token) - if os.path.exists(CREDENTIAL_FILE_NAME): - with open(CREDENTIAL_FILE_NAME, "rb") as token: + if os.path.exists(TOKEN_FILE_NAME): + with open(TOKEN_FILE_NAME, "rb") as token: creds = pickle.load(token) # If there are no valid credentials available, prompt the user to log in @@ -99,11 +100,15 @@ def get_credentials(): if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: - flow = InstalledAppFlow.from_client_secrets_file("client_secret.json", SCOPES) + if not os.path.exists(GOOGLE_CLIENT_SECRET): + with open(GOOGLE_CLIENT_SECRET, "w") as f: + f.write(os.getenv("GOOGLE_CLIENT_SECRET", "")) + + flow = InstalledAppFlow.from_client_secrets_file(GOOGLE_CLIENT_SECRET, SCOPES) creds = flow.run_local_server(port=0) # Save the credentials for the next run - with open(CREDENTIAL_FILE_NAME, "wb") as token: + with open(TOKEN_FILE_NAME, "wb") as token: pickle.dump(creds, token) return creds diff --git a/docs/infrastructure/journal.md b/docs/infrastructure/journal.md index d18d6abda..aeb165233 100644 --- a/docs/infrastructure/journal.md +++ b/docs/infrastructure/journal.md @@ -67,3 +67,11 @@ Reasons for the change: - `[all]` `GOOGLE_SECRET` setted. - `[dev]` `GOOGLE_CLIENT_ID` setted. - `[all]` `GOOGLE_REDIRECT_URL` setted. + +## 11/09/2024 + +- `[all]` `GOOGLE_CLIENT_SECRET` setted. + +Why: + +- To enable the oauth flow for the Google Meet API. From 7c43ae2689ad32182194b554b089a0a59686a8cb Mon Sep 17 00:00:00 2001 From: jefer94 Date: Wed, 11 Sep 2024 20:09:10 -0500 Subject: [PATCH 19/30] add traceback --- breathecode/mentorship/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/breathecode/mentorship/views.py b/breathecode/mentorship/views.py index ddb98a037..663040152 100644 --- a/breathecode/mentorship/views.py +++ b/breathecode/mentorship/views.py @@ -4,6 +4,7 @@ import os import re import time +import traceback import timeago from django.contrib import messages @@ -518,6 +519,7 @@ def __call__(self): try: sessions = self.get_pending_sessions_or_create(mentor, service, mentee) except Exception as e: + traceback.print_exc() return render_message(self.request, str(e), status=400, academy=mentor.academy) if not is_token_of_mentee and sessions.count() > 0 and str(sessions.first().id) != self.query_params["session"]: From a79ca5022e2774481e3d612c764987a9c9fa0711 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Fri, 13 Sep 2024 15:24:00 -0500 Subject: [PATCH 20/30] add google credentials for academies --- breathecode/authenticate/models.py | 22 +++--- breathecode/authenticate/serializers.py | 1 + breathecode/authenticate/urls.py | 2 +- breathecode/mentorship/actions.py | 17 +++- .../tests_get_pending_sessions_or_create.py | 78 ++++++++++++++++++- .../services/google_meet/google_meet.py | 74 +++++++++++------- .../authenticate_models_mixin.py | 14 ++++ .../mentorship_models_mixin.py | 3 + 8 files changed, 165 insertions(+), 46 deletions(-) diff --git a/breathecode/authenticate/models.py b/breathecode/authenticate/models.py index f98993a2e..939f2d41a 100644 --- a/breathecode/authenticate/models.py +++ b/breathecode/authenticate/models.py @@ -337,18 +337,18 @@ class AcademyAuthSettings(models.Model): blank=True, default=None, null=True, - # TODO: uncomment this - # help_text="Github auth token for this user will be used for any admin call to the google cloud api, for example: inviting users to the academy", + help_text="Github auth token for this user will be used for any admin call to the google cloud api, " + "for example: inviting users to the academy", + ) + google_cloud_owner = models.ForeignKey( + User, + on_delete=models.SET_NULL, + blank=True, + default=None, + null=True, + help_text="Google auth token for this user will be used for any admin call to the google cloud api, " + "for example: creating classroom video calls", ) - # TODO: uncomment this - # google_cloud_owner = models.ForeignKey( - # User, - # on_delete=models.SET_NULL, - # blank=True, - # default=None, - # null=True, - # help_text="Google auth token for this user will be used for any admin call to the google cloud api, for example: creating classroom video calls", - # ) github_default_team_ids = models.CharField( max_length=40, blank=True, diff --git a/breathecode/authenticate/serializers.py b/breathecode/authenticate/serializers.py index ba5b0502d..44061412e 100644 --- a/breathecode/authenticate/serializers.py +++ b/breathecode/authenticate/serializers.py @@ -470,6 +470,7 @@ class AuthSettingsBigSerializer(serpy.Serializer): academy = AcademyTinySerializer() github_username = serpy.Field() github_owner = UserSmallSerializer(required=False) + google_cloud_owner = UserSmallSerializer(required=False) github_default_team_ids = serpy.Field() github_is_sync = serpy.Field() github_error_log = serpy.Field() diff --git a/breathecode/authenticate/urls.py b/breathecode/authenticate/urls.py index 1cc5166a2..f64f2172e 100644 --- a/breathecode/authenticate/urls.py +++ b/breathecode/authenticate/urls.py @@ -142,7 +142,7 @@ path("user/me", UserMeView.as_view(), name="user_me"), path("user/me/invite", MeInviteView.as_view(), name="user_me_invite"), path("user/me/invite/", MeInviteView.as_view(), name="user_me_invite_status"), - path("academy/settings", AcademyAuthSettingsView.as_view(), name="academy_me_settings"), + path("academy/settings", AcademyAuthSettingsView.as_view(), name="academy_settings"), # google authentication oath2.0 path("google/callback", save_google_token, name="google_callback"), path("google/", get_google_token, name="google_token"), diff --git a/breathecode/mentorship/actions.py b/breathecode/mentorship/actions.py index 7cd8ae3d2..7ea0870b3 100644 --- a/breathecode/mentorship/actions.py +++ b/breathecode/mentorship/actions.py @@ -11,7 +11,7 @@ from google.apps.meet_v2.types import Space, SpaceConfig import breathecode.activity.tasks as tasks_activity -from breathecode.authenticate.models import User +from breathecode.authenticate.models import AcademyAuthSettings, User from breathecode.mentorship.exceptions import ExtendSessionException from breathecode.services.daily.client import DailyClient from breathecode.services.google_meet.google_meet import GoogleMeet @@ -438,9 +438,22 @@ def create_room_on_google_meet(session: MentorshipSession, mentee: User) -> None if not session.service: raise Exception("Mentorship session doesn't have a service associated with it") + settings = AcademyAuthSettings.objects.filter( + academy=session.service.academy, google_cloud_owner__isnull=False + ).first() + + if not settings: + raise Exception("Academy doesn't have auth settings for google cloud") + + if hasattr(settings.google_cloud_owner, "credentialsgoogle") is False: + raise Exception("Academy doesn't have a google cloud owner") + # mentor = session.mentor - meet = GoogleMeet() + meet = GoogleMeet( + token=settings.google_cloud_owner.credentialsgoogle.token, + refresh_token=settings.google_cloud_owner.credentialsgoogle.refresh_token, + ) if session.id is None: session.save() diff --git a/breathecode/mentorship/tests/actions/tests_get_pending_sessions_or_create.py b/breathecode/mentorship/tests/actions/tests_get_pending_sessions_or_create.py index 0dd065385..c19050bdf 100644 --- a/breathecode/mentorship/tests/actions/tests_get_pending_sessions_or_create.py +++ b/breathecode/mentorship/tests/actions/tests_get_pending_sessions_or_create.py @@ -6,6 +6,7 @@ from unittest.mock import MagicMock, call, patch from django.utils import timezone +import pytest from breathecode.authenticate.models import Token from breathecode.tests.mocks.requests import REQUESTS_PATH, apply_requests_request_mock @@ -107,6 +108,65 @@ def test_create_session_mentor_first_no_previous_nothing__daily(self): self.assertEqual(actions.close_older_sessions.call_args_list, [call()]) + @patch.multiple( + "breathecode.services.google_meet.google_meet.GoogleMeet", + __init__=MagicMock(return_value=None), + create_space=MagicMock(return_value=GoogleMeetMock(meeting_uri="https://meet.google.com/fake")), + ) + @patch("breathecode.mentorship.signals.mentorship_session_status.send", MagicMock()) + @patch("django.utils.timezone.now", MagicMock(return_value=ENDS_AT)) + @patch("breathecode.mentorship.actions.close_older_sessions", MagicMock()) + def test_no_auth_settings__google_meet(self): + """ + When the mentor gets into the room before the mentee + if should create a room with status 'pending' + """ + + models = self.bc.database.create( + mentor_profile=1, + user=1, + mentorship_service={"video_provider": "GOOGLE_MEET"}, + ) + + mentor = models.mentor_profile + mentor_token, created = Token.get_or_create(mentor.user, token_type="permanent") + + with pytest.raises(Exception, match="Academy doesn't have auth settings for google cloud"): + get_pending_sessions_or_create(mentor_token, mentor, models.mentorship_service, mentee=None) + + self.assertEqual(self.bc.database.list_of("mentorship.MentorshipSession"), []) + self.assertEqual(actions.close_older_sessions.call_args_list, [call()]) + + @patch.multiple( + "breathecode.services.google_meet.google_meet.GoogleMeet", + __init__=MagicMock(return_value=None), + create_space=MagicMock(return_value=GoogleMeetMock(meeting_uri="https://meet.google.com/fake")), + ) + @patch("breathecode.mentorship.signals.mentorship_session_status.send", MagicMock()) + @patch("django.utils.timezone.now", MagicMock(return_value=ENDS_AT)) + @patch("breathecode.mentorship.actions.close_older_sessions", MagicMock()) + def test_no_google_cloud_owner__google_meet(self): + """ + When the mentor gets into the room before the mentee + if should create a room with status 'pending' + """ + + models = self.bc.database.create( + mentor_profile=1, + user=1, + mentorship_service={"video_provider": "GOOGLE_MEET"}, + academy_auth_settings=1, + ) + + mentor = models.mentor_profile + mentor_token, created = Token.get_or_create(mentor.user, token_type="permanent") + + with pytest.raises(Exception, match="Academy doesn't have a google cloud owner"): + get_pending_sessions_or_create(mentor_token, mentor, models.mentorship_service, mentee=None) + + self.assertEqual(self.bc.database.list_of("mentorship.MentorshipSession"), []) + self.assertEqual(actions.close_older_sessions.call_args_list, [call()]) + @patch.multiple( "breathecode.services.google_meet.google_meet.GoogleMeet", __init__=MagicMock(return_value=None), @@ -121,7 +181,13 @@ def test_create_session_mentor_first_no_previous_nothing__google_meet(self): if should create a room with status 'pending' """ - models = self.bc.database.create(mentor_profile=1, user=1, mentorship_service={"video_provider": "GOOGLE_MEET"}) + models = self.bc.database.create( + mentor_profile=1, + user=1, + mentorship_service={"video_provider": "GOOGLE_MEET"}, + credentials_google=1, + academy_auth_settings=1, + ) mentor = models.mentor_profile mentor_token, created = Token.get_or_create(mentor.user, token_type="permanent") @@ -344,7 +410,13 @@ def test_create_session_mentee_first_no_previous_nothing__google_meet(self): it should return a brand new sessions with started at already started """ - models = self.bc.database.create(mentor_profile=1, user=2, mentorship_service={"video_provider": "GOOGLE_MEET"}) + models = self.bc.database.create( + mentor_profile=1, + user=2, + mentorship_service={"video_provider": "GOOGLE_MEET"}, + credentials_google=1, + academy_auth_settings=1, + ) mentor = models.mentor_profile mentee = models.user[1] @@ -526,6 +598,8 @@ def test_create_session_mentee_first_with_another_mentee__google_meet(self): user=1, mentorship_session=mentorship_session, mentorship_service={"video_provider": "GOOGLE_MEET"}, + credentials_google=1, + academy_auth_settings=1, ) new_mentee = self.bc.database.create(user=1).user diff --git a/breathecode/services/google_meet/google_meet.py b/breathecode/services/google_meet/google_meet.py index 75c717898..4650efa16 100644 --- a/breathecode/services/google_meet/google_meet.py +++ b/breathecode/services/google_meet/google_meet.py @@ -1,14 +1,12 @@ import os.path -import pickle from typing import Optional, TypedDict, Unpack import google.apps.meet_v2.services.conference_records_service.pagers as pagers from asgiref.sync import async_to_sync from google.apps import meet_v2 from google.apps.meet_v2.types import Space -from google.auth.transport.requests import Request from google.protobuf.field_mask_pb2 import FieldMask -from google_auth_oauthlib.flow import InstalledAppFlow +from google.oauth2.credentials import Credentials __all__ = ["GoogleMeet"] @@ -88,56 +86,72 @@ class ListParticipantsRequest(TypedDict): GOOGLE_CLIENT_SECRET = "client_secret.json" -def get_credentials(): - creds = None - # Check if google_meet_oauth_token.pickle exists (to reuse the token) - if os.path.exists(TOKEN_FILE_NAME): - with open(TOKEN_FILE_NAME, "rb") as token: - creds = pickle.load(token) +# def get_credentials(): +# # creds = None +# # # Check if google_meet_oauth_token.pickle exists (to reuse the token) +# # if os.path.exists(TOKEN_FILE_NAME): +# # with open(TOKEN_FILE_NAME, "rb") as token: +# # creds = pickle.load(token) - # If there are no valid credentials available, prompt the user to log in - if not creds or not creds.valid: - if creds and creds.expired and creds.refresh_token: - creds.refresh(Request()) - else: - if not os.path.exists(GOOGLE_CLIENT_SECRET): - with open(GOOGLE_CLIENT_SECRET, "w") as f: - f.write(os.getenv("GOOGLE_CLIENT_SECRET", "")) +# # # If there are no valid credentials available, prompt the user to log in +# # if not creds or not creds.valid: +# # if creds and creds.expired and creds.refresh_token: +# # creds.refresh(Request()) +# # else: +# # if not os.path.exists(GOOGLE_CLIENT_SECRET): +# # with open(GOOGLE_CLIENT_SECRET, "w") as f: +# # f.write(os.getenv("GOOGLE_CLIENT_SECRET", "")) - flow = InstalledAppFlow.from_client_secrets_file(GOOGLE_CLIENT_SECRET, SCOPES) - creds = flow.run_local_server(port=0) +# # flow = InstalledAppFlow.from_client_secrets_file(GOOGLE_CLIENT_SECRET, SCOPES) +# # creds = flow.run_local_server(port=0) +# # flow.fetch_token() +# # # flow. - # Save the credentials for the next run - with open(TOKEN_FILE_NAME, "wb") as token: - pickle.dump(creds, token) +# # # Save the credentials for the next run +# # with open(TOKEN_FILE_NAME, "wb") as token: +# # pickle.dump(creds, token) - return creds +# return Credentials( +# token=..., +# refresh_token=..., +# token_uri="https://oauth2.googleapis.com/token", +# client_id=os.getenv("GOOGLE_CLIENT_ID"), +# client_secret=..., +# ) + +# return creds class GoogleMeet: _spaces_service_client: Optional[meet_v2.SpacesServiceAsyncClient] _conference_records_service_client: Optional[meet_v2.ConferenceRecordsServiceAsyncClient] - def __init__(self): - from breathecode.setup import resolve_gcloud_credentials + def __init__(self, token: str, refresh_token: Optional[str] = None): + # from breathecode.setup import resolve_gcloud_credentials - resolve_gcloud_credentials() + # resolve_gcloud_credentials() + self._credentials = Credentials( + token=token, + refresh_token=refresh_token, + token_uri="https://oauth2.googleapis.com/token", + client_id=os.getenv("GOOGLE_CLIENT_ID", ""), + client_secret=os.getenv("GOOGLE_SECRET", ""), + # granted_scopes=SCOPES, + ) self._spaces_service_client = None self._conference_records_service_client = None async def spaces_service_client(self): if self._spaces_service_client is None: - credentials = get_credentials() - self._spaces_service_client = meet_v2.SpacesServiceAsyncClient(credentials=credentials) + self._spaces_service_client = meet_v2.SpacesServiceAsyncClient(credentials=self._credentials) return self._spaces_service_client async def conference_records_service_client(self): if self._conference_records_service_client is None: - credentials = get_credentials() self._conference_records_service_client = meet_v2.ConferenceRecordsServiceAsyncClient( - credentials=credentials + credentials=self._credentials ) return self._conference_records_service_client diff --git a/breathecode/tests/mixins/generate_models_mixin/authenticate_models_mixin.py b/breathecode/tests/mixins/generate_models_mixin/authenticate_models_mixin.py index 44c6c8b23..98715e3cc 100644 --- a/breathecode/tests/mixins/generate_models_mixin/authenticate_models_mixin.py +++ b/breathecode/tests/mixins/generate_models_mixin/authenticate_models_mixin.py @@ -34,6 +34,7 @@ def generate_authenticate_models( user_setting=False, github_academy_user_log=False, pending_github_user=False, + credentials_google=False, profile_kwargs={}, device_id_kwargs={}, capability_kwargs={}, @@ -144,6 +145,9 @@ def generate_authenticate_models( if "user" in models: kargs["github_owner"] = just_one(models["user"]) + if "user" in models: + kargs["google_cloud_owner"] = just_one(models["user"]) + if "academy" in models: kargs["academy"] = just_one(models["academy"]) @@ -184,6 +188,16 @@ def generate_authenticate_models( credentials_facebook, "authenticate.CredentialsFacebook", **{**kargs, **credentials_facebook_kwargs} ) + if not "credentials_google" in models and is_valid(credentials_google): + kargs = {} + + if "user" in models: + kargs["user"] = just_one(models["user"]) + + models["credentials_facebook"] = create_models( + credentials_google, "authenticate.CredentialsGoogle", **kargs + ) + if not "cohort_user" in models and is_valid(cohort_user): kargs = {} diff --git a/breathecode/tests/mixins/generate_models_mixin/mentorship_models_mixin.py b/breathecode/tests/mixins/generate_models_mixin/mentorship_models_mixin.py index d385d49cb..bf95a4e4e 100644 --- a/breathecode/tests/mixins/generate_models_mixin/mentorship_models_mixin.py +++ b/breathecode/tests/mixins/generate_models_mixin/mentorship_models_mixin.py @@ -76,6 +76,9 @@ def generate_mentorship_models( if "mentorship_service" in models: kargs["service"] = just_one(models["mentorship_service"]) + if "academy" in models: + kargs["academy"] = just_one(models["academy"]) + models["mentorship_session"] = create_models(mentorship_session, "mentorship.MentorshipSession", **kargs) return models From efd40c354e481477ebda7641324bc2826eb1f1b1 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Fri, 13 Sep 2024 15:35:07 -0500 Subject: [PATCH 21/30] add migrations --- ...uthsettings_google_cloud_owner_and_more.py | 41 +++++++++++++++++++ breathecode/authenticate/models.py | 1 + .../mentorship_models_mixin.py | 3 -- 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 breathecode/authenticate/migrations/0056_academyauthsettings_google_cloud_owner_and_more.py diff --git a/breathecode/authenticate/migrations/0056_academyauthsettings_google_cloud_owner_and_more.py b/breathecode/authenticate/migrations/0056_academyauthsettings_google_cloud_owner_and_more.py new file mode 100644 index 000000000..6bfe75ac6 --- /dev/null +++ b/breathecode/authenticate/migrations/0056_academyauthsettings_google_cloud_owner_and_more.py @@ -0,0 +1,41 @@ +# Generated by Django 5.1.1 on 2024-09-13 20:26 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("authenticate", "0055_alter_profile_github_username_and_more"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="academyauthsettings", + name="google_cloud_owner", + field=models.ForeignKey( + blank=True, + default=None, + help_text="Google auth token for this user will be used for any admin call to the google cloud api, for example: creating classroom video calls", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="google_cloud_academy_auth_settings", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AlterField( + model_name="academyauthsettings", + name="github_owner", + field=models.ForeignKey( + blank=True, + default=None, + help_text="Github auth token for this user will be used for any admin call to the google cloud api, for example: inviting users to the academy", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/breathecode/authenticate/models.py b/breathecode/authenticate/models.py index 939f2d41a..96415a6a2 100644 --- a/breathecode/authenticate/models.py +++ b/breathecode/authenticate/models.py @@ -348,6 +348,7 @@ class AcademyAuthSettings(models.Model): null=True, help_text="Google auth token for this user will be used for any admin call to the google cloud api, " "for example: creating classroom video calls", + related_name="google_cloud_academy_auth_settings", ) github_default_team_ids = models.CharField( max_length=40, diff --git a/breathecode/tests/mixins/generate_models_mixin/mentorship_models_mixin.py b/breathecode/tests/mixins/generate_models_mixin/mentorship_models_mixin.py index bf95a4e4e..d385d49cb 100644 --- a/breathecode/tests/mixins/generate_models_mixin/mentorship_models_mixin.py +++ b/breathecode/tests/mixins/generate_models_mixin/mentorship_models_mixin.py @@ -76,9 +76,6 @@ def generate_mentorship_models( if "mentorship_service" in models: kargs["service"] = just_one(models["mentorship_service"]) - if "academy" in models: - kargs["academy"] = just_one(models["academy"]) - models["mentorship_session"] = create_models(mentorship_session, "mentorship.MentorshipSession", **kargs) return models From 04607878cdec7b92395561087031dd06bad72654 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Fri, 13 Sep 2024 15:49:17 -0500 Subject: [PATCH 22/30] update admin --- breathecode/authenticate/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/breathecode/authenticate/admin.py b/breathecode/authenticate/admin.py index 986c36d5a..05ef34d05 100644 --- a/breathecode/authenticate/admin.py +++ b/breathecode/authenticate/admin.py @@ -475,7 +475,7 @@ class AcademyAuthSettingsAdmin(admin.ModelAdmin): list_display = ("academy", "github_is_sync", "github_errors", "github_username", "github_owner", "authenticate") search_fields = ["academy__slug", "academy__name", "github__username", "academy__id"] actions = (clean_errors, activate_github_sync, deactivate_github_sync, sync_github_members) - raw_id_fields = ["github_owner"] + raw_id_fields = ["github_owner", "google_cloud_owner"] def get_queryset(self, request): From 784379f10d12d3d434faa954b25a3df4bd26ad55 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Fri, 13 Sep 2024 16:15:34 -0500 Subject: [PATCH 23/30] changes in google credentials --- .../services/google_meet/google_meet.py | 79 ++++++++----------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/breathecode/services/google_meet/google_meet.py b/breathecode/services/google_meet/google_meet.py index 4650efa16..bc8ce9b9c 100644 --- a/breathecode/services/google_meet/google_meet.py +++ b/breathecode/services/google_meet/google_meet.py @@ -8,6 +8,10 @@ from google.protobuf.field_mask_pb2 import FieldMask from google.oauth2.credentials import Credentials + +import pickle +from google.auth.transport.requests import Request + __all__ = ["GoogleMeet"] @@ -86,62 +90,41 @@ class ListParticipantsRequest(TypedDict): GOOGLE_CLIENT_SECRET = "client_secret.json" -# def get_credentials(): -# # creds = None -# # # Check if google_meet_oauth_token.pickle exists (to reuse the token) -# # if os.path.exists(TOKEN_FILE_NAME): -# # with open(TOKEN_FILE_NAME, "rb") as token: -# # creds = pickle.load(token) - -# # # If there are no valid credentials available, prompt the user to log in -# # if not creds or not creds.valid: -# # if creds and creds.expired and creds.refresh_token: -# # creds.refresh(Request()) -# # else: -# # if not os.path.exists(GOOGLE_CLIENT_SECRET): -# # with open(GOOGLE_CLIENT_SECRET, "w") as f: -# # f.write(os.getenv("GOOGLE_CLIENT_SECRET", "")) - -# # flow = InstalledAppFlow.from_client_secrets_file(GOOGLE_CLIENT_SECRET, SCOPES) -# # creds = flow.run_local_server(port=0) -# # flow.fetch_token() -# # # flow. - -# # # Save the credentials for the next run -# # with open(TOKEN_FILE_NAME, "wb") as token: -# # pickle.dump(creds, token) - -# return Credentials( -# token=..., -# refresh_token=..., -# token_uri="https://oauth2.googleapis.com/token", -# client_id=os.getenv("GOOGLE_CLIENT_ID"), -# client_secret=..., -# ) - -# return creds - - class GoogleMeet: _spaces_service_client: Optional[meet_v2.SpacesServiceAsyncClient] _conference_records_service_client: Optional[meet_v2.ConferenceRecordsServiceAsyncClient] def __init__(self, token: str, refresh_token: Optional[str] = None): - # from breathecode.setup import resolve_gcloud_credentials - - # resolve_gcloud_credentials() - self._credentials = Credentials( - token=token, - refresh_token=refresh_token, - token_uri="https://oauth2.googleapis.com/token", - client_id=os.getenv("GOOGLE_CLIENT_ID", ""), - client_secret=os.getenv("GOOGLE_SECRET", ""), - # granted_scopes=SCOPES, - ) - + self._credentials = self._get_credentials(token, refresh_token) self._spaces_service_client = None self._conference_records_service_client = None + def _get_credentials( + self, token: Optional[str] = None, refresh_token: Optional[str] = None + ) -> Optional[Credentials]: + creds = None + + if token: + creds = Credentials( + token=token, + refresh_token=refresh_token, + token_uri="https://oauth2.googleapis.com/token", + client_id=os.getenv("GOOGLE_CLIENT_ID"), + client_secret=os.getenv("GOOGLE_CLIENT_SECRET"), + ) + elif os.path.exists(TOKEN_FILE_NAME): + with open(TOKEN_FILE_NAME, "rb") as token: + creds = pickle.load(token) + + # If there are no valid credentials available, raise an exception + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + raise Exception("Invalid or expired credentials. Please provide a valid token.") + + return creds + async def spaces_service_client(self): if self._spaces_service_client is None: self._spaces_service_client = meet_v2.SpacesServiceAsyncClient(credentials=self._credentials) From 9f1027c0bf6177631037604a0ecb011fcf41f3d3 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Fri, 13 Sep 2024 16:47:52 -0500 Subject: [PATCH 24/30] refresh google token --- breathecode/services/google_meet/google_meet.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/breathecode/services/google_meet/google_meet.py b/breathecode/services/google_meet/google_meet.py index bc8ce9b9c..e0d9ce600 100644 --- a/breathecode/services/google_meet/google_meet.py +++ b/breathecode/services/google_meet/google_meet.py @@ -112,9 +112,11 @@ def _get_credentials( client_id=os.getenv("GOOGLE_CLIENT_ID"), client_secret=os.getenv("GOOGLE_CLIENT_SECRET"), ) + creds.refresh(Request()) elif os.path.exists(TOKEN_FILE_NAME): with open(TOKEN_FILE_NAME, "rb") as token: creds = pickle.load(token) + creds.refresh(Request()) # If there are no valid credentials available, raise an exception if not creds or not creds.valid: From fe0b685f27d877edbfed799f7232cfa9c0a512c9 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Fri, 13 Sep 2024 20:04:59 -0500 Subject: [PATCH 25/30] fix a environment, thank you powerful ia for this error --- breathecode/services/google_meet/google_meet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/breathecode/services/google_meet/google_meet.py b/breathecode/services/google_meet/google_meet.py index e0d9ce600..ecee31f04 100644 --- a/breathecode/services/google_meet/google_meet.py +++ b/breathecode/services/google_meet/google_meet.py @@ -110,7 +110,7 @@ def _get_credentials( refresh_token=refresh_token, token_uri="https://oauth2.googleapis.com/token", client_id=os.getenv("GOOGLE_CLIENT_ID"), - client_secret=os.getenv("GOOGLE_CLIENT_SECRET"), + client_secret=os.getenv("GOOGLE_SECRET"), ) creds.refresh(Request()) elif os.path.exists(TOKEN_FILE_NAME): From 2110463858724072b9b8d3a51823662d02dd8e21 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Fri, 13 Sep 2024 20:24:47 -0500 Subject: [PATCH 26/30] changes in scope --- breathecode/authenticate/views.py | 2 +- breathecode/services/google_meet/google_meet.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index 24f4e82de..4eb3015d6 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2027,7 +2027,7 @@ def get_google_token(request, token=None): "client_id": os.getenv("GOOGLE_CLIENT_ID", ""), "redirect_uri": os.getenv("GOOGLE_REDIRECT_URL", ""), "access_type": "offline", # we need offline access to receive refresh token and avoid total expiration - "scope": "https://www.googleapis.com/auth/calendar.events", + "scope": "calendar.events,meetings.space.readonly,meetings.space.created,drive.readonly ", "state": f"token={token.key}&url={url}", } diff --git a/breathecode/services/google_meet/google_meet.py b/breathecode/services/google_meet/google_meet.py index ecee31f04..e646d3e82 100644 --- a/breathecode/services/google_meet/google_meet.py +++ b/breathecode/services/google_meet/google_meet.py @@ -85,7 +85,12 @@ class ListParticipantsRequest(TypedDict): # Scopes for Google Calendar API (used for creating Google Meet links) -SCOPES = ["https://www.googleapis.com/auth/calendar"] +# https://www.googleapis.com/auth/meetings.space.created +# https://www.googleapis.com/auth/meetings.space.readonly +# SCOPES = [ +# "google.apps.meet.v2.SpacesService.CreateSpace", +# "google.apps.meet.v2.SpacesService.GetSpace", +# ] TOKEN_FILE_NAME = "google_cloud_oauth_token.pickle" GOOGLE_CLIENT_SECRET = "client_secret.json" From c123aefb086704fdcaa2039405dce23e1805a985 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Fri, 13 Sep 2024 20:36:55 -0500 Subject: [PATCH 27/30] remove space --- breathecode/authenticate/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index 4eb3015d6..9de814704 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2027,7 +2027,7 @@ def get_google_token(request, token=None): "client_id": os.getenv("GOOGLE_CLIENT_ID", ""), "redirect_uri": os.getenv("GOOGLE_REDIRECT_URL", ""), "access_type": "offline", # we need offline access to receive refresh token and avoid total expiration - "scope": "calendar.events,meetings.space.readonly,meetings.space.created,drive.readonly ", + "scope": "calendar.events,meetings.space.readonly,meetings.space.created,drive.readonly", "state": f"token={token.key}&url={url}", } From 268f179365d5a8ed94ddad0b728dd18b4d16bfe0 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Fri, 13 Sep 2024 21:28:33 -0500 Subject: [PATCH 28/30] changes in scope --- breathecode/authenticate/views.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index 9de814704..74307e23c 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2027,7 +2027,15 @@ def get_google_token(request, token=None): "client_id": os.getenv("GOOGLE_CLIENT_ID", ""), "redirect_uri": os.getenv("GOOGLE_REDIRECT_URL", ""), "access_type": "offline", # we need offline access to receive refresh token and avoid total expiration - "scope": "calendar.events,meetings.space.readonly,meetings.space.created,drive.readonly", + "scope": " ".join( + [ + "https://www.googleapis.com/auth/meetings.space.created", + "https://www.googleapis.com/auth/meetings.space.readonly", + "https://www.googleapis.com/auth/drive.meet.readonly", + "https://www.googleapis.com/auth/calendar.events", + ] + ), + # "scope": "calendar.events,meetings.space.readonly,meetings.space.created,drive.readonly", "state": f"token={token.key}&url={url}", } From 87dc60b226f8d66d05d48ba926f96b62740fd24e Mon Sep 17 00:00:00 2001 From: jefer94 Date: Sat, 14 Sep 2024 07:38:59 -0500 Subject: [PATCH 29/30] update scopes --- breathecode/authenticate/views.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index 74307e23c..948a44e98 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2030,12 +2030,11 @@ def get_google_token(request, token=None): "scope": " ".join( [ "https://www.googleapis.com/auth/meetings.space.created", - "https://www.googleapis.com/auth/meetings.space.readonly", + # "https://www.googleapis.com/auth/meetings.space.readonly", "https://www.googleapis.com/auth/drive.meet.readonly", - "https://www.googleapis.com/auth/calendar.events", + # "https://www.googleapis.com/auth/calendar.events", ] ), - # "scope": "calendar.events,meetings.space.readonly,meetings.space.created,drive.readonly", "state": f"token={token.key}&url={url}", } From 8ddd16d084bd29c87b0bb60e5d4caf512703e4e0 Mon Sep 17 00:00:00 2001 From: jefer94 Date: Mon, 16 Sep 2024 11:55:19 -0500 Subject: [PATCH 30/30] update google callback and forward meet url --- .../tests/urls/tests_google_token.py | 9 +- breathecode/authenticate/views.py | 17 ++-- .../tests_meet_slug_service_slug.py | 94 +++++++++++++++++++ breathecode/mentorship/views.py | 3 + 4 files changed, 116 insertions(+), 7 deletions(-) diff --git a/breathecode/authenticate/tests/urls/tests_google_token.py b/breathecode/authenticate/tests/urls/tests_google_token.py index 0ac67b6aa..cc0f84544 100644 --- a/breathecode/authenticate/tests/urls/tests_google_token.py +++ b/breathecode/authenticate/tests/urls/tests_google_token.py @@ -80,7 +80,14 @@ def test_redirect(database: capy.Database, client: capy.Client, token: Any): "client_id": "123456.apps.googleusercontent.com", "redirect_uri": "https://breathecode.herokuapp.com/v1/auth/google/callback", "access_type": "offline", - "scope": "https://www.googleapis.com/auth/calendar.events", + "scope": " ".join( + [ + "https://www.googleapis.com/auth/meetings.space.created", + # "https://www.googleapis.com/auth/meetings.space.readonly", + "https://www.googleapis.com/auth/drive.meet.readonly", + # "https://www.googleapis.com/auth/calendar.events", + ] + ), "state": f"token={model.token.key}&url={callback_url}", } diff --git a/breathecode/authenticate/views.py b/breathecode/authenticate/views.py index 948a44e98..e64a180d2 100644 --- a/breathecode/authenticate/views.py +++ b/breathecode/authenticate/views.py @@ -2104,15 +2104,20 @@ async def save_google_token(request): if "refresh_token" in body: refresh = body["refresh_token"] - await CredentialsGoogle.objects.filter(user__id=user.id).adelete() - - google_credentials = CredentialsGoogle( + google_credentials, created = await CredentialsGoogle.objects.aget_or_create( user=user, - token=body["access_token"], - refresh_token=refresh, expires_at=timezone.now() + timedelta(seconds=body["expires_in"]), + defaults={ + "token": body["access_token"], + "refresh_token": refresh, + }, ) - await google_credentials.asave() + if created is False: + google_credentials.token = body["access_token"] + if refresh: + google_credentials.refresh_token = refresh + + await google_credentials.asave() return HttpResponseRedirect(redirect_to=state["url"][0] + "?token=" + token.key) diff --git a/breathecode/mentorship/tests/urls_shortner/tests_meet_slug_service_slug.py b/breathecode/mentorship/tests/urls_shortner/tests_meet_slug_service_slug.py index 9a2c5f01c..3143e0cd2 100644 --- a/breathecode/mentorship/tests/urls_shortner/tests_meet_slug_service_slug.py +++ b/breathecode/mentorship/tests/urls_shortner/tests_meet_slug_service_slug.py @@ -1363,6 +1363,100 @@ def test_with_mentor_profile__passing_session__passing_mentee__passing_redirect( self.bc.database.delete("auth.Permission") self.bc.database.delete("payments.Service") + """ + 🔽🔽🔽 Google Meet + """ + + @patch("breathecode.mentorship.actions.mentor_is_ready", MagicMock()) + @patch( + "os.getenv", + MagicMock( + side_effect=apply_get_env( + { + "DAILY_API_URL": URL, + "DAILY_API_KEY": API_KEY, + } + ) + ), + ) + @patch( + "requests.request", + apply_requests_request_mock( + [ + ( + 201, + f"{URL}/v1/rooms", + { + "name": ROOM_NAME, + "url": ROOM_URL, + }, + ) + ] + ), + ) + def test_google_meet_redirect(self): + cases = [ + { + "status": x, + "online_meeting_url": self.bc.fake.url(), + "booking_url": self.bc.fake.url(), + } + for x in ["ACTIVE", "UNLISTED"] + ] + service = {"consumer": "JOIN_MENTORSHIP"} + + id = 0 + for mentor_profile in cases: + id += 1 + + user = {"first_name": "", "last_name": ""} + base = self.bc.database.create(user=user, token=1, service=service) + + mentorship_session = {"mentee_id": None, "online_meeting_url": "https://meet.google.com/abc123"} + academy = {"available_as_saas": False} + model = self.bc.database.create( + mentor_profile=mentor_profile, + mentorship_session=mentorship_session, + user=user, + mentorship_service={"language": "en", "video_provider": "GOOGLE_MEET"}, + academy=academy, + ) + + model.mentorship_session.mentee = None + model.mentorship_session.save() + + querystring = self.bc.format.to_querystring( + { + "token": base.token.key, + } + ) + url = ( + reverse_lazy( + "mentorship_shortner:meet_slug_service_slug", + kwargs={"mentor_slug": model.mentor_profile.slug, "service_slug": model.mentorship_service.slug}, + ) + + f"?{querystring}" + ) + response = self.client.get(url) + + self.assertEqual(response.status_code, status.HTTP_302_FOUND) + self.assertEqual(response.url, model.mentorship_session.online_meeting_url) + + self.assertEqual( + self.bc.database.list_of("mentorship.MentorProfile"), + [ + self.bc.format.to_dict(model.mentor_profile), + ], + ) + self.assertEqual(self.bc.database.list_of("payments.Consumable"), []) + self.assertEqual(self.bc.database.list_of("payments.ConsumptionSession"), []) + + # teardown + self.bc.database.delete("mentorship.MentorProfile") + self.bc.database.delete("auth.Permission") + self.bc.database.delete("auth.User") + self.bc.database.delete("payments.Service") + """ 🔽🔽🔽 GET without MentorProfile, good statuses with mentor urls, MentorshipSession without mentee passing session and mentee but mentee does not exist, user without name diff --git a/breathecode/mentorship/views.py b/breathecode/mentorship/views.py index 663040152..bf496635d 100644 --- a/breathecode/mentorship/views.py +++ b/breathecode/mentorship/views.py @@ -434,6 +434,9 @@ def render_start_session(self, session): if "heading" not in obj: obj["heading"] = session.mentor.academy.name + if session.online_meeting_url and "meet.google.com" in session.online_meeting_url: + return HttpResponseRedirect(session.online_meeting_url) + return render( self.request, "message.html",