├── .DS_Store ├── .dockerignore ├── .github └── workflows │ └── autoci.yml ├── .gitignore ├── .python-version ├── Dockerfile ├── README.md ├── _auth.py ├── _cronjobs.py ├── _crypto.py ├── _redis.py ├── _search.py ├── _trend.py ├── _utils.py ├── app.py ├── pyproject.toml ├── requirements.txt └── uv.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binaryYuki/animeAPI/50abf03c523a391f91620b39bf3a69aab610d472/.DS_Store -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Include any files or directories that you don't want to be copied to your 2 | # container here (e.g., local build artifacts, temporary files, etc.). 3 | # 4 | # For more help, visit the .dockerignore file reference guide at 5 | # https://docs.docker.com/go/build-context-dockerignore/ 6 | 7 | **/.DS_Store 8 | **/__pycache__ 9 | **/.venv 10 | **/.classpath 11 | **/.dockerignore 12 | **/.env 13 | **/.git 14 | **/.gitignore 15 | **/.project 16 | **/.settings 17 | **/.toolstarget 18 | **/.vs 19 | **/.vscode 20 | **/*.*proj.user 21 | **/*.dbmdl 22 | **/*.jfm 23 | **/charts 24 | **/docker-compose* 25 | **/compose.y*ml 26 | **/Dockerfile* 27 | **/node_modules 28 | **/npm-debug.log 29 | **/obj 30 | **/secrets.dev.yaml 31 | **/values.dev.yaml 32 | # 排除 Python 缓存文件 33 | # 排除虚拟环境目录 34 | venv/ 35 | 36 | # 排除版本控制系统文件 37 | # 排除日志和临时文件 38 | *.log 39 | *.tmp 40 | 41 | # 排除 IDE 配置文件 42 | *.vscode/ 43 | .idea/ 44 | 45 | -------------------------------------------------------------------------------- /.github/workflows/autoci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | 9 | jobs: 10 | analyze: 11 | name: Analyze (${{ matrix.language }}) 12 | # Runner size impacts CodeQL analysis time. To learn more, please see: 13 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 14 | # - https://gh.io/supported-runners-and-hardware-resources 15 | # - https://gh.io/using-larger-runners (GitHub.com only) 16 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 17 | runs-on: ubuntu-24.04 18 | permissions: 19 | # required for all workflows 20 | security-events: write 21 | 22 | # required to fetch internal or private CodeQL packs 23 | packages: read 24 | 25 | # only required for workflows in private repositories 26 | actions: read 27 | contents: read 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | include: 33 | - language: python 34 | build-mode: none 35 | # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' 36 | # Use `c-cpp` to analyze code written in C, C++ or both 37 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 38 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 39 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 40 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 41 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 42 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 43 | steps: 44 | - name: Checkout repository 45 | uses: actions/checkout@v4 46 | 47 | # Initializes the CodeQL tools for scanning. 48 | - name: Initialize CodeQL 49 | uses: github/codeql-action/init@v3 50 | with: 51 | languages: ${{ matrix.language }} 52 | build-mode: ${{ matrix.build-mode }} 53 | # If you wish to specify custom queries, you can do so here or in a config file. 54 | # By default, queries listed here will override any specified in a config file. 55 | # Prefix the list here with "+" to use these queries and those in the config file. 56 | 57 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 58 | # queries: security-extended,security-and-quality 59 | 60 | # If the analyze step fails for one of the languages you are analyzing with 61 | # "We were unable to automatically build your code", modify the matrix above 62 | # to set the build mode to "manual" for that language. Then modify this step 63 | # to build your code. 64 | # ℹ️ Command-line programs to run using the OS shell. 65 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 66 | - if: matrix.build-mode == 'manual' 67 | shell: bash 68 | run: | 69 | echo 'If you are using a "manual" build mode for one or more of the' \ 70 | 'languages you are analyzing, replace this with the commands to build' \ 71 | 'your code, for example:' 72 | echo ' make bootstrap' 73 | echo ' make release' 74 | exit 1 75 | 76 | - name: Perform CodeQL Analysis 77 | uses: github/codeql-action/analyze@v3 78 | with: 79 | category: "/language:${{matrix.language}}" 80 | docker: 81 | runs-on: ubuntu-24.04 82 | needs: analyze 83 | env: 84 | # Configure a constant location for the uv cache 85 | UV_CACHE_DIR: /tmp/.uv-cache 86 | steps: 87 | - name: Cache Docker layers 88 | uses: actions/cache@v3 89 | with: 90 | path: /tmp/.buildx-cache 91 | key: Linux-buildx-69a42aa61e1e332fe10d4019eec4e113b737898a 92 | restore-keys: | 93 | my-project-${{ matrix.arch }}-buildx- 94 | 95 | - name: Restore uv cache 96 | uses: actions/cache@v4 97 | with: 98 | path: /tmp/.uv-cache 99 | key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }} 100 | restore-keys: | 101 | uv-${{ runner.os }}-${{ hashFiles('uv.lock') }} 102 | uv-${{ runner.os }} 103 | 104 | - name: Checkout 105 | uses: actions/checkout@v4 106 | 107 | - name: Set up QEMU 108 | uses: docker/setup-qemu-action@v3 109 | 110 | - name: Set up Docker Buildx 111 | uses: docker/setup-buildx-action@v3 112 | 113 | - name: Read Commit ID 114 | id: commit_id 115 | run: echo "COMMIT_ID=$(git rev-parse HEAD)" >> $GITHUB_ENV 116 | 117 | - name: Read Build Date 118 | id: build_date 119 | run: echo "BUILD_AT=$(date -u +"%Y-%m-%d %H:%M:%S")" >> $GITHUB_ENV 120 | 121 | - name: Verify Environment Variables 122 | run: echo "COMMIT_ID=${{ env.COMMIT_ID }} BUILD_AT=${{ env.BUILD_AT }}" 123 | 124 | - name: Login to Docker Hub 125 | uses: docker/login-action@v3 126 | with: 127 | username: ${{ secrets.DOCKERHUB_USERNAME }} 128 | password: ${{ secrets.DOCKERHUB_TOKEN }} 129 | 130 | - name: Build and push 131 | uses: docker/build-push-action@v6 132 | with: 133 | context: . 134 | platforms: linux/amd64,linux/arm64 135 | push: true 136 | tags: akkk1234/oleapi:test 137 | cache-from: type=local,src=/tmp/.buildx-cache 138 | cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max 139 | build-args: | 140 | COMMIT_ID=${{ env.COMMIT_ID }} 141 | BUILD_AT=${{ env.BUILD_AT }} 142 | 143 | # Temp fix 144 | # https://github.com/docker/build-push-action/issues/252 145 | # https://github.com/moby/buildkit/issues/1896 146 | - name: Move cache 147 | run: | 148 | rm -rf /tmp/.buildx-cache 149 | mv /tmp/.buildx-cache-new /tmp/.buildx-cache 150 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .idea 3 | node_modules 4 | .idea/* 5 | .venv 6 | .venv/* 7 | *.db 8 | __pycache__ 9 | test.db 10 | __pycache__/* 11 | private.pem 12 | public.pem 13 | *.pem 14 | .DS_Store -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.13 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Start from the base image 2 | FROM ghcr.io/astral-sh/uv:python3.13-bookworm-slim AS base 3 | 4 | # Define build arguments and environment variables 5 | ARG COMMIT_ID 6 | ENV COMMIT_ID=${COMMIT_ID} 7 | 8 | ARG BUILD_AT 9 | ENV BUILD_AT=${BUILD_AT} 10 | 11 | WORKDIR /app 12 | 13 | # Create a non-privileged user for the app. 14 | ARG UID=10001 15 | RUN adduser \ 16 | --disabled-password \ 17 | --gecos "" \ 18 | --home "/home/appuser" \ 19 | --shell "/sbin/nologin" \ 20 | --uid "${UID}" \ 21 | appuser 22 | 23 | # Install required packages 24 | RUN apt-get update && apt-get install -y --no-install-recommends \ 25 | gcc \ 26 | libmariadb-dev-compat \ 27 | libmariadb-dev \ 28 | curl \ 29 | pkg-config && \ 30 | rm -rf /var/lib/apt/lists/* \ 31 | && apt-get clean 32 | 33 | 34 | # Copy the source code into the container in the final stage 35 | FROM base AS final 36 | 37 | # Set working directory 38 | WORKDIR /app 39 | 40 | # Copy the source code into the container 41 | COPY . . 42 | 43 | RUN chown -R appuser:appuser /app 44 | # Switch to the non-privileged user to run the application. 45 | USER appuser 46 | 47 | # Expose the port that the application listens on. 48 | EXPOSE 8000 49 | 50 | RUN uv sync 51 | 52 | # Run the application 53 | CMD ["uv", "run", "--with", "gunicorn", "gunicorn", "app:app", "--workers", "4", "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"] 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Anime API Project 2 | 3 | THIS PROJECT IS ONLY FOR EDUCATIONAL PURPOSES. DO NOT USE IT FOR COMMERCIAL PURPOSES. 4 | 5 | ## Overview 6 | 7 | This project is a Python-based web application with a framework of video player's backend that uses FastAPI for the 8 | backend. It includes health checks for Redis and MySQL, middleware for processing time, and session management. The 9 | application is containerized using Docker. 10 | 11 | ## Features 12 | 13 | - FastAPI backend 14 | - Health checks for Redis and MySQL 15 | - Middleware for processing time 16 | - Session management 17 | - CORS support 18 | - GZip compression 19 | 20 | ## Requirements 21 | 22 | - Python 3.12.4 23 | - Docker 24 | - Redis 25 | - MySQL 26 | 27 | ## Installation 28 | 29 | ### Prerequisites 30 | 31 | 1. Install Python 3.12.4: 32 | ```sh 33 | sudo apt update 34 | sudo apt install python3.12 35 | ``` 36 | 37 | 2. Install Docker: 38 | ```sh 39 | curl -fsSL https://get.docker.com -o get-docker.sh 40 | sudo sh get-docker.sh 41 | ``` 42 | 43 | ### Using Docker 44 | 45 | 1. Clone the repository: 46 | ```sh 47 | git clone https://github.com/binaryYuki/oleapi.git 48 | cd oleapi.git 49 | ``` 50 | 51 | 2. Build the Docker image: 52 | ```sh 53 | docker build --build-arg COMMIT_ID=$(git rev-parse HEAD) --build-arg BUILD_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ") -t oleapi:latest . 54 | ``` 55 | 56 | 3. Run the Docker container: 57 | ```sh 58 | docker run -p 8000:8000 -e SESSION_SECRET=$(python -c 'import binascii, os; print(binascii.hexlify(os.urandom(16)).decode()') oleapi:latest 59 | ``` 60 | 61 | ### Without Docker 62 | 63 | 1. Clone the repository: 64 | ```sh 65 | git clone https://github.com/binaryYuki/oleapi.git 66 | cd oleapi 67 | ``` 68 | 69 | 2. Create a virtual environment and activate it: 70 | ```sh 71 | python -m venv venv 72 | source venv/bin/activate # On Windows use `venv\Scripts\activate` 73 | ``` 74 | 75 | 3. Install the dependencies: 76 | ```sh 77 | pip install -r requirements.txt 78 | ``` 79 | 80 | 4. Set environment variables: 81 | ```sh 82 | export COMMIT_ID=$(git rev-parse HEAD) 83 | export BUILD_AT=$(date -u +"%Y-%m-%dT%H:%M:%SZ") 84 | export SESSION_SECRET=$(python -c 'import binascii, os; print(binascii.hexlify(os.urandom(16)).decode())') 85 | ``` 86 | 87 | 5. Run the application: 88 | ```sh 89 | uvicorn app:app --host 0.0.0.0 --port 8000 90 | ``` 91 | 92 | ## Endpoints 93 | 94 | ### Health Check 95 | 96 | - **GET** `/healthz` 97 | - Checks the status of Redis and MySQL connections. 98 | - Response: 99 | ```json 100 | { 101 | "status": "ok", 102 | "redis": true, 103 | "mysql": true, 104 | "live_servers": [] 105 | } 106 | ``` 107 | 108 | ## Middleware 109 | 110 | - **Process Time Header**: Adds the processing time to the response headers. 111 | - **Session Middleware**: Manages user sessions. 112 | - **Trusted Host Middleware**: Allows requests from all hosts. 113 | - **GZip Middleware**: Compresses responses larger than 1000 bytes. 114 | - **CORS Middleware**: Configures CORS settings based on the environment. 115 | 116 | ## Environment Variables 117 | 118 | - `COMMIT_ID`: The current commit ID. 119 | - `BUILD_AT`: The build timestamp. 120 | - `SESSION_SECRET`: The secret key for session management. 121 | - `DEBUG`: Set to `true` or `false` to enable or disable debug mode. 122 | 123 | ## License 124 | 125 | This project is licensed under the MIT License. See the `LICENSE` file for more details. 126 | -------------------------------------------------------------------------------- /_auth.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import os 3 | from logging import getLogger 4 | 5 | import dotenv 6 | import jwt 7 | from fastapi import APIRouter, BackgroundTasks, Request 8 | from starlette.responses import JSONResponse 9 | 10 | logger = getLogger(__name__) 11 | 12 | dotenv.load_dotenv() 13 | 14 | authRoute = APIRouter(prefix='/api/auth', tags=['Auth', 'Authentication']) 15 | 16 | 17 | async def generateJWT(payload: dict): 18 | """ 19 | 生成 JWT Token 20 | :return: str 21 | """ 22 | payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(hours=12) 23 | return jwt.encode(payload, os.getenv('SESSION_SECRET'), algorithm="HS256") 24 | 25 | 26 | async def eventVerifier(event: str): 27 | """ 28 | Verifies the event received from the webhook 29 | :param event: 30 | :return: Boolean 31 | """ 32 | allowedEvent = ['PostRegister', 'PostResetPassword', 'PostSignIn'] 33 | if event in allowedEvent: 34 | return True 35 | return False 36 | 37 | 38 | async def timeFrameVerifier(timeStamp: str): 39 | """ 40 | Verifies the time frame received from the webhook 41 | :param timeStamp: str (time frame) 42 | :return: Boolean 43 | """ 44 | try: 45 | if (datetime.datetime.strptime(timeStamp, '%Y-%m-%dT%H:%M:%S.%fZ') > 46 | datetime.datetime.now() - datetime.timedelta(minutes=1)): 47 | return True 48 | else: 49 | return False 50 | except ValueError: 51 | return False 52 | 53 | 54 | async def store_webhook_data(data: dict): 55 | """ 56 | :param data: 57 | """ 58 | pass 59 | 60 | 61 | @authRoute.api_route('/hook', methods=['POST']) 62 | async def logtoEventHandler(request: Request, background_tasks: BackgroundTasks): 63 | """ 64 | 65 | :param request: 66 | :param background_tasks: 67 | :return: 68 | """ 69 | try: 70 | data = await request.json() 71 | except Exception as e: 72 | logger.debug(e) 73 | return JSONResponse(status_code=401, content={'error': 'Invalid request'}) 74 | if not await eventVerifier(data.get('event')) or not await timeFrameVerifier(data.get('createdAt')): 75 | return JSONResponse(status_code=401, content={'error': 'Invalid request', 'step': 2}) 76 | background_tasks.add_task(store_webhook_data, data) 77 | return JSONResponse(status_code=200, content={'message': 'Webhook received successfully'}) 78 | 79 | 80 | @authRoute.api_route('/jwt', methods=['POST']) 81 | async def generateJWTToken(request: Request): 82 | """ 83 | 生成 JWT Token 84 | :param request: 85 | :return: 86 | """ 87 | try: 88 | data = await request.json() 89 | payload = { 90 | "sub": data.get('sub'), 91 | "name": data.get('name'), 92 | "picture": data.get('picture'), 93 | "username": data.get('username'), 94 | "sid": data.get('sid'), 95 | "exp": data.get('exp'), 96 | } 97 | token = await generateJWT(payload) 98 | return JSONResponse(status_code=200, content={'token': token}) 99 | except KeyError: 100 | return JSONResponse(status_code=401, content={'error': 'Invalid request'}) 101 | except Exception as e: 102 | return JSONResponse(status_code=401, content={'error': str(e)}) 103 | 104 | 105 | @authRoute.api_route('/verify', methods=['POST']) 106 | async def verifyJWTToken(request: Request): 107 | """ 108 | 验证 JWT Token 109 | :param request: 110 | :return: JSONResponse 111 | """ 112 | try: 113 | data = await request.json() 114 | token = data.get('token') 115 | payload = jwt.decode(token, os.getenv('SESSION_SECRET'), algorithms=["HS256"]) 116 | return JSONResponse(status_code=200, content={'payload': payload, 'header': jwt.get_unverified_header(token)}) 117 | except jwt.ExpiredSignatureError: 118 | return JSONResponse(status_code=401, content={'error': 'Token expired'}) 119 | except jwt.InvalidTokenError: 120 | return JSONResponse(status_code=401, content={'error': 'Invalid token'}) 121 | except Exception as e: 122 | return JSONResponse(status_code=401, content={'error': str(e)}) 123 | -------------------------------------------------------------------------------- /_cronjobs.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging 3 | 4 | from fastapi_utils.tasks import repeat_every 5 | from httpx import AsyncClient 6 | 7 | from _redis import delete_key, get_key, get_keys_by_pattern, redis_client, set_key as redis_set_key 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | async def logPushTask(taskId: str, data: dict): 13 | """ 14 | 记录推送任务 15 | :param taskId: str 16 | :param data: dict 17 | :return: Boolean 18 | :example: {'data': {'baseURL': 'https://api.day.app/uKeSrwm3ainGgn5SAmRyg9/', 'msg': 'You have a new notification!', 'push_receiver': 'yuki', 'icon': 'https://static.olelive.com/snap/fa77502e442ee6bbd39be20b2a2810ee.jpg?_n=202409290554', 'click_url': 'https://example.com', 'is_passive': False, 'headers': {'Authorization': 'Bearer your_token_here', 'Content-Type': 'application/json'}, 'log_data': {'push_id': '12345', 'push_receiver': 'user@example.com', 'push_by': 'system'}}, 'result': 'success'} 19 | """ 20 | pass 21 | 22 | @repeat_every(seconds=30, wait_first=True) # wait_first=True 表示等待第一次执行 也就是启动时执行 23 | async def pushTaskExecQueue() -> bool: 24 | """ 25 | Process push tasks from the Redis keys matching 'pushTask:*' pattern. 26 | :return: Boolean indicating success. 27 | """ 28 | try: 29 | all_keys = await get_keys_by_pattern('pushTask:*') 30 | if not all_keys: 31 | return False 32 | logger.info(f"Found {len(all_keys)} push tasks in the queue.") 33 | 34 | async with AsyncClient() as client: 35 | for key in all_keys: 36 | value = await get_key(key) 37 | if not value: 38 | continue 39 | 40 | data = json.loads(value) 41 | logger.info(f"Processing push task: {data}") 42 | url = ( 43 | f"{data['baseURL']}{data['msg']}?" 44 | f"icon={data['icon']}&" 45 | f"url={data['click_url']}&" 46 | f"passive={data['is_passive']}" 47 | ) 48 | url.replace("//", "/").replace("https:/", "https://") 49 | response = await client.post(url) 50 | if response.status_code == 200: 51 | await delete_key(key) 52 | data['result'] = 'success' 53 | logger.info(f"Push task successful: {data}") 54 | else: 55 | data['result'] = 'failed' 56 | logger.error(f"Failed to push task: {data}") 57 | 58 | try: 59 | # taskID 取 pushTask: 后面的字符串 60 | taskID = key.split(":")[1] 61 | print(taskID) 62 | await logPushTask(taskID, data) 63 | except Exception as e: 64 | logger.error(f"Failed to log push task: {e}", exc_info=True) 65 | 66 | return True 67 | 68 | except Exception as e: 69 | logger.error(f"Error in pushTaskExecQueue: {e}", exc_info=True) 70 | return False 71 | 72 | 73 | @repeat_every(seconds=3 * 60, wait_first=True) 74 | async def keerRedisAlive(): 75 | """ 76 | Keep Redis alive avoid server from cool startup 77 | """ 78 | await redis_set_key("alive", "yes", ex=60 * 60 * 24) 79 | # print("Redis is alive") 80 | await redis_client.delete("alive") 81 | return True 82 | 83 | 84 | @repeat_every(seconds=3 * 60, wait_first=True) 85 | async def keepMySQLAlive(): 86 | """ 87 | Keep MySQL alive avoid server from cool startup 88 | """ 89 | pass 90 | -------------------------------------------------------------------------------- /_crypto.py: -------------------------------------------------------------------------------- 1 | import base64 2 | from logging import getLogger 3 | 4 | from cryptography.hazmat.primitives import hashes, serialization 5 | from cryptography.hazmat.primitives.asymmetric import padding, rsa 6 | from fastapi import Depends, Request, Response 7 | from fastapi.routing import APIRouter 8 | from fastapi_limiter.depends import RateLimiter 9 | 10 | from _redis import delete_key as redis_delete_key, get_key as redis_get_key, set_key as redis_set_key 11 | 12 | logger = getLogger(__name__) 13 | 14 | cryptoRouter = APIRouter(prefix='/api/crypto', tags=['Crypto', 'Crypto Api']) 15 | 16 | 17 | async def init_crypto(): 18 | """ 19 | 初始化加密模块 20 | :return: 21 | """ 22 | try: 23 | a = await redis_get_key("private_key") 24 | b = await redis_get_key("public_key") 25 | if a and b: 26 | return True 27 | else: 28 | try: 29 | await redis_delete_key("private_key") 30 | await redis_delete_key("public_key") 31 | except Exception as e: 32 | pass 33 | # 生成一对rsa密钥 并且保存到redis 34 | private_key = rsa.generate_private_key( 35 | public_exponent=65537, 36 | key_size=2048 37 | ) 38 | public_key = private_key.public_key() 39 | # 保存到redis 40 | private_pem = private_key.private_bytes( 41 | encoding=serialization.Encoding.PEM, 42 | format=serialization.PrivateFormat.PKCS8, 43 | encryption_algorithm=serialization.NoEncryption() 44 | ) 45 | public_pem = public_key.public_bytes( 46 | encoding=serialization.Encoding.PEM, 47 | format=serialization.PublicFormat.SubjectPublicKeyInfo 48 | ) 49 | await redis_set_key("private_key", private_pem.decode()) 50 | await redis_set_key("public_key", public_pem.decode()) 51 | 52 | return True 53 | except Exception as e: 54 | raise Exception(f"Failed to init crypto: {e}") 55 | 56 | 57 | @cryptoRouter.api_route('/getPublicKey', dependencies=[Depends(RateLimiter(times=1, seconds=1))], 58 | methods=['OPTIONS'], summary='Get Public Key', description='Get Public Key') 59 | async def get_public_key(request: Request): 60 | """ 61 | 获取公钥 62 | :param request: 63 | :return: 64 | """ 65 | public_key = await redis_get_key("public_key") 66 | return Response(content=public_key, media_type="text/plain") 67 | 68 | 69 | async def decryptData(data: str): 70 | """ 71 | 解密数据 72 | :param data: str 73 | :return: 74 | """ 75 | try: 76 | private_key = await redis_get_key("private_key") 77 | except Exception as e: 78 | raise Exception(f"redis error") 79 | try: 80 | private_key_data = await redis_get_key("private_key") 81 | 82 | # 检查是否正确获取了私钥 83 | if not private_key_data: 84 | raise Exception("Internal Server Error") 85 | 86 | private_key = serialization.load_pem_private_key( 87 | private_key_data.encode(), 88 | password=None 89 | ) 90 | 91 | # 使用 Base64 解码 92 | encrypted_data = base64.b64decode(data) 93 | 94 | # 解密数据 95 | decrypted_data = private_key.decrypt( 96 | encrypted_data, 97 | padding.OAEP( 98 | mgf=padding.MGF1(algorithm=hashes.SHA1()), 99 | algorithm=hashes.SHA1(), 100 | label=None 101 | ) 102 | ) 103 | 104 | # print("decrypted_data", decrypted_data.decode('utf-8')) 105 | 106 | return decrypted_data.decode('utf-8') 107 | 108 | except Exception as e: 109 | print(f"Decryption error: {e}") 110 | raise Exception("Unexpected error") 111 | -------------------------------------------------------------------------------- /_redis.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from typing import Optional 4 | 5 | import dotenv 6 | from redis import asyncio as redis 7 | 8 | dotenv.load_dotenv() 9 | 10 | # Configuration 11 | if os.getenv("REDIS_CONN") is not None: 12 | REDIS_CONN = os.getenv("REDIS_CONN") 13 | else: 14 | REDIS_HOST = os.getenv("REDIS_HOST", "localhost") 15 | REDIS_PORT = int(os.getenv("REDIS_PORT", 6379)) 16 | REDIS_DB = int(os.getenv("REDIS_DB", 0)) 17 | REDIS_PASSWORD = os.getenv("REDIS_PASSWORD", None) 18 | # 在集群环境下,使用 redis:// 连接字符串 并且 tcp()包裹 19 | REDIS_CONN = f"redis://default:{REDIS_PASSWORD}@{REDIS_HOST}:{REDIS_PORT}/{REDIS_DB}" 20 | 21 | # Initialize Redis client 22 | redis_client = redis.from_url(REDIS_CONN) 23 | # set max size of redis connection pool 24 | redis_client.connection_pool.max_connections = 3 25 | 26 | 27 | async def test_redis(): 28 | try: 29 | await redis_client.ping() 30 | await redis_client.delete("InstanceRegister") 31 | return True 32 | except redis.RedisError as e: 33 | print(f"Error connecting to Redis: {e}") 34 | return False 35 | 36 | 37 | async def get_keys_by_pattern(pattern: str) -> list: 38 | """ 39 | Get a list of keys matching a pattern. 40 | """ 41 | maxAttempts = 3 42 | try: 43 | keys = [] 44 | async for key in redis_client.scan_iter(match=pattern): 45 | keys.append(key.decode()) 46 | return keys 47 | except redis.ConnectionError as e: 48 | if maxAttempts > 0: 49 | data = await get_keys_by_pattern(pattern) 50 | maxAttempts -= 1 51 | return data 52 | else: 53 | print(f"Error connecting to Redis: {e}") 54 | return [] 55 | except Exception as e: 56 | if maxAttempts > 0: 57 | data = await get_keys_by_pattern(pattern) 58 | maxAttempts -= 1 59 | return data 60 | else: 61 | print(f"Error getting keys by pattern from Redis: {e}") 62 | return [] 63 | 64 | 65 | # Set a key-value pair in Redis 66 | async def set_key(key: str, value: str, ex: Optional[int] = None) -> bool: 67 | """ 68 | Set a value in Redis with an optional expiration time (in seconds). 69 | """ 70 | try: 71 | if type(value) is dict: 72 | value = json.dumps(value) 73 | await redis_client.set(name=key, value=value, ex=ex) 74 | return True 75 | except redis.RedisError as e: 76 | print(f"Error setting key in Redis: {e}") 77 | return False 78 | 79 | 80 | # Get a value from Redis by key 81 | async def get_key(key: str) -> Optional[str]: 82 | """ 83 | Get a value from Redis by key. Returns None if the key does not exist. 84 | """ 85 | # 返回 string 86 | try: 87 | data = await redis_client.get(key) 88 | if data: 89 | return data.decode() 90 | else: 91 | return None 92 | except redis.RedisError as e: 93 | print(f"Error getting key from Redis: {e}") 94 | return None 95 | 96 | 97 | # Delete a key from Redis 98 | async def delete_key(key: str) -> bool: 99 | """ 100 | Delete a key from Redis. 101 | """ 102 | try: 103 | await redis_client.delete(key) 104 | return True 105 | except redis.RedisError as e: 106 | print(f"Error deleting key from Redis: {e}") 107 | return False 108 | 109 | 110 | # Check if a key exists in Redis 111 | async def key_exists(key: str) -> bool: 112 | """ 113 | Check if a key exists in Redis. 114 | """ 115 | try: 116 | return await redis_client.exists(key) == 1 117 | except redis.RedisError as e: 118 | print(f"Error checking key in Redis: {e}") 119 | return False 120 | -------------------------------------------------------------------------------- /_search.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import logging 4 | from http.client import HTTPException 5 | from time import time 6 | 7 | import httpx 8 | from fastapi import BackgroundTasks, Depends 9 | from fastapi.routing import APIRouter 10 | from fastapi_limiter.depends import RateLimiter 11 | from starlette.requests import Request 12 | from starlette.responses import JSONResponse, RedirectResponse 13 | 14 | from _crypto import decryptData 15 | from _redis import delete_key as redis_delete_key, get_key as redis_get_key, key_exists as redis_key_exists, \ 16 | set_key as redis_set_key 17 | from _utils import _getRandomUserAgent, generate_vv_detail, url_encode 18 | 19 | searchRouter = APIRouter(prefix='/api/query/ole', tags=['Search', 'Search Api']) 20 | 21 | 22 | async def _getProxy(): 23 | return None # 废弃接口,直接返回 None 24 | 25 | 26 | async def checkSum(data): 27 | """ 28 | 解密数据 29 | :param data: 加密数据 30 | :return: 解密后的数据 31 | """ 32 | try: 33 | timestamp = data.get('timestamp') 34 | if not await checkTimeStamp(timestamp): 35 | raise HTTPException("Invalid Request, timestamp expired") 36 | except Exception as e: 37 | raise HTTPException("Invalid Request") 38 | try: 39 | data = await decryptData(data.get('data')) 40 | except Exception as e: 41 | raise HTTPException("Invalid Request") 42 | return json.loads(data) 43 | 44 | 45 | async def checkTimeStamp(ts): 46 | """ 47 | 检查时间戳是否在有效范围内 1分钟 48 | """ 49 | if int(time()) - int(ts) > 60: 50 | return False 51 | return True 52 | 53 | 54 | async def search_api(keyword, page=1, size=4): 55 | """ 56 | 搜索 API 57 | :param keyword: 搜索关键词 58 | :param page: 页码 59 | :param size: 每页数量x` 60 | :return: 返回搜索结果 61 | """ 62 | vv = await generate_vv_detail() 63 | # 关键词是个中文字符串,需要进行 URL 编码 64 | keyword = url_encode(keyword) 65 | base_url = f"https://api.olelive.com/v1/pub/index/search/{keyword}/vod/0/{page}/{size}?_vv={str(vv)}" 66 | headers = { 67 | 'User-Agent': _getRandomUserAgent(), 68 | 'Referer': 'https://www.olevod.com/', 69 | 'Origin': 'https://www.olevod.com/', 70 | } 71 | logging.info(f"Search API: {base_url}") 72 | async with httpx.AsyncClient() as client: 73 | response = await client.get(base_url, headers=headers) 74 | if response.status_code != 200: 75 | logging.error(f"Upstream Error, base_url: {base_url}, headers: {headers}") 76 | raise Exception("Upstream Error") 77 | return response.json() 78 | 79 | 80 | async def link_keywords(keyword): 81 | vv = await generate_vv_detail() 82 | if type(vv) is bytes: 83 | vv = vv.decode() 84 | # 关键词是个中文字符串,需要进行 URL 编码 85 | keyword_encoded = url_encode(keyword) 86 | base_url = f"https://api.olelive.com/v1/pub/index/search/keywords/{keyword_encoded}?_vv={vv}" 87 | headers = { 88 | 'User-Agent': _getRandomUserAgent(), 89 | 'Referer': 'https://www.olevod.com/', 90 | 'Origin': 'https://www.olevod.com/', 91 | 'sec-fetch-dest': 'empty', 92 | 'sec-fetch-mode': 'cors', 93 | 'sec-fetch-site': 'cross-site', 94 | 'accept': 'application/json, text/plain, */*', 95 | 'accept-encoding': 'gzip, deflate, br, zstd', 96 | 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7', 97 | } 98 | async with httpx.AsyncClient() as client: 99 | response = await client.get(base_url, headers=headers) 100 | if response.status_code != 200: 101 | return JSONResponse(content={"error": "Upstream Error"}, status_code=507) 102 | try: 103 | words = response.json()["data"][0]["words"] 104 | words = [word for word in words if word != "" and word != keyword] 105 | # 去重 以及 空字符串 106 | words2 = list(set(words)) 107 | words3 = list(sorted(words2, key=lambda x: len(x))) 108 | newResponse = response.json() 109 | newResponse["data"][0]["words"] = words3 110 | return newResponse 111 | except Exception as e: 112 | return response.json() 113 | 114 | 115 | @searchRouter.api_route('/search', dependencies=[Depends(RateLimiter(times=3, seconds=1))], methods=['POST'], 116 | name='search') 117 | async def search(request: Request, background_tasks: BackgroundTasks): 118 | data = await request.json() 119 | data = await checkSum(data) 120 | keyword, page, size = data.get('keyword'), data.get('page'), data.get('size') 121 | if keyword == '' or keyword == 'your keyword': 122 | return JSONResponse({}, status_code=200) 123 | page, size = int(page), int(size) 124 | try: 125 | id = f"search_{keyword}_{page}_{size}_{datetime.datetime.now().strftime('%Y-%m-%d')}" 126 | if await redis_key_exists(id): 127 | data = json.loads(await redis_get_key(id)) 128 | data["msg"] = "cached" 129 | return JSONResponse(data) 130 | except Exception as e: 131 | pass 132 | try: 133 | result = await search_api(keyword, page, size) 134 | except Exception as e: 135 | return JSONResponse({"error": str(e)}, status_code=503) 136 | if result and result['data']['total'] == 0: 137 | return JSONResponse({"error": "No result Found"}, status_code=200) 138 | if result: 139 | background_tasks.add_task(redis_set_key, id, json.dumps(result), ex=86400) # 缓存一天 140 | try: 141 | return JSONResponse(result) 142 | except: 143 | return JSONResponse(json.dumps(result), status_code=200) 144 | 145 | 146 | @searchRouter.api_route('/keyword', dependencies=[Depends(RateLimiter(times=2, seconds=1))], methods=['POST'], 147 | name='keyword') 148 | async def keyword(request: Request): 149 | data = await request.json() 150 | try: 151 | data = await checkSum(data) 152 | except HTTPException as e: 153 | return JSONResponse({"error": str(e)}, status_code=400) 154 | except Exception as e: 155 | logging.info(f"Invalid Request: {data}, {e}") 156 | return JSONResponse({"error": "Invalid Request"}, status_code=400) 157 | _keyword = data.get('keyword') 158 | if _keyword == '' or _keyword == 'your keyword': 159 | return JSONResponse({}, status_code=200) 160 | if _keyword == 'Yuki Forever💗': 161 | return JSONResponse( 162 | {"code": 0, "data": [{"type": "vod", "words": ["每一个未来的瞬间", "都有你的名字", "Yuki Forever💗"]}], 163 | "msg": "ok"}, status_code=200) 164 | redis_key = f"keyword_{datetime.datetime.now().strftime('%Y-%m-%d')}_{_keyword}" 165 | try: 166 | if await redis_get_key(redis_key): 167 | data = await redis_get_key(redis_key) 168 | data = json.loads(data) 169 | data["msg"] = "cached" 170 | else: 171 | data = await link_keywords(_keyword) 172 | await redis_set_key(redis_key, json.dumps(data), ex=86400) # 缓存一天 173 | except Exception as e: 174 | logging.error("Error: " + str(e), stack_info=True) 175 | return JSONResponse({"error": str(e)}, status_code=501) 176 | try: 177 | return JSONResponse(data) 178 | except: 179 | return JSONResponse(json.loads(data), status_code=200) 180 | 181 | 182 | @searchRouter.api_route('/detail', methods=['POST'], name='detail', 183 | dependencies=[Depends(RateLimiter(times=1, seconds=3))]) 184 | async def detail(request: Request): 185 | data = await request.json() 186 | data = await checkSum(data) 187 | try: 188 | id = data.get('id') 189 | except Exception as e: 190 | return JSONResponse({"error": "Invalid Request, missing param: id"}, status_code=400, 191 | headers={"X-Error": str(e)}) 192 | vv = await generate_vv_detail() 193 | url = f"https://api.olelive.com/v1/pub/vod/detail/{id}/true?_vv={vv}" 194 | headers = { 195 | 'User-Agent': _getRandomUserAgent(), 196 | 'Referer': 'https://www.olevod.com/', 197 | 'Origin': 'https://www.olevod.com/', 198 | } 199 | try: 200 | async with httpx.AsyncClient() as client: 201 | response = await client.get(url, headers=headers) 202 | response_data = response.json() 203 | return JSONResponse(response_data, status_code=200) 204 | except: 205 | return JSONResponse({"error": "Upstream Error"}, status_code=501) 206 | # direct play https://player.viloud.tv/embed/play?url=https://www.olevod.com/vod/detail/5f4b3b7b7f3c1d0001b2b3b3&autoplay=1 207 | 208 | 209 | @searchRouter.api_route('/report/keyword', methods=['POST'], name='report_keyword', 210 | dependencies=[Depends(RateLimiter(times=1, seconds=3))]) 211 | async def report_keyword(request: Request): 212 | """ 213 | 上报搜索关键词 针对搜索结果为空的情况 214 | """ 215 | # purge cache for the keyword and search result 216 | data = await request.json() 217 | # print(data, "checkpoint 1") 218 | data = await checkSum(data) 219 | # print(data, "checkpoint 2") 220 | keyword = data.get('keyword') 221 | if keyword == '' or keyword == 'your keyword': 222 | return JSONResponse({}, status_code=200) 223 | try: 224 | key = f"keyword_{datetime.datetime.now().strftime('%Y-%m-%d')}_{keyword}" 225 | await redis_delete_key(key) 226 | except Exception as e: 227 | logging.error("Error: " + str(e), stack_info=True) 228 | return JSONResponse({"error": 'trace stack b1'}, status_code=501) 229 | return RedirectResponse(url='/api/query/ole/keyword', status_code=308) 230 | -------------------------------------------------------------------------------- /_trend.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import logging 4 | from json import JSONDecodeError 5 | from typing import Optional 6 | 7 | import httpx 8 | from fastapi import Depends 9 | from fastapi.routing import APIRouter 10 | from fastapi_limiter.depends import RateLimiter 11 | from starlette.requests import Request 12 | from starlette.responses import JSONResponse 13 | 14 | from _redis import get_key, set_key 15 | from _utils import _getRandomUserAgent, generate_vv_detail as gen_vv 16 | 17 | trendingRoute = APIRouter(prefix='/api/trending', tags=['Trending']) 18 | 19 | 20 | async def gen_url(typeID: int, period: str, amount=10): 21 | """ 22 | 传入的值必须经过检查,否则可能会导致 API 请求失败。 23 | """ 24 | if period not in ['day', 'week', 'month', 'all']: 25 | return JSONResponse(status_code=400, 26 | content={'error': 'Invalid period parameter, must be one of: day, week, month, all'}) 27 | if typeID not in [1, 2, 3, 4]: 28 | return JSONResponse(status_code=400, content={ 29 | 'error': 'Invalid typeID parameter, must be one of: 1 --> 电影, 2 --> 电视剧(连续剧), 3 --> 综艺, 4 --> 动漫'}) 30 | vv = await gen_vv() 31 | url = f"https://api.olelive.com/v1/pub/index/vod/data/rank/{period}/{typeID}/{amount}?_vv={vv}" 32 | return url 33 | 34 | 35 | async def gen_url_v2(typeID: int, amount=10): 36 | """ 37 | 传入的值必须经过检查,否则可能会导致 API 请求失败。 38 | """ 39 | if typeID not in [1, 2, 3, 4]: 40 | return JSONResponse(status_code=400, content={ 41 | 'error': 'Invalid typeID parameter, must be one of: 1 --> 电影, 2 --> 电视剧(连续剧), 3 --> 综艺, 4 --> 动漫'}) 42 | vv = await gen_vv() 43 | url = f"https://api.olelive.com/v1/pub/index/vod/hot/{typeID}/0/{amount}?_vv={vv}" 44 | return url 45 | 46 | 47 | @trendingRoute.post('/{period}/trend') 48 | async def fetch_trending_data(request: Request, period: Optional[str] = 'day'): 49 | """ 50 | Fetch trending data from the OLE API. 51 | :param request: The incoming request. 52 | :parameter period: The period of time to fetch trending data for. --> str Options: 'day', 'week', 'month', 'all' 53 | :parameter typeID: The type ID of the item. --> int 54 | typeID docs: 55 | 1: 电影 56 | 2: 电视剧(连续剧) 57 | 3: 综艺 58 | 4: 动漫 59 | :parameter amount: The number of items to fetch. --> int default: 10 60 | """ 61 | try: 62 | data = await request.json() 63 | try: 64 | typeID = data['params']['typeID'] 65 | logging.info(f"typeID1: {typeID}") 66 | except KeyError as e: 67 | return JSONResponse(status_code=400, content={'error': f"Where is your param?"}) 68 | except JSONDecodeError as e: 69 | logging.error(f"JSONDecodeError: {e}, hint: request.json() failed, step fetch_trending_data") 70 | return JSONResponse(status_code=400, content={'error': f"Where is your param?"}) 71 | if period is None: 72 | logging.error(f"period: {period}, hint: period is None, step fetch_trending_data") 73 | return JSONResponse(status_code=400, content={'error': 'Missing required parameters: period'}) 74 | if typeID is None: 75 | logging.info(f"typeID: {typeID}, hint:typeID is None, step fetch_trending_data") 76 | return JSONResponse(status_code=400, content={'error': 'Missing required parameters: typeID'}) 77 | if period not in ['day', 'week', 'month', 'all']: 78 | logging.error(f"period: {period}, hint:period not in ['day', 'week', 'month', 'all]") 79 | return JSONResponse(status_code=400, 80 | content={'error': 'Invalid period parameter, must be one of: day, week, month, all'}) 81 | if typeID not in [1, 2, 3, 4]: 82 | logging.error(f"typeID: {typeID}, hint:typeID not in [1,2,3,4]") 83 | return JSONResponse(status_code=400, content={ 84 | 'error': 'Invalid typeID parameter, must be one of: 1 --> 电影, 2 --> 电视剧(连续剧), 3 --> 综艺, 4 --> 动漫'}) 85 | url = await gen_url(typeID, period, amount=10) 86 | logging.info(f"Fetching trending data from: {url}") 87 | try: 88 | async with httpx.AsyncClient() as client: 89 | response = await client.get(url, headers={'User-Agent': _getRandomUserAgent()}, timeout=30) 90 | data = response.json() 91 | return JSONResponse(status_code=200, content=data) 92 | except httpx.RequestError as e: 93 | print(data) 94 | return JSONResponse(status_code=500, content={'error': f"An error occurred: {e}"}) 95 | except httpx.HTTPStatusError as e: 96 | print(data) 97 | return JSONResponse(status_code=500, content={'error': f"An HTTP error occurred: {e}"}) 98 | except Exception as e: 99 | print(data) 100 | return JSONResponse(status_code=500, content={'error': f"An error occurred: {e}, response: {response.text}"}) 101 | 102 | 103 | @trendingRoute.api_route('/v2/{typeID}', methods=['POST'], dependencies=[Depends(RateLimiter(times=2, seconds=1))]) 104 | async def fetch_trending_data_v2(request: Request, typeID: Optional[int] = None): 105 | """ 106 | Fetch trending data from the OLE API. 107 | :param request: The incoming request. 108 | :parameter typeID: The type ID of the item. --> int 109 | typeID docs: 110 | 1: 电影 111 | 2: 电视剧(连续剧) 112 | 3: 综艺 113 | 4: 动漫 114 | :parameter amount: The number of items to fetch. --> int default: 10 115 | """ 116 | try: 117 | amount = request.query_params['amount'] 118 | except KeyError as e: 119 | amount = 10 120 | if typeID is None: 121 | logging.info(f"typeID: {typeID}, hint:typeID is None, step fetch_trending_data") 122 | return JSONResponse(status_code=400, content={'error': 'Missing required parameters: typeID'}) 123 | if typeID not in [1, 2, 3, 4]: 124 | logging.error(f"typeID: {typeID}, hint:typeID not in [1,2,3,4]") 125 | return JSONResponse(status_code=400, content={ 126 | 'error': 'Invalid typeID parameter, must be one of: 1 --> 电影, 2 --> 电视剧(连续剧), 3 --> 综艺, 4 --> 动漫'}) 127 | redis_key = f"trending_v2_cache_{datetime.datetime.now().strftime('%Y-%m-%d')}_{typeID}_{amount}" 128 | if await get_key(redis_key): 129 | logging.info(f"Hit cache for key: {redis_key}") 130 | data = await get_key(redis_key) 131 | data = json.loads(data) 132 | return JSONResponse(status_code=200, content=data) 133 | else: 134 | url = await gen_url_v2(typeID, amount) 135 | logging.info(f"Fetching trending data from: {url}") 136 | try: 137 | async with httpx.AsyncClient() as client: 138 | response = await client.get(url, headers={'User-Agent': _getRandomUserAgent()}, timeout=30) 139 | data = json.dumps(response.json()) 140 | await set_key(redis_key, data, 60 * 60 * 24) 141 | return JSONResponse(status_code=200, content=json.loads(data)) 142 | except httpx.RequestError as e: 143 | return JSONResponse(status_code=500, content={'error': f"An error occurred: {e}"}) 144 | -------------------------------------------------------------------------------- /_utils.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import hashlib 3 | import json 4 | import logging 5 | import urllib 6 | import uuid 7 | 8 | from fake_useragent import UserAgent 9 | from httpx import AsyncClient 10 | 11 | from _redis import get_key, set_key # noqa 12 | 13 | ua = UserAgent() 14 | 15 | logger = logging.getLogger(__name__) 16 | 17 | 18 | def he(char): 19 | # 将字符转换为二进制字符串,保持至少6位长度 20 | return bin(int(char))[2:].zfill(6) 21 | 22 | 23 | def C(t): 24 | # 使用 hashlib 生成 MD5 哈希值 25 | return hashlib.md5(t.encode('utf-8')).hexdigest() 26 | 27 | 28 | def vv_generator(): 29 | """ 30 | 生成 vv 参数 31 | :return: 32 | """ 33 | # 获取当前法国时间的 Unix 时间戳(秒) 34 | france_time = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=2))) 35 | timestamp = int(france_time.timestamp()) 36 | 37 | # 将时间戳转换为字符串 38 | t = str(timestamp) 39 | 40 | r = ["", "", "", ""] 41 | 42 | # 遍历时间戳字符串并处理二进制表示 43 | for char in t: 44 | e = he(char) 45 | r[0] += e[2] if len(e) > 2 else '0' 46 | r[1] += e[3] if len(e) > 3 else '0' 47 | r[2] += e[4] if len(e) > 4 else '0' 48 | r[3] += e[5] if len(e) > 5 else '0' 49 | 50 | a = [] 51 | 52 | # 将二进制字符串转换为十六进制字符串 53 | for binary_str in r: 54 | hex_str = format(int(binary_str, 2), 'x').zfill(3) 55 | a.append(hex_str) 56 | 57 | n = C(t) 58 | 59 | # 组合最终结果字符串 60 | vv = n[:3] + a[0] + n[6:11] + a[1] + n[14:19] + a[2] + n[22:27] + a[3] + n[30:] 61 | 62 | return vv 63 | 64 | 65 | async def generate_vv_detail(): 66 | """ 67 | 生成 vv 参数 68 | :return: str 69 | """ 70 | vv = await get_key('vv') 71 | 72 | if not vv: 73 | vv = vv_generator() 74 | success = await set_key('vv', vv, 60 * 5) 75 | if not success: 76 | raise Exception('Failed to set vv') 77 | 78 | return vv 79 | 80 | 81 | def _getRandomUserAgent(): 82 | return ua.random 83 | 84 | 85 | async def pushNotification(baseURL: str, msg: str, icon: str = '', click_url: str = '', is_passive: bool = False, 86 | headers=None): 87 | """ 88 | 推送通知 89 | :param baseURL: str 90 | :param msg: str 91 | :param icon: str 92 | :param click_url: str 93 | :param is_passive: bool 94 | :param headers: dict 95 | :param data: dict 96 | :param log_data: dict -> {'push_id': str, 'push_receiver': str} 97 | """ 98 | # url = https://api.day.app/uKeSrwm3ainGgn5SAmRyg9/{msg}?icon={icon}&url={url}&passive={is_passive} 99 | if headers is None: 100 | headers = {} 101 | print(f"Pushing notification to {baseURL}/{msg}?") 102 | url = f'{baseURL}/{msg}?' 103 | if icon: 104 | url += f'&icon={icon}' 105 | if click_url: 106 | url += f'&url={click_url}' 107 | if is_passive: 108 | url += f'&passive=true' 109 | print(f"Pushing to {url}") 110 | async with AsyncClient() as client: 111 | response = await client.post(url, headers=headers) 112 | print(response.status_code) 113 | if response.status_code != 200: 114 | return False 115 | else: 116 | return True 117 | 118 | 119 | # url 编码关键词 120 | def url_encode(keyword): 121 | return urllib.parse.quote(keyword.encode()) 122 | 123 | 124 | async def generatePushTask(baseURL: str, msg: str, user_id: str, receiver: str, icon=None, click_url=None, 125 | is_passive=None, headers: dict = None, taskID: str = uuid.uuid4().hex, 126 | push_receiver: str = "yuki", push_by: str = "system"): 127 | """ 128 | :param push_by: 推送者 默认为system 129 | :param push_channel: 推送渠道 默认为bark 130 | :param push_receiver: 用户email 131 | :param taskID: 任务ID 默认为uuid 132 | :param headers: 请求头 133 | :param icon: 图标 134 | :param user_id: 用户ID 135 | :param msg: 消息 136 | :param baseURL: 基础URL 137 | :param click_url: 点击通知后跳转的URL 138 | :param receiver: 接收者 139 | :param is_passive: 是否被动推送 就是不会有声音 140 | 示例: generatePushTask("https://api.day.app/uKeSrwm3ainGgn5SAmRyg9/", "You have a new notification!", str(12345), 141 | "https://example.com", False, None, None, None, uuid.uuid4().hex, "system", 142 | "bark") 143 | """ 144 | data = { 145 | "baseURL": baseURL, 146 | "msg": msg, 147 | "push_receiver": receiver, 148 | "icon": icon if icon else "https://static.olelive.com/snap/fa77502e442ee6bbd39be20b2a2810ee.jpg?_n=202409290554", 149 | "click_url": click_url if click_url else "", 150 | "is_passive": is_passive if is_passive is not None else False, 151 | "headers": headers if headers else { 152 | "content-type": "application/json", 153 | }, 154 | "log_data": { 155 | "push_id": taskID, 156 | "push_receiver": push_receiver, 157 | "push_by": push_by if push_by else "system", 158 | "user_id": user_id 159 | } 160 | } 161 | await set_key(f"pushTask:{taskID}", json.dumps(data), 60 * 5) 162 | return True 163 | 164 | 165 | if __name__ == '__main__': 166 | print(vv_generator()) 167 | print(_getRandomUserAgent()) 168 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | # 标准库 2 | import binascii 3 | import logging 4 | import os 5 | import random 6 | import subprocess 7 | import time 8 | import uuid 9 | from contextlib import asynccontextmanager 10 | 11 | import httpx 12 | import redis.asyncio as redis 13 | from asgi_correlation_id import CorrelationIdMiddleware 14 | from dotenv import load_dotenv 15 | from fastapi import FastAPI, Request 16 | from fastapi.middleware.cors import CORSMiddleware 17 | from fastapi.middleware.gzip import GZipMiddleware 18 | from fastapi.responses import JSONResponse 19 | from fastapi_limiter import FastAPILimiter 20 | from fastapi_utils.tasks import repeat_every 21 | from starlette.middleware.sessions import SessionMiddleware 22 | 23 | from _auth import authRoute 24 | from _cronjobs import keerRedisAlive, pushTaskExecQueue 25 | from _crypto import cryptoRouter, init_crypto 26 | from _redis import get_keys_by_pattern, redis_client, set_key as redis_set_key 27 | from _search import searchRouter 28 | from _trend import trendingRoute 29 | 30 | load_dotenv() 31 | loglevel = os.getenv("LOG_LEVEL", "ERROR") 32 | logging.basicConfig(level=logging.getLevelName(loglevel)) 33 | logger = logging.getLogger(__name__) 34 | 35 | instanceID = uuid.uuid4().hex 36 | 37 | 38 | @repeat_every(seconds=60 * 3, wait_first=True) 39 | async def registerInstance(): 40 | """ 41 | 注册实例 42 | :return: 43 | """ 44 | try: 45 | await redis_set_key(f"node:{instanceID}", str(int(time.time())), 60 * 3) # re-register every 3 minutes 46 | except Exception as e: 47 | logger.error(f"Failed to register instance: {e}", exc_info=True) 48 | exit(-1) 49 | return True 50 | 51 | 52 | def is_valid_uuid4(uuid_string: str) -> bool: 53 | """ 54 | 检查是否是有效的 UUID4 55 | """ 56 | try: 57 | uuid.UUID(uuid_string, version=4) 58 | except ValueError: 59 | return False 60 | return True 61 | 62 | 63 | async def getLiveInstances(): 64 | """ 65 | 获取活跃实例 66 | :return: 67 | """ 68 | try: 69 | f = await get_keys_by_pattern("node:*") 70 | return f 71 | except Exception as e: 72 | logger.error(f"Failed to get live instances: {e}", exc_info=True) 73 | return [] 74 | 75 | 76 | @repeat_every(seconds=60 * 60, wait_first=True) 77 | async def testPushServer(): 78 | """ 79 | 测试推送服务器 80 | """ 81 | baseURL = os.getenv("PUSH_SERVER_URL", "").replace("https://", "").replace("http://", "") 82 | if not baseURL: 83 | return 84 | async with httpx.AsyncClient() as client: 85 | f = await client.get(f"https://{baseURL}/healthz") 86 | if f.status_code == 200: 87 | await redis_set_key("server_status", "running") 88 | 89 | 90 | @asynccontextmanager 91 | async def lifespan(_: FastAPI): 92 | """ 93 | 整个 FastAPI 生命周期的上下文管理器 94 | :param _: FastAPI 实例 95 | :return: None 96 | :param _: 97 | :return: 98 | """ 99 | redis_connection = redis.from_url( 100 | f"redis://default:{os.getenv('REDIS_PASSWORD', '')}@{os.getenv('REDIS_HOST', 'localhost')}:{os.getenv('REDIS_PORT', 6379)}") 101 | await FastAPILimiter.init(redis_connection) 102 | if await redis_connection.ping(): 103 | logger.info("Redis connection established") 104 | await testPushServer() 105 | await registerInstance() 106 | print("Instance registered", instanceID) 107 | await pushTaskExecQueue() 108 | await keerRedisAlive() 109 | await init_crypto() 110 | yield 111 | await FastAPILimiter.close() 112 | await redis_client.connection_pool.disconnect() 113 | print("Instance unregistered", instanceID) 114 | print("graceful shutdown") 115 | 116 | 117 | # 禁用 openapi.json 118 | app = FastAPI(lifespan=lifespan, title="Anime API", version="1.1.4", openapi_url=None) 119 | 120 | app.include_router(authRoute) 121 | app.include_router(searchRouter) 122 | app.include_router(trendingRoute) 123 | app.include_router(cryptoRouter) 124 | 125 | 126 | @app.middleware("http") 127 | async def instance_id_header_middleware(request, call_next): 128 | """ 129 | 添加 Instance ID 到响应头 130 | :param request: 131 | :param call_next: 132 | :return: 133 | """ 134 | response = await call_next(request) 135 | response.headers["X-Instance-ID"] = instanceID 136 | return response 137 | 138 | 139 | @app.get('/test') 140 | async def test(): 141 | """ 142 | 测试接口 143 | :return: 144 | """ 145 | f = await get_keys_by_pattern("node:*") 146 | return f 147 | 148 | 149 | @app.get('/') 150 | async def index(request: Request): 151 | """ 152 | 首页 153 | :return: 154 | """ 155 | version_suffix = os.getenv("COMMIT_ID", "")[:8] 156 | if request.headers.get("Cf-Ray"): 157 | via = "Cloudflare" 158 | rayId = request.headers.get("Cf-Ray") 159 | realIp = request.headers.get("Cf-Connecting-Ip") 160 | dataCenter = request.headers.get("Cf-Ipcountry") 161 | elif request.headers.get("Eagleeye-Traceid"): 162 | via = "Aliyun" 163 | rayId = request.headers.get("Eagleeye-Traceid") 164 | realIp = request.headers.get("X-Real-Ip") 165 | dataCenter = request.headers.get("Via") 166 | else: 167 | return JSONResponse(content={"status": "error", "error": "Direct access not allowed"}, status_code=403) 168 | info = { 169 | "version": "v2.2-prod-" + version_suffix, 170 | "buildAt": os.environ.get("BUILD_AT", ""), 171 | "author": "binaryYuki ", 172 | "arch": subprocess.run(['uname', '-m'], stdout=subprocess.PIPE).stdout.decode().strip(), 173 | "commit": os.getenv("COMMIT_ID", "")[:8], 174 | "instance-id": instanceID[:8], 175 | "request-id": request.headers.get("x-request-id", ""), 176 | "ray-id": rayId, 177 | "protocol": request.headers.get("X-Forwarded-Proto", ""), 178 | "ip": realIp, 179 | "dataCenter": dataCenter, 180 | "via": via, 181 | "code": 200, 182 | "message": "OK" 183 | } 184 | 185 | return JSONResponse(content=info) 186 | 187 | 188 | @app.api_route('/healthz', methods=['GET']) 189 | async def healthz(): 190 | """ 191 | 健康检查 192 | :return: 193 | """ 194 | try: 195 | f = await redis_client.ping() 196 | if f: 197 | return JSONResponse(content={"status": "ok", "message": "Redis connection established"}, status_code=200) 198 | else: 199 | return JSONResponse(content={"status": "error", "error": "redis conection failed code: 1000"}, 200 | status_code=500) 201 | except Exception as e: 202 | return JSONResponse(content={"status": "error", "error": f"redis conection failed code: 1001"}, status_code=500) 203 | 204 | 205 | @app.middleware("http") 206 | async def check_cdn(request: Request, call_next): 207 | """ 208 | 检查 CDN 209 | 可用: cloudflare / alicdn 210 | custom headers: 211 | - cf-ray 212 | - Eagleeye-Traceid 213 | :param request: 214 | :param call_next: 215 | :return: 216 | """ 217 | if request.headers.get("Cf-Ray") or request.headers.get("Eagleeye-Traceid"): 218 | return await call_next(request) 219 | elif request.headers.get("X-Via") == "internal": 220 | return await call_next(request) 221 | else: 222 | return JSONResponse(content={"status": "error", "error": "Direct access not allowed"}, status_code=403) 223 | 224 | 225 | @app.middleware("http") 226 | async def add_process_time_header(request, call_next): 227 | """ 228 | 添加处理时间到响应头 229 | :param request: 230 | :param call_next: 231 | :return: 232 | """ 233 | start_time = time.time() 234 | response = await call_next(request) 235 | process_time = time.time() - start_time 236 | # round it to 3 decimal places and add the unit which is seconds 237 | process_time = round(process_time, 3) 238 | response.headers["X-Process-Time"] = str(process_time) + "s" 239 | return response 240 | 241 | 242 | secret_key = os.environ.get("SESSION_SECRET") 243 | if not secret_key: 244 | secret_key = binascii.hexlify(random.randbytes(16)).decode('utf-8') 245 | 246 | # noinspection PyTypeChecker 247 | app.add_middleware(SessionMiddleware, secret_key=secret_key, 248 | session_cookie='session', max_age=60 * 60 * 12, same_site='lax', https_only=True) 249 | # noinspection PyTypeChecker 250 | app.add_middleware(GZipMiddleware, minimum_size=1000) 251 | if os.getenv("DEBUG", "false").lower() == "false": 252 | # noinspection PyTypeChecker 253 | app.add_middleware( 254 | CORSMiddleware, 255 | allow_origin_regex=r'^https?:\/\/(localhost:3000|.*\.tzpro\.xyz|.*\.tzpro\.uk)(\/.*)?$', 256 | allow_credentials=True, 257 | allow_methods=['GET', 'POST', 'OPTIONS'], # options 请求是预检请求,需要单独处理 258 | allow_headers=['Authorization', 'Content-Type', 'Accept', 'Accept-Encoding', 'Accept-Language', 'Origin', 259 | 'Referer', 'Cookie', 'User-Agent'], # 允许跨域的请求头 260 | ) 261 | app.add_middleware( 262 | CorrelationIdMiddleware, 263 | header_name='X-Request-ID', 264 | update_request_header=True, 265 | generator=lambda: uuid.uuid4().hex, 266 | validator=is_valid_uuid4, 267 | transformer=lambda a: a, 268 | ) 269 | else: 270 | # noinspection PyTypeChecker 271 | app.add_middleware( 272 | CORSMiddleware, 273 | allow_origins=['*'], 274 | allow_credentials=True, 275 | allow_methods=['GET', 'POST', 'OPTIONS', 'PUT'], # options 请求是预检请求,需要单独处理 276 | allow_headers=['Authorization', 'Content-Type', 'Accept', 'Accept-Encoding', 'Accept-Language', 'Origin', 277 | 'Referer', 'Cookie', 'User-Agent'], 278 | ) 279 | 280 | if __name__ == '__main__': 281 | import uvicorn 282 | import watchfiles 283 | 284 | watchfiles.filters = ["*venv", "\\.env$"] 285 | uvicorn.run(app, host="0.0.0.0", port=8000) 286 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "oleapi" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.13" 7 | dependencies = [ 8 | "asgi-correlation-id>=4.3.4", 9 | "fake-useragent>=2.2.0", 10 | "fastapi-limiter>=0.1.6", 11 | "fastapi-utils>=0.8.0", 12 | "fastapi[standard]>=0.115.12", 13 | "itsdangerous>=2.2.0", 14 | "passlib[bcrypt]>=1.7.4", 15 | "pre-commit>=4.2.0", 16 | "pyjwt>=2.10.1", 17 | "python-dotenv>=1.1.0", 18 | "python-jose[cryptography]>=3.4.0", 19 | "redis>=6.0.0", 20 | "typing-inspect>=0.9.0", 21 | "useragent>=0.1.1", 22 | ] 23 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | annotated-types==0.7.0 2 | anyio==4.9.0 3 | asgi-correlation-id==4.3.4 4 | bcrypt==4.3.0 5 | certifi==2025.4.26 6 | cffi==1.17.1 7 | cfgv==3.4.0 8 | click==8.1.8 9 | cryptography==44.0.3 10 | distlib==0.3.9 11 | dnspython==2.7.0 12 | ecdsa==0.19.1 13 | email-validator==2.2.0 14 | fake-useragent==2.2.0 15 | fastapi==0.115.12 16 | fastapi-cli==0.0.7 17 | fastapi-limiter==0.1.6 18 | fastapi-utils==0.8.0 19 | filelock==3.18.0 20 | h11==0.16.0 21 | httpcore==1.0.9 22 | httptools==0.6.4 23 | httpx==0.28.1 24 | identify==2.6.10 25 | idna==3.10 26 | itsdangerous==2.2.0 27 | jinja2==3.1.6 28 | markdown-it-py==3.0.0 29 | markupsafe==3.0.2 30 | mdurl==0.1.2 31 | mypy-extensions==1.1.0 32 | nodeenv==1.9.1 33 | packaging==25.0 34 | passlib==1.7.4 35 | platformdirs==4.3.8 36 | pre-commit==4.2.0 37 | psutil==5.9.8 38 | pyasn1==0.4.8 39 | pycparser==2.22 40 | pydantic==2.11.4 41 | pydantic-core==2.33.2 42 | pygments==2.19.1 43 | pyjwt==2.10.1 44 | python-dotenv==1.1.0 45 | python-jose==3.4.0 46 | python-multipart==0.0.20 47 | pyyaml==6.0.2 48 | redis==6.0.0 49 | rich==14.0.0 50 | rich-toolkit==0.14.5 51 | rsa==4.9.1 52 | shellingham==1.5.4 53 | six==1.17.0 54 | sniffio==1.3.1 55 | starlette==0.46.2 56 | typer==0.15.3 57 | typing-extensions==4.13.2 58 | typing-inspect==0.9.0 59 | typing-inspection==0.4.0 60 | useragent==0.1.1 61 | uvicorn==0.34.2 62 | uvloop==0.21.0 63 | virtualenv==20.31.1 64 | watchfiles==1.0.5 65 | websockets==15.0.1 66 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 2 3 | requires-python = ">=3.13" 4 | 5 | [[package]] 6 | name = "annotated-types" 7 | version = "0.7.0" 8 | source = { registry = "https://pypi.org/simple" } 9 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } 10 | wheels = [ 11 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, 12 | ] 13 | 14 | [[package]] 15 | name = "anyio" 16 | version = "4.9.0" 17 | source = { registry = "https://pypi.org/simple" } 18 | dependencies = [ 19 | { name = "idna" }, 20 | { name = "sniffio" }, 21 | ] 22 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } 23 | wheels = [ 24 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, 25 | ] 26 | 27 | [[package]] 28 | name = "asgi-correlation-id" 29 | version = "4.3.4" 30 | source = { registry = "https://pypi.org/simple" } 31 | dependencies = [ 32 | { name = "packaging" }, 33 | { name = "starlette" }, 34 | ] 35 | sdist = { url = "https://files.pythonhosted.org/packages/f4/ff/a6538245ac1eaa7733ec6740774e9d5add019e2c63caa29e758c16c0afdd/asgi_correlation_id-4.3.4.tar.gz", hash = "sha256:ea6bc310380373cb9f731dc2e8b2b6fb978a76afe33f7a2384f697b8d6cd811d", size = 20075, upload-time = "2024-10-17T11:44:30.324Z" } 36 | wheels = [ 37 | { url = "https://files.pythonhosted.org/packages/d9/ab/6936e2663c47a926e0659437b9333ad87d1ff49b1375d239026e0a268eba/asgi_correlation_id-4.3.4-py3-none-any.whl", hash = "sha256:36ce69b06c7d96b4acb89c7556a4c4f01a972463d3d49c675026cbbd08e9a0a2", size = 15262, upload-time = "2024-10-17T11:44:28.739Z" }, 38 | ] 39 | 40 | [[package]] 41 | name = "bcrypt" 42 | version = "4.3.0" 43 | source = { registry = "https://pypi.org/simple" } 44 | sdist = { url = "https://files.pythonhosted.org/packages/bb/5d/6d7433e0f3cd46ce0b43cd65e1db465ea024dbb8216fb2404e919c2ad77b/bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", size = 25697, upload-time = "2025-02-28T01:24:09.174Z" } 45 | wheels = [ 46 | { url = "https://files.pythonhosted.org/packages/bf/2c/3d44e853d1fe969d229bd58d39ae6902b3d924af0e2b5a60d17d4b809ded/bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", size = 483719, upload-time = "2025-02-28T01:22:34.539Z" }, 47 | { url = "https://files.pythonhosted.org/packages/a1/e2/58ff6e2a22eca2e2cff5370ae56dba29d70b1ea6fc08ee9115c3ae367795/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", size = 272001, upload-time = "2025-02-28T01:22:38.078Z" }, 48 | { url = "https://files.pythonhosted.org/packages/37/1f/c55ed8dbe994b1d088309e366749633c9eb90d139af3c0a50c102ba68a1a/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", size = 277451, upload-time = "2025-02-28T01:22:40.787Z" }, 49 | { url = "https://files.pythonhosted.org/packages/d7/1c/794feb2ecf22fe73dcfb697ea7057f632061faceb7dcf0f155f3443b4d79/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", size = 272792, upload-time = "2025-02-28T01:22:43.144Z" }, 50 | { url = "https://files.pythonhosted.org/packages/13/b7/0b289506a3f3598c2ae2bdfa0ea66969812ed200264e3f61df77753eee6d/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", size = 289752, upload-time = "2025-02-28T01:22:45.56Z" }, 51 | { url = "https://files.pythonhosted.org/packages/dc/24/d0fb023788afe9e83cc118895a9f6c57e1044e7e1672f045e46733421fe6/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", size = 277762, upload-time = "2025-02-28T01:22:47.023Z" }, 52 | { url = "https://files.pythonhosted.org/packages/e4/38/cde58089492e55ac4ef6c49fea7027600c84fd23f7520c62118c03b4625e/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", size = 272384, upload-time = "2025-02-28T01:22:49.221Z" }, 53 | { url = "https://files.pythonhosted.org/packages/de/6a/d5026520843490cfc8135d03012a413e4532a400e471e6188b01b2de853f/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", size = 277329, upload-time = "2025-02-28T01:22:51.603Z" }, 54 | { url = "https://files.pythonhosted.org/packages/b3/a3/4fc5255e60486466c389e28c12579d2829b28a527360e9430b4041df4cf9/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", size = 305241, upload-time = "2025-02-28T01:22:53.283Z" }, 55 | { url = "https://files.pythonhosted.org/packages/c7/15/2b37bc07d6ce27cc94e5b10fd5058900eb8fb11642300e932c8c82e25c4a/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", size = 309617, upload-time = "2025-02-28T01:22:55.461Z" }, 56 | { url = "https://files.pythonhosted.org/packages/5f/1f/99f65edb09e6c935232ba0430c8c13bb98cb3194b6d636e61d93fe60ac59/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", size = 335751, upload-time = "2025-02-28T01:22:57.81Z" }, 57 | { url = "https://files.pythonhosted.org/packages/00/1b/b324030c706711c99769988fcb694b3cb23f247ad39a7823a78e361bdbb8/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", size = 355965, upload-time = "2025-02-28T01:22:59.181Z" }, 58 | { url = "https://files.pythonhosted.org/packages/aa/dd/20372a0579dd915dfc3b1cd4943b3bca431866fcb1dfdfd7518c3caddea6/bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", size = 155316, upload-time = "2025-02-28T01:23:00.763Z" }, 59 | { url = "https://files.pythonhosted.org/packages/6d/52/45d969fcff6b5577c2bf17098dc36269b4c02197d551371c023130c0f890/bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", size = 147752, upload-time = "2025-02-28T01:23:02.908Z" }, 60 | { url = "https://files.pythonhosted.org/packages/11/22/5ada0b9af72b60cbc4c9a399fdde4af0feaa609d27eb0adc61607997a3fa/bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d", size = 498019, upload-time = "2025-02-28T01:23:05.838Z" }, 61 | { url = "https://files.pythonhosted.org/packages/b8/8c/252a1edc598dc1ce57905be173328eda073083826955ee3c97c7ff5ba584/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", size = 279174, upload-time = "2025-02-28T01:23:07.274Z" }, 62 | { url = "https://files.pythonhosted.org/packages/29/5b/4547d5c49b85f0337c13929f2ccbe08b7283069eea3550a457914fc078aa/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", size = 283870, upload-time = "2025-02-28T01:23:09.151Z" }, 63 | { url = "https://files.pythonhosted.org/packages/be/21/7dbaf3fa1745cb63f776bb046e481fbababd7d344c5324eab47f5ca92dd2/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", size = 279601, upload-time = "2025-02-28T01:23:11.461Z" }, 64 | { url = "https://files.pythonhosted.org/packages/6d/64/e042fc8262e971347d9230d9abbe70d68b0a549acd8611c83cebd3eaec67/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", size = 297660, upload-time = "2025-02-28T01:23:12.989Z" }, 65 | { url = "https://files.pythonhosted.org/packages/50/b8/6294eb84a3fef3b67c69b4470fcdd5326676806bf2519cda79331ab3c3a9/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", size = 284083, upload-time = "2025-02-28T01:23:14.5Z" }, 66 | { url = "https://files.pythonhosted.org/packages/62/e6/baff635a4f2c42e8788fe1b1633911c38551ecca9a749d1052d296329da6/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", size = 279237, upload-time = "2025-02-28T01:23:16.686Z" }, 67 | { url = "https://files.pythonhosted.org/packages/39/48/46f623f1b0c7dc2e5de0b8af5e6f5ac4cc26408ac33f3d424e5ad8da4a90/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", size = 283737, upload-time = "2025-02-28T01:23:18.897Z" }, 68 | { url = "https://files.pythonhosted.org/packages/49/8b/70671c3ce9c0fca4a6cc3cc6ccbaa7e948875a2e62cbd146e04a4011899c/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", size = 312741, upload-time = "2025-02-28T01:23:21.041Z" }, 69 | { url = "https://files.pythonhosted.org/packages/27/fb/910d3a1caa2d249b6040a5caf9f9866c52114d51523ac2fb47578a27faee/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", size = 316472, upload-time = "2025-02-28T01:23:23.183Z" }, 70 | { url = "https://files.pythonhosted.org/packages/dc/cf/7cf3a05b66ce466cfb575dbbda39718d45a609daa78500f57fa9f36fa3c0/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", size = 343606, upload-time = "2025-02-28T01:23:25.361Z" }, 71 | { url = "https://files.pythonhosted.org/packages/e3/b8/e970ecc6d7e355c0d892b7f733480f4aa8509f99b33e71550242cf0b7e63/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", size = 362867, upload-time = "2025-02-28T01:23:26.875Z" }, 72 | { url = "https://files.pythonhosted.org/packages/a9/97/8d3118efd8354c555a3422d544163f40d9f236be5b96c714086463f11699/bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", size = 160589, upload-time = "2025-02-28T01:23:28.381Z" }, 73 | { url = "https://files.pythonhosted.org/packages/29/07/416f0b99f7f3997c69815365babbc2e8754181a4b1899d921b3c7d5b6f12/bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", size = 152794, upload-time = "2025-02-28T01:23:30.187Z" }, 74 | { url = "https://files.pythonhosted.org/packages/6e/c1/3fa0e9e4e0bfd3fd77eb8b52ec198fd6e1fd7e9402052e43f23483f956dd/bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", size = 498969, upload-time = "2025-02-28T01:23:31.945Z" }, 75 | { url = "https://files.pythonhosted.org/packages/ce/d4/755ce19b6743394787fbd7dff6bf271b27ee9b5912a97242e3caf125885b/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", size = 279158, upload-time = "2025-02-28T01:23:34.161Z" }, 76 | { url = "https://files.pythonhosted.org/packages/9b/5d/805ef1a749c965c46b28285dfb5cd272a7ed9fa971f970435a5133250182/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", size = 284285, upload-time = "2025-02-28T01:23:35.765Z" }, 77 | { url = "https://files.pythonhosted.org/packages/ab/2b/698580547a4a4988e415721b71eb45e80c879f0fb04a62da131f45987b96/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", size = 279583, upload-time = "2025-02-28T01:23:38.021Z" }, 78 | { url = "https://files.pythonhosted.org/packages/f2/87/62e1e426418204db520f955ffd06f1efd389feca893dad7095bf35612eec/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", size = 297896, upload-time = "2025-02-28T01:23:39.575Z" }, 79 | { url = "https://files.pythonhosted.org/packages/cb/c6/8fedca4c2ada1b6e889c52d2943b2f968d3427e5d65f595620ec4c06fa2f/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", size = 284492, upload-time = "2025-02-28T01:23:40.901Z" }, 80 | { url = "https://files.pythonhosted.org/packages/4d/4d/c43332dcaaddb7710a8ff5269fcccba97ed3c85987ddaa808db084267b9a/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", size = 279213, upload-time = "2025-02-28T01:23:42.653Z" }, 81 | { url = "https://files.pythonhosted.org/packages/dc/7f/1e36379e169a7df3a14a1c160a49b7b918600a6008de43ff20d479e6f4b5/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", size = 284162, upload-time = "2025-02-28T01:23:43.964Z" }, 82 | { url = "https://files.pythonhosted.org/packages/1c/0a/644b2731194b0d7646f3210dc4d80c7fee3ecb3a1f791a6e0ae6bb8684e3/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", size = 312856, upload-time = "2025-02-28T01:23:46.011Z" }, 83 | { url = "https://files.pythonhosted.org/packages/dc/62/2a871837c0bb6ab0c9a88bf54de0fc021a6a08832d4ea313ed92a669d437/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", size = 316726, upload-time = "2025-02-28T01:23:47.575Z" }, 84 | { url = "https://files.pythonhosted.org/packages/0c/a1/9898ea3faac0b156d457fd73a3cb9c2855c6fd063e44b8522925cdd8ce46/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", size = 343664, upload-time = "2025-02-28T01:23:49.059Z" }, 85 | { url = "https://files.pythonhosted.org/packages/40/f2/71b4ed65ce38982ecdda0ff20c3ad1b15e71949c78b2c053df53629ce940/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", size = 363128, upload-time = "2025-02-28T01:23:50.399Z" }, 86 | { url = "https://files.pythonhosted.org/packages/11/99/12f6a58eca6dea4be992d6c681b7ec9410a1d9f5cf368c61437e31daa879/bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", size = 160598, upload-time = "2025-02-28T01:23:51.775Z" }, 87 | { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload-time = "2025-02-28T01:23:53.139Z" }, 88 | ] 89 | 90 | [[package]] 91 | name = "certifi" 92 | version = "2025.4.26" 93 | source = { registry = "https://pypi.org/simple" } 94 | sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } 95 | wheels = [ 96 | { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, 97 | ] 98 | 99 | [[package]] 100 | name = "cffi" 101 | version = "1.17.1" 102 | source = { registry = "https://pypi.org/simple" } 103 | dependencies = [ 104 | { name = "pycparser" }, 105 | ] 106 | 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" } 107 | wheels = [ 108 | { 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" }, 109 | { 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" }, 110 | { 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" }, 111 | { 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" }, 112 | { 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" }, 113 | { 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" }, 114 | { 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" }, 115 | { 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" }, 116 | { 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" }, 117 | { 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" }, 118 | { 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" }, 119 | ] 120 | 121 | [[package]] 122 | name = "cfgv" 123 | version = "3.4.0" 124 | source = { registry = "https://pypi.org/simple" } 125 | sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } 126 | wheels = [ 127 | { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, 128 | ] 129 | 130 | [[package]] 131 | name = "click" 132 | version = "8.1.8" 133 | source = { registry = "https://pypi.org/simple" } 134 | dependencies = [ 135 | { name = "colorama", marker = "sys_platform == 'win32'" }, 136 | ] 137 | sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } 138 | wheels = [ 139 | { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, 140 | ] 141 | 142 | [[package]] 143 | name = "colorama" 144 | version = "0.4.6" 145 | source = { registry = "https://pypi.org/simple" } 146 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } 147 | wheels = [ 148 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, 149 | ] 150 | 151 | [[package]] 152 | name = "cryptography" 153 | version = "44.0.3" 154 | source = { registry = "https://pypi.org/simple" } 155 | dependencies = [ 156 | { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, 157 | ] 158 | sdist = { url = "https://files.pythonhosted.org/packages/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096, upload-time = "2025-05-02T19:36:04.667Z" } 159 | wheels = [ 160 | { url = "https://files.pythonhosted.org/packages/08/53/c776d80e9d26441bb3868457909b4e74dd9ccabd182e10b2b0ae7a07e265/cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88", size = 6670281, upload-time = "2025-05-02T19:34:50.665Z" }, 161 | { url = "https://files.pythonhosted.org/packages/6a/06/af2cf8d56ef87c77319e9086601bef621bedf40f6f59069e1b6d1ec498c5/cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137", size = 3959305, upload-time = "2025-05-02T19:34:53.042Z" }, 162 | { url = "https://files.pythonhosted.org/packages/ae/01/80de3bec64627207d030f47bf3536889efee8913cd363e78ca9a09b13c8e/cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c", size = 4171040, upload-time = "2025-05-02T19:34:54.675Z" }, 163 | { url = "https://files.pythonhosted.org/packages/bd/48/bb16b7541d207a19d9ae8b541c70037a05e473ddc72ccb1386524d4f023c/cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76", size = 3963411, upload-time = "2025-05-02T19:34:56.61Z" }, 164 | { url = "https://files.pythonhosted.org/packages/42/b2/7d31f2af5591d217d71d37d044ef5412945a8a8e98d5a2a8ae4fd9cd4489/cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359", size = 3689263, upload-time = "2025-05-02T19:34:58.591Z" }, 165 | { url = "https://files.pythonhosted.org/packages/25/50/c0dfb9d87ae88ccc01aad8eb93e23cfbcea6a6a106a9b63a7b14c1f93c75/cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43", size = 4196198, upload-time = "2025-05-02T19:35:00.988Z" }, 166 | { url = "https://files.pythonhosted.org/packages/66/c9/55c6b8794a74da652690c898cb43906310a3e4e4f6ee0b5f8b3b3e70c441/cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01", size = 3966502, upload-time = "2025-05-02T19:35:03.091Z" }, 167 | { url = "https://files.pythonhosted.org/packages/b6/f7/7cb5488c682ca59a02a32ec5f975074084db4c983f849d47b7b67cc8697a/cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d", size = 4196173, upload-time = "2025-05-02T19:35:05.018Z" }, 168 | { url = "https://files.pythonhosted.org/packages/d2/0b/2f789a8403ae089b0b121f8f54f4a3e5228df756e2146efdf4a09a3d5083/cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904", size = 4087713, upload-time = "2025-05-02T19:35:07.187Z" }, 169 | { url = "https://files.pythonhosted.org/packages/1d/aa/330c13655f1af398fc154089295cf259252f0ba5df93b4bc9d9c7d7f843e/cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44", size = 4299064, upload-time = "2025-05-02T19:35:08.879Z" }, 170 | { url = "https://files.pythonhosted.org/packages/10/a8/8c540a421b44fd267a7d58a1fd5f072a552d72204a3f08194f98889de76d/cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d", size = 2773887, upload-time = "2025-05-02T19:35:10.41Z" }, 171 | { url = "https://files.pythonhosted.org/packages/b9/0d/c4b1657c39ead18d76bbd122da86bd95bdc4095413460d09544000a17d56/cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d", size = 3209737, upload-time = "2025-05-02T19:35:12.12Z" }, 172 | { url = "https://files.pythonhosted.org/packages/34/a3/ad08e0bcc34ad436013458d7528e83ac29910943cea42ad7dd4141a27bbb/cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f", size = 6673501, upload-time = "2025-05-02T19:35:13.775Z" }, 173 | { url = "https://files.pythonhosted.org/packages/b1/f0/7491d44bba8d28b464a5bc8cc709f25a51e3eac54c0a4444cf2473a57c37/cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759", size = 3960307, upload-time = "2025-05-02T19:35:15.917Z" }, 174 | { url = "https://files.pythonhosted.org/packages/f7/c8/e5c5d0e1364d3346a5747cdcd7ecbb23ca87e6dea4f942a44e88be349f06/cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645", size = 4170876, upload-time = "2025-05-02T19:35:18.138Z" }, 175 | { url = "https://files.pythonhosted.org/packages/73/96/025cb26fc351d8c7d3a1c44e20cf9a01e9f7cf740353c9c7a17072e4b264/cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2", size = 3964127, upload-time = "2025-05-02T19:35:19.864Z" }, 176 | { url = "https://files.pythonhosted.org/packages/01/44/eb6522db7d9f84e8833ba3bf63313f8e257729cf3a8917379473fcfd6601/cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54", size = 3689164, upload-time = "2025-05-02T19:35:21.449Z" }, 177 | { url = "https://files.pythonhosted.org/packages/68/fb/d61a4defd0d6cee20b1b8a1ea8f5e25007e26aeb413ca53835f0cae2bcd1/cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93", size = 4198081, upload-time = "2025-05-02T19:35:23.187Z" }, 178 | { url = "https://files.pythonhosted.org/packages/1b/50/457f6911d36432a8811c3ab8bd5a6090e8d18ce655c22820994913dd06ea/cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c", size = 3967716, upload-time = "2025-05-02T19:35:25.426Z" }, 179 | { url = "https://files.pythonhosted.org/packages/35/6e/dca39d553075980ccb631955c47b93d87d27f3596da8d48b1ae81463d915/cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f", size = 4197398, upload-time = "2025-05-02T19:35:27.678Z" }, 180 | { url = "https://files.pythonhosted.org/packages/9b/9d/d1f2fe681eabc682067c66a74addd46c887ebacf39038ba01f8860338d3d/cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5", size = 4087900, upload-time = "2025-05-02T19:35:29.312Z" }, 181 | { url = "https://files.pythonhosted.org/packages/c4/f5/3599e48c5464580b73b236aafb20973b953cd2e7b44c7c2533de1d888446/cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b", size = 4301067, upload-time = "2025-05-02T19:35:31.547Z" }, 182 | { url = "https://files.pythonhosted.org/packages/a7/6c/d2c48c8137eb39d0c193274db5c04a75dab20d2f7c3f81a7dcc3a8897701/cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028", size = 2775467, upload-time = "2025-05-02T19:35:33.805Z" }, 183 | { url = "https://files.pythonhosted.org/packages/c9/ad/51f212198681ea7b0deaaf8846ee10af99fba4e894f67b353524eab2bbe5/cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334", size = 3210375, upload-time = "2025-05-02T19:35:35.369Z" }, 184 | ] 185 | 186 | [[package]] 187 | name = "distlib" 188 | version = "0.3.9" 189 | source = { registry = "https://pypi.org/simple" } 190 | sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" } 191 | wheels = [ 192 | { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, 193 | ] 194 | 195 | [[package]] 196 | name = "dnspython" 197 | version = "2.7.0" 198 | source = { registry = "https://pypi.org/simple" } 199 | 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" } 200 | wheels = [ 201 | { 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" }, 202 | ] 203 | 204 | [[package]] 205 | name = "ecdsa" 206 | version = "0.19.1" 207 | source = { registry = "https://pypi.org/simple" } 208 | dependencies = [ 209 | { name = "six" }, 210 | ] 211 | sdist = { url = "https://files.pythonhosted.org/packages/c0/1f/924e3caae75f471eae4b26bd13b698f6af2c44279f67af317439c2f4c46a/ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61", size = 201793, upload-time = "2025-03-13T11:52:43.25Z" } 212 | wheels = [ 213 | { url = "https://files.pythonhosted.org/packages/cb/a3/460c57f094a4a165c84a1341c373b0a4f5ec6ac244b998d5021aade89b77/ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3", size = 150607, upload-time = "2025-03-13T11:52:41.757Z" }, 214 | ] 215 | 216 | [[package]] 217 | name = "email-validator" 218 | version = "2.2.0" 219 | source = { registry = "https://pypi.org/simple" } 220 | dependencies = [ 221 | { name = "dnspython" }, 222 | { name = "idna" }, 223 | ] 224 | 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" } 225 | wheels = [ 226 | { 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" }, 227 | ] 228 | 229 | [[package]] 230 | name = "fake-useragent" 231 | version = "2.2.0" 232 | source = { registry = "https://pypi.org/simple" } 233 | sdist = { url = "https://files.pythonhosted.org/packages/41/43/948d10bf42735709edb5ae51e23297d034086f17fc7279fef385a7acb473/fake_useragent-2.2.0.tar.gz", hash = "sha256:4e6ab6571e40cc086d788523cf9e018f618d07f9050f822ff409a4dfe17c16b2", size = 158898, upload-time = "2025-04-14T15:32:19.238Z" } 234 | wheels = [ 235 | { url = "https://files.pythonhosted.org/packages/51/37/b3ea9cd5558ff4cb51957caca2193981c6b0ff30bd0d2630ac62505d99d0/fake_useragent-2.2.0-py3-none-any.whl", hash = "sha256:67f35ca4d847b0d298187443aaf020413746e56acd985a611908c73dba2daa24", size = 161695, upload-time = "2025-04-14T15:32:17.732Z" }, 236 | ] 237 | 238 | [[package]] 239 | name = "fastapi" 240 | version = "0.115.12" 241 | source = { registry = "https://pypi.org/simple" } 242 | dependencies = [ 243 | { name = "pydantic" }, 244 | { name = "starlette" }, 245 | { name = "typing-extensions" }, 246 | ] 247 | sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236, upload-time = "2025-03-23T22:55:43.822Z" } 248 | wheels = [ 249 | { url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164, upload-time = "2025-03-23T22:55:42.101Z" }, 250 | ] 251 | 252 | [package.optional-dependencies] 253 | standard = [ 254 | { name = "email-validator" }, 255 | { name = "fastapi-cli", extra = ["standard"] }, 256 | { name = "httpx" }, 257 | { name = "jinja2" }, 258 | { name = "python-multipart" }, 259 | { name = "uvicorn", extra = ["standard"] }, 260 | ] 261 | 262 | [[package]] 263 | name = "fastapi-cli" 264 | version = "0.0.7" 265 | source = { registry = "https://pypi.org/simple" } 266 | dependencies = [ 267 | { name = "rich-toolkit" }, 268 | { name = "typer" }, 269 | { name = "uvicorn", extra = ["standard"] }, 270 | ] 271 | sdist = { url = "https://files.pythonhosted.org/packages/fe/73/82a5831fbbf8ed75905bacf5b2d9d3dfd6f04d6968b29fe6f72a5ae9ceb1/fastapi_cli-0.0.7.tar.gz", hash = "sha256:02b3b65956f526412515907a0793c9094abd4bfb5457b389f645b0ea6ba3605e", size = 16753, upload-time = "2024-12-15T14:28:10.028Z" } 272 | wheels = [ 273 | { url = "https://files.pythonhosted.org/packages/a1/e6/5daefc851b514ce2287d8f5d358ae4341089185f78f3217a69d0ce3a390c/fastapi_cli-0.0.7-py3-none-any.whl", hash = "sha256:d549368ff584b2804336c61f192d86ddea080c11255f375959627911944804f4", size = 10705, upload-time = "2024-12-15T14:28:06.18Z" }, 274 | ] 275 | 276 | [package.optional-dependencies] 277 | standard = [ 278 | { name = "uvicorn", extra = ["standard"] }, 279 | ] 280 | 281 | [[package]] 282 | name = "fastapi-limiter" 283 | version = "0.1.6" 284 | source = { registry = "https://pypi.org/simple" } 285 | dependencies = [ 286 | { name = "fastapi" }, 287 | { name = "redis" }, 288 | ] 289 | sdist = { url = "https://files.pythonhosted.org/packages/7f/99/c7903234488d4dca5f9bccb4f88c2f582a234f0dca33348781c9cf8a48c6/fastapi_limiter-0.1.6.tar.gz", hash = "sha256:6f5fde8efebe12eb33861bdffb91009f699369a3c2862cdc7c1d9acf912ff443", size = 8307, upload-time = "2024-01-05T09:14:48.628Z" } 290 | wheels = [ 291 | { url = "https://files.pythonhosted.org/packages/cd/b5/6f6b4d18bee1cafc857eae12738b3a03b7d1102b833668be868938c57b9d/fastapi_limiter-0.1.6-py3-none-any.whl", hash = "sha256:2e53179a4208b8f2c8795e38bb001324d3dc37d2800ff49fd28ec5caabf7a240", size = 15829, upload-time = "2024-01-05T09:14:47.613Z" }, 292 | ] 293 | 294 | [[package]] 295 | name = "fastapi-utils" 296 | version = "0.8.0" 297 | source = { registry = "https://pypi.org/simple" } 298 | dependencies = [ 299 | { name = "fastapi" }, 300 | { name = "psutil" }, 301 | { name = "pydantic" }, 302 | ] 303 | sdist = { url = "https://files.pythonhosted.org/packages/dd/af/57c949675176acf389d94fbccd369b486579a952637fc6fb104f1bc3d0c3/fastapi_utils-0.8.0.tar.gz", hash = "sha256:eca834e80c09f85df30004fe5e861981262b296f60c93d5a1a1416fe4c784140", size = 16496, upload-time = "2024-11-11T08:30:03.852Z" } 304 | wheels = [ 305 | { url = "https://files.pythonhosted.org/packages/43/8b/cef8cfed7ed77d52fc772b1c7b966ba019a3f50b65a2b3625a0f3b7f6f53/fastapi_utils-0.8.0-py3-none-any.whl", hash = "sha256:6c4d507a76bab9a016cee0c4fa3a4638c636b2b2689e39c62254b1b2e4e81825", size = 18495, upload-time = "2024-11-11T08:30:01.914Z" }, 306 | ] 307 | 308 | [[package]] 309 | name = "filelock" 310 | version = "3.18.0" 311 | source = { registry = "https://pypi.org/simple" } 312 | sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } 313 | wheels = [ 314 | { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, 315 | ] 316 | 317 | [[package]] 318 | name = "h11" 319 | version = "0.16.0" 320 | source = { registry = "https://pypi.org/simple" } 321 | sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } 322 | wheels = [ 323 | { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, 324 | ] 325 | 326 | [[package]] 327 | name = "httpcore" 328 | version = "1.0.9" 329 | source = { registry = "https://pypi.org/simple" } 330 | dependencies = [ 331 | { name = "certifi" }, 332 | { name = "h11" }, 333 | ] 334 | sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } 335 | wheels = [ 336 | { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, 337 | ] 338 | 339 | [[package]] 340 | name = "httptools" 341 | version = "0.6.4" 342 | source = { registry = "https://pypi.org/simple" } 343 | sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } 344 | wheels = [ 345 | { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" }, 346 | { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" }, 347 | { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" }, 348 | { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" }, 349 | { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" }, 350 | { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" }, 351 | { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, 352 | ] 353 | 354 | [[package]] 355 | name = "httpx" 356 | version = "0.28.1" 357 | source = { registry = "https://pypi.org/simple" } 358 | dependencies = [ 359 | { name = "anyio" }, 360 | { name = "certifi" }, 361 | { name = "httpcore" }, 362 | { name = "idna" }, 363 | ] 364 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } 365 | wheels = [ 366 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, 367 | ] 368 | 369 | [[package]] 370 | name = "identify" 371 | version = "2.6.10" 372 | source = { registry = "https://pypi.org/simple" } 373 | sdist = { url = "https://files.pythonhosted.org/packages/0c/83/b6ea0334e2e7327084a46aaaf71f2146fc061a192d6518c0d020120cd0aa/identify-2.6.10.tar.gz", hash = "sha256:45e92fd704f3da71cc3880036633f48b4b7265fd4de2b57627cb157216eb7eb8", size = 99201, upload-time = "2025-04-19T15:10:38.32Z" } 374 | wheels = [ 375 | { url = "https://files.pythonhosted.org/packages/2b/d3/85feeba1d097b81a44bcffa6a0beab7b4dfffe78e82fc54978d3ac380736/identify-2.6.10-py2.py3-none-any.whl", hash = "sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25", size = 99101, upload-time = "2025-04-19T15:10:36.701Z" }, 376 | ] 377 | 378 | [[package]] 379 | name = "idna" 380 | version = "3.10" 381 | source = { registry = "https://pypi.org/simple" } 382 | 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" } 383 | wheels = [ 384 | { 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" }, 385 | ] 386 | 387 | [[package]] 388 | name = "itsdangerous" 389 | version = "2.2.0" 390 | source = { registry = "https://pypi.org/simple" } 391 | sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } 392 | wheels = [ 393 | { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, 394 | ] 395 | 396 | [[package]] 397 | name = "jinja2" 398 | version = "3.1.6" 399 | source = { registry = "https://pypi.org/simple" } 400 | dependencies = [ 401 | { name = "markupsafe" }, 402 | ] 403 | sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } 404 | wheels = [ 405 | { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, 406 | ] 407 | 408 | [[package]] 409 | name = "markdown-it-py" 410 | version = "3.0.0" 411 | source = { registry = "https://pypi.org/simple" } 412 | dependencies = [ 413 | { name = "mdurl" }, 414 | ] 415 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } 416 | wheels = [ 417 | { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, 418 | ] 419 | 420 | [[package]] 421 | name = "markupsafe" 422 | version = "3.0.2" 423 | source = { registry = "https://pypi.org/simple" } 424 | sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } 425 | wheels = [ 426 | { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274, upload-time = "2024-10-18T15:21:24.577Z" }, 427 | { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352, upload-time = "2024-10-18T15:21:25.382Z" }, 428 | { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122, upload-time = "2024-10-18T15:21:26.199Z" }, 429 | { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085, upload-time = "2024-10-18T15:21:27.029Z" }, 430 | { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978, upload-time = "2024-10-18T15:21:27.846Z" }, 431 | { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208, upload-time = "2024-10-18T15:21:28.744Z" }, 432 | { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357, upload-time = "2024-10-18T15:21:29.545Z" }, 433 | { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344, upload-time = "2024-10-18T15:21:30.366Z" }, 434 | { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101, upload-time = "2024-10-18T15:21:31.207Z" }, 435 | { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603, upload-time = "2024-10-18T15:21:32.032Z" }, 436 | { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510, upload-time = "2024-10-18T15:21:33.625Z" }, 437 | { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486, upload-time = "2024-10-18T15:21:34.611Z" }, 438 | { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480, upload-time = "2024-10-18T15:21:35.398Z" }, 439 | { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914, upload-time = "2024-10-18T15:21:36.231Z" }, 440 | { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796, upload-time = "2024-10-18T15:21:37.073Z" }, 441 | { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473, upload-time = "2024-10-18T15:21:37.932Z" }, 442 | { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114, upload-time = "2024-10-18T15:21:39.799Z" }, 443 | { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098, upload-time = "2024-10-18T15:21:40.813Z" }, 444 | { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208, upload-time = "2024-10-18T15:21:41.814Z" }, 445 | { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" }, 446 | ] 447 | 448 | [[package]] 449 | name = "mdurl" 450 | version = "0.1.2" 451 | source = { registry = "https://pypi.org/simple" } 452 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } 453 | wheels = [ 454 | { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, 455 | ] 456 | 457 | [[package]] 458 | name = "mypy-extensions" 459 | version = "1.1.0" 460 | source = { registry = "https://pypi.org/simple" } 461 | sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } 462 | wheels = [ 463 | { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, 464 | ] 465 | 466 | [[package]] 467 | name = "nodeenv" 468 | version = "1.9.1" 469 | source = { registry = "https://pypi.org/simple" } 470 | sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } 471 | wheels = [ 472 | { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, 473 | ] 474 | 475 | [[package]] 476 | name = "oleapi" 477 | version = "0.1.0" 478 | source = { virtual = "." } 479 | dependencies = [ 480 | { name = "asgi-correlation-id" }, 481 | { name = "fake-useragent" }, 482 | { name = "fastapi", extra = ["standard"] }, 483 | { name = "fastapi-limiter" }, 484 | { name = "fastapi-utils" }, 485 | { name = "itsdangerous" }, 486 | { name = "passlib", extra = ["bcrypt"] }, 487 | { name = "pre-commit" }, 488 | { name = "pyjwt" }, 489 | { name = "python-dotenv" }, 490 | { name = "python-jose", extra = ["cryptography"] }, 491 | { name = "redis" }, 492 | { name = "typing-inspect" }, 493 | { name = "useragent" }, 494 | ] 495 | 496 | [package.metadata] 497 | requires-dist = [ 498 | { name = "asgi-correlation-id", specifier = ">=4.3.4" }, 499 | { name = "fake-useragent", specifier = ">=2.2.0" }, 500 | { name = "fastapi", extras = ["standard"], specifier = ">=0.115.12" }, 501 | { name = "fastapi-limiter", specifier = ">=0.1.6" }, 502 | { name = "fastapi-utils", specifier = ">=0.8.0" }, 503 | { name = "itsdangerous", specifier = ">=2.2.0" }, 504 | { name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" }, 505 | { name = "pre-commit", specifier = ">=4.2.0" }, 506 | { name = "pyjwt", specifier = ">=2.10.1" }, 507 | { name = "python-dotenv", specifier = ">=1.1.0" }, 508 | { name = "python-jose", extras = ["cryptography"], specifier = ">=3.4.0" }, 509 | { name = "redis", specifier = ">=6.0.0" }, 510 | { name = "typing-inspect", specifier = ">=0.9.0" }, 511 | { name = "useragent", specifier = ">=0.1.1" }, 512 | ] 513 | 514 | [[package]] 515 | name = "packaging" 516 | version = "25.0" 517 | source = { registry = "https://pypi.org/simple" } 518 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } 519 | wheels = [ 520 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, 521 | ] 522 | 523 | [[package]] 524 | name = "passlib" 525 | version = "1.7.4" 526 | source = { registry = "https://pypi.org/simple" } 527 | sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844, upload-time = "2020-10-08T19:00:52.121Z" } 528 | wheels = [ 529 | { url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554, upload-time = "2020-10-08T19:00:49.856Z" }, 530 | ] 531 | 532 | [package.optional-dependencies] 533 | bcrypt = [ 534 | { name = "bcrypt" }, 535 | ] 536 | 537 | [[package]] 538 | name = "platformdirs" 539 | version = "4.3.8" 540 | source = { registry = "https://pypi.org/simple" } 541 | sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } 542 | wheels = [ 543 | { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, 544 | ] 545 | 546 | [[package]] 547 | name = "pre-commit" 548 | version = "4.2.0" 549 | source = { registry = "https://pypi.org/simple" } 550 | dependencies = [ 551 | { name = "cfgv" }, 552 | { name = "identify" }, 553 | { name = "nodeenv" }, 554 | { name = "pyyaml" }, 555 | { name = "virtualenv" }, 556 | ] 557 | sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" } 558 | wheels = [ 559 | { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, 560 | ] 561 | 562 | [[package]] 563 | name = "psutil" 564 | version = "5.9.8" 565 | source = { registry = "https://pypi.org/simple" } 566 | sdist = { url = "https://files.pythonhosted.org/packages/90/c7/6dc0a455d111f68ee43f27793971cf03fe29b6ef972042549db29eec39a2/psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c", size = 503247, upload-time = "2024-01-19T20:47:09.517Z" } 567 | wheels = [ 568 | { url = "https://files.pythonhosted.org/packages/e7/e3/07ae864a636d70a8a6f58da27cb1179192f1140d5d1da10886ade9405797/psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81", size = 248702, upload-time = "2024-01-19T20:47:36.303Z" }, 569 | { url = "https://files.pythonhosted.org/packages/b3/bd/28c5f553667116b2598b9cc55908ec435cb7f77a34f2bff3e3ca765b0f78/psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421", size = 285242, upload-time = "2024-01-19T20:47:39.65Z" }, 570 | { url = "https://files.pythonhosted.org/packages/c5/4f/0e22aaa246f96d6ac87fe5ebb9c5a693fbe8877f537a1022527c47ca43c5/psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4", size = 288191, upload-time = "2024-01-19T20:47:43.078Z" }, 571 | { url = "https://files.pythonhosted.org/packages/6e/f5/2aa3a4acdc1e5940b59d421742356f133185667dd190b166dbcfcf5d7b43/psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0", size = 251252, upload-time = "2024-01-19T20:47:52.88Z" }, 572 | { url = "https://files.pythonhosted.org/packages/93/52/3e39d26feae7df0aa0fd510b14012c3678b36ed068f7d78b8d8784d61f0e/psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf", size = 255090, upload-time = "2024-01-19T20:47:56.019Z" }, 573 | { url = "https://files.pythonhosted.org/packages/05/33/2d74d588408caedd065c2497bdb5ef83ce6082db01289a1e1147f6639802/psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8", size = 249898, upload-time = "2024-01-19T20:47:59.238Z" }, 574 | ] 575 | 576 | [[package]] 577 | name = "pyasn1" 578 | version = "0.4.8" 579 | source = { registry = "https://pypi.org/simple" } 580 | sdist = { url = "https://files.pythonhosted.org/packages/a4/db/fffec68299e6d7bad3d504147f9094830b704527a7fc098b721d38cc7fa7/pyasn1-0.4.8.tar.gz", hash = "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", size = 146820, upload-time = "2019-11-16T17:27:38.772Z" } 581 | wheels = [ 582 | { url = "https://files.pythonhosted.org/packages/62/1e/a94a8d635fa3ce4cfc7f506003548d0a2447ae76fd5ca53932970fe3053f/pyasn1-0.4.8-py2.py3-none-any.whl", hash = "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", size = 77145, upload-time = "2019-11-16T17:27:11.07Z" }, 583 | ] 584 | 585 | [[package]] 586 | name = "pycparser" 587 | version = "2.22" 588 | source = { registry = "https://pypi.org/simple" } 589 | 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" } 590 | wheels = [ 591 | { 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" }, 592 | ] 593 | 594 | [[package]] 595 | name = "pydantic" 596 | version = "2.11.4" 597 | source = { registry = "https://pypi.org/simple" } 598 | dependencies = [ 599 | { name = "annotated-types" }, 600 | { name = "pydantic-core" }, 601 | { name = "typing-extensions" }, 602 | { name = "typing-inspection" }, 603 | ] 604 | sdist = { url = "https://files.pythonhosted.org/packages/77/ab/5250d56ad03884ab5efd07f734203943c8a8ab40d551e208af81d0257bf2/pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d", size = 786540, upload-time = "2025-04-29T20:38:55.02Z" } 605 | wheels = [ 606 | { url = "https://files.pythonhosted.org/packages/e7/12/46b65f3534d099349e38ef6ec98b1a5a81f42536d17e0ba382c28c67ba67/pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb", size = 443900, upload-time = "2025-04-29T20:38:52.724Z" }, 607 | ] 608 | 609 | [[package]] 610 | name = "pydantic-core" 611 | version = "2.33.2" 612 | source = { registry = "https://pypi.org/simple" } 613 | dependencies = [ 614 | { name = "typing-extensions" }, 615 | ] 616 | sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" } 617 | wheels = [ 618 | { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688, upload-time = "2025-04-23T18:31:53.175Z" }, 619 | { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808, upload-time = "2025-04-23T18:31:54.79Z" }, 620 | { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580, upload-time = "2025-04-23T18:31:57.393Z" }, 621 | { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859, upload-time = "2025-04-23T18:31:59.065Z" }, 622 | { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810, upload-time = "2025-04-23T18:32:00.78Z" }, 623 | { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498, upload-time = "2025-04-23T18:32:02.418Z" }, 624 | { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611, upload-time = "2025-04-23T18:32:04.152Z" }, 625 | { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924, upload-time = "2025-04-23T18:32:06.129Z" }, 626 | { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196, upload-time = "2025-04-23T18:32:08.178Z" }, 627 | { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389, upload-time = "2025-04-23T18:32:10.242Z" }, 628 | { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223, upload-time = "2025-04-23T18:32:12.382Z" }, 629 | { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473, upload-time = "2025-04-23T18:32:14.034Z" }, 630 | { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269, upload-time = "2025-04-23T18:32:15.783Z" }, 631 | { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921, upload-time = "2025-04-23T18:32:18.473Z" }, 632 | { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162, upload-time = "2025-04-23T18:32:20.188Z" }, 633 | { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560, upload-time = "2025-04-23T18:32:22.354Z" }, 634 | { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777, upload-time = "2025-04-23T18:32:25.088Z" }, 635 | ] 636 | 637 | [[package]] 638 | name = "pygments" 639 | version = "2.19.1" 640 | source = { registry = "https://pypi.org/simple" } 641 | sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } 642 | wheels = [ 643 | { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, 644 | ] 645 | 646 | [[package]] 647 | name = "pyjwt" 648 | version = "2.10.1" 649 | source = { registry = "https://pypi.org/simple" } 650 | 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" } 651 | wheels = [ 652 | { 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" }, 653 | ] 654 | 655 | [[package]] 656 | name = "python-dotenv" 657 | version = "1.1.0" 658 | source = { registry = "https://pypi.org/simple" } 659 | sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920, upload-time = "2025-03-25T10:14:56.835Z" } 660 | wheels = [ 661 | { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256, upload-time = "2025-03-25T10:14:55.034Z" }, 662 | ] 663 | 664 | [[package]] 665 | name = "python-jose" 666 | version = "3.4.0" 667 | source = { registry = "https://pypi.org/simple" } 668 | dependencies = [ 669 | { name = "ecdsa" }, 670 | { name = "pyasn1" }, 671 | { name = "rsa" }, 672 | ] 673 | sdist = { url = "https://files.pythonhosted.org/packages/8e/a0/c49687cf40cb6128ea4e0559855aff92cd5ebd1a60a31c08526818c0e51e/python-jose-3.4.0.tar.gz", hash = "sha256:9a9a40f418ced8ecaf7e3b28d69887ceaa76adad3bcaa6dae0d9e596fec1d680", size = 92145, upload-time = "2025-02-18T17:26:41.985Z" } 674 | wheels = [ 675 | { url = "https://files.pythonhosted.org/packages/63/b0/2586ea6b6fd57a994ece0b56418cbe93fff0efb85e2c9eb6b0caf24a4e37/python_jose-3.4.0-py2.py3-none-any.whl", hash = "sha256:9c9f616819652d109bd889ecd1e15e9a162b9b94d682534c9c2146092945b78f", size = 34616, upload-time = "2025-02-18T17:26:40.826Z" }, 676 | ] 677 | 678 | [package.optional-dependencies] 679 | cryptography = [ 680 | { name = "cryptography" }, 681 | ] 682 | 683 | [[package]] 684 | name = "python-multipart" 685 | version = "0.0.20" 686 | source = { registry = "https://pypi.org/simple" } 687 | sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } 688 | wheels = [ 689 | { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, 690 | ] 691 | 692 | [[package]] 693 | name = "pyyaml" 694 | version = "6.0.2" 695 | source = { registry = "https://pypi.org/simple" } 696 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } 697 | wheels = [ 698 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, 699 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, 700 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, 701 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, 702 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, 703 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, 704 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, 705 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, 706 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, 707 | ] 708 | 709 | [[package]] 710 | name = "redis" 711 | version = "6.0.0" 712 | source = { registry = "https://pypi.org/simple" } 713 | sdist = { url = "https://files.pythonhosted.org/packages/79/12/dffaaa4374b8d5f3b7ff5c40025c9db387e06264302d5a9da6043cd84e1f/redis-6.0.0.tar.gz", hash = "sha256:5446780d2425b787ed89c91ddbfa1be6d32370a636c8fdb687f11b1c26c1fa88", size = 4620969, upload-time = "2025-04-30T19:09:30.798Z" } 714 | wheels = [ 715 | { url = "https://files.pythonhosted.org/packages/08/c8/68081c9d3531f7b2a4d663326b96a9dcbc2aef47df3c6b5c38dea90dff02/redis-6.0.0-py3-none-any.whl", hash = "sha256:a2e040aee2cdd947be1fa3a32e35a956cd839cc4c1dbbe4b2cdee5b9623fd27c", size = 268950, upload-time = "2025-04-30T19:09:28.432Z" }, 716 | ] 717 | 718 | [[package]] 719 | name = "rich" 720 | version = "14.0.0" 721 | source = { registry = "https://pypi.org/simple" } 722 | dependencies = [ 723 | { name = "markdown-it-py" }, 724 | { name = "pygments" }, 725 | ] 726 | sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } 727 | wheels = [ 728 | { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, 729 | ] 730 | 731 | [[package]] 732 | name = "rich-toolkit" 733 | version = "0.14.5" 734 | source = { registry = "https://pypi.org/simple" } 735 | dependencies = [ 736 | { name = "click" }, 737 | { name = "rich" }, 738 | { name = "typing-extensions" }, 739 | ] 740 | sdist = { url = "https://files.pythonhosted.org/packages/d1/24/f0678256fbe0643b4ba00a460f4b736ef07042e459f8d4087c8b7011ab81/rich_toolkit-0.14.5.tar.gz", hash = "sha256:1cb7a3fa0bdbf35793460708664f3f797e8b18cedec9cd41a7e6125e4bc6272b", size = 104799, upload-time = "2025-05-05T10:19:24.521Z" } 741 | wheels = [ 742 | { url = "https://files.pythonhosted.org/packages/d1/13/621cc551b72de51e6e5cb7cfc510a141e1858bd380ee3c8108fbda4a6be0/rich_toolkit-0.14.5-py3-none-any.whl", hash = "sha256:2fe9846ecbf5d0cdf236c7f43452b68d9da1436a81594aba6b79b3c48b05703b", size = 24791, upload-time = "2025-05-05T10:19:23.346Z" }, 743 | ] 744 | 745 | [[package]] 746 | name = "rsa" 747 | version = "4.9.1" 748 | source = { registry = "https://pypi.org/simple" } 749 | dependencies = [ 750 | { name = "pyasn1" }, 751 | ] 752 | sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } 753 | wheels = [ 754 | { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, 755 | ] 756 | 757 | [[package]] 758 | name = "shellingham" 759 | version = "1.5.4" 760 | source = { registry = "https://pypi.org/simple" } 761 | sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } 762 | wheels = [ 763 | { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, 764 | ] 765 | 766 | [[package]] 767 | name = "six" 768 | version = "1.17.0" 769 | source = { registry = "https://pypi.org/simple" } 770 | sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } 771 | wheels = [ 772 | { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, 773 | ] 774 | 775 | [[package]] 776 | name = "sniffio" 777 | version = "1.3.1" 778 | source = { registry = "https://pypi.org/simple" } 779 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } 780 | wheels = [ 781 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, 782 | ] 783 | 784 | [[package]] 785 | name = "starlette" 786 | version = "0.46.2" 787 | source = { registry = "https://pypi.org/simple" } 788 | dependencies = [ 789 | { name = "anyio" }, 790 | ] 791 | sdist = { url = "https://files.pythonhosted.org/packages/ce/20/08dfcd9c983f6a6f4a1000d934b9e6d626cff8d2eeb77a89a68eef20a2b7/starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5", size = 2580846, upload-time = "2025-04-13T13:56:17.942Z" } 792 | wheels = [ 793 | { url = "https://files.pythonhosted.org/packages/8b/0c/9d30a4ebeb6db2b25a841afbb80f6ef9a854fc3b41be131d249a977b4959/starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35", size = 72037, upload-time = "2025-04-13T13:56:16.21Z" }, 794 | ] 795 | 796 | [[package]] 797 | name = "typer" 798 | version = "0.15.3" 799 | source = { registry = "https://pypi.org/simple" } 800 | dependencies = [ 801 | { name = "click" }, 802 | { name = "rich" }, 803 | { name = "shellingham" }, 804 | { name = "typing-extensions" }, 805 | ] 806 | sdist = { url = "https://files.pythonhosted.org/packages/98/1a/5f36851f439884bcfe8539f6a20ff7516e7b60f319bbaf69a90dc35cc2eb/typer-0.15.3.tar.gz", hash = "sha256:818873625d0569653438316567861899f7e9972f2e6e0c16dab608345ced713c", size = 101641, upload-time = "2025-04-28T21:40:59.204Z" } 807 | wheels = [ 808 | { url = "https://files.pythonhosted.org/packages/48/20/9d953de6f4367163d23ec823200eb3ecb0050a2609691e512c8b95827a9b/typer-0.15.3-py3-none-any.whl", hash = "sha256:c86a65ad77ca531f03de08d1b9cb67cd09ad02ddddf4b34745b5008f43b239bd", size = 45253, upload-time = "2025-04-28T21:40:56.269Z" }, 809 | ] 810 | 811 | [[package]] 812 | name = "typing-extensions" 813 | version = "4.13.2" 814 | source = { registry = "https://pypi.org/simple" } 815 | sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } 816 | wheels = [ 817 | { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, 818 | ] 819 | 820 | [[package]] 821 | name = "typing-inspect" 822 | version = "0.9.0" 823 | source = { registry = "https://pypi.org/simple" } 824 | dependencies = [ 825 | { name = "mypy-extensions" }, 826 | { name = "typing-extensions" }, 827 | ] 828 | sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" } 829 | wheels = [ 830 | { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, 831 | ] 832 | 833 | [[package]] 834 | name = "typing-inspection" 835 | version = "0.4.0" 836 | source = { registry = "https://pypi.org/simple" } 837 | dependencies = [ 838 | { name = "typing-extensions" }, 839 | ] 840 | sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" } 841 | wheels = [ 842 | { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" }, 843 | ] 844 | 845 | [[package]] 846 | name = "useragent" 847 | version = "0.1.1" 848 | source = { registry = "https://pypi.org/simple" } 849 | sdist = { url = "https://files.pythonhosted.org/packages/22/7a/6380332af6b9e7bb1267adc8ac663758b329feb0d0baa20e46940a26b36d/useragent-0.1.1.tar.gz", hash = "sha256:7ef271df2dfdaa8d7eedcc9b7247009acdbd4e29d60924daba92f0b482ca32e2", size = 134844, upload-time = "2012-10-19T22:50:49.761Z" } 850 | 851 | [[package]] 852 | name = "uvicorn" 853 | version = "0.34.2" 854 | source = { registry = "https://pypi.org/simple" } 855 | dependencies = [ 856 | { name = "click" }, 857 | { name = "h11" }, 858 | ] 859 | sdist = { url = "https://files.pythonhosted.org/packages/a6/ae/9bbb19b9e1c450cf9ecaef06463e40234d98d95bf572fab11b4f19ae5ded/uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328", size = 76815, upload-time = "2025-04-19T06:02:50.101Z" } 860 | wheels = [ 861 | { url = "https://files.pythonhosted.org/packages/b1/4b/4cef6ce21a2aaca9d852a6e84ef4f135d99fcd74fa75105e2fc0c8308acd/uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403", size = 62483, upload-time = "2025-04-19T06:02:48.42Z" }, 862 | ] 863 | 864 | [package.optional-dependencies] 865 | standard = [ 866 | { name = "colorama", marker = "sys_platform == 'win32'" }, 867 | { name = "httptools" }, 868 | { name = "python-dotenv" }, 869 | { name = "pyyaml" }, 870 | { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, 871 | { name = "watchfiles" }, 872 | { name = "websockets" }, 873 | ] 874 | 875 | [[package]] 876 | name = "uvloop" 877 | version = "0.21.0" 878 | source = { registry = "https://pypi.org/simple" } 879 | sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } 880 | wheels = [ 881 | { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" }, 882 | { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" }, 883 | { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" }, 884 | { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" }, 885 | { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" }, 886 | { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, 887 | ] 888 | 889 | [[package]] 890 | name = "virtualenv" 891 | version = "20.31.1" 892 | source = { registry = "https://pypi.org/simple" } 893 | dependencies = [ 894 | { name = "distlib" }, 895 | { name = "filelock" }, 896 | { name = "platformdirs" }, 897 | ] 898 | sdist = { url = "https://files.pythonhosted.org/packages/53/07/655f4fb9592967f49197b00015bb5538d3ed1f8f96621a10bebc3bb822e2/virtualenv-20.31.1.tar.gz", hash = "sha256:65442939608aeebb9284cd30baca5865fcd9f12b58bb740a24b220030df46d26", size = 6076234, upload-time = "2025-05-05T22:45:39.829Z" } 899 | wheels = [ 900 | { url = "https://files.pythonhosted.org/packages/c5/67/7d7559264a6f8ec9ce4e397ddd9157a510be1e174dc98be898b6c18eeef4/virtualenv-20.31.1-py3-none-any.whl", hash = "sha256:f448cd2f1604c831afb9ea238021060be2c0edbcad8eb0a4e8b4e14ff11a5482", size = 6057843, upload-time = "2025-05-05T22:45:37.127Z" }, 901 | ] 902 | 903 | [[package]] 904 | name = "watchfiles" 905 | version = "1.0.5" 906 | source = { registry = "https://pypi.org/simple" } 907 | dependencies = [ 908 | { name = "anyio" }, 909 | ] 910 | sdist = { url = "https://files.pythonhosted.org/packages/03/e2/8ed598c42057de7aa5d97c472254af4906ff0a59a66699d426fc9ef795d7/watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9", size = 94537, upload-time = "2025-04-08T10:36:26.722Z" } 911 | wheels = [ 912 | { url = "https://files.pythonhosted.org/packages/c7/62/435766874b704f39b2fecd8395a29042db2b5ec4005bd34523415e9bd2e0/watchfiles-1.0.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0b289572c33a0deae62daa57e44a25b99b783e5f7aed81b314232b3d3c81a11d", size = 401531, upload-time = "2025-04-08T10:35:35.792Z" }, 913 | { url = "https://files.pythonhosted.org/packages/6e/a6/e52a02c05411b9cb02823e6797ef9bbba0bfaf1bb627da1634d44d8af833/watchfiles-1.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a056c2f692d65bf1e99c41045e3bdcaea3cb9e6b5a53dcaf60a5f3bd95fc9763", size = 392417, upload-time = "2025-04-08T10:35:37.048Z" }, 914 | { url = "https://files.pythonhosted.org/packages/3f/53/c4af6819770455932144e0109d4854437769672d7ad897e76e8e1673435d/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9dca99744991fc9850d18015c4f0438865414e50069670f5f7eee08340d8b40", size = 453423, upload-time = "2025-04-08T10:35:38.357Z" }, 915 | { url = "https://files.pythonhosted.org/packages/cb/d1/8e88df58bbbf819b8bc5cfbacd3c79e01b40261cad0fc84d1e1ebd778a07/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:894342d61d355446d02cd3988a7326af344143eb33a2fd5d38482a92072d9563", size = 458185, upload-time = "2025-04-08T10:35:39.708Z" }, 916 | { url = "https://files.pythonhosted.org/packages/ff/70/fffaa11962dd5429e47e478a18736d4e42bec42404f5ee3b92ef1b87ad60/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab44e1580924d1ffd7b3938e02716d5ad190441965138b4aa1d1f31ea0877f04", size = 486696, upload-time = "2025-04-08T10:35:41.469Z" }, 917 | { url = "https://files.pythonhosted.org/packages/39/db/723c0328e8b3692d53eb273797d9a08be6ffb1d16f1c0ba2bdbdc2a3852c/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6f9367b132078b2ceb8d066ff6c93a970a18c3029cea37bfd7b2d3dd2e5db8f", size = 522327, upload-time = "2025-04-08T10:35:43.289Z" }, 918 | { url = "https://files.pythonhosted.org/packages/cd/05/9fccc43c50c39a76b68343484b9da7b12d42d0859c37c61aec018c967a32/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2e55a9b162e06e3f862fb61e399fe9f05d908d019d87bf5b496a04ef18a970a", size = 499741, upload-time = "2025-04-08T10:35:44.574Z" }, 919 | { url = "https://files.pythonhosted.org/packages/23/14/499e90c37fa518976782b10a18b18db9f55ea73ca14641615056f8194bb3/watchfiles-1.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0125f91f70e0732a9f8ee01e49515c35d38ba48db507a50c5bdcad9503af5827", size = 453995, upload-time = "2025-04-08T10:35:46.336Z" }, 920 | { url = "https://files.pythonhosted.org/packages/61/d9/f75d6840059320df5adecd2c687fbc18960a7f97b55c300d20f207d48aef/watchfiles-1.0.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:13bb21f8ba3248386337c9fa51c528868e6c34a707f729ab041c846d52a0c69a", size = 629693, upload-time = "2025-04-08T10:35:48.161Z" }, 921 | { url = "https://files.pythonhosted.org/packages/fc/17/180ca383f5061b61406477218c55d66ec118e6c0c51f02d8142895fcf0a9/watchfiles-1.0.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:839ebd0df4a18c5b3c1b890145b5a3f5f64063c2a0d02b13c76d78fe5de34936", size = 624677, upload-time = "2025-04-08T10:35:49.65Z" }, 922 | { url = "https://files.pythonhosted.org/packages/bf/15/714d6ef307f803f236d69ee9d421763707899d6298d9f3183e55e366d9af/watchfiles-1.0.5-cp313-cp313-win32.whl", hash = "sha256:4a8ec1e4e16e2d5bafc9ba82f7aaecfeec990ca7cd27e84fb6f191804ed2fcfc", size = 277804, upload-time = "2025-04-08T10:35:51.093Z" }, 923 | { url = "https://files.pythonhosted.org/packages/a8/b4/c57b99518fadf431f3ef47a610839e46e5f8abf9814f969859d1c65c02c7/watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11", size = 291087, upload-time = "2025-04-08T10:35:52.458Z" }, 924 | ] 925 | 926 | [[package]] 927 | name = "websockets" 928 | version = "15.0.1" 929 | source = { registry = "https://pypi.org/simple" } 930 | sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } 931 | wheels = [ 932 | { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, 933 | { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, 934 | { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, 935 | { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, 936 | { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, 937 | { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, 938 | { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, 939 | { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, 940 | { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, 941 | { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, 942 | { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, 943 | { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, 944 | ] 945 | --------------------------------------------------------------------------------