commit 771a20225fbc5e4927b482ded80ae0762a9a0070 Author: Lectom C Han Date: Mon Oct 13 14:20:00 2025 +0900 initial commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..bd6739d --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +DESCOPE_PROJECT_ID="YOUR_PROJECT_ID" +DESCOPE_MANAGEMENT_KEY="YOUR_MANAGEMENT_KEY" diff --git a/.gitea/workflows/create_bulk_users.yml b/.gitea/workflows/create_bulk_users.yml new file mode 100644 index 0000000..91a2fb8 --- /dev/null +++ b/.gitea/workflows/create_bulk_users.yml @@ -0,0 +1,52 @@ +name: Descope 사용자 일괄 처리 + +on: + workflow_dispatch: + inputs: + google_sheet_url: + description: "처리할 Google Sheets의 CSV 내보내기 URL" + required: true + type: string + action_type: + description: "실행할 작업" + required: true + type: choice + options: + - create + - change_password + expiry_date: + description: "라이선스 만료일 (YYYY-MM-DD). 입력 시 모든 사용자의 만료일을 덮어씁니다." + required: false + type: string + +jobs: + run_bulk_operation: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: uv 설치 + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + cache-dependency-glob: "**/uv.lock" + + - name: uv 가상 환경 캐시 + uses: actions/cache@v4 + with: + path: .venv + key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }} + restore-keys: | + ${{ runner.os }}-uv- + + - name: 스크립트 실행 + env: + DESCOPE_PROJECT_ID: ${{ secrets.DESCOPE_PROJECT_ID }} + DESCOPE_MANAGEMENT_KEY: ${{ secrets.DESCOPE_MANAGEMENT_KEY }} + run: | + EXPIRY_DATE_ARG="" + if [[ -n "${{ github.event.inputs.expiry_date }}" ]]; then + EXPIRY_DATE_ARG="--expiry-date ${{ github.event.inputs.expiry_date }}" + fi + uv run python main.py "${{ github.event.inputs.google_sheet_url }}" --action ${{ github.event.inputs.action_type }} $EXPIRY_DATE_ARG \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c65b218 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv +.env +jobs +jobs/* \ No newline at end of file diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..a1a48e9 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,16 @@ +# 프로젝트 개요 + +CSV 파일을 이용해 사용자 계정을 일괄 생성하고 비밀번호를 변경하는 Python 스크립트를 개발합니다. + +## 주요 기능 + +- CSV 파일 입력 처리 +- CSV 데이터를 JSON으로 변환 +- 사용자 계정 일괄 생성 +- 사용자 비밀번호 일괄 변경 + +## 기술 스택 + +- Python +- uv (가상 환경 및 패키지 관리) +- 표준 라이브러리: `csv`, `json` diff --git a/README.md b/README.md new file mode 100644 index 0000000..179561a --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +# Descope 사용자 일괄 관리 스크립트 + +이 프로젝트는 CSV 파일 또는 Google Sheets URL을 사용하여 Descope 사용자를 일괄 생성하거나 비밀번호를 변경하는 Python 스크립트입니다. + +## 주요 기능 + +- CSV 파일 또는 Google Sheets URL을 이용한 사용자 일괄 생성 +- CSV 파일 또는 Google Sheets URL을 이용한 사용자 비밀번호 일괄 변경 +- 사용자 라이선스 만료일(`egBimLExpiryDate`) 일괄 지정 기능 +- Gitea Actions를 통한 워크플로우 자동화 + +## 요구 사항 + +- Python 3.13 이상 +- [uv](https://github.com/astral-sh/uv) (가상 환경 및 패키지 관리 도구) +- Descope Project ID 및 Management Key + +## 로컬 환경에서 실행 + +### 1. 설치 및 설정 + +1. **저장소 복제** + ```bash + git clone https://gitea.hmac.kr/lectom/descope-bulk-user-script.git + cd descope-bulk-user-script + ``` + +2. **가상 환경 생성 및 의존성 설치** + ```bash + uv venv + source .venv/bin/activate + uv sync + ``` + +3. **환경 변수 설정** + 프로젝트 루트에 `.env` 파일을 생성하고 Descope 키를 추가합니다. (`.env.example` 참고) + ``` + DESCOPE_PROJECT_ID="YOUR_PROJECT_ID" + DESCOPE_MANAGEMENT_KEY="YOUR_MANAGEMENT_KEY" + ``` + +### 2. 사용법 + +스크립트는 `main.py`를 통해 실행하며, 처리할 데이터 소스(CSV 파일 또는 Google Sheets URL)와 함께 수행할 작업을 지정합니다. + +- `source`: 처리할 CSV 파일 경로 또는 Google Sheets URL +- `--action`: 수행할 작업 (`create`, `change_password`, `test`) +- `--expiry-date`: (선택 사항) 라이선스 만료일을 `YYYY-MM-DD` 형식으로 지정. 지정 시 모든 사용자의 `egBimLExpiryDate` 값을 덮어씁니다. + +#### Google Sheets URL 준비 +Google Sheets를 데이터 소스로 사용하려면, 해당 시트에서 **파일 > 공유 > 웹에 게시**를 선택하세요. **링크** 탭에서 **"쉼표로 구분된 값(.csv)"** 형식을 선택하고 게시하여 생성된 URL을 사용해야 합니다. + +#### 실행 예시 + +- **로컬 CSV 파일로 사용자 생성** + ```bash + python main.py jobs/sample.csv --action create + ``` + +- **Google Sheets URL로 사용자 생성 및 만료일 지정** + ```bash + python main.py "https://docs.google.com/spreadsheets/d/e/.../pub?output=csv" --action create --expiry-date 2025-12-31 + ``` + +- **비밀번호 변경** + ```bash + python main.py users_passwords.csv --action change_password + ``` + +- **단일 사용자 생성 테스트** + CSV 파일 없이 API 연동을 빠르게 테스트합니다. + ```bash + python main.py --action test + ``` + +## Gitea Actions를 이용한 실행 + +이 저장소의 Gitea Actions 워크플로우를 사용하면 로컬 환경 설정 없이 웹 UI에서 스크립트를 실행할 수 있습니다. + +### 1. 사전 준비: Gitea Secrets 설정 + +워크플로우가 Descope API에 접근하려면 Gitea 저장소에 API 키를 Secret으로 등록해야 합니다. 이 작업은 저장소 관리자가 한 번만 수행하면 됩니다. + +1. Gitea 저장소에서 **Settings** > **Secrets** 탭으로 이동합니다. +2. **Create Secret** 버튼을 클릭하여 다음 두 개의 Secret을 추가합니다. + - **`DESCOPE_PROJECT_ID`**: Descope 프로젝트 ID를 값으로 입력합니다. + - **`DESCOPE_MANAGEMENT_KEY`**: Descope 관리 키를 값으로 입력합니다. + +### 2. 워크플로우 실행 방법 + +1. 저장소의 **Actions** 탭으로 이동합니다. +2. 왼쪽 사이드바에서 **Descope 사용자 일괄 처리** 워크플로우를 선택합니다. +3. **Run Workflow** 버튼을 클릭합니다. +4. 나타나는 입력 필드에 값을 채웁니다. + - **`google_sheet_url`**: 처리할 Google Sheets의 CSV 내보내기 URL. (자세한 내용은 "Google Sheets URL 준비" 섹션 참고) + - **`action_type`**: 실행할 작업 (`create` 또는 `change_password`) + - **`expiry_date`**: (선택 사항) `YYYY-MM-DD` 형식의 라이선스 만료일. 입력 시 모든 사용자의 만료일을 이 값으로 덮어씁니다. +5. 입력 확인 후, **Run Workflow** 버튼을 다시 클릭하여 실행을 시작합니다. + +### 3. 실행 결과 확인 + +워크플로우가 실행되면 Actions 목록에 새로운 실행 항목이 나타납니다. + +1. 해당 실행 항목을 클릭하여 상세 페이지로 들어갑니다. +2. `run_bulk_operation` 작업을 클릭하면 스크립트의 실행 로그를 실시간으로 확인할 수 있습니다. +3. 사용자 생성 또는 비밀번호 변경 과정에서 발생하는 모든 성공 및 오류 메시지가 이 로그에 표시됩니다. 작업이 예상대로 완료되었는지 반드시 로그를 통해 확인하세요. + +## CSV 데이터 형식 + +### 사용자 생성 (`create`) + +| 헤더 | 설명 | 필수 | +| ------------------ | ------------------------------------------------------------------ | ---- | +| `login_id` | 사용자의 로그인 ID | 예 | +| `email` | 사용자 이메일 주소 | 예 | +| `display_name` | 사용자 표시 이름 | 예 | +| `tenants` | 사용자를 할당할 테넌트 ID의 배열 (JSON 형식 문자열) | 아니요 | +| `role_name` | `tenants`에 명시된 모든 테넌트에서 할당할 역할 이름 | 아니요 | +| `company` | 사용자 정의 속성 'company' | 아니요 | +| `egBimLExpiryDate` | 사용자 정의 속성, **UTC 타임스탬프(초)** 값. `--expiry-date`로 덮어쓸 수 있음. | 아니요 | + +**참고:** +- `tenants` 필드는 `["tenant-id-1", "tenant-id-2"]` 와 같이 JSON 배열 형식의 문자열로 입력해야 합니다. + +### 비밀번호 변경 (`change_password`) + +| 헤더 | 설명 | 필수 | +| ---------------- | ------------------ | ---- | +| `login_id` | 사용자의 로그인 ID | 예 | +| `new_password` | 새 비밀번호 | 예 | \ No newline at end of file diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..c1e9863 --- /dev/null +++ b/TODO.md @@ -0,0 +1,8 @@ +# TODO List + +- [x] 프로젝트 계획 수립 및 `GEMINI.md`, `TODO.md` 파일 생성 (2025-08-21 15:03:02) +- [x] 기본 Python 스크립트 구조 작성 (2025-08-21 15:08:34) +- [x] CSV 파일 파싱 및 JSON 변환 기능 구현 (2025-08-21 15:08:34) +- [x] 사용자 계정 생성 기능 구현 (벌크 처리) (2025-08-21 15:08:34) +- [x] 사용자 비밀번호 변경 기능 구현 (벌크 처리) (2025-08-21 15:08:34) +- [ ] 스크립트 실행 및 테스트 diff --git a/main.py b/main.py new file mode 100644 index 0000000..ea8bfea --- /dev/null +++ b/main.py @@ -0,0 +1,263 @@ +import csv +import argparse +import os +import time +import ast +import requests +import io +from datetime import datetime, timezone +from descope import ( + AssociatedTenant, + DescopeClient, + AuthException, +) + +def get_descope_client(): + """ + 환경 변수에서 Descope Project ID와 Management Key를 읽어 + DescopeClient를 초기화하고 반환합니다. + """ + project_id = os.getenv("DESCOPE_PROJECT_ID") + management_key = os.getenv("DESCOPE_MANAGEMENT_KEY") + + if not project_id or not management_key: + raise ValueError("DESCOPE_PROJECT_ID와 DESCOPE_MANAGEMENT_KEY 환경 변수를 설정해야 합니다.") + + return DescopeClient(project_id=project_id, management_key=management_key) + + +def csv_to_dict(csv_file_path): + """ + CSV 파일을 읽어 딕셔너리 리스트로 변환합니다. + """ + data = [] + with open(csv_file_path, mode='r', encoding='utf-8') as csv_file: + csv_reader = csv.DictReader(csv_file) + for row in csv_reader: + data.append(row) + return data + +def csv_from_google_sheet_url(url: str) -> list[dict]: + """ + Google Sheets URL에서 CSV 데이터를 가져와 딕셔너리 리스트로 변환합니다. + URL은 '.../export?format=csv' 형식이어야 합니다. + """ + print(f"Google Sheets에서 데이터 다운로드 중: {url}") + try: + response = requests.get(url) + response.raise_for_status() # HTTP 오류가 발생하면 예외를 발생시킵니다. + + # Google Sheets는 종종 UTF-8-sig로 인코딩된 CSV를 반환합니다 (BOM 포함). + csv_content = response.content.decode('utf-8-sig') + + # 문자열을 파일처럼 다루기 위해 io.StringIO를 사용합니다. + csv_file = io.StringIO(csv_content) + + csv_reader = csv.DictReader(csv_file) + data = [row for row in csv_reader] + print("다운로드 및 파싱 완료.") + return data + except requests.exceptions.RequestException as e: + raise RuntimeError(f"Google Sheets URL에서 데이터를 가져오는 데 실패했습니다: {e}") + except Exception as e: + raise RuntimeError(f"CSV 데이터 처리 중 오류 발생: {e}") + + +def create_users(users_data, expiry_timestamp=None): + """ + CSV에서 읽어온 사용자 데이터를 기반으로 Descope를 통해 계정을 일괄 생성합니다. + 각 요청 사이에 0.5초의 지연을 줍니다. + """ + print("--- 사용자 계정 일괄 생성을 시작합니다 ---") + try: + descope_client = get_descope_client() + for user in users_data: + try: + login_id = user.get('login_id') + if not login_id: + print(f"경고: 'login_id'가 없는 행을 건너뜁니다: {user}") + continue + + email = user.get('email') + display_name = user.get('display_name') + + user_tenants = [] + tenants_str = user.get('tenants') + if tenants_str: + try: + # ast.literal_eval을 사용하여 안전하게 문자열을 Python 객체로 변환 + tenant_ids = ast.literal_eval(tenants_str) + roles = [user.get('role_name')] if user.get('role_name') else [] + + if isinstance(tenant_ids, list): + for tenant_id in tenant_ids: + user_tenants.append(AssociatedTenant(tenant_id, roles)) + else: + print(f"경고: {login_id}의 'tenants' 필드가 리스트 형식이 아닙니다: {tenants_str}") + except (ValueError, SyntaxError): + print(f"경고: {login_id}의 'tenants' 필드를 파싱할 수 없습니다: {tenants_str}") + + custom_attributes = {} + if user.get('custom_attributes_key') and user.get('custom_attributes_value'): + custom_attributes[user['custom_attributes_key']] = user['custom_attributes_value'] + + if user.get('company'): + custom_attributes['company'] = user['company'] + + custom_attributes['completeForm'] = True + + # egBimLExpiryDate 처리: 인자로 받은 타임스탬프가 있으면 덮어쓰고, 없으면 CSV 값 사용 + if expiry_timestamp is not None: + custom_attributes['egBimLExpiryDate'] = expiry_timestamp + elif user.get('egBimLExpiryDate'): + custom_attributes['egBimLExpiryDate'] = user['egBimLExpiryDate'] + + + status = user.get('status', 'activated') # 기본값 'activated' + + print(f"사용자 생성 시도: {login_id}") + descope_client.mgmt.user.create( + login_id=login_id, + email=email, + display_name=display_name, + user_tenants=user_tenants, + custom_attributes=custom_attributes, + ) + print(f"성공: {login_id} 사용자가 생성되었습니다.") + + # 상태 업데이트가 필요한 경우 (기본값과 다른 경우) + if status != 'activated': + try: + print(f"사용자 상태 업데이트 시도: {login_id} -> {status}") + descope_client.mgmt.user.update_status(login_id=login_id, status=status) + print(f"성공: {login_id} 사용자의 상태가 {status}로 변경되었습니다.") + except AuthException as e: + print(f"오류: {login_id} 사용자 상태 업데이트 실패 - {e}") + + time.sleep(0.5) + + except AuthException as e: + print(f"오류: {login_id} 사용자 생성 실패 - {e}") + except Exception as e: + print(f"예상치 못한 오류 발생 ({login_id}): {e}") + + except ValueError as e: + print(f"오류: {e}") + except Exception as e: + print(f"Descope 클라이언트 초기화 중 오류 발생: {e}") + + print("--- 사용자 계정 일괄 생성이 완료되었습니다 ---") + + +def change_passwords(users_data): + """ + CSV에서 읽어온 사용자 데이터를 기반으로 Descope를 통해 비밀번호를 일괄 변경합니다. + """ + print("--- 사용자 비밀번호 일괄 변경을 시작합니다 ---") + try: + descope_client = get_descope_client() + for user in users_data: + try: + login_id = user.get('login_id') + new_password = user.get('new_password') + + if not login_id or not new_password: + print(f"경고: 'login_id' 또는 'new_password'가 없는 행을 건너뜁니다: {user}") + continue + + print(f"비밀번호 변경 시도: {login_id}") + descope_client.mgmt.user.set_active_password( + login_id=login_id, + password=new_password, + ) + descope_client.mgmt.user.activate( + login_id=login_id + ) + print(f"성공: {login_id} 사용자의 비밀번호가 변경되었습니다.") + time.sleep(0.5) + + except AuthException as e: + print(f"오류: {login_id} 사용자의 비밀번호 변경 실패 - {e}") + except Exception as e: + print(f"예상치 못한 오류 발생 ({login_id}): {e}") + + except ValueError as e: + print(f"오류: {e}") + except Exception as e: + print(f"Descope 클라이언트 초기화 중 오류 발생: {e}") + + print("--- 사용자 비밀번호 일괄 변경이 완료되었습니다 ---") + +def test_create_single_user(): + """ + 단일 사용자 생성을 테스트하는 함수입니다. + """ + print("--- 단일 사용자 생성 테스트를 시작합니다 ---") + test_user_data = [{ + 'login_id': 'testuser.gemini@example.com', + 'email': 'testuser.gemini@example.com', + 'display_name': 'Gemini Test User', + 'tenants': '["T31ZmUcwOZbwk0y3YmMxrPCpzpQR"]', # 실제 테스트용 Tenant ID로 변경 필요 + 'company': 'Gemini Test Inc.', + 'egBimLExpiryDate': 1798729200, + 'status': 'activated', + 'verifiedEmail': True, + 'is_test_user': True + }] + create_users(test_user_data) + print("--- 단일 사용자 생성 테스트가 완료되었습니다 ---") + + +def main(): + """ + 메인 실행 함수 + """ + parser = argparse.ArgumentParser(description="CSV 파일 또는 Google Sheets URL을 이용해 Descope 사용자를 일괄 생성하거나 비밀번호를 변경합니다.") + parser.add_argument("source", nargs='?', default=None, help="입력으로 사용할 CSV 파일 경로 또는 Google Sheets URL. 'test' action의 경우 필요하지 않습니다.") + parser.add_argument("--action", choices=['create', 'change_password', 'test'], required=True, help="수행할 작업 (create: 사용자 생성, change_password: 비밀번호 변경, test: 단일 사용자 생성 테스트)") + parser.add_argument("--expiry-date", help="사용자 라이선스 만료일을 'YYYY-MM-DD' 형식으로 지정합니다. 이 값을 지정하면 CSV 파일의 모든 'egBimLExpiryDate' 값을 덮어씁니다.") + + args = parser.parse_args() + + if args.action == 'test': + test_create_single_user() + return + + if not args.source: + print("오류: 'create' 또는 'change_password' 작업을 위해서는 CSV 파일 경로 또는 Google Sheets URL이 필요합니다.") + parser.print_help() + return + + try: + users_data = [] + if args.source.startswith('http'): + users_data = csv_from_google_sheet_url(args.source) + else: + users_data = csv_to_dict(args.source) + + expiry_timestamp = None + if args.expiry_date: + try: + # 'YYYY-MM-DD' 형식의 문자열을 datetime 객체로 변환 + dt = datetime.strptime(args.expiry_date, '%Y-%m-%d') + # UTC 타임존을 명시적으로 설정 + dt_utc = dt.replace(tzinfo=timezone.utc) + # UTC 타임스탬프(초)로 변환 + expiry_timestamp = int(dt_utc.timestamp()) + print(f"만료 날짜가 {args.expiry_date}로 설정되었습니다 (UTC 타임스탬프: {expiry_timestamp}).") + except ValueError: + print(f"오류: 날짜 형식이 잘못되었습니다. 'YYYY-MM-DD' 형식을 사용해주세요. (입력값: {args.expiry_date})") + return + + if args.action == 'create': + create_users(users_data, expiry_timestamp) + elif args.action == 'change_password': + change_passwords(users_data) + + except FileNotFoundError: + print(f"오류: 파일을 찾을 수 없습니다 - {args.source}") + except Exception as e: + print(f"오류가 발생했습니다: {e}") + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..f6f3ddd --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "descope-test" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "descope>=1.7.9", +] diff --git a/sample_import.csv b/sample_import.csv new file mode 100644 index 0000000..6500506 --- /dev/null +++ b/sample_import.csv @@ -0,0 +1,2 @@ +login_id,status,display_name,email,Is Email Verified,Phone,Is Phone Verified,tenants,role,Is Test User,Given Name,Middle Name,Family Name,Consent Expiration,company,completeForm,egBimLExpiryDate,new_password +sample@hanmaceng.co.kr,activated,전산-샘플,sample@hanmaceng.co.kr,TRUE,,FALSE,"[""TENANT_ID_FROM_DESCOPE""]",,FALSE,,,,,한맥기술그룹,TRUE,1798729200000,NEW_PASSWORD_FOR_RESET diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..2043d7a --- /dev/null +++ b/uv.lock @@ -0,0 +1,235 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "certifi" +version = "2025.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989, upload-time = "2024-09-04T20:44:28.956Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802, upload-time = "2024-09-04T20:44:30.289Z" }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792, upload-time = "2024-09-04T20:44:32.01Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893, upload-time = "2024-09-04T20:44:33.606Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810, upload-time = "2024-09-04T20:44:35.191Z" }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200, upload-time = "2024-09-04T20:44:36.743Z" }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447, upload-time = "2024-09-04T20:44:38.492Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358, upload-time = "2024-09-04T20:44:40.046Z" }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469, upload-time = "2024-09-04T20:44:41.616Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475, upload-time = "2024-09-04T20:44:43.733Z" }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +] + +[[package]] +name = "cryptography" +version = "45.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/0d/d13399c94234ee8f3df384819dc67e0c5ce215fb751d567a55a1f4b028c7/cryptography-45.0.6.tar.gz", hash = "sha256:5c966c732cf6e4a276ce83b6e4c729edda2df6929083a952cc7da973c539c719", size = 744949, upload-time = "2025-08-05T23:59:27.93Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/29/2793d178d0eda1ca4a09a7c4e09a5185e75738cc6d526433e8663b460ea6/cryptography-45.0.6-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:048e7ad9e08cf4c0ab07ff7f36cc3115924e22e2266e034450a890d9e312dd74", size = 7042702, upload-time = "2025-08-05T23:58:23.464Z" }, + { url = "https://files.pythonhosted.org/packages/b3/b6/cabd07410f222f32c8d55486c464f432808abaa1f12af9afcbe8f2f19030/cryptography-45.0.6-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:44647c5d796f5fc042bbc6d61307d04bf29bccb74d188f18051b635f20a9c75f", size = 4206483, upload-time = "2025-08-05T23:58:27.132Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9e/f9c7d36a38b1cfeb1cc74849aabe9bf817990f7603ff6eb485e0d70e0b27/cryptography-45.0.6-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e40b80ecf35ec265c452eea0ba94c9587ca763e739b8e559c128d23bff7ebbbf", size = 4429679, upload-time = "2025-08-05T23:58:29.152Z" }, + { url = "https://files.pythonhosted.org/packages/9c/2a/4434c17eb32ef30b254b9e8b9830cee4e516f08b47fdd291c5b1255b8101/cryptography-45.0.6-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:00e8724bdad672d75e6f069b27970883179bd472cd24a63f6e620ca7e41cc0c5", size = 4210553, upload-time = "2025-08-05T23:58:30.596Z" }, + { url = "https://files.pythonhosted.org/packages/ef/1d/09a5df8e0c4b7970f5d1f3aff1b640df6d4be28a64cae970d56c6cf1c772/cryptography-45.0.6-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a3085d1b319d35296176af31c90338eeb2ddac8104661df79f80e1d9787b8b2", size = 3894499, upload-time = "2025-08-05T23:58:32.03Z" }, + { url = "https://files.pythonhosted.org/packages/79/62/120842ab20d9150a9d3a6bdc07fe2870384e82f5266d41c53b08a3a96b34/cryptography-45.0.6-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1b7fa6a1c1188c7ee32e47590d16a5a0646270921f8020efc9a511648e1b2e08", size = 4458484, upload-time = "2025-08-05T23:58:33.526Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/1bc3634d45ddfed0871bfba52cf8f1ad724761662a0c792b97a951fb1b30/cryptography-45.0.6-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:275ba5cc0d9e320cd70f8e7b96d9e59903c815ca579ab96c1e37278d231fc402", size = 4210281, upload-time = "2025-08-05T23:58:35.445Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fe/ffb12c2d83d0ee625f124880a1f023b5878f79da92e64c37962bbbe35f3f/cryptography-45.0.6-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f4028f29a9f38a2025abedb2e409973709c660d44319c61762202206ed577c42", size = 4456890, upload-time = "2025-08-05T23:58:36.923Z" }, + { url = "https://files.pythonhosted.org/packages/8c/8e/b3f3fe0dc82c77a0deb5f493b23311e09193f2268b77196ec0f7a36e3f3e/cryptography-45.0.6-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ee411a1b977f40bd075392c80c10b58025ee5c6b47a822a33c1198598a7a5f05", size = 4333247, upload-time = "2025-08-05T23:58:38.781Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a6/c3ef2ab9e334da27a1d7b56af4a2417d77e7806b2e0f90d6267ce120d2e4/cryptography-45.0.6-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e2a21a8eda2d86bb604934b6b37691585bd095c1f788530c1fcefc53a82b3453", size = 4565045, upload-time = "2025-08-05T23:58:40.415Z" }, + { url = "https://files.pythonhosted.org/packages/31/c3/77722446b13fa71dddd820a5faab4ce6db49e7e0bf8312ef4192a3f78e2f/cryptography-45.0.6-cp311-abi3-win32.whl", hash = "sha256:d063341378d7ee9c91f9d23b431a3502fc8bfacd54ef0a27baa72a0843b29159", size = 2928923, upload-time = "2025-08-05T23:58:41.919Z" }, + { url = "https://files.pythonhosted.org/packages/38/63/a025c3225188a811b82932a4dcc8457a26c3729d81578ccecbcce2cb784e/cryptography-45.0.6-cp311-abi3-win_amd64.whl", hash = "sha256:833dc32dfc1e39b7376a87b9a6a4288a10aae234631268486558920029b086ec", size = 3403805, upload-time = "2025-08-05T23:58:43.792Z" }, + { url = "https://files.pythonhosted.org/packages/5b/af/bcfbea93a30809f126d51c074ee0fac5bd9d57d068edf56c2a73abedbea4/cryptography-45.0.6-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:3436128a60a5e5490603ab2adbabc8763613f638513ffa7d311c900a8349a2a0", size = 7020111, upload-time = "2025-08-05T23:58:45.316Z" }, + { url = "https://files.pythonhosted.org/packages/98/c6/ea5173689e014f1a8470899cd5beeb358e22bb3cf5a876060f9d1ca78af4/cryptography-45.0.6-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0d9ef57b6768d9fa58e92f4947cea96ade1233c0e236db22ba44748ffedca394", size = 4198169, upload-time = "2025-08-05T23:58:47.121Z" }, + { url = "https://files.pythonhosted.org/packages/ba/73/b12995edc0c7e2311ffb57ebd3b351f6b268fed37d93bfc6f9856e01c473/cryptography-45.0.6-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea3c42f2016a5bbf71825537c2ad753f2870191134933196bee408aac397b3d9", size = 4421273, upload-time = "2025-08-05T23:58:48.557Z" }, + { url = "https://files.pythonhosted.org/packages/f7/6e/286894f6f71926bc0da67408c853dd9ba953f662dcb70993a59fd499f111/cryptography-45.0.6-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:20ae4906a13716139d6d762ceb3e0e7e110f7955f3bc3876e3a07f5daadec5f3", size = 4199211, upload-time = "2025-08-05T23:58:50.139Z" }, + { url = "https://files.pythonhosted.org/packages/de/34/a7f55e39b9623c5cb571d77a6a90387fe557908ffc44f6872f26ca8ae270/cryptography-45.0.6-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dac5ec199038b8e131365e2324c03d20e97fe214af051d20c49db129844e8b3", size = 3883732, upload-time = "2025-08-05T23:58:52.253Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b9/c6d32edbcba0cd9f5df90f29ed46a65c4631c4fbe11187feb9169c6ff506/cryptography-45.0.6-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:18f878a34b90d688982e43f4b700408b478102dd58b3e39de21b5ebf6509c301", size = 4450655, upload-time = "2025-08-05T23:58:53.848Z" }, + { url = "https://files.pythonhosted.org/packages/77/2d/09b097adfdee0227cfd4c699b3375a842080f065bab9014248933497c3f9/cryptography-45.0.6-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5bd6020c80c5b2b2242d6c48487d7b85700f5e0038e67b29d706f98440d66eb5", size = 4198956, upload-time = "2025-08-05T23:58:55.209Z" }, + { url = "https://files.pythonhosted.org/packages/55/66/061ec6689207d54effdff535bbdf85cc380d32dd5377173085812565cf38/cryptography-45.0.6-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:eccddbd986e43014263eda489abbddfbc287af5cddfd690477993dbb31e31016", size = 4449859, upload-time = "2025-08-05T23:58:56.639Z" }, + { url = "https://files.pythonhosted.org/packages/41/ff/e7d5a2ad2d035e5a2af116e1a3adb4d8fcd0be92a18032917a089c6e5028/cryptography-45.0.6-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:550ae02148206beb722cfe4ef0933f9352bab26b087af00e48fdfb9ade35c5b3", size = 4320254, upload-time = "2025-08-05T23:58:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/82/27/092d311af22095d288f4db89fcaebadfb2f28944f3d790a4cf51fe5ddaeb/cryptography-45.0.6-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5b64e668fc3528e77efa51ca70fadcd6610e8ab231e3e06ae2bab3b31c2b8ed9", size = 4554815, upload-time = "2025-08-05T23:59:00.283Z" }, + { url = "https://files.pythonhosted.org/packages/7e/01/aa2f4940262d588a8fdf4edabe4cda45854d00ebc6eaac12568b3a491a16/cryptography-45.0.6-cp37-abi3-win32.whl", hash = "sha256:780c40fb751c7d2b0c6786ceee6b6f871e86e8718a8ff4bc35073ac353c7cd02", size = 2912147, upload-time = "2025-08-05T23:59:01.716Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bc/16e0276078c2de3ceef6b5a34b965f4436215efac45313df90d55f0ba2d2/cryptography-45.0.6-cp37-abi3-win_amd64.whl", hash = "sha256:20d15aed3ee522faac1a39fbfdfee25d17b1284bafd808e1640a74846d7c4d1b", size = 3390459, upload-time = "2025-08-05T23:59:03.358Z" }, +] + +[[package]] +name = "descope" +version = "1.7.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "email-validator" }, + { name = "liccheck" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/2f/9a977d8b266a4f4c0676c3b24b55694611c3694ec315f60089198e3b7350/descope-1.7.9.tar.gz", hash = "sha256:60c265cdf95d07a9e7e807386818185529d4d4bc530d9ccd296c8d0b4f096bef", size = 75821, upload-time = "2025-08-20T21:51:37.214Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/1f/ae0706b953478474d0e8abadb46ee1993d9d7ede9db8a2b8b2078b689747/descope-1.7.9-py3-none-any.whl", hash = "sha256:a24a7d81da27fdee0ee9d35b4ad2109733c2b0617762bbc9407fb3dd63c8c644", size = 82990, upload-time = "2025-08-20T21:51:36.129Z" }, +] + +[[package]] +name = "descope-test" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "descope" }, +] + +[package.metadata] +requires-dist = [{ name = "descope", specifier = ">=1.7.9" }] + +[[package]] +name = "dnspython" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/4a/263763cb2ba3816dd94b08ad3a33d5fdae34ecb856678773cc40a3605829/dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1", size = 345197, upload-time = "2024-10-05T20:14:59.362Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, +] + +[[package]] +name = "email-validator" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/48/ce/13508a1ec3f8bb981ae4ca79ea40384becc868bfae97fd1c942bb3a001b1/email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7", size = 48967, upload-time = "2024-06-20T11:30:30.034Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload-time = "2024-06-20T11:30:28.248Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "liccheck" +version = "0.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "semantic-version" }, + { name = "toml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/84/f0/962ba77fae91ad0cca2ead4fb0ff5aa00f9793c1a78cd807672f9e5a9aa3/liccheck-0.9.2.tar.gz", hash = "sha256:bdc2190f8e95af3c8f9c19edb784ba7d41ecb2bf9189422eae6112bf84c08cd5", size = 16020, upload-time = "2023-09-22T14:23:59.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/bb/fbc7dd6ea215b97b90c35efc8c8f3dbfcbacb91af8c806dff1f49deddd8e/liccheck-0.9.2-py2.py3-none-any.whl", hash = "sha256:15cbedd042515945fe9d58b62e0a5af2f2a7795def216f163bb35b3016a16637", size = 13652, upload-time = "2023-09-22T14:23:57.849Z" }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "semantic-version" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289, upload-time = "2022-05-26T13:35:23.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552, upload-time = "2022-05-26T13:35:21.206Z" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +]