Add files using upload-large-folder tool
Browse files- server.py +975 -0
- venv/bin/Activate.ps1 +247 -0
- venv/bin/activate +69 -0
- venv/bin/activate.csh +26 -0
- venv/bin/activate.fish +69 -0
- venv/bin/alembic +8 -0
- venv/bin/dotenv +8 -0
- venv/bin/ds +6 -0
- venv/bin/fonttools +8 -0
- venv/bin/huggingface-cli +8 -0
- venv/bin/imageio_download_bin +8 -0
- venv/bin/imageio_remove_bin +8 -0
- venv/bin/insightface-cli +8 -0
- venv/bin/isympy +8 -0
- venv/bin/lsm2bin +8 -0
- venv/bin/mako-render +8 -0
- venv/bin/normalizer +8 -0
- venv/bin/pip +8 -0
- venv/bin/pip3 +8 -0
- venv/bin/pip3.10 +8 -0
- venv/bin/proton +8 -0
- venv/bin/proton-viewer +8 -0
- venv/bin/pyav +8 -0
- venv/bin/pyftmerge +8 -0
- venv/bin/sz_split +8 -0
- venv/bin/sz_wc +8 -0
- venv/bin/tiff2fsspec +8 -0
- venv/bin/tiffcomment +8 -0
- venv/bin/tifffile +8 -0
- venv/bin/tiny-agents +8 -0
- venv/bin/torchfrtrace +8 -0
- venv/bin/torchrun +8 -0
- venv/bin/tqdm +8 -0
- venv/bin/transformers +8 -0
- venv/bin/transformers-cli +8 -0
- venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/INSTALLER +1 -0
- venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/METADATA +103 -0
- venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/RECORD +337 -0
- venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/REQUESTED +0 -0
- venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/WHEEL +5 -0
- venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/top_level.txt +1 -0
- venv/lib/python3.10/site-packages/_soundfile.py +11 -0
- venv/lib/python3.10/site-packages/cython.py +29 -0
- venv/lib/python3.10/site-packages/github/WorkflowStep.py +109 -0
- venv/lib/python3.10/site-packages/isympy.py +342 -0
- venv/lib/python3.10/site-packages/pylab.py +3 -0
- venv/lib/python3.10/site-packages/six.py +1003 -0
- venv/lib/python3.10/site-packages/soundfile.py +1674 -0
- venv/lib/python3.10/site-packages/threadpoolctl.py +1292 -0
- venv/lib/python3.10/site-packages/typing_extensions.py +0 -0
server.py
ADDED
|
@@ -0,0 +1,975 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import asyncio
|
| 4 |
+
import traceback
|
| 5 |
+
|
| 6 |
+
import nodes
|
| 7 |
+
import folder_paths
|
| 8 |
+
import execution
|
| 9 |
+
import uuid
|
| 10 |
+
import urllib
|
| 11 |
+
import json
|
| 12 |
+
import glob
|
| 13 |
+
import struct
|
| 14 |
+
import ssl
|
| 15 |
+
import socket
|
| 16 |
+
import ipaddress
|
| 17 |
+
from PIL import Image, ImageOps
|
| 18 |
+
from PIL.PngImagePlugin import PngInfo
|
| 19 |
+
from io import BytesIO
|
| 20 |
+
|
| 21 |
+
import aiohttp
|
| 22 |
+
from aiohttp import web
|
| 23 |
+
import logging
|
| 24 |
+
|
| 25 |
+
import mimetypes
|
| 26 |
+
from comfy.cli_args import args
|
| 27 |
+
import comfy.utils
|
| 28 |
+
import comfy.model_management
|
| 29 |
+
from comfy_api import feature_flags
|
| 30 |
+
import node_helpers
|
| 31 |
+
from comfyui_version import __version__
|
| 32 |
+
from app.frontend_management import FrontendManager
|
| 33 |
+
|
| 34 |
+
from app.user_manager import UserManager
|
| 35 |
+
from app.model_manager import ModelFileManager
|
| 36 |
+
from app.custom_node_manager import CustomNodeManager
|
| 37 |
+
from typing import Optional, Union
|
| 38 |
+
from api_server.routes.internal.internal_routes import InternalRoutes
|
| 39 |
+
from protocol import BinaryEventTypes
|
| 40 |
+
|
| 41 |
+
async def send_socket_catch_exception(function, message):
|
| 42 |
+
try:
|
| 43 |
+
await function(message)
|
| 44 |
+
except (aiohttp.ClientError, aiohttp.ClientPayloadError, ConnectionResetError, BrokenPipeError, ConnectionError) as err:
|
| 45 |
+
logging.warning("send error: {}".format(err))
|
| 46 |
+
|
| 47 |
+
@web.middleware
|
| 48 |
+
async def cache_control(request: web.Request, handler):
|
| 49 |
+
response: web.Response = await handler(request)
|
| 50 |
+
if request.path.endswith('.js') or request.path.endswith('.css') or request.path.endswith('index.json'):
|
| 51 |
+
response.headers.setdefault('Cache-Control', 'no-cache')
|
| 52 |
+
return response
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
@web.middleware
|
| 56 |
+
async def compress_body(request: web.Request, handler):
|
| 57 |
+
accept_encoding = request.headers.get("Accept-Encoding", "")
|
| 58 |
+
response: web.Response = await handler(request)
|
| 59 |
+
if not isinstance(response, web.Response):
|
| 60 |
+
return response
|
| 61 |
+
if response.content_type not in ["application/json", "text/plain"]:
|
| 62 |
+
return response
|
| 63 |
+
if response.body and "gzip" in accept_encoding:
|
| 64 |
+
response.enable_compression()
|
| 65 |
+
return response
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def create_cors_middleware(allowed_origin: str):
|
| 69 |
+
@web.middleware
|
| 70 |
+
async def cors_middleware(request: web.Request, handler):
|
| 71 |
+
if request.method == "OPTIONS":
|
| 72 |
+
# Pre-flight request. Reply successfully:
|
| 73 |
+
response = web.Response()
|
| 74 |
+
else:
|
| 75 |
+
response = await handler(request)
|
| 76 |
+
|
| 77 |
+
response.headers['Access-Control-Allow-Origin'] = allowed_origin
|
| 78 |
+
response.headers['Access-Control-Allow-Methods'] = 'POST, GET, DELETE, PUT, OPTIONS'
|
| 79 |
+
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
|
| 80 |
+
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
| 81 |
+
return response
|
| 82 |
+
|
| 83 |
+
return cors_middleware
|
| 84 |
+
|
| 85 |
+
def is_loopback(host):
|
| 86 |
+
if host is None:
|
| 87 |
+
return False
|
| 88 |
+
try:
|
| 89 |
+
if ipaddress.ip_address(host).is_loopback:
|
| 90 |
+
return True
|
| 91 |
+
else:
|
| 92 |
+
return False
|
| 93 |
+
except:
|
| 94 |
+
pass
|
| 95 |
+
|
| 96 |
+
loopback = False
|
| 97 |
+
for family in (socket.AF_INET, socket.AF_INET6):
|
| 98 |
+
try:
|
| 99 |
+
r = socket.getaddrinfo(host, None, family, socket.SOCK_STREAM)
|
| 100 |
+
for family, _, _, _, sockaddr in r:
|
| 101 |
+
if not ipaddress.ip_address(sockaddr[0]).is_loopback:
|
| 102 |
+
return loopback
|
| 103 |
+
else:
|
| 104 |
+
loopback = True
|
| 105 |
+
except socket.gaierror:
|
| 106 |
+
pass
|
| 107 |
+
|
| 108 |
+
return loopback
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def create_origin_only_middleware():
|
| 112 |
+
@web.middleware
|
| 113 |
+
async def origin_only_middleware(request: web.Request, handler):
|
| 114 |
+
#this code is used to prevent the case where a random website can queue comfy workflows by making a POST to 127.0.0.1 which browsers don't prevent for some dumb reason.
|
| 115 |
+
#in that case the Host and Origin hostnames won't match
|
| 116 |
+
#I know the proper fix would be to add a cookie but this should take care of the problem in the meantime
|
| 117 |
+
if 'Host' in request.headers and 'Origin' in request.headers:
|
| 118 |
+
host = request.headers['Host']
|
| 119 |
+
origin = request.headers['Origin']
|
| 120 |
+
host_domain = host.lower()
|
| 121 |
+
parsed = urllib.parse.urlparse(origin)
|
| 122 |
+
origin_domain = parsed.netloc.lower()
|
| 123 |
+
host_domain_parsed = urllib.parse.urlsplit('//' + host_domain)
|
| 124 |
+
|
| 125 |
+
#limit the check to when the host domain is localhost, this makes it slightly less safe but should still prevent the exploit
|
| 126 |
+
loopback = is_loopback(host_domain_parsed.hostname)
|
| 127 |
+
|
| 128 |
+
if parsed.port is None: #if origin doesn't have a port strip it from the host to handle weird browsers, same for host
|
| 129 |
+
host_domain = host_domain_parsed.hostname
|
| 130 |
+
if host_domain_parsed.port is None:
|
| 131 |
+
origin_domain = parsed.hostname
|
| 132 |
+
|
| 133 |
+
if loopback and host_domain is not None and origin_domain is not None and len(host_domain) > 0 and len(origin_domain) > 0:
|
| 134 |
+
if host_domain != origin_domain:
|
| 135 |
+
logging.warning("WARNING: request with non matching host and origin {} != {}, returning 403".format(host_domain, origin_domain))
|
| 136 |
+
return web.Response(status=403)
|
| 137 |
+
|
| 138 |
+
if request.method == "OPTIONS":
|
| 139 |
+
response = web.Response()
|
| 140 |
+
else:
|
| 141 |
+
response = await handler(request)
|
| 142 |
+
|
| 143 |
+
return response
|
| 144 |
+
|
| 145 |
+
return origin_only_middleware
|
| 146 |
+
|
| 147 |
+
class PromptServer():
|
| 148 |
+
def __init__(self, loop):
|
| 149 |
+
PromptServer.instance = self
|
| 150 |
+
|
| 151 |
+
mimetypes.init()
|
| 152 |
+
mimetypes.add_type('application/javascript; charset=utf-8', '.js')
|
| 153 |
+
mimetypes.add_type('image/webp', '.webp')
|
| 154 |
+
|
| 155 |
+
self.user_manager = UserManager()
|
| 156 |
+
self.model_file_manager = ModelFileManager()
|
| 157 |
+
self.custom_node_manager = CustomNodeManager()
|
| 158 |
+
self.internal_routes = InternalRoutes(self)
|
| 159 |
+
self.supports = ["custom_nodes_from_web"]
|
| 160 |
+
self.prompt_queue = execution.PromptQueue(self)
|
| 161 |
+
self.loop = loop
|
| 162 |
+
self.messages = asyncio.Queue()
|
| 163 |
+
self.client_session:Optional[aiohttp.ClientSession] = None
|
| 164 |
+
self.number = 0
|
| 165 |
+
|
| 166 |
+
middlewares = [cache_control]
|
| 167 |
+
if args.enable_compress_response_body:
|
| 168 |
+
middlewares.append(compress_body)
|
| 169 |
+
|
| 170 |
+
if args.enable_cors_header:
|
| 171 |
+
middlewares.append(create_cors_middleware(args.enable_cors_header))
|
| 172 |
+
else:
|
| 173 |
+
middlewares.append(create_origin_only_middleware())
|
| 174 |
+
|
| 175 |
+
max_upload_size = round(args.max_upload_size * 1024 * 1024)
|
| 176 |
+
self.app = web.Application(client_max_size=max_upload_size, middlewares=middlewares)
|
| 177 |
+
self.sockets = dict()
|
| 178 |
+
self.sockets_metadata = dict()
|
| 179 |
+
self.web_root = (
|
| 180 |
+
FrontendManager.init_frontend(args.front_end_version)
|
| 181 |
+
if args.front_end_root is None
|
| 182 |
+
else args.front_end_root
|
| 183 |
+
)
|
| 184 |
+
logging.info(f"[Prompt Server] web root: {self.web_root}")
|
| 185 |
+
routes = web.RouteTableDef()
|
| 186 |
+
self.routes = routes
|
| 187 |
+
self.last_node_id = None
|
| 188 |
+
self.client_id = None
|
| 189 |
+
|
| 190 |
+
self.on_prompt_handlers = []
|
| 191 |
+
|
| 192 |
+
@routes.get('/ws')
|
| 193 |
+
async def websocket_handler(request):
|
| 194 |
+
ws = web.WebSocketResponse()
|
| 195 |
+
await ws.prepare(request)
|
| 196 |
+
sid = request.rel_url.query.get('clientId', '')
|
| 197 |
+
if sid:
|
| 198 |
+
# Reusing existing session, remove old
|
| 199 |
+
self.sockets.pop(sid, None)
|
| 200 |
+
else:
|
| 201 |
+
sid = uuid.uuid4().hex
|
| 202 |
+
|
| 203 |
+
# Store WebSocket for backward compatibility
|
| 204 |
+
self.sockets[sid] = ws
|
| 205 |
+
# Store metadata separately
|
| 206 |
+
self.sockets_metadata[sid] = {"feature_flags": {}}
|
| 207 |
+
|
| 208 |
+
try:
|
| 209 |
+
# Send initial state to the new client
|
| 210 |
+
await self.send("status", {"status": self.get_queue_info(), "sid": sid}, sid)
|
| 211 |
+
# On reconnect if we are the currently executing client send the current node
|
| 212 |
+
if self.client_id == sid and self.last_node_id is not None:
|
| 213 |
+
await self.send("executing", { "node": self.last_node_id }, sid)
|
| 214 |
+
|
| 215 |
+
# Flag to track if we've received the first message
|
| 216 |
+
first_message = True
|
| 217 |
+
|
| 218 |
+
async for msg in ws:
|
| 219 |
+
if msg.type == aiohttp.WSMsgType.ERROR:
|
| 220 |
+
logging.warning('ws connection closed with exception %s' % ws.exception())
|
| 221 |
+
elif msg.type == aiohttp.WSMsgType.TEXT:
|
| 222 |
+
try:
|
| 223 |
+
data = json.loads(msg.data)
|
| 224 |
+
# Check if first message is feature flags
|
| 225 |
+
if first_message and data.get("type") == "feature_flags":
|
| 226 |
+
# Store client feature flags
|
| 227 |
+
client_flags = data.get("data", {})
|
| 228 |
+
self.sockets_metadata[sid]["feature_flags"] = client_flags
|
| 229 |
+
|
| 230 |
+
# Send server feature flags in response
|
| 231 |
+
await self.send(
|
| 232 |
+
"feature_flags",
|
| 233 |
+
feature_flags.get_server_features(),
|
| 234 |
+
sid,
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
logging.info(
|
| 238 |
+
f"Feature flags negotiated for client {sid}: {client_flags}"
|
| 239 |
+
)
|
| 240 |
+
first_message = False
|
| 241 |
+
except json.JSONDecodeError:
|
| 242 |
+
logging.warning(
|
| 243 |
+
f"Invalid JSON received from client {sid}: {msg.data}"
|
| 244 |
+
)
|
| 245 |
+
except Exception as e:
|
| 246 |
+
logging.error(f"Error processing WebSocket message: {e}")
|
| 247 |
+
finally:
|
| 248 |
+
self.sockets.pop(sid, None)
|
| 249 |
+
self.sockets_metadata.pop(sid, None)
|
| 250 |
+
return ws
|
| 251 |
+
|
| 252 |
+
@routes.get("/")
|
| 253 |
+
async def get_root(request):
|
| 254 |
+
response = web.FileResponse(os.path.join(self.web_root, "index.html"))
|
| 255 |
+
response.headers['Cache-Control'] = 'no-cache'
|
| 256 |
+
response.headers["Pragma"] = "no-cache"
|
| 257 |
+
response.headers["Expires"] = "0"
|
| 258 |
+
return response
|
| 259 |
+
|
| 260 |
+
@routes.get("/embeddings")
|
| 261 |
+
def get_embeddings(request):
|
| 262 |
+
embeddings = folder_paths.get_filename_list("embeddings")
|
| 263 |
+
return web.json_response(list(map(lambda a: os.path.splitext(a)[0], embeddings)))
|
| 264 |
+
|
| 265 |
+
@routes.get("/models")
|
| 266 |
+
def list_model_types(request):
|
| 267 |
+
model_types = list(folder_paths.folder_names_and_paths.keys())
|
| 268 |
+
|
| 269 |
+
return web.json_response(model_types)
|
| 270 |
+
|
| 271 |
+
@routes.get("/models/{folder}")
|
| 272 |
+
async def get_models(request):
|
| 273 |
+
folder = request.match_info.get("folder", None)
|
| 274 |
+
if not folder in folder_paths.folder_names_and_paths:
|
| 275 |
+
return web.Response(status=404)
|
| 276 |
+
files = folder_paths.get_filename_list(folder)
|
| 277 |
+
return web.json_response(files)
|
| 278 |
+
|
| 279 |
+
@routes.get("/extensions")
|
| 280 |
+
async def get_extensions(request):
|
| 281 |
+
files = glob.glob(os.path.join(
|
| 282 |
+
glob.escape(self.web_root), 'extensions/**/*.js'), recursive=True)
|
| 283 |
+
|
| 284 |
+
extensions = list(map(lambda f: "/" + os.path.relpath(f, self.web_root).replace("\\", "/"), files))
|
| 285 |
+
|
| 286 |
+
for name, dir in nodes.EXTENSION_WEB_DIRS.items():
|
| 287 |
+
files = glob.glob(os.path.join(glob.escape(dir), '**/*.js'), recursive=True)
|
| 288 |
+
extensions.extend(list(map(lambda f: "/extensions/" + urllib.parse.quote(
|
| 289 |
+
name) + "/" + os.path.relpath(f, dir).replace("\\", "/"), files)))
|
| 290 |
+
|
| 291 |
+
return web.json_response(extensions)
|
| 292 |
+
|
| 293 |
+
def get_dir_by_type(dir_type):
|
| 294 |
+
if dir_type is None:
|
| 295 |
+
dir_type = "input"
|
| 296 |
+
|
| 297 |
+
if dir_type == "input":
|
| 298 |
+
type_dir = folder_paths.get_input_directory()
|
| 299 |
+
elif dir_type == "temp":
|
| 300 |
+
type_dir = folder_paths.get_temp_directory()
|
| 301 |
+
elif dir_type == "output":
|
| 302 |
+
type_dir = folder_paths.get_output_directory()
|
| 303 |
+
|
| 304 |
+
return type_dir, dir_type
|
| 305 |
+
|
| 306 |
+
def compare_image_hash(filepath, image):
|
| 307 |
+
hasher = node_helpers.hasher()
|
| 308 |
+
|
| 309 |
+
# function to compare hashes of two images to see if it already exists, fix to #3465
|
| 310 |
+
if os.path.exists(filepath):
|
| 311 |
+
a = hasher()
|
| 312 |
+
b = hasher()
|
| 313 |
+
with open(filepath, "rb") as f:
|
| 314 |
+
a.update(f.read())
|
| 315 |
+
b.update(image.file.read())
|
| 316 |
+
image.file.seek(0)
|
| 317 |
+
return a.hexdigest() == b.hexdigest()
|
| 318 |
+
return False
|
| 319 |
+
|
| 320 |
+
def image_upload(post, image_save_function=None):
|
| 321 |
+
image = post.get("image")
|
| 322 |
+
overwrite = post.get("overwrite")
|
| 323 |
+
image_is_duplicate = False
|
| 324 |
+
|
| 325 |
+
image_upload_type = post.get("type")
|
| 326 |
+
upload_dir, image_upload_type = get_dir_by_type(image_upload_type)
|
| 327 |
+
|
| 328 |
+
if image and image.file:
|
| 329 |
+
filename = image.filename
|
| 330 |
+
if not filename:
|
| 331 |
+
return web.Response(status=400)
|
| 332 |
+
|
| 333 |
+
subfolder = post.get("subfolder", "")
|
| 334 |
+
full_output_folder = os.path.join(upload_dir, os.path.normpath(subfolder))
|
| 335 |
+
filepath = os.path.abspath(os.path.join(full_output_folder, filename))
|
| 336 |
+
|
| 337 |
+
if os.path.commonpath((upload_dir, filepath)) != upload_dir:
|
| 338 |
+
return web.Response(status=400)
|
| 339 |
+
|
| 340 |
+
if not os.path.exists(full_output_folder):
|
| 341 |
+
os.makedirs(full_output_folder)
|
| 342 |
+
|
| 343 |
+
split = os.path.splitext(filename)
|
| 344 |
+
|
| 345 |
+
if overwrite is not None and (overwrite == "true" or overwrite == "1"):
|
| 346 |
+
pass
|
| 347 |
+
else:
|
| 348 |
+
i = 1
|
| 349 |
+
while os.path.exists(filepath):
|
| 350 |
+
if compare_image_hash(filepath, image): #compare hash to prevent saving of duplicates with same name, fix for #3465
|
| 351 |
+
image_is_duplicate = True
|
| 352 |
+
break
|
| 353 |
+
filename = f"{split[0]} ({i}){split[1]}"
|
| 354 |
+
filepath = os.path.join(full_output_folder, filename)
|
| 355 |
+
i += 1
|
| 356 |
+
|
| 357 |
+
if not image_is_duplicate:
|
| 358 |
+
if image_save_function is not None:
|
| 359 |
+
image_save_function(image, post, filepath)
|
| 360 |
+
else:
|
| 361 |
+
with open(filepath, "wb") as f:
|
| 362 |
+
f.write(image.file.read())
|
| 363 |
+
|
| 364 |
+
return web.json_response({"name" : filename, "subfolder": subfolder, "type": image_upload_type})
|
| 365 |
+
else:
|
| 366 |
+
return web.Response(status=400)
|
| 367 |
+
|
| 368 |
+
@routes.post("/upload/image")
|
| 369 |
+
async def upload_image(request):
|
| 370 |
+
post = await request.post()
|
| 371 |
+
return image_upload(post)
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
@routes.post("/upload/mask")
|
| 375 |
+
async def upload_mask(request):
|
| 376 |
+
post = await request.post()
|
| 377 |
+
|
| 378 |
+
def image_save_function(image, post, filepath):
|
| 379 |
+
original_ref = json.loads(post.get("original_ref"))
|
| 380 |
+
filename, output_dir = folder_paths.annotated_filepath(original_ref['filename'])
|
| 381 |
+
|
| 382 |
+
if not filename:
|
| 383 |
+
return web.Response(status=400)
|
| 384 |
+
|
| 385 |
+
# validation for security: prevent accessing arbitrary path
|
| 386 |
+
if filename[0] == '/' or '..' in filename:
|
| 387 |
+
return web.Response(status=400)
|
| 388 |
+
|
| 389 |
+
if output_dir is None:
|
| 390 |
+
type = original_ref.get("type", "output")
|
| 391 |
+
output_dir = folder_paths.get_directory_by_type(type)
|
| 392 |
+
|
| 393 |
+
if output_dir is None:
|
| 394 |
+
return web.Response(status=400)
|
| 395 |
+
|
| 396 |
+
if original_ref.get("subfolder", "") != "":
|
| 397 |
+
full_output_dir = os.path.join(output_dir, original_ref["subfolder"])
|
| 398 |
+
if os.path.commonpath((os.path.abspath(full_output_dir), output_dir)) != output_dir:
|
| 399 |
+
return web.Response(status=403)
|
| 400 |
+
output_dir = full_output_dir
|
| 401 |
+
|
| 402 |
+
file = os.path.join(output_dir, filename)
|
| 403 |
+
|
| 404 |
+
if os.path.isfile(file):
|
| 405 |
+
with Image.open(file) as original_pil:
|
| 406 |
+
metadata = PngInfo()
|
| 407 |
+
if hasattr(original_pil,'text'):
|
| 408 |
+
for key in original_pil.text:
|
| 409 |
+
metadata.add_text(key, original_pil.text[key])
|
| 410 |
+
original_pil = original_pil.convert('RGBA')
|
| 411 |
+
mask_pil = Image.open(image.file).convert('RGBA')
|
| 412 |
+
|
| 413 |
+
# alpha copy
|
| 414 |
+
new_alpha = mask_pil.getchannel('A')
|
| 415 |
+
original_pil.putalpha(new_alpha)
|
| 416 |
+
original_pil.save(filepath, compress_level=4, pnginfo=metadata)
|
| 417 |
+
|
| 418 |
+
return image_upload(post, image_save_function)
|
| 419 |
+
|
| 420 |
+
@routes.get("/view")
|
| 421 |
+
async def view_image(request):
|
| 422 |
+
if "filename" in request.rel_url.query:
|
| 423 |
+
filename = request.rel_url.query["filename"]
|
| 424 |
+
filename, output_dir = folder_paths.annotated_filepath(filename)
|
| 425 |
+
|
| 426 |
+
if not filename:
|
| 427 |
+
return web.Response(status=400)
|
| 428 |
+
|
| 429 |
+
# validation for security: prevent accessing arbitrary path
|
| 430 |
+
if filename[0] == '/' or '..' in filename:
|
| 431 |
+
return web.Response(status=400)
|
| 432 |
+
|
| 433 |
+
if output_dir is None:
|
| 434 |
+
type = request.rel_url.query.get("type", "output")
|
| 435 |
+
output_dir = folder_paths.get_directory_by_type(type)
|
| 436 |
+
|
| 437 |
+
if output_dir is None:
|
| 438 |
+
return web.Response(status=400)
|
| 439 |
+
|
| 440 |
+
if "subfolder" in request.rel_url.query:
|
| 441 |
+
full_output_dir = os.path.join(output_dir, request.rel_url.query["subfolder"])
|
| 442 |
+
if os.path.commonpath((os.path.abspath(full_output_dir), output_dir)) != output_dir:
|
| 443 |
+
return web.Response(status=403)
|
| 444 |
+
output_dir = full_output_dir
|
| 445 |
+
|
| 446 |
+
filename = os.path.basename(filename)
|
| 447 |
+
file = os.path.join(output_dir, filename)
|
| 448 |
+
|
| 449 |
+
if os.path.isfile(file):
|
| 450 |
+
if 'preview' in request.rel_url.query:
|
| 451 |
+
with Image.open(file) as img:
|
| 452 |
+
preview_info = request.rel_url.query['preview'].split(';')
|
| 453 |
+
image_format = preview_info[0]
|
| 454 |
+
if image_format not in ['webp', 'jpeg'] or 'a' in request.rel_url.query.get('channel', ''):
|
| 455 |
+
image_format = 'webp'
|
| 456 |
+
|
| 457 |
+
quality = 90
|
| 458 |
+
if preview_info[-1].isdigit():
|
| 459 |
+
quality = int(preview_info[-1])
|
| 460 |
+
|
| 461 |
+
buffer = BytesIO()
|
| 462 |
+
if image_format in ['jpeg'] or request.rel_url.query.get('channel', '') == 'rgb':
|
| 463 |
+
img = img.convert("RGB")
|
| 464 |
+
img.save(buffer, format=image_format, quality=quality)
|
| 465 |
+
buffer.seek(0)
|
| 466 |
+
|
| 467 |
+
return web.Response(body=buffer.read(), content_type=f'image/{image_format}',
|
| 468 |
+
headers={"Content-Disposition": f"filename=\"{filename}\""})
|
| 469 |
+
|
| 470 |
+
if 'channel' not in request.rel_url.query:
|
| 471 |
+
channel = 'rgba'
|
| 472 |
+
else:
|
| 473 |
+
channel = request.rel_url.query["channel"]
|
| 474 |
+
|
| 475 |
+
if channel == 'rgb':
|
| 476 |
+
with Image.open(file) as img:
|
| 477 |
+
if img.mode == "RGBA":
|
| 478 |
+
r, g, b, a = img.split()
|
| 479 |
+
new_img = Image.merge('RGB', (r, g, b))
|
| 480 |
+
else:
|
| 481 |
+
new_img = img.convert("RGB")
|
| 482 |
+
|
| 483 |
+
buffer = BytesIO()
|
| 484 |
+
new_img.save(buffer, format='PNG')
|
| 485 |
+
buffer.seek(0)
|
| 486 |
+
|
| 487 |
+
return web.Response(body=buffer.read(), content_type='image/png',
|
| 488 |
+
headers={"Content-Disposition": f"filename=\"{filename}\""})
|
| 489 |
+
|
| 490 |
+
elif channel == 'a':
|
| 491 |
+
with Image.open(file) as img:
|
| 492 |
+
if img.mode == "RGBA":
|
| 493 |
+
_, _, _, a = img.split()
|
| 494 |
+
else:
|
| 495 |
+
a = Image.new('L', img.size, 255)
|
| 496 |
+
|
| 497 |
+
# alpha img
|
| 498 |
+
alpha_img = Image.new('RGBA', img.size)
|
| 499 |
+
alpha_img.putalpha(a)
|
| 500 |
+
alpha_buffer = BytesIO()
|
| 501 |
+
alpha_img.save(alpha_buffer, format='PNG')
|
| 502 |
+
alpha_buffer.seek(0)
|
| 503 |
+
|
| 504 |
+
return web.Response(body=alpha_buffer.read(), content_type='image/png',
|
| 505 |
+
headers={"Content-Disposition": f"filename=\"{filename}\""})
|
| 506 |
+
else:
|
| 507 |
+
# Get content type from mimetype, defaulting to 'application/octet-stream'
|
| 508 |
+
content_type = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
| 509 |
+
|
| 510 |
+
# For security, force certain mimetypes to download instead of display
|
| 511 |
+
if content_type in {'text/html', 'text/html-sandboxed', 'application/xhtml+xml', 'text/javascript', 'text/css'}:
|
| 512 |
+
content_type = 'application/octet-stream' # Forces download
|
| 513 |
+
|
| 514 |
+
return web.FileResponse(
|
| 515 |
+
file,
|
| 516 |
+
headers={
|
| 517 |
+
"Content-Disposition": f"filename=\"{filename}\"",
|
| 518 |
+
"Content-Type": content_type
|
| 519 |
+
}
|
| 520 |
+
)
|
| 521 |
+
|
| 522 |
+
return web.Response(status=404)
|
| 523 |
+
|
| 524 |
+
@routes.get("/view_metadata/{folder_name}")
|
| 525 |
+
async def view_metadata(request):
|
| 526 |
+
folder_name = request.match_info.get("folder_name", None)
|
| 527 |
+
if folder_name is None:
|
| 528 |
+
return web.Response(status=404)
|
| 529 |
+
if not "filename" in request.rel_url.query:
|
| 530 |
+
return web.Response(status=404)
|
| 531 |
+
|
| 532 |
+
filename = request.rel_url.query["filename"]
|
| 533 |
+
if not filename.endswith(".safetensors"):
|
| 534 |
+
return web.Response(status=404)
|
| 535 |
+
|
| 536 |
+
safetensors_path = folder_paths.get_full_path(folder_name, filename)
|
| 537 |
+
if safetensors_path is None:
|
| 538 |
+
return web.Response(status=404)
|
| 539 |
+
out = comfy.utils.safetensors_header(safetensors_path, max_size=1024*1024)
|
| 540 |
+
if out is None:
|
| 541 |
+
return web.Response(status=404)
|
| 542 |
+
dt = json.loads(out)
|
| 543 |
+
if not "__metadata__" in dt:
|
| 544 |
+
return web.Response(status=404)
|
| 545 |
+
return web.json_response(dt["__metadata__"])
|
| 546 |
+
|
| 547 |
+
@routes.get("/system_stats")
|
| 548 |
+
async def system_stats(request):
|
| 549 |
+
device = comfy.model_management.get_torch_device()
|
| 550 |
+
device_name = comfy.model_management.get_torch_device_name(device)
|
| 551 |
+
cpu_device = comfy.model_management.torch.device("cpu")
|
| 552 |
+
ram_total = comfy.model_management.get_total_memory(cpu_device)
|
| 553 |
+
ram_free = comfy.model_management.get_free_memory(cpu_device)
|
| 554 |
+
vram_total, torch_vram_total = comfy.model_management.get_total_memory(device, torch_total_too=True)
|
| 555 |
+
vram_free, torch_vram_free = comfy.model_management.get_free_memory(device, torch_free_too=True)
|
| 556 |
+
|
| 557 |
+
system_stats = {
|
| 558 |
+
"system": {
|
| 559 |
+
"os": os.name,
|
| 560 |
+
"ram_total": ram_total,
|
| 561 |
+
"ram_free": ram_free,
|
| 562 |
+
"comfyui_version": __version__,
|
| 563 |
+
"python_version": sys.version,
|
| 564 |
+
"pytorch_version": comfy.model_management.torch_version,
|
| 565 |
+
"embedded_python": os.path.split(os.path.split(sys.executable)[0])[1] == "python_embeded",
|
| 566 |
+
"argv": sys.argv
|
| 567 |
+
},
|
| 568 |
+
"devices": [
|
| 569 |
+
{
|
| 570 |
+
"name": device_name,
|
| 571 |
+
"type": device.type,
|
| 572 |
+
"index": device.index,
|
| 573 |
+
"vram_total": vram_total,
|
| 574 |
+
"vram_free": vram_free,
|
| 575 |
+
"torch_vram_total": torch_vram_total,
|
| 576 |
+
"torch_vram_free": torch_vram_free,
|
| 577 |
+
}
|
| 578 |
+
]
|
| 579 |
+
}
|
| 580 |
+
return web.json_response(system_stats)
|
| 581 |
+
|
| 582 |
+
@routes.get("/features")
|
| 583 |
+
async def get_features(request):
|
| 584 |
+
return web.json_response(feature_flags.get_server_features())
|
| 585 |
+
|
| 586 |
+
@routes.get("/prompt")
|
| 587 |
+
async def get_prompt(request):
|
| 588 |
+
return web.json_response(self.get_queue_info())
|
| 589 |
+
|
| 590 |
+
def node_info(node_class):
|
| 591 |
+
obj_class = nodes.NODE_CLASS_MAPPINGS[node_class]
|
| 592 |
+
info = {}
|
| 593 |
+
info['input'] = obj_class.INPUT_TYPES()
|
| 594 |
+
info['input_order'] = {key: list(value.keys()) for (key, value) in obj_class.INPUT_TYPES().items()}
|
| 595 |
+
info['output'] = obj_class.RETURN_TYPES
|
| 596 |
+
info['output_is_list'] = obj_class.OUTPUT_IS_LIST if hasattr(obj_class, 'OUTPUT_IS_LIST') else [False] * len(obj_class.RETURN_TYPES)
|
| 597 |
+
info['output_name'] = obj_class.RETURN_NAMES if hasattr(obj_class, 'RETURN_NAMES') else info['output']
|
| 598 |
+
info['name'] = node_class
|
| 599 |
+
info['display_name'] = nodes.NODE_DISPLAY_NAME_MAPPINGS[node_class] if node_class in nodes.NODE_DISPLAY_NAME_MAPPINGS.keys() else node_class
|
| 600 |
+
info['description'] = obj_class.DESCRIPTION if hasattr(obj_class,'DESCRIPTION') else ''
|
| 601 |
+
info['python_module'] = getattr(obj_class, "RELATIVE_PYTHON_MODULE", "nodes")
|
| 602 |
+
info['category'] = 'sd'
|
| 603 |
+
if hasattr(obj_class, 'OUTPUT_NODE') and obj_class.OUTPUT_NODE == True:
|
| 604 |
+
info['output_node'] = True
|
| 605 |
+
else:
|
| 606 |
+
info['output_node'] = False
|
| 607 |
+
|
| 608 |
+
if hasattr(obj_class, 'CATEGORY'):
|
| 609 |
+
info['category'] = obj_class.CATEGORY
|
| 610 |
+
|
| 611 |
+
if hasattr(obj_class, 'OUTPUT_TOOLTIPS'):
|
| 612 |
+
info['output_tooltips'] = obj_class.OUTPUT_TOOLTIPS
|
| 613 |
+
|
| 614 |
+
if getattr(obj_class, "DEPRECATED", False):
|
| 615 |
+
info['deprecated'] = True
|
| 616 |
+
if getattr(obj_class, "EXPERIMENTAL", False):
|
| 617 |
+
info['experimental'] = True
|
| 618 |
+
|
| 619 |
+
if hasattr(obj_class, 'API_NODE'):
|
| 620 |
+
info['api_node'] = obj_class.API_NODE
|
| 621 |
+
return info
|
| 622 |
+
|
| 623 |
+
@routes.get("/object_info")
|
| 624 |
+
async def get_object_info(request):
|
| 625 |
+
with folder_paths.cache_helper:
|
| 626 |
+
out = {}
|
| 627 |
+
for x in nodes.NODE_CLASS_MAPPINGS:
|
| 628 |
+
try:
|
| 629 |
+
out[x] = node_info(x)
|
| 630 |
+
except Exception:
|
| 631 |
+
logging.error(f"[ERROR] An error occurred while retrieving information for the '{x}' node.")
|
| 632 |
+
logging.error(traceback.format_exc())
|
| 633 |
+
return web.json_response(out)
|
| 634 |
+
|
| 635 |
+
@routes.get("/object_info/{node_class}")
|
| 636 |
+
async def get_object_info_node(request):
|
| 637 |
+
node_class = request.match_info.get("node_class", None)
|
| 638 |
+
out = {}
|
| 639 |
+
if (node_class is not None) and (node_class in nodes.NODE_CLASS_MAPPINGS):
|
| 640 |
+
out[node_class] = node_info(node_class)
|
| 641 |
+
return web.json_response(out)
|
| 642 |
+
|
| 643 |
+
@routes.get("/history")
|
| 644 |
+
async def get_history(request):
|
| 645 |
+
max_items = request.rel_url.query.get("max_items", None)
|
| 646 |
+
if max_items is not None:
|
| 647 |
+
max_items = int(max_items)
|
| 648 |
+
return web.json_response(self.prompt_queue.get_history(max_items=max_items))
|
| 649 |
+
|
| 650 |
+
@routes.get("/history/{prompt_id}")
|
| 651 |
+
async def get_history_prompt_id(request):
|
| 652 |
+
prompt_id = request.match_info.get("prompt_id", None)
|
| 653 |
+
return web.json_response(self.prompt_queue.get_history(prompt_id=prompt_id))
|
| 654 |
+
|
| 655 |
+
@routes.get("/queue")
|
| 656 |
+
async def get_queue(request):
|
| 657 |
+
queue_info = {}
|
| 658 |
+
current_queue = self.prompt_queue.get_current_queue_volatile()
|
| 659 |
+
queue_info['queue_running'] = current_queue[0]
|
| 660 |
+
queue_info['queue_pending'] = current_queue[1]
|
| 661 |
+
return web.json_response(queue_info)
|
| 662 |
+
|
| 663 |
+
@routes.post("/prompt")
|
| 664 |
+
async def post_prompt(request):
|
| 665 |
+
logging.info("got prompt")
|
| 666 |
+
json_data = await request.json()
|
| 667 |
+
json_data = self.trigger_on_prompt(json_data)
|
| 668 |
+
|
| 669 |
+
if "number" in json_data:
|
| 670 |
+
number = float(json_data['number'])
|
| 671 |
+
else:
|
| 672 |
+
number = self.number
|
| 673 |
+
if "front" in json_data:
|
| 674 |
+
if json_data['front']:
|
| 675 |
+
number = -number
|
| 676 |
+
|
| 677 |
+
self.number += 1
|
| 678 |
+
|
| 679 |
+
if "prompt" in json_data:
|
| 680 |
+
prompt = json_data["prompt"]
|
| 681 |
+
prompt_id = str(json_data.get("prompt_id", uuid.uuid4()))
|
| 682 |
+
valid = await execution.validate_prompt(prompt_id, prompt)
|
| 683 |
+
extra_data = {}
|
| 684 |
+
if "extra_data" in json_data:
|
| 685 |
+
extra_data = json_data["extra_data"]
|
| 686 |
+
|
| 687 |
+
if "client_id" in json_data:
|
| 688 |
+
extra_data["client_id"] = json_data["client_id"]
|
| 689 |
+
if valid[0]:
|
| 690 |
+
outputs_to_execute = valid[2]
|
| 691 |
+
self.prompt_queue.put((number, prompt_id, prompt, extra_data, outputs_to_execute))
|
| 692 |
+
response = {"prompt_id": prompt_id, "number": number, "node_errors": valid[3]}
|
| 693 |
+
return web.json_response(response)
|
| 694 |
+
else:
|
| 695 |
+
logging.warning("invalid prompt: {}".format(valid[1]))
|
| 696 |
+
return web.json_response({"error": valid[1], "node_errors": valid[3]}, status=400)
|
| 697 |
+
else:
|
| 698 |
+
error = {
|
| 699 |
+
"type": "no_prompt",
|
| 700 |
+
"message": "No prompt provided",
|
| 701 |
+
"details": "No prompt provided",
|
| 702 |
+
"extra_info": {}
|
| 703 |
+
}
|
| 704 |
+
return web.json_response({"error": error, "node_errors": {}}, status=400)
|
| 705 |
+
|
| 706 |
+
@routes.post("/queue")
|
| 707 |
+
async def post_queue(request):
|
| 708 |
+
json_data = await request.json()
|
| 709 |
+
if "clear" in json_data:
|
| 710 |
+
if json_data["clear"]:
|
| 711 |
+
self.prompt_queue.wipe_queue()
|
| 712 |
+
if "delete" in json_data:
|
| 713 |
+
to_delete = json_data['delete']
|
| 714 |
+
for id_to_delete in to_delete:
|
| 715 |
+
delete_func = lambda a: a[1] == id_to_delete
|
| 716 |
+
self.prompt_queue.delete_queue_item(delete_func)
|
| 717 |
+
|
| 718 |
+
return web.Response(status=200)
|
| 719 |
+
|
| 720 |
+
@routes.post("/interrupt")
|
| 721 |
+
async def post_interrupt(request):
|
| 722 |
+
nodes.interrupt_processing()
|
| 723 |
+
return web.Response(status=200)
|
| 724 |
+
|
| 725 |
+
@routes.post("/free")
|
| 726 |
+
async def post_free(request):
|
| 727 |
+
json_data = await request.json()
|
| 728 |
+
unload_models = json_data.get("unload_models", False)
|
| 729 |
+
free_memory = json_data.get("free_memory", False)
|
| 730 |
+
if unload_models:
|
| 731 |
+
self.prompt_queue.set_flag("unload_models", unload_models)
|
| 732 |
+
if free_memory:
|
| 733 |
+
self.prompt_queue.set_flag("free_memory", free_memory)
|
| 734 |
+
return web.Response(status=200)
|
| 735 |
+
|
| 736 |
+
@routes.post("/history")
|
| 737 |
+
async def post_history(request):
|
| 738 |
+
json_data = await request.json()
|
| 739 |
+
if "clear" in json_data:
|
| 740 |
+
if json_data["clear"]:
|
| 741 |
+
self.prompt_queue.wipe_history()
|
| 742 |
+
if "delete" in json_data:
|
| 743 |
+
to_delete = json_data['delete']
|
| 744 |
+
for id_to_delete in to_delete:
|
| 745 |
+
self.prompt_queue.delete_history_item(id_to_delete)
|
| 746 |
+
|
| 747 |
+
return web.Response(status=200)
|
| 748 |
+
|
| 749 |
+
async def setup(self):
|
| 750 |
+
timeout = aiohttp.ClientTimeout(total=None) # no timeout
|
| 751 |
+
self.client_session = aiohttp.ClientSession(timeout=timeout)
|
| 752 |
+
|
| 753 |
+
def add_routes(self):
|
| 754 |
+
self.user_manager.add_routes(self.routes)
|
| 755 |
+
self.model_file_manager.add_routes(self.routes)
|
| 756 |
+
self.custom_node_manager.add_routes(self.routes, self.app, nodes.LOADED_MODULE_DIRS.items())
|
| 757 |
+
self.app.add_subapp('/internal', self.internal_routes.get_app())
|
| 758 |
+
|
| 759 |
+
# Prefix every route with /api for easier matching for delegation.
|
| 760 |
+
# This is very useful for frontend dev server, which need to forward
|
| 761 |
+
# everything except serving of static files.
|
| 762 |
+
# Currently both the old endpoints without prefix and new endpoints with
|
| 763 |
+
# prefix are supported.
|
| 764 |
+
api_routes = web.RouteTableDef()
|
| 765 |
+
for route in self.routes:
|
| 766 |
+
# Custom nodes might add extra static routes. Only process non-static
|
| 767 |
+
# routes to add /api prefix.
|
| 768 |
+
if isinstance(route, web.RouteDef):
|
| 769 |
+
api_routes.route(route.method, "/api" + route.path)(route.handler, **route.kwargs)
|
| 770 |
+
self.app.add_routes(api_routes)
|
| 771 |
+
self.app.add_routes(self.routes)
|
| 772 |
+
|
| 773 |
+
# Add routes from web extensions.
|
| 774 |
+
for name, dir in nodes.EXTENSION_WEB_DIRS.items():
|
| 775 |
+
self.app.add_routes([web.static('/extensions/' + name, dir)])
|
| 776 |
+
|
| 777 |
+
workflow_templates_path = FrontendManager.templates_path()
|
| 778 |
+
if workflow_templates_path:
|
| 779 |
+
self.app.add_routes([
|
| 780 |
+
web.static('/templates', workflow_templates_path)
|
| 781 |
+
])
|
| 782 |
+
|
| 783 |
+
# Serve embedded documentation from the package
|
| 784 |
+
embedded_docs_path = FrontendManager.embedded_docs_path()
|
| 785 |
+
if embedded_docs_path:
|
| 786 |
+
self.app.add_routes([
|
| 787 |
+
web.static('/docs', embedded_docs_path)
|
| 788 |
+
])
|
| 789 |
+
|
| 790 |
+
self.app.add_routes([
|
| 791 |
+
web.static('/', self.web_root),
|
| 792 |
+
])
|
| 793 |
+
|
| 794 |
+
def get_queue_info(self):
|
| 795 |
+
prompt_info = {}
|
| 796 |
+
exec_info = {}
|
| 797 |
+
exec_info['queue_remaining'] = self.prompt_queue.get_tasks_remaining()
|
| 798 |
+
prompt_info['exec_info'] = exec_info
|
| 799 |
+
return prompt_info
|
| 800 |
+
|
| 801 |
+
async def send(self, event, data, sid=None):
|
| 802 |
+
if event == BinaryEventTypes.UNENCODED_PREVIEW_IMAGE:
|
| 803 |
+
await self.send_image(data, sid=sid)
|
| 804 |
+
elif event == BinaryEventTypes.PREVIEW_IMAGE_WITH_METADATA:
|
| 805 |
+
# data is (preview_image, metadata)
|
| 806 |
+
preview_image, metadata = data
|
| 807 |
+
await self.send_image_with_metadata(preview_image, metadata, sid=sid)
|
| 808 |
+
elif isinstance(data, (bytes, bytearray)):
|
| 809 |
+
await self.send_bytes(event, data, sid)
|
| 810 |
+
else:
|
| 811 |
+
await self.send_json(event, data, sid)
|
| 812 |
+
|
| 813 |
+
def encode_bytes(self, event, data):
|
| 814 |
+
if not isinstance(event, int):
|
| 815 |
+
raise RuntimeError(f"Binary event types must be integers, got {event}")
|
| 816 |
+
|
| 817 |
+
packed = struct.pack(">I", event)
|
| 818 |
+
message = bytearray(packed)
|
| 819 |
+
message.extend(data)
|
| 820 |
+
return message
|
| 821 |
+
|
| 822 |
+
async def send_image(self, image_data, sid=None):
|
| 823 |
+
image_type = image_data[0]
|
| 824 |
+
image = image_data[1]
|
| 825 |
+
max_size = image_data[2]
|
| 826 |
+
if max_size is not None:
|
| 827 |
+
if hasattr(Image, 'Resampling'):
|
| 828 |
+
resampling = Image.Resampling.BILINEAR
|
| 829 |
+
else:
|
| 830 |
+
resampling = Image.Resampling.LANCZOS
|
| 831 |
+
|
| 832 |
+
image = ImageOps.contain(image, (max_size, max_size), resampling)
|
| 833 |
+
type_num = 1
|
| 834 |
+
if image_type == "JPEG":
|
| 835 |
+
type_num = 1
|
| 836 |
+
elif image_type == "PNG":
|
| 837 |
+
type_num = 2
|
| 838 |
+
|
| 839 |
+
bytesIO = BytesIO()
|
| 840 |
+
header = struct.pack(">I", type_num)
|
| 841 |
+
bytesIO.write(header)
|
| 842 |
+
image.save(bytesIO, format=image_type, quality=95, compress_level=1)
|
| 843 |
+
preview_bytes = bytesIO.getvalue()
|
| 844 |
+
await self.send_bytes(BinaryEventTypes.PREVIEW_IMAGE, preview_bytes, sid=sid)
|
| 845 |
+
|
| 846 |
+
async def send_image_with_metadata(self, image_data, metadata=None, sid=None):
|
| 847 |
+
image_type = image_data[0]
|
| 848 |
+
image = image_data[1]
|
| 849 |
+
max_size = image_data[2]
|
| 850 |
+
if max_size is not None:
|
| 851 |
+
if hasattr(Image, 'Resampling'):
|
| 852 |
+
resampling = Image.Resampling.BILINEAR
|
| 853 |
+
else:
|
| 854 |
+
resampling = Image.Resampling.LANCZOS
|
| 855 |
+
|
| 856 |
+
image = ImageOps.contain(image, (max_size, max_size), resampling)
|
| 857 |
+
|
| 858 |
+
mimetype = "image/png" if image_type == "PNG" else "image/jpeg"
|
| 859 |
+
|
| 860 |
+
# Prepare metadata
|
| 861 |
+
if metadata is None:
|
| 862 |
+
metadata = {}
|
| 863 |
+
metadata["image_type"] = mimetype
|
| 864 |
+
|
| 865 |
+
# Serialize metadata as JSON
|
| 866 |
+
import json
|
| 867 |
+
metadata_json = json.dumps(metadata).encode('utf-8')
|
| 868 |
+
metadata_length = len(metadata_json)
|
| 869 |
+
|
| 870 |
+
# Prepare image data
|
| 871 |
+
bytesIO = BytesIO()
|
| 872 |
+
image.save(bytesIO, format=image_type, quality=95, compress_level=1)
|
| 873 |
+
image_bytes = bytesIO.getvalue()
|
| 874 |
+
|
| 875 |
+
# Combine metadata and image
|
| 876 |
+
combined_data = bytearray()
|
| 877 |
+
combined_data.extend(struct.pack(">I", metadata_length))
|
| 878 |
+
combined_data.extend(metadata_json)
|
| 879 |
+
combined_data.extend(image_bytes)
|
| 880 |
+
|
| 881 |
+
await self.send_bytes(BinaryEventTypes.PREVIEW_IMAGE_WITH_METADATA, combined_data, sid=sid)
|
| 882 |
+
|
| 883 |
+
async def send_bytes(self, event, data, sid=None):
|
| 884 |
+
message = self.encode_bytes(event, data)
|
| 885 |
+
|
| 886 |
+
if sid is None:
|
| 887 |
+
sockets = list(self.sockets.values())
|
| 888 |
+
for ws in sockets:
|
| 889 |
+
await send_socket_catch_exception(ws.send_bytes, message)
|
| 890 |
+
elif sid in self.sockets:
|
| 891 |
+
await send_socket_catch_exception(self.sockets[sid].send_bytes, message)
|
| 892 |
+
|
| 893 |
+
async def send_json(self, event, data, sid=None):
|
| 894 |
+
message = {"type": event, "data": data}
|
| 895 |
+
|
| 896 |
+
if sid is None:
|
| 897 |
+
sockets = list(self.sockets.values())
|
| 898 |
+
for ws in sockets:
|
| 899 |
+
await send_socket_catch_exception(ws.send_json, message)
|
| 900 |
+
elif sid in self.sockets:
|
| 901 |
+
await send_socket_catch_exception(self.sockets[sid].send_json, message)
|
| 902 |
+
|
| 903 |
+
def send_sync(self, event, data, sid=None):
|
| 904 |
+
self.loop.call_soon_threadsafe(
|
| 905 |
+
self.messages.put_nowait, (event, data, sid))
|
| 906 |
+
|
| 907 |
+
def queue_updated(self):
|
| 908 |
+
self.send_sync("status", { "status": self.get_queue_info() })
|
| 909 |
+
|
| 910 |
+
async def publish_loop(self):
|
| 911 |
+
while True:
|
| 912 |
+
msg = await self.messages.get()
|
| 913 |
+
await self.send(*msg)
|
| 914 |
+
|
| 915 |
+
async def start(self, address, port, verbose=True, call_on_start=None):
|
| 916 |
+
await self.start_multi_address([(address, port)], call_on_start=call_on_start)
|
| 917 |
+
|
| 918 |
+
async def start_multi_address(self, addresses, call_on_start=None, verbose=True):
|
| 919 |
+
runner = web.AppRunner(self.app, access_log=None)
|
| 920 |
+
await runner.setup()
|
| 921 |
+
ssl_ctx = None
|
| 922 |
+
scheme = "http"
|
| 923 |
+
if args.tls_keyfile and args.tls_certfile:
|
| 924 |
+
ssl_ctx = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER, verify_mode=ssl.CERT_NONE)
|
| 925 |
+
ssl_ctx.load_cert_chain(certfile=args.tls_certfile,
|
| 926 |
+
keyfile=args.tls_keyfile)
|
| 927 |
+
scheme = "https"
|
| 928 |
+
|
| 929 |
+
if verbose:
|
| 930 |
+
logging.info("Starting server\n")
|
| 931 |
+
for addr in addresses:
|
| 932 |
+
address = addr[0]
|
| 933 |
+
port = addr[1]
|
| 934 |
+
site = web.TCPSite(runner, address, port, ssl_context=ssl_ctx)
|
| 935 |
+
await site.start()
|
| 936 |
+
|
| 937 |
+
if not hasattr(self, 'address'):
|
| 938 |
+
self.address = address #TODO: remove this
|
| 939 |
+
self.port = port
|
| 940 |
+
|
| 941 |
+
if ':' in address:
|
| 942 |
+
address_print = "[{}]".format(address)
|
| 943 |
+
else:
|
| 944 |
+
address_print = address
|
| 945 |
+
|
| 946 |
+
if verbose:
|
| 947 |
+
logging.info("To see the GUI go to: {}://{}:{}".format(scheme, address_print, port))
|
| 948 |
+
|
| 949 |
+
if call_on_start is not None:
|
| 950 |
+
call_on_start(scheme, self.address, self.port)
|
| 951 |
+
|
| 952 |
+
def add_on_prompt_handler(self, handler):
|
| 953 |
+
self.on_prompt_handlers.append(handler)
|
| 954 |
+
|
| 955 |
+
def trigger_on_prompt(self, json_data):
|
| 956 |
+
for handler in self.on_prompt_handlers:
|
| 957 |
+
try:
|
| 958 |
+
json_data = handler(json_data)
|
| 959 |
+
except Exception:
|
| 960 |
+
logging.warning("[ERROR] An error occurred during the on_prompt_handler processing")
|
| 961 |
+
logging.warning(traceback.format_exc())
|
| 962 |
+
|
| 963 |
+
return json_data
|
| 964 |
+
|
| 965 |
+
def send_progress_text(
|
| 966 |
+
self, text: Union[bytes, bytearray, str], node_id: str, sid=None
|
| 967 |
+
):
|
| 968 |
+
if isinstance(text, str):
|
| 969 |
+
text = text.encode("utf-8")
|
| 970 |
+
node_id_bytes = str(node_id).encode("utf-8")
|
| 971 |
+
|
| 972 |
+
# Pack the node_id length as a 4-byte unsigned integer, followed by the node_id bytes
|
| 973 |
+
message = struct.pack(">I", len(node_id_bytes)) + node_id_bytes + text
|
| 974 |
+
|
| 975 |
+
self.send_sync(BinaryEventTypes.TEXT, message, sid)
|
venv/bin/Activate.ps1
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<#
|
| 2 |
+
.Synopsis
|
| 3 |
+
Activate a Python virtual environment for the current PowerShell session.
|
| 4 |
+
|
| 5 |
+
.Description
|
| 6 |
+
Pushes the python executable for a virtual environment to the front of the
|
| 7 |
+
$Env:PATH environment variable and sets the prompt to signify that you are
|
| 8 |
+
in a Python virtual environment. Makes use of the command line switches as
|
| 9 |
+
well as the `pyvenv.cfg` file values present in the virtual environment.
|
| 10 |
+
|
| 11 |
+
.Parameter VenvDir
|
| 12 |
+
Path to the directory that contains the virtual environment to activate. The
|
| 13 |
+
default value for this is the parent of the directory that the Activate.ps1
|
| 14 |
+
script is located within.
|
| 15 |
+
|
| 16 |
+
.Parameter Prompt
|
| 17 |
+
The prompt prefix to display when this virtual environment is activated. By
|
| 18 |
+
default, this prompt is the name of the virtual environment folder (VenvDir)
|
| 19 |
+
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
| 20 |
+
|
| 21 |
+
.Example
|
| 22 |
+
Activate.ps1
|
| 23 |
+
Activates the Python virtual environment that contains the Activate.ps1 script.
|
| 24 |
+
|
| 25 |
+
.Example
|
| 26 |
+
Activate.ps1 -Verbose
|
| 27 |
+
Activates the Python virtual environment that contains the Activate.ps1 script,
|
| 28 |
+
and shows extra information about the activation as it executes.
|
| 29 |
+
|
| 30 |
+
.Example
|
| 31 |
+
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
| 32 |
+
Activates the Python virtual environment located in the specified location.
|
| 33 |
+
|
| 34 |
+
.Example
|
| 35 |
+
Activate.ps1 -Prompt "MyPython"
|
| 36 |
+
Activates the Python virtual environment that contains the Activate.ps1 script,
|
| 37 |
+
and prefixes the current prompt with the specified string (surrounded in
|
| 38 |
+
parentheses) while the virtual environment is active.
|
| 39 |
+
|
| 40 |
+
.Notes
|
| 41 |
+
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
| 42 |
+
execution policy for the user. You can do this by issuing the following PowerShell
|
| 43 |
+
command:
|
| 44 |
+
|
| 45 |
+
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
| 46 |
+
|
| 47 |
+
For more information on Execution Policies:
|
| 48 |
+
https://go.microsoft.com/fwlink/?LinkID=135170
|
| 49 |
+
|
| 50 |
+
#>
|
| 51 |
+
Param(
|
| 52 |
+
[Parameter(Mandatory = $false)]
|
| 53 |
+
[String]
|
| 54 |
+
$VenvDir,
|
| 55 |
+
[Parameter(Mandatory = $false)]
|
| 56 |
+
[String]
|
| 57 |
+
$Prompt
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
<# Function declarations --------------------------------------------------- #>
|
| 61 |
+
|
| 62 |
+
<#
|
| 63 |
+
.Synopsis
|
| 64 |
+
Remove all shell session elements added by the Activate script, including the
|
| 65 |
+
addition of the virtual environment's Python executable from the beginning of
|
| 66 |
+
the PATH variable.
|
| 67 |
+
|
| 68 |
+
.Parameter NonDestructive
|
| 69 |
+
If present, do not remove this function from the global namespace for the
|
| 70 |
+
session.
|
| 71 |
+
|
| 72 |
+
#>
|
| 73 |
+
function global:deactivate ([switch]$NonDestructive) {
|
| 74 |
+
# Revert to original values
|
| 75 |
+
|
| 76 |
+
# The prior prompt:
|
| 77 |
+
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
| 78 |
+
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
| 79 |
+
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
# The prior PYTHONHOME:
|
| 83 |
+
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
| 84 |
+
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
| 85 |
+
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
# The prior PATH:
|
| 89 |
+
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
| 90 |
+
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
| 91 |
+
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
# Just remove the VIRTUAL_ENV altogether:
|
| 95 |
+
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
| 96 |
+
Remove-Item -Path env:VIRTUAL_ENV
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
| 100 |
+
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
| 101 |
+
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
| 105 |
+
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
| 106 |
+
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
# Leave deactivate function in the global namespace if requested:
|
| 110 |
+
if (-not $NonDestructive) {
|
| 111 |
+
Remove-Item -Path function:deactivate
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
<#
|
| 116 |
+
.Description
|
| 117 |
+
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
| 118 |
+
given folder, and returns them in a map.
|
| 119 |
+
|
| 120 |
+
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
| 121 |
+
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
| 122 |
+
then it is considered a `key = value` line. The left hand string is the key,
|
| 123 |
+
the right hand is the value.
|
| 124 |
+
|
| 125 |
+
If the value starts with a `'` or a `"` then the first and last character is
|
| 126 |
+
stripped from the value before being captured.
|
| 127 |
+
|
| 128 |
+
.Parameter ConfigDir
|
| 129 |
+
Path to the directory that contains the `pyvenv.cfg` file.
|
| 130 |
+
#>
|
| 131 |
+
function Get-PyVenvConfig(
|
| 132 |
+
[String]
|
| 133 |
+
$ConfigDir
|
| 134 |
+
) {
|
| 135 |
+
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
| 136 |
+
|
| 137 |
+
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
| 138 |
+
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
| 139 |
+
|
| 140 |
+
# An empty map will be returned if no config file is found.
|
| 141 |
+
$pyvenvConfig = @{ }
|
| 142 |
+
|
| 143 |
+
if ($pyvenvConfigPath) {
|
| 144 |
+
|
| 145 |
+
Write-Verbose "File exists, parse `key = value` lines"
|
| 146 |
+
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
| 147 |
+
|
| 148 |
+
$pyvenvConfigContent | ForEach-Object {
|
| 149 |
+
$keyval = $PSItem -split "\s*=\s*", 2
|
| 150 |
+
if ($keyval[0] -and $keyval[1]) {
|
| 151 |
+
$val = $keyval[1]
|
| 152 |
+
|
| 153 |
+
# Remove extraneous quotations around a string value.
|
| 154 |
+
if ("'""".Contains($val.Substring(0, 1))) {
|
| 155 |
+
$val = $val.Substring(1, $val.Length - 2)
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
$pyvenvConfig[$keyval[0]] = $val
|
| 159 |
+
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
}
|
| 163 |
+
return $pyvenvConfig
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
<# Begin Activate script --------------------------------------------------- #>
|
| 168 |
+
|
| 169 |
+
# Determine the containing directory of this script
|
| 170 |
+
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
| 171 |
+
$VenvExecDir = Get-Item -Path $VenvExecPath
|
| 172 |
+
|
| 173 |
+
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
| 174 |
+
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
| 175 |
+
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
| 176 |
+
|
| 177 |
+
# Set values required in priority: CmdLine, ConfigFile, Default
|
| 178 |
+
# First, get the location of the virtual environment, it might not be
|
| 179 |
+
# VenvExecDir if specified on the command line.
|
| 180 |
+
if ($VenvDir) {
|
| 181 |
+
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
| 182 |
+
}
|
| 183 |
+
else {
|
| 184 |
+
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
| 185 |
+
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
| 186 |
+
Write-Verbose "VenvDir=$VenvDir"
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
# Next, read the `pyvenv.cfg` file to determine any required value such
|
| 190 |
+
# as `prompt`.
|
| 191 |
+
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
| 192 |
+
|
| 193 |
+
# Next, set the prompt from the command line, or the config file, or
|
| 194 |
+
# just use the name of the virtual environment folder.
|
| 195 |
+
if ($Prompt) {
|
| 196 |
+
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
| 197 |
+
}
|
| 198 |
+
else {
|
| 199 |
+
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
| 200 |
+
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
| 201 |
+
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
| 202 |
+
$Prompt = $pyvenvCfg['prompt'];
|
| 203 |
+
}
|
| 204 |
+
else {
|
| 205 |
+
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
| 206 |
+
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
| 207 |
+
$Prompt = Split-Path -Path $venvDir -Leaf
|
| 208 |
+
}
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
Write-Verbose "Prompt = '$Prompt'"
|
| 212 |
+
Write-Verbose "VenvDir='$VenvDir'"
|
| 213 |
+
|
| 214 |
+
# Deactivate any currently active virtual environment, but leave the
|
| 215 |
+
# deactivate function in place.
|
| 216 |
+
deactivate -nondestructive
|
| 217 |
+
|
| 218 |
+
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
| 219 |
+
# that there is an activated venv.
|
| 220 |
+
$env:VIRTUAL_ENV = $VenvDir
|
| 221 |
+
|
| 222 |
+
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
| 223 |
+
|
| 224 |
+
Write-Verbose "Setting prompt to '$Prompt'"
|
| 225 |
+
|
| 226 |
+
# Set the prompt to include the env name
|
| 227 |
+
# Make sure _OLD_VIRTUAL_PROMPT is global
|
| 228 |
+
function global:_OLD_VIRTUAL_PROMPT { "" }
|
| 229 |
+
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
| 230 |
+
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
| 231 |
+
|
| 232 |
+
function global:prompt {
|
| 233 |
+
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
| 234 |
+
_OLD_VIRTUAL_PROMPT
|
| 235 |
+
}
|
| 236 |
+
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
# Clear PYTHONHOME
|
| 240 |
+
if (Test-Path -Path Env:PYTHONHOME) {
|
| 241 |
+
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
| 242 |
+
Remove-Item -Path Env:PYTHONHOME
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
# Add the venv to the PATH
|
| 246 |
+
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
| 247 |
+
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
venv/bin/activate
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file must be used with "source bin/activate" *from bash*
|
| 2 |
+
# you cannot run it directly
|
| 3 |
+
|
| 4 |
+
deactivate () {
|
| 5 |
+
# reset old environment variables
|
| 6 |
+
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
| 7 |
+
PATH="${_OLD_VIRTUAL_PATH:-}"
|
| 8 |
+
export PATH
|
| 9 |
+
unset _OLD_VIRTUAL_PATH
|
| 10 |
+
fi
|
| 11 |
+
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
| 12 |
+
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
| 13 |
+
export PYTHONHOME
|
| 14 |
+
unset _OLD_VIRTUAL_PYTHONHOME
|
| 15 |
+
fi
|
| 16 |
+
|
| 17 |
+
# This should detect bash and zsh, which have a hash command that must
|
| 18 |
+
# be called to get it to forget past commands. Without forgetting
|
| 19 |
+
# past commands the $PATH changes we made may not be respected
|
| 20 |
+
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
| 21 |
+
hash -r 2> /dev/null
|
| 22 |
+
fi
|
| 23 |
+
|
| 24 |
+
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
| 25 |
+
PS1="${_OLD_VIRTUAL_PS1:-}"
|
| 26 |
+
export PS1
|
| 27 |
+
unset _OLD_VIRTUAL_PS1
|
| 28 |
+
fi
|
| 29 |
+
|
| 30 |
+
unset VIRTUAL_ENV
|
| 31 |
+
unset VIRTUAL_ENV_PROMPT
|
| 32 |
+
if [ ! "${1:-}" = "nondestructive" ] ; then
|
| 33 |
+
# Self destruct!
|
| 34 |
+
unset -f deactivate
|
| 35 |
+
fi
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
# unset irrelevant variables
|
| 39 |
+
deactivate nondestructive
|
| 40 |
+
|
| 41 |
+
VIRTUAL_ENV="/workspace/ComfyUI/venv"
|
| 42 |
+
export VIRTUAL_ENV
|
| 43 |
+
|
| 44 |
+
_OLD_VIRTUAL_PATH="$PATH"
|
| 45 |
+
PATH="$VIRTUAL_ENV/bin:$PATH"
|
| 46 |
+
export PATH
|
| 47 |
+
|
| 48 |
+
# unset PYTHONHOME if set
|
| 49 |
+
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
| 50 |
+
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
| 51 |
+
if [ -n "${PYTHONHOME:-}" ] ; then
|
| 52 |
+
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
| 53 |
+
unset PYTHONHOME
|
| 54 |
+
fi
|
| 55 |
+
|
| 56 |
+
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
| 57 |
+
_OLD_VIRTUAL_PS1="${PS1:-}"
|
| 58 |
+
PS1="(venv) ${PS1:-}"
|
| 59 |
+
export PS1
|
| 60 |
+
VIRTUAL_ENV_PROMPT="(venv) "
|
| 61 |
+
export VIRTUAL_ENV_PROMPT
|
| 62 |
+
fi
|
| 63 |
+
|
| 64 |
+
# This should detect bash and zsh, which have a hash command that must
|
| 65 |
+
# be called to get it to forget past commands. Without forgetting
|
| 66 |
+
# past commands the $PATH changes we made may not be respected
|
| 67 |
+
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
| 68 |
+
hash -r 2> /dev/null
|
| 69 |
+
fi
|
venv/bin/activate.csh
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file must be used with "source bin/activate.csh" *from csh*.
|
| 2 |
+
# You cannot run it directly.
|
| 3 |
+
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
| 4 |
+
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
| 5 |
+
|
| 6 |
+
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
| 7 |
+
|
| 8 |
+
# Unset irrelevant variables.
|
| 9 |
+
deactivate nondestructive
|
| 10 |
+
|
| 11 |
+
setenv VIRTUAL_ENV "/workspace/ComfyUI/venv"
|
| 12 |
+
|
| 13 |
+
set _OLD_VIRTUAL_PATH="$PATH"
|
| 14 |
+
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
set _OLD_VIRTUAL_PROMPT="$prompt"
|
| 18 |
+
|
| 19 |
+
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
| 20 |
+
set prompt = "(venv) $prompt"
|
| 21 |
+
setenv VIRTUAL_ENV_PROMPT "(venv) "
|
| 22 |
+
endif
|
| 23 |
+
|
| 24 |
+
alias pydoc python -m pydoc
|
| 25 |
+
|
| 26 |
+
rehash
|
venv/bin/activate.fish
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
| 2 |
+
# (https://fishshell.com/); you cannot run it directly.
|
| 3 |
+
|
| 4 |
+
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
| 5 |
+
# reset old environment variables
|
| 6 |
+
if test -n "$_OLD_VIRTUAL_PATH"
|
| 7 |
+
set -gx PATH $_OLD_VIRTUAL_PATH
|
| 8 |
+
set -e _OLD_VIRTUAL_PATH
|
| 9 |
+
end
|
| 10 |
+
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
| 11 |
+
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
| 12 |
+
set -e _OLD_VIRTUAL_PYTHONHOME
|
| 13 |
+
end
|
| 14 |
+
|
| 15 |
+
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
| 16 |
+
set -e _OLD_FISH_PROMPT_OVERRIDE
|
| 17 |
+
# prevents error when using nested fish instances (Issue #93858)
|
| 18 |
+
if functions -q _old_fish_prompt
|
| 19 |
+
functions -e fish_prompt
|
| 20 |
+
functions -c _old_fish_prompt fish_prompt
|
| 21 |
+
functions -e _old_fish_prompt
|
| 22 |
+
end
|
| 23 |
+
end
|
| 24 |
+
|
| 25 |
+
set -e VIRTUAL_ENV
|
| 26 |
+
set -e VIRTUAL_ENV_PROMPT
|
| 27 |
+
if test "$argv[1]" != "nondestructive"
|
| 28 |
+
# Self-destruct!
|
| 29 |
+
functions -e deactivate
|
| 30 |
+
end
|
| 31 |
+
end
|
| 32 |
+
|
| 33 |
+
# Unset irrelevant variables.
|
| 34 |
+
deactivate nondestructive
|
| 35 |
+
|
| 36 |
+
set -gx VIRTUAL_ENV "/workspace/ComfyUI/venv"
|
| 37 |
+
|
| 38 |
+
set -gx _OLD_VIRTUAL_PATH $PATH
|
| 39 |
+
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
| 40 |
+
|
| 41 |
+
# Unset PYTHONHOME if set.
|
| 42 |
+
if set -q PYTHONHOME
|
| 43 |
+
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
| 44 |
+
set -e PYTHONHOME
|
| 45 |
+
end
|
| 46 |
+
|
| 47 |
+
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
| 48 |
+
# fish uses a function instead of an env var to generate the prompt.
|
| 49 |
+
|
| 50 |
+
# Save the current fish_prompt function as the function _old_fish_prompt.
|
| 51 |
+
functions -c fish_prompt _old_fish_prompt
|
| 52 |
+
|
| 53 |
+
# With the original prompt function renamed, we can override with our own.
|
| 54 |
+
function fish_prompt
|
| 55 |
+
# Save the return status of the last command.
|
| 56 |
+
set -l old_status $status
|
| 57 |
+
|
| 58 |
+
# Output the venv prompt; color taken from the blue of the Python logo.
|
| 59 |
+
printf "%s%s%s" (set_color 4B8BBE) "(venv) " (set_color normal)
|
| 60 |
+
|
| 61 |
+
# Restore the return status of the previous command.
|
| 62 |
+
echo "exit $old_status" | .
|
| 63 |
+
# Output the original/"old" prompt.
|
| 64 |
+
_old_fish_prompt
|
| 65 |
+
end
|
| 66 |
+
|
| 67 |
+
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
| 68 |
+
set -gx VIRTUAL_ENV_PROMPT "(venv) "
|
| 69 |
+
end
|
venv/bin/alembic
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from alembic.config import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/dotenv
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from dotenv.__main__ import cli
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(cli())
|
venv/bin/ds
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
|
| 3 |
+
from deepspeed.launcher.runner import main
|
| 4 |
+
|
| 5 |
+
if __name__ == '__main__':
|
| 6 |
+
main()
|
venv/bin/fonttools
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from fontTools.__main__ import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/huggingface-cli
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from huggingface_hub.commands.huggingface_cli import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/imageio_download_bin
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from imageio.__main__ import download_bin_main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(download_bin_main())
|
venv/bin/imageio_remove_bin
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from imageio.__main__ import remove_bin_main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(remove_bin_main())
|
venv/bin/insightface-cli
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from insightface.commands.insightface_cli import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/isympy
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from isympy import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/lsm2bin
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from tifffile.lsm2bin import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/mako-render
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from mako.cmd import cmdline
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(cmdline())
|
venv/bin/normalizer
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from charset_normalizer import cli
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(cli.cli_detect())
|
venv/bin/pip
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from pip._internal.cli.main import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/pip3
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from pip._internal.cli.main import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/pip3.10
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from pip._internal.cli.main import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/proton
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from triton.profiler.proton import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/proton-viewer
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from triton.profiler.viewer import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/pyav
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from av.__main__ import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/pyftmerge
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from fontTools.merge import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/sz_split
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from cli.split import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/sz_wc
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from cli.wc import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/tiff2fsspec
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from tifffile.tiff2fsspec import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/tiffcomment
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from tifffile.tiffcomment import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/tifffile
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from tifffile import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/tiny-agents
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from huggingface_hub.inference._mcp.cli import app
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(app())
|
venv/bin/torchfrtrace
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from tools.flight_recorder.fr_trace import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/torchrun
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from torch.distributed.run import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/tqdm
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from tqdm.cli import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/transformers
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from transformers.commands.transformers_cli import main
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main())
|
venv/bin/transformers-cli
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/workspace/ComfyUI/venv/bin/python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
from transformers.commands.transformers_cli import main_cli
|
| 6 |
+
if __name__ == '__main__':
|
| 7 |
+
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
| 8 |
+
sys.exit(main_cli())
|
venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pip
|
venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/METADATA
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.2
|
| 2 |
+
Name: PyGithub
|
| 3 |
+
Version: 2.6.1
|
| 4 |
+
Summary: Use the full Github API v3
|
| 5 |
+
Author-email: Vincent Jacques <vincent@vincent-jacques.net>
|
| 6 |
+
Project-URL: Documentation, https://pygithub.readthedocs.io/en/stable/
|
| 7 |
+
Project-URL: Source, https://github.com/pygithub/pygithub
|
| 8 |
+
Project-URL: Tracker, https://github.com/pygithub/pygithub/issues
|
| 9 |
+
Keywords: github
|
| 10 |
+
Classifier: Development Status :: 5 - Production/Stable
|
| 11 |
+
Classifier: Environment :: Web Environment
|
| 12 |
+
Classifier: Intended Audience :: Developers
|
| 13 |
+
Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)
|
| 14 |
+
Classifier: Operating System :: OS Independent
|
| 15 |
+
Classifier: Programming Language :: Python
|
| 16 |
+
Classifier: Programming Language :: Python :: 3
|
| 17 |
+
Classifier: Programming Language :: Python :: 3.8
|
| 18 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 19 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 21 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 22 |
+
Classifier: Topic :: Software Development
|
| 23 |
+
Requires-Python: >=3.8
|
| 24 |
+
Description-Content-Type: text/markdown
|
| 25 |
+
License-File: COPYING
|
| 26 |
+
License-File: COPYING.LESSER
|
| 27 |
+
Requires-Dist: pynacl>=1.4.0
|
| 28 |
+
Requires-Dist: requests>=2.14.0
|
| 29 |
+
Requires-Dist: pyjwt[crypto]>=2.4.0
|
| 30 |
+
Requires-Dist: typing-extensions>=4.0.0
|
| 31 |
+
Requires-Dist: urllib3>=1.26.0
|
| 32 |
+
Requires-Dist: Deprecated
|
| 33 |
+
Provides-Extra: integrations
|
| 34 |
+
|
| 35 |
+
# PyGitHub
|
| 36 |
+
|
| 37 |
+
[](https://pypi.python.org/pypi/PyGithub)
|
| 38 |
+

|
| 39 |
+
[](https://pygithub.readthedocs.io/en/stable/?badge=stable)
|
| 40 |
+
[](https://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License)
|
| 41 |
+
[](https://join.slack.com/t/pygithub-project/shared_invite/zt-duj89xtx-uKFZtgAg209o6Vweqm8xeQ)
|
| 42 |
+
[](https://www.codetriage.com/pygithub/pygithub)
|
| 43 |
+
[](https://codecov.io/gh/PyGithub/PyGithub)
|
| 44 |
+
[](https://github.com/psf/black)
|
| 45 |
+
|
| 46 |
+
PyGitHub is a Python library to access the [GitHub REST API].
|
| 47 |
+
This library enables you to manage [GitHub] resources such as repositories, user profiles, and organizations in your Python applications.
|
| 48 |
+
|
| 49 |
+
[GitHub REST API]: https://docs.github.com/en/rest
|
| 50 |
+
[GitHub]: https://github.com
|
| 51 |
+
|
| 52 |
+
## Install
|
| 53 |
+
|
| 54 |
+
```bash
|
| 55 |
+
pip install PyGithub
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
## Simple Demo
|
| 59 |
+
|
| 60 |
+
```python
|
| 61 |
+
from github import Github
|
| 62 |
+
|
| 63 |
+
# Authentication is defined via github.Auth
|
| 64 |
+
from github import Auth
|
| 65 |
+
|
| 66 |
+
# using an access token
|
| 67 |
+
auth = Auth.Token("access_token")
|
| 68 |
+
|
| 69 |
+
# First create a Github instance:
|
| 70 |
+
|
| 71 |
+
# Public Web Github
|
| 72 |
+
g = Github(auth=auth)
|
| 73 |
+
|
| 74 |
+
# Github Enterprise with custom hostname
|
| 75 |
+
g = Github(base_url="https://{hostname}/api/v3", auth=auth)
|
| 76 |
+
|
| 77 |
+
# Then play with your Github objects:
|
| 78 |
+
for repo in g.get_user().get_repos():
|
| 79 |
+
print(repo.name)
|
| 80 |
+
|
| 81 |
+
# To close connections after use
|
| 82 |
+
g.close()
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
## Documentation
|
| 86 |
+
|
| 87 |
+
More information can be found on the [PyGitHub documentation site.](https://pygithub.readthedocs.io/en/stable/introduction.html)
|
| 88 |
+
|
| 89 |
+
## Development
|
| 90 |
+
|
| 91 |
+
### Contributing
|
| 92 |
+
|
| 93 |
+
Long-term discussion and bug reports are maintained via GitHub Issues.
|
| 94 |
+
Code review is done via GitHub Pull Requests.
|
| 95 |
+
|
| 96 |
+
For more information read [CONTRIBUTING.md].
|
| 97 |
+
|
| 98 |
+
[CONTRIBUTING.md]: https://github.com/PyGithub/PyGithub/blob/main/CONTRIBUTING.md
|
| 99 |
+
|
| 100 |
+
### Maintainership
|
| 101 |
+
|
| 102 |
+
We're actively seeking maintainers that will triage issues and pull requests and cut releases.
|
| 103 |
+
If you work on a project that leverages PyGitHub and have a vested interest in keeping the code alive and well, send an email to someone in the MAINTAINERS file.
|
venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/RECORD
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
PyGithub-2.6.1.dist-info/COPYING,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
|
| 2 |
+
PyGithub-2.6.1.dist-info/COPYING.LESSER,sha256=2n6rt7r999OuXp8iOqW9we7ORaxWncIbOwN1ILRGR2g,7651
|
| 3 |
+
PyGithub-2.6.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
| 4 |
+
PyGithub-2.6.1.dist-info/METADATA,sha256=6GM2H6ECoFpjMHvmsQMTQvrkHNNqLQxqLrSaduB7pow,3889
|
| 5 |
+
PyGithub-2.6.1.dist-info/RECORD,,
|
| 6 |
+
PyGithub-2.6.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 7 |
+
PyGithub-2.6.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
| 8 |
+
PyGithub-2.6.1.dist-info/top_level.txt,sha256=X_Dn8Q-zPGudQV37mRBM5uHhwhEsv4x1AeAuB1dfEls,7
|
| 9 |
+
github/AccessToken.py,sha256=_Ryw-pme8fxvSY-2dqDlnH7x1akRrxry_EKWGex4mEU,6756
|
| 10 |
+
github/AdvisoryBase.py,sha256=61onXFv5hVi0GpmUjKCPAJo8bRKJAwxA6cxix8V3M3I,7196
|
| 11 |
+
github/AdvisoryCredit.py,sha256=Sth4hayVCtuAS8vkCBITY7iAQ9Z_2_QEh8vIYXtxuKs,5213
|
| 12 |
+
github/AdvisoryCreditDetailed.py,sha256=0Jns6-Wgi2da_nAc_-cdKrdrcx6q1LTn73VXz-1kI24,5163
|
| 13 |
+
github/AdvisoryVulnerability.py,sha256=kOYX8pNMO2YA2bA3j02w69GVUSBfqvOL4VXFcppT9ss,8607
|
| 14 |
+
github/AdvisoryVulnerabilityPackage.py,sha256=Bh_BiNS34PBv5aZYFR9SUNUNdXRvhMPh1Nll11x3Tpg,4972
|
| 15 |
+
github/AppAuthentication.py,sha256=8ItYtxH1Z3qUCFiI5pIrUgWkfxrNfDtv_PTLlhAUhCs,3362
|
| 16 |
+
github/ApplicationOAuth.py,sha256=bXK9Vuw4d_VlgqfR3I4eDLflTWiJWqYDT2ee6W3TLPA,8200
|
| 17 |
+
github/Artifact.py,sha256=5wWfNLP61VqVBBnmy0OPgX_XXezpyLw3UO3D7V53M8o,8364
|
| 18 |
+
github/Auth.py,sha256=j4n343bx20ZYTFu7KjdBXIe8JvtGh-VxNaRwWGRBWDc,18777
|
| 19 |
+
github/AuthenticatedUser.py,sha256=RPSbudt7Fwf93A6y0MagxCjqGS6cZ74IianKlvfVSfM,51604
|
| 20 |
+
github/Authorization.py,sha256=X1HngQla9ail9EMVvd7hIYe3D7iD8bkP7VOEyEn5Oi8,8589
|
| 21 |
+
github/AuthorizationApplication.py,sha256=AO_iJkla_Z-SlGBdjDgU4vdIhQvC-xyi3g9ZavwLPXY,3912
|
| 22 |
+
github/Autolink.py,sha256=oca0RVXVIcjZJ-m9_2i01-8aaz7Ric8QUcWq-kc8AY8,4945
|
| 23 |
+
github/Branch.py,sha256=xMHCSGF6atubtwMTWev9ImwRASMXQCLQVHxIcpjnRQU,32091
|
| 24 |
+
github/BranchProtection.py,sha256=emtYLjbzekTi5VxvVO7fk1bwCdyY0fkxHBjEB3qxvHs,10599
|
| 25 |
+
github/CVSS.py,sha256=O7nf9sgk1XVZ8n0CnHgxXZ_A5NH8oarvWfUIUTamL7I,4658
|
| 26 |
+
github/CWE.py,sha256=liISfggat6zcylarfj0SB67SIBNuTHJjVcPmoOUuzxg,4072
|
| 27 |
+
github/CheckRun.py,sha256=8oVyQ0iR2VyiLLRoHNR0ABXwwI6m8MI1LQWP1WJNNq4,12905
|
| 28 |
+
github/CheckRunAnnotation.py,sha256=3ZmsqDEoxIX0ggVk87cczYeyQVv9WMjwZRP_3cAdR6o,5647
|
| 29 |
+
github/CheckRunOutput.py,sha256=28uFE7IsNBxdm2nrpsRrXmkH-zKKR6J3JWgZa5b6wzU,5058
|
| 30 |
+
github/CheckSuite.py,sha256=ZVbfdkDmmJxhwnD5LYBofRcOdHXjPHIp9JVX_IVHeQg,12650
|
| 31 |
+
github/Clones.py,sha256=IDesxzVgVHIf4OyJeunH6--5V-fUn0z4a4YeSgFDcpA,4862
|
| 32 |
+
github/CodeScanAlert.py,sha256=kzdKASKNHz4zqjLWuQ4j8bsk8ukDpWnRalRGq-QYpSU,7166
|
| 33 |
+
github/CodeScanAlertInstance.py,sha256=A5OdwJBXmvYnTQgpFEMN5iadIMbwobWbL-JTd-Kanvo,5463
|
| 34 |
+
github/CodeScanAlertInstanceLocation.py,sha256=5o4nPcL5VZgwys6EnvhGshXTOqplZgxDw7zB6wzNDTs,4539
|
| 35 |
+
github/CodeScanRule.py,sha256=fqunSyh14WpluH95E5aKF7zCVLr8kv3El5rHNmXrPqA,5182
|
| 36 |
+
github/CodeScanTool.py,sha256=z4gipqMPyBArAqX4bUvnINCc3P5cABzes1fY3D1_IWY,4545
|
| 37 |
+
github/CodeSecurityConfig.py,sha256=Z36edlAPbKh3B5V4MmuJUyQaU0zvsaOvDnf2NENuE30,9907
|
| 38 |
+
github/CodeSecurityConfigRepository.py,sha256=TvM3cdov7dpiJnBzsSpiWOatBMcSDxVP6cXl6ebiTJ0,3291
|
| 39 |
+
github/Commit.py,sha256=jJO2eLxCfkGj3fKavpKEnda8Yn1hLqu_A7HEhKeualU,16463
|
| 40 |
+
github/CommitCombinedStatus.py,sha256=rZIoDhiJtdSAhKlKIXcg2AaOedb5F8tymHPdgVqsRRE,5892
|
| 41 |
+
github/CommitComment.py,sha256=KTavqBGP56fFVWjI_ycuR6aC1rvag5kMjXd3BAY2E1o,11391
|
| 42 |
+
github/CommitStats.py,sha256=1YwhWBFBGzUE6wE8BVTYhMMIUDAVQaz6SDnc8Z8Ck6A,4012
|
| 43 |
+
github/CommitStatus.py,sha256=u3ZpM_zwyQSPRlagL_nuNDWQ0X9WtU0KZhq2bRTFwL4,6973
|
| 44 |
+
github/Comparison.py,sha256=II8s1H54jBhyEmgVBNHzM3qklbj1LnQHbhh9Ep7HscE,8010
|
| 45 |
+
github/Consts.py,sha256=GJUQi2O4WHJTjNyB42fYj5x2eQFDrm8aQhfB7ahicRQ,10492
|
| 46 |
+
github/ContentFile.py,sha256=8g-Qasve1GdVok9Ym_3sVUaQ2nPjT9FgI4xH75BPliE,12595
|
| 47 |
+
github/Copilot.py,sha256=GoE5Goq3wI9Aeel7KsjodX_Dm4YYRGJla5GZM52J0gI,4532
|
| 48 |
+
github/CopilotSeat.py,sha256=xvhOo-A6sAKkGMhiXWaZAcW4p9qIqE7FeU3BZPVf6BQ,4794
|
| 49 |
+
github/DefaultCodeSecurityConfig.py,sha256=PGrYyWk7WdLK_NqP7mTW6VnPT0H4b8wXKPjzx0rBJik,4741
|
| 50 |
+
github/DependabotAlert.py,sha256=0Hq4RgnanHzOsXOMSTG544g9NTbwG5SNFJBR1B255bo,7711
|
| 51 |
+
github/DependabotAlertAdvisory.py,sha256=7TI5mRyg6PJmZUkLSdHL2Zypq6hKmc8FqeWShA61Cag,3591
|
| 52 |
+
github/DependabotAlertDependency.py,sha256=2Cg3e6G2W6LajWZ-xBArSsRtz4zW5wab-8oKINU0h_Y,3665
|
| 53 |
+
github/DependabotAlertVulnerability.py,sha256=eAt2Fy-REMD8lSzTSxyAtn6Nf9P0NEyTffRXc2sgKr4,3988
|
| 54 |
+
github/Deployment.py,sha256=D1tYEGGmn62nqexHQzda5oVHDhBPihB0Y6o8dN57oBM,13654
|
| 55 |
+
github/DeploymentStatus.py,sha256=qq-qMMBHAtw3hIIkMNiXbaD_7JdPpLvywHwF0lSb1Hs,9370
|
| 56 |
+
github/DiscussionBase.py,sha256=54013rlbYF4_MItFXuH0AYzmuhMW6Z8kpvDVhc5Z1gQ,6784
|
| 57 |
+
github/DiscussionCommentBase.py,sha256=Hnuox_kzfZ9xruwYWzRtqbDnl5Ehndr5TynkcNC2WlA,6835
|
| 58 |
+
github/Download.py,sha256=MKVNGIqD4kZo5fhdF2HUnyweLnPcgs7nWuQSMAbo1Kk,11218
|
| 59 |
+
github/Enterprise.py,sha256=x-qYVrFUFlE6ZR1LeZgg8eFkvrucLwGshJnn3EAJgQo,5327
|
| 60 |
+
github/EnterpriseConsumedLicenses.py,sha256=eDO5MrobiG5bRXGGIYOnIl8hgftq3IO_IooOu4LjmMs,5762
|
| 61 |
+
github/Environment.py,sha256=iynT0fU-E7m8Tt9jqa94QdOezGjchayxw2nfI3Lh1k8,13623
|
| 62 |
+
github/EnvironmentDeploymentBranchPolicy.py,sha256=vZeU3Y4nQJSHJ3Lz_rd5PF_J7rJdvdWkIaXR330q2yY,4090
|
| 63 |
+
github/EnvironmentProtectionRule.py,sha256=91IbsSTP2lW4n8xWd8FAjpJEe26frqqg-6yVputx-Gw,5574
|
| 64 |
+
github/EnvironmentProtectionRuleReviewer.py,sha256=_HLqHz4nSJeaYnk38bbcb-283Hg7jQ1nmTnUDxehQd8,5217
|
| 65 |
+
github/Event.py,sha256=hkNr9ps_wiJCCKsk1-3aBR0uhJBxronm7ebSGyoHTMs,6182
|
| 66 |
+
github/File.py,sha256=aO9qr6VoVWVrcn7ej-mR0mq-nTbJLpFYK1jy94xKTCk,6684
|
| 67 |
+
github/Gist.py,sha256=PPa2kPhY142xQ_hqXtBtKJjR_tu3UXkGKHCKaMIcri8,15230
|
| 68 |
+
github/GistComment.py,sha256=Bh4j2lvIl7lmRtPYuneOAU9C2ENtGUTdL5SjDu6sYZQ,7187
|
| 69 |
+
github/GistFile.py,sha256=RZWinuJBhSQ2V4K2jU9PLVEeMvp6nLWM3b4LxsOAItY,5168
|
| 70 |
+
github/GistHistoryState.py,sha256=B2CY5KWBySuIqJfIWdUfSyHklRPiyuSi2lgdmVuvXIE,10660
|
| 71 |
+
github/GitAuthor.py,sha256=KkvmNGbJLdBb2KaBI9b51YCHwqHVbhlNT-AkK5fW634,4925
|
| 72 |
+
github/GitBlob.py,sha256=qIsFepFXDhtHeV7FePc2K9ArcZTEPqeTrH6DYydoVgA,5133
|
| 73 |
+
github/GitCommit.py,sha256=P50SxhMzC41208FYNgt24q_5OCEjCer5pnv_eowNTIk,9603
|
| 74 |
+
github/GitCommitVerification.py,sha256=mNB8LYL3HAK7H9j7tlnL7bsQNHFzIy-7ocFfVehkvF8,5178
|
| 75 |
+
github/GitObject.py,sha256=gSKCYAv4aMnLYrIyfPbmwxuD1tFzdnmP6aLqul9YpJ4,4263
|
| 76 |
+
github/GitRef.py,sha256=QdXAK6l-hEou-fz73agqmUhWGb2ubXHT8mB_UWeoFbk,5958
|
| 77 |
+
github/GitRelease.py,sha256=mdi6VKI8fMPl3zqVG7fi3ixC8BXlm0BSCUJ3SNqZCuM,19855
|
| 78 |
+
github/GitReleaseAsset.py,sha256=ej3ZvO5deyNABVOO6USbghjywYe5YHXbzRu9wYE2Qy8,9336
|
| 79 |
+
github/GitTag.py,sha256=wOTNQy1WQcsm44SiA2Q4yyB-Y9HbjPO2K7pAR51IxR8,6682
|
| 80 |
+
github/GitTree.py,sha256=iGdDGSup7MfQ3HuS4F5e7W_JWsU8dyz_XqdNqt39fR8,5460
|
| 81 |
+
github/GitTreeElement.py,sha256=XiHf3uk9SBzdK7eqzVvuD8JzWzn2rg3LuS9hZzE-wAo,4975
|
| 82 |
+
github/GithubApp.py,sha256=UaoMUb-6lwxz5Ho-rrixLl7qQLuwXUePICqGmr_ydgY,10064
|
| 83 |
+
github/GithubException.py,sha256=OBCphJtOygdOID_-6pekI13tFoO1xPF_-B6TbvIP-jI,7175
|
| 84 |
+
github/GithubIntegration.py,sha256=iO4OX27rJjr8BsVmwbue1IpJpMZlQJVUEeLu2OsT3S0,15186
|
| 85 |
+
github/GithubObject.py,sha256=2R3MxaWqsBwSWmjN0X0ZYVEGXEyL5_qNsJabflNhj-k,22219
|
| 86 |
+
github/GithubRetry.py,sha256=Rzz4pN-0x5tEZ9Z-q9VrhS04y6rficm9SnSmru6dLH0,12063
|
| 87 |
+
github/GitignoreTemplate.py,sha256=el6be_upqLHbVc9N36K71LT1iR82bGBNftg7j1zpepM,4178
|
| 88 |
+
github/GlobalAdvisory.py,sha256=K98M-E0zPg5MU60GFMJwFtH-fPsgwClQGaNLjNthdic,6500
|
| 89 |
+
github/Hook.py,sha256=GWufDuLdPEtPYE__BumrZN-cITS41mD2c0dibd4SQ0U,10162
|
| 90 |
+
github/HookDelivery.py,sha256=xassYw8R-YdLEzzsmP3l7_nmrd9ED57lI8ybe-Nd58w,9555
|
| 91 |
+
github/HookDescription.py,sha256=uiAtb4k8CDPcNG63aal3zyQTTEer7b3cPqh9hm517Lk,4587
|
| 92 |
+
github/HookResponse.py,sha256=y8lPbm9h9APbhrHQ0nKPBardAqTozQYin5tD0eHuIAo,4250
|
| 93 |
+
github/InputFileContent.py,sha256=U8bCrl2z9KHQhWtxc-fwliIwJDZlcvUbzY8WbhKfIx0,3480
|
| 94 |
+
github/InputGitAuthor.py,sha256=iW7vxND5eUq6Ull_aH7J8iMKfTZ2ejGLtoSoSGGDX2E,3820
|
| 95 |
+
github/InputGitTreeElement.py,sha256=gAq3OTo0NOOmyjqqtNbwk3wagrmaOp1n-XK990tcYss,3933
|
| 96 |
+
github/Installation.py,sha256=tuVqoD2xRT-2tjYfFlFf4MYr2k-NJMKGRmo6_ugfexk,12641
|
| 97 |
+
github/InstallationAuthorization.py,sha256=4_nDj-7Ltcr1Wdc9yqs-sOuMH6BPA5Pcc_cI067bNZI,6901
|
| 98 |
+
github/Invitation.py,sha256=3KGXH57B5VbU1dUk83AqE9GZ3rBeR79QtXN8WD7yY0o,7305
|
| 99 |
+
github/Issue.py,sha256=7T-S9QZ1ifQEIRxRJ0HPRL9nVe5UpuIi3pJhBfOTTvs,30560
|
| 100 |
+
github/IssueComment.py,sha256=bjwtq1QDFxq3q2jOeNt6fF3Ia85xvr0Xc240pmQ6Wps,13141
|
| 101 |
+
github/IssueEvent.py,sha256=GXMUXdk0vT4EFT-l3HJoLt__7iUMB7WiPFb3jb4gJWM,12108
|
| 102 |
+
github/IssuePullRequest.py,sha256=yYYgYohKY46v9Eq9eWNQAV7R8fsHrx8d6vNeUxOgx-Y,4869
|
| 103 |
+
github/Label.py,sha256=Ohli5Ya-vaNtyKnUUoanRs8hcBvcD58pOcyh5nJkgwU,7617
|
| 104 |
+
github/License.py,sha256=ffxavMjTTK1inKTmt96Avrv6d9sB0OUnV4tCaO5udmc,7992
|
| 105 |
+
github/MainClass.py,sha256=mwPg14ViVjmlz9AAFLjlDttgAzC0AYvERVY76PsCpRc,49018
|
| 106 |
+
github/Membership.py,sha256=PoZy0XAAtNPGvU2G1h90zBQzpYUJzvjDEhWJkVunCtA,6575
|
| 107 |
+
github/MergedUpstream.py,sha256=Mxqfh8GV2aVs682hwnWsVala6O6OsuvkvL4PuD3vYLE,3372
|
| 108 |
+
github/Migration.py,sha256=FD4X2ZyjH9zFIGE-wABt_pd7qY09Gs1BdPNSfoTAChc,12145
|
| 109 |
+
github/Milestone.py,sha256=ScAUsM3UwHKmY5uUdI0XB-92fqAT-4xPrFm_uORsjiw,10985
|
| 110 |
+
github/NamedEnterpriseUser.py,sha256=IGLTM3XL7gouxmbTxZDlfMzROiQu_bEJ7bNG-2DJTbs,10543
|
| 111 |
+
github/NamedUser.py,sha256=8fbxtu98KHZ5TlcgaxQilmfidh-VuvGKSvVKBD-25cg,30543
|
| 112 |
+
github/Notification.py,sha256=G3ZlYtbwEVX2K7SGMfaKNbCMXlEaEslIcN_Tohec2e4,8434
|
| 113 |
+
github/NotificationSubject.py,sha256=9Vl6b_j98V4UhGIkHRXYj_v3bAUIH0qSbh-AJs-bJPU,4960
|
| 114 |
+
github/Organization.py,sha256=jUoT6Pt0X6qiwuLKdv4_1O2d1ZlkhmRBAWtrgbh2iDY,95864
|
| 115 |
+
github/OrganizationCustomProperty.py,sha256=7AgcQgTpOkYgv2mAmwvqmHEHFPaEHWLTDNqCWpMeJPc,7941
|
| 116 |
+
github/OrganizationDependabotAlert.py,sha256=rI7b3NS4Dhs4Cz3SZBmZaF95qMGf5k0zsfUc9Rgqz2I,2983
|
| 117 |
+
github/OrganizationSecret.py,sha256=c34t5QiHgYfPEYoj4I4j5HnvK0ALuR_Cj7Y-TGXPFDM,6336
|
| 118 |
+
github/OrganizationVariable.py,sha256=qQcoHpGjdhb0IPD76GjL8Biqvgdeq_2nD3tO2IOu600,6065
|
| 119 |
+
github/PaginatedList.py,sha256=GdBlOD9FxR2wVb8CQwtmG1t9UIy7jcL4i1Qr6gFMUH0,19548
|
| 120 |
+
github/Path.py,sha256=ZmUgLnYu-WPHt54qhA2GjFZxFb1oR--lHKoiGrmgfRQ,4952
|
| 121 |
+
github/Permissions.py,sha256=EqchnebEsf8zhxa1_ONIS2BlWPO-KmjUK6Q9J5W73Zk,5403
|
| 122 |
+
github/Plan.py,sha256=FtYnYjHjsEGVM4gs6aTCSVPIHp6eKY2dtYW88yDC5BQ,5297
|
| 123 |
+
github/Project.py,sha256=9_DwCeIUTU7QMYEuMnQk0g--qUy4puErSA9nQXguiWs,11991
|
| 124 |
+
github/ProjectCard.py,sha256=ICxGVyuzNDaQ6oWe1O65g4eVJqSrZjh6xDGuh0isKsU,10881
|
| 125 |
+
github/ProjectColumn.py,sha256=2dvDWGAT1fbE0Z75WOz9EevmHJVe3AfzOfrDSXgGgmU,10141
|
| 126 |
+
github/PublicKey.py,sha256=gD5-H7eqCvYGW1MDJLIra7wz0HZI5br9GgUVqMtKuI0,6587
|
| 127 |
+
github/PullRequest.py,sha256=yOWWfKFnvQcAJ0zGmecqFSNZU5UPdiPsvFnjIHx_6Kc,51191
|
| 128 |
+
github/PullRequestComment.py,sha256=-y9Y8BspYRe8hgt5pSRFOCuv-HQ4P45XSAlpAIuT25s,17133
|
| 129 |
+
github/PullRequestMergeStatus.py,sha256=o2x4A_YxXAF-1dMiOAZLP8rbfl-6nbsgkVgU5iYc8c0,4503
|
| 130 |
+
github/PullRequestPart.py,sha256=8G3qy1aFvjdWsck7GErNS1heApGSOIHD0KQ_wVjf3vU,5385
|
| 131 |
+
github/PullRequestReview.py,sha256=Zh1spNf3mST9VTfPVvDd052I-ksoGLX1EcBy7cqWOXE,7766
|
| 132 |
+
github/Rate.py,sha256=zppWBE9wwGgg28TtGQZom6caG-kYz7Edw8O6bK4o-jk,5006
|
| 133 |
+
github/RateLimit.py,sha256=aXmT5-B6W4AUQD1K79g012MCmmwITRaUG4VbHW6gIa8,7871
|
| 134 |
+
github/Reaction.py,sha256=YauwrJTcoPhUfP85sgMg0FuLRvJceloqktJ6wfXesu8,5576
|
| 135 |
+
github/Referrer.py,sha256=_ZLDv74O26rySNT8OWCYJRxlPw27V7bw_3bRZlVNMhw,4701
|
| 136 |
+
github/RepoCodeSecurityConfig.py,sha256=TaMX2mYEW-6nsDbxYs-OAhnV2aveHGtiPa09mH-W4tk,4682
|
| 137 |
+
github/Repository.py,sha256=wOlqESLJqZpohEclXLxpHS_-jDWohaVyzPplG58w_i8,204970
|
| 138 |
+
github/RepositoryAdvisory.py,sha256=iu6LtELT85aC7PPxqDsOu1FFPZW_XUIls2Pf_233QFM,18310
|
| 139 |
+
github/RepositoryDiscussion.py,sha256=imjaUm7riDnuNENvUVIB0GotoN2TXsXO3qthMcOOtU8,9163
|
| 140 |
+
github/RepositoryDiscussionCategory.py,sha256=Wu8tplwDQdfzc8kfuKxMKDHPg3LOVLOg9pMFydyHNqQ,6803
|
| 141 |
+
github/RepositoryDiscussionComment.py,sha256=xKCbqPHdN4Qa8MO02Q4hkjvid3qtpeUJgq99VmN_uoY,6581
|
| 142 |
+
github/RepositoryKey.py,sha256=-g6f9PPaMwpTNCeGnfc787gSE7s3WH1jR8Vd-BjaRAA,7508
|
| 143 |
+
github/RepositoryPreferences.py,sha256=WhXwIqydh4PewCjKt7WXGzmVZU3CZodkilLojSSESrM,4555
|
| 144 |
+
github/Requester.py,sha256=0BuG_dB_aol27T-QEIieZrgqHxT8idx9GlTaxiSoRds,52603
|
| 145 |
+
github/RequiredPullRequestReviews.py,sha256=13mLV-Fa2ZQj30v0Zl_5vOoaeZLoAIaMCz2PfN_XmdU,7704
|
| 146 |
+
github/RequiredStatusChecks.py,sha256=QQuY8o1H6cv7HafvpBhu8Mm45hsYoroQtNdhwVHoFO8,4966
|
| 147 |
+
github/Secret.py,sha256=OzFE9tp62nd02_rf1KMKxYpTzxV5DFvJKMRh4IJ5jls,5741
|
| 148 |
+
github/SecurityAndAnalysis.py,sha256=31x-3HrAqWc0dfiZfnwcw-HmQIeXWvMOXFjWTFiXuJ0,8178
|
| 149 |
+
github/SecurityAndAnalysisFeature.py,sha256=CJJ6rR4AzGigv4RfVbPCZKHq6USQAGS6b9BW8uE29XI,4258
|
| 150 |
+
github/SelfHostedActionsRunner.py,sha256=60ucM62i80VDG9migGM0pFWeJ4seDczNGu_Tp4OanjU,5524
|
| 151 |
+
github/SourceImport.py,sha256=Bcpc_MlsYB0VjwwuviLelh_3bSfZGxssnIQKm9PMqC0,11372
|
| 152 |
+
github/Stargazer.py,sha256=7nNVcVeIqjK9SWr1QMG0M_fb6pHijPzm3AMLmGpL5ww,4825
|
| 153 |
+
github/StatsCodeFrequency.py,sha256=R7XWZsjSExFjQvNWc9TZ2bJhBGLxywoU5LfVjKao7V0,4290
|
| 154 |
+
github/StatsCommitActivity.py,sha256=YmuVoKMvfTcYpdqZgfYddZ1fIML_I8786-zXxS0vdU4,4472
|
| 155 |
+
github/StatsContributor.py,sha256=7e46cpqLCfwCojxjzVVA4kBO-76IA50gZP8hbX5C2lw,5805
|
| 156 |
+
github/StatsParticipation.py,sha256=TsW0s5CK2S042aZARHZtmhRntUIEY-BioaCsxa3mQxA,4244
|
| 157 |
+
github/StatsPunchCard.py,sha256=JBAWWEUowsCiMAY98kT7iZRGQ2KUsq4n8b27zI2qQHA,4012
|
| 158 |
+
github/Tag.py,sha256=1vPAqpjcJ9k-Gfl2L6zaiYEGeTE5JlmBvMrN5qw4O1g,5402
|
| 159 |
+
github/Team.py,sha256=LKEFCKmx4oE7MnUV-6tkA0g5AX7lhPhuBb8KDV5I_Jg,23893
|
| 160 |
+
github/TeamDiscussion.py,sha256=BLVmJYU3Er9VQO1005gzPvWqpy82GLULlDFFzriegRQ,6599
|
| 161 |
+
github/TimelineEvent.py,sha256=QYM38PNDtiDeXZuQURHc86OGw6PflmXyUN6W0S4_e_I,7533
|
| 162 |
+
github/TimelineEventSource.py,sha256=tf5x3asgFzz3z3SbOmINxBAA4BMuoSVS-X_ZkkHU4hA,4324
|
| 163 |
+
github/Topic.py,sha256=codJNHBB5imQvI4bVJUXl7QH8NEgsJ_JA2YgtvPupg4,8373
|
| 164 |
+
github/Traffic.py,sha256=RTkvDq-RyFRkveTpi_kwZ3qITFkz249pXh3g1SlENbg,4768
|
| 165 |
+
github/UserKey.py,sha256=gRGKuveCFtZI_ZhId6hHKodS8El3snf9Mk7wMmCLEwM,6272
|
| 166 |
+
github/Variable.py,sha256=3sOXF7nJlia_YEzTBF9nrZd5BPsWo5akXCiAeZE0xvc,5699
|
| 167 |
+
github/View.py,sha256=9cP39jexYuQOhYDHheWCy1qOEVUlDG_0ACF1LJXf8fA,4845
|
| 168 |
+
github/Workflow.py,sha256=YA3a37RAeIjBGmFn1kXicg_ZyV02WnOBGLe56KObIdI,12088
|
| 169 |
+
github/WorkflowJob.py,sha256=FckU3PIOfHK-8RMlkwv9v_xNypGM82aG4-Gjy0TblJI,10652
|
| 170 |
+
github/WorkflowRun.py,sha256=zm4lX7BCoPzpMuAiOB8CSgNzgQ4vFOyXKlSFlTLFL8c,19556
|
| 171 |
+
github/WorkflowStep.py,sha256=vloX2qGP9tzjpeAIcU1B_EoY9TLn-Z15QZQnx8I4LeQ,5643
|
| 172 |
+
github/__init__.py,sha256=62guPyYWH5dj0djnitPdPX-GpGa1xH4bc3WCYncXE60,5401
|
| 173 |
+
github/__pycache__/AccessToken.cpython-310.pyc,,
|
| 174 |
+
github/__pycache__/AdvisoryBase.cpython-310.pyc,,
|
| 175 |
+
github/__pycache__/AdvisoryCredit.cpython-310.pyc,,
|
| 176 |
+
github/__pycache__/AdvisoryCreditDetailed.cpython-310.pyc,,
|
| 177 |
+
github/__pycache__/AdvisoryVulnerability.cpython-310.pyc,,
|
| 178 |
+
github/__pycache__/AdvisoryVulnerabilityPackage.cpython-310.pyc,,
|
| 179 |
+
github/__pycache__/AppAuthentication.cpython-310.pyc,,
|
| 180 |
+
github/__pycache__/ApplicationOAuth.cpython-310.pyc,,
|
| 181 |
+
github/__pycache__/Artifact.cpython-310.pyc,,
|
| 182 |
+
github/__pycache__/Auth.cpython-310.pyc,,
|
| 183 |
+
github/__pycache__/AuthenticatedUser.cpython-310.pyc,,
|
| 184 |
+
github/__pycache__/Authorization.cpython-310.pyc,,
|
| 185 |
+
github/__pycache__/AuthorizationApplication.cpython-310.pyc,,
|
| 186 |
+
github/__pycache__/Autolink.cpython-310.pyc,,
|
| 187 |
+
github/__pycache__/Branch.cpython-310.pyc,,
|
| 188 |
+
github/__pycache__/BranchProtection.cpython-310.pyc,,
|
| 189 |
+
github/__pycache__/CVSS.cpython-310.pyc,,
|
| 190 |
+
github/__pycache__/CWE.cpython-310.pyc,,
|
| 191 |
+
github/__pycache__/CheckRun.cpython-310.pyc,,
|
| 192 |
+
github/__pycache__/CheckRunAnnotation.cpython-310.pyc,,
|
| 193 |
+
github/__pycache__/CheckRunOutput.cpython-310.pyc,,
|
| 194 |
+
github/__pycache__/CheckSuite.cpython-310.pyc,,
|
| 195 |
+
github/__pycache__/Clones.cpython-310.pyc,,
|
| 196 |
+
github/__pycache__/CodeScanAlert.cpython-310.pyc,,
|
| 197 |
+
github/__pycache__/CodeScanAlertInstance.cpython-310.pyc,,
|
| 198 |
+
github/__pycache__/CodeScanAlertInstanceLocation.cpython-310.pyc,,
|
| 199 |
+
github/__pycache__/CodeScanRule.cpython-310.pyc,,
|
| 200 |
+
github/__pycache__/CodeScanTool.cpython-310.pyc,,
|
| 201 |
+
github/__pycache__/CodeSecurityConfig.cpython-310.pyc,,
|
| 202 |
+
github/__pycache__/CodeSecurityConfigRepository.cpython-310.pyc,,
|
| 203 |
+
github/__pycache__/Commit.cpython-310.pyc,,
|
| 204 |
+
github/__pycache__/CommitCombinedStatus.cpython-310.pyc,,
|
| 205 |
+
github/__pycache__/CommitComment.cpython-310.pyc,,
|
| 206 |
+
github/__pycache__/CommitStats.cpython-310.pyc,,
|
| 207 |
+
github/__pycache__/CommitStatus.cpython-310.pyc,,
|
| 208 |
+
github/__pycache__/Comparison.cpython-310.pyc,,
|
| 209 |
+
github/__pycache__/Consts.cpython-310.pyc,,
|
| 210 |
+
github/__pycache__/ContentFile.cpython-310.pyc,,
|
| 211 |
+
github/__pycache__/Copilot.cpython-310.pyc,,
|
| 212 |
+
github/__pycache__/CopilotSeat.cpython-310.pyc,,
|
| 213 |
+
github/__pycache__/DefaultCodeSecurityConfig.cpython-310.pyc,,
|
| 214 |
+
github/__pycache__/DependabotAlert.cpython-310.pyc,,
|
| 215 |
+
github/__pycache__/DependabotAlertAdvisory.cpython-310.pyc,,
|
| 216 |
+
github/__pycache__/DependabotAlertDependency.cpython-310.pyc,,
|
| 217 |
+
github/__pycache__/DependabotAlertVulnerability.cpython-310.pyc,,
|
| 218 |
+
github/__pycache__/Deployment.cpython-310.pyc,,
|
| 219 |
+
github/__pycache__/DeploymentStatus.cpython-310.pyc,,
|
| 220 |
+
github/__pycache__/DiscussionBase.cpython-310.pyc,,
|
| 221 |
+
github/__pycache__/DiscussionCommentBase.cpython-310.pyc,,
|
| 222 |
+
github/__pycache__/Download.cpython-310.pyc,,
|
| 223 |
+
github/__pycache__/Enterprise.cpython-310.pyc,,
|
| 224 |
+
github/__pycache__/EnterpriseConsumedLicenses.cpython-310.pyc,,
|
| 225 |
+
github/__pycache__/Environment.cpython-310.pyc,,
|
| 226 |
+
github/__pycache__/EnvironmentDeploymentBranchPolicy.cpython-310.pyc,,
|
| 227 |
+
github/__pycache__/EnvironmentProtectionRule.cpython-310.pyc,,
|
| 228 |
+
github/__pycache__/EnvironmentProtectionRuleReviewer.cpython-310.pyc,,
|
| 229 |
+
github/__pycache__/Event.cpython-310.pyc,,
|
| 230 |
+
github/__pycache__/File.cpython-310.pyc,,
|
| 231 |
+
github/__pycache__/Gist.cpython-310.pyc,,
|
| 232 |
+
github/__pycache__/GistComment.cpython-310.pyc,,
|
| 233 |
+
github/__pycache__/GistFile.cpython-310.pyc,,
|
| 234 |
+
github/__pycache__/GistHistoryState.cpython-310.pyc,,
|
| 235 |
+
github/__pycache__/GitAuthor.cpython-310.pyc,,
|
| 236 |
+
github/__pycache__/GitBlob.cpython-310.pyc,,
|
| 237 |
+
github/__pycache__/GitCommit.cpython-310.pyc,,
|
| 238 |
+
github/__pycache__/GitCommitVerification.cpython-310.pyc,,
|
| 239 |
+
github/__pycache__/GitObject.cpython-310.pyc,,
|
| 240 |
+
github/__pycache__/GitRef.cpython-310.pyc,,
|
| 241 |
+
github/__pycache__/GitRelease.cpython-310.pyc,,
|
| 242 |
+
github/__pycache__/GitReleaseAsset.cpython-310.pyc,,
|
| 243 |
+
github/__pycache__/GitTag.cpython-310.pyc,,
|
| 244 |
+
github/__pycache__/GitTree.cpython-310.pyc,,
|
| 245 |
+
github/__pycache__/GitTreeElement.cpython-310.pyc,,
|
| 246 |
+
github/__pycache__/GithubApp.cpython-310.pyc,,
|
| 247 |
+
github/__pycache__/GithubException.cpython-310.pyc,,
|
| 248 |
+
github/__pycache__/GithubIntegration.cpython-310.pyc,,
|
| 249 |
+
github/__pycache__/GithubObject.cpython-310.pyc,,
|
| 250 |
+
github/__pycache__/GithubRetry.cpython-310.pyc,,
|
| 251 |
+
github/__pycache__/GitignoreTemplate.cpython-310.pyc,,
|
| 252 |
+
github/__pycache__/GlobalAdvisory.cpython-310.pyc,,
|
| 253 |
+
github/__pycache__/Hook.cpython-310.pyc,,
|
| 254 |
+
github/__pycache__/HookDelivery.cpython-310.pyc,,
|
| 255 |
+
github/__pycache__/HookDescription.cpython-310.pyc,,
|
| 256 |
+
github/__pycache__/HookResponse.cpython-310.pyc,,
|
| 257 |
+
github/__pycache__/InputFileContent.cpython-310.pyc,,
|
| 258 |
+
github/__pycache__/InputGitAuthor.cpython-310.pyc,,
|
| 259 |
+
github/__pycache__/InputGitTreeElement.cpython-310.pyc,,
|
| 260 |
+
github/__pycache__/Installation.cpython-310.pyc,,
|
| 261 |
+
github/__pycache__/InstallationAuthorization.cpython-310.pyc,,
|
| 262 |
+
github/__pycache__/Invitation.cpython-310.pyc,,
|
| 263 |
+
github/__pycache__/Issue.cpython-310.pyc,,
|
| 264 |
+
github/__pycache__/IssueComment.cpython-310.pyc,,
|
| 265 |
+
github/__pycache__/IssueEvent.cpython-310.pyc,,
|
| 266 |
+
github/__pycache__/IssuePullRequest.cpython-310.pyc,,
|
| 267 |
+
github/__pycache__/Label.cpython-310.pyc,,
|
| 268 |
+
github/__pycache__/License.cpython-310.pyc,,
|
| 269 |
+
github/__pycache__/MainClass.cpython-310.pyc,,
|
| 270 |
+
github/__pycache__/Membership.cpython-310.pyc,,
|
| 271 |
+
github/__pycache__/MergedUpstream.cpython-310.pyc,,
|
| 272 |
+
github/__pycache__/Migration.cpython-310.pyc,,
|
| 273 |
+
github/__pycache__/Milestone.cpython-310.pyc,,
|
| 274 |
+
github/__pycache__/NamedEnterpriseUser.cpython-310.pyc,,
|
| 275 |
+
github/__pycache__/NamedUser.cpython-310.pyc,,
|
| 276 |
+
github/__pycache__/Notification.cpython-310.pyc,,
|
| 277 |
+
github/__pycache__/NotificationSubject.cpython-310.pyc,,
|
| 278 |
+
github/__pycache__/Organization.cpython-310.pyc,,
|
| 279 |
+
github/__pycache__/OrganizationCustomProperty.cpython-310.pyc,,
|
| 280 |
+
github/__pycache__/OrganizationDependabotAlert.cpython-310.pyc,,
|
| 281 |
+
github/__pycache__/OrganizationSecret.cpython-310.pyc,,
|
| 282 |
+
github/__pycache__/OrganizationVariable.cpython-310.pyc,,
|
| 283 |
+
github/__pycache__/PaginatedList.cpython-310.pyc,,
|
| 284 |
+
github/__pycache__/Path.cpython-310.pyc,,
|
| 285 |
+
github/__pycache__/Permissions.cpython-310.pyc,,
|
| 286 |
+
github/__pycache__/Plan.cpython-310.pyc,,
|
| 287 |
+
github/__pycache__/Project.cpython-310.pyc,,
|
| 288 |
+
github/__pycache__/ProjectCard.cpython-310.pyc,,
|
| 289 |
+
github/__pycache__/ProjectColumn.cpython-310.pyc,,
|
| 290 |
+
github/__pycache__/PublicKey.cpython-310.pyc,,
|
| 291 |
+
github/__pycache__/PullRequest.cpython-310.pyc,,
|
| 292 |
+
github/__pycache__/PullRequestComment.cpython-310.pyc,,
|
| 293 |
+
github/__pycache__/PullRequestMergeStatus.cpython-310.pyc,,
|
| 294 |
+
github/__pycache__/PullRequestPart.cpython-310.pyc,,
|
| 295 |
+
github/__pycache__/PullRequestReview.cpython-310.pyc,,
|
| 296 |
+
github/__pycache__/Rate.cpython-310.pyc,,
|
| 297 |
+
github/__pycache__/RateLimit.cpython-310.pyc,,
|
| 298 |
+
github/__pycache__/Reaction.cpython-310.pyc,,
|
| 299 |
+
github/__pycache__/Referrer.cpython-310.pyc,,
|
| 300 |
+
github/__pycache__/RepoCodeSecurityConfig.cpython-310.pyc,,
|
| 301 |
+
github/__pycache__/Repository.cpython-310.pyc,,
|
| 302 |
+
github/__pycache__/RepositoryAdvisory.cpython-310.pyc,,
|
| 303 |
+
github/__pycache__/RepositoryDiscussion.cpython-310.pyc,,
|
| 304 |
+
github/__pycache__/RepositoryDiscussionCategory.cpython-310.pyc,,
|
| 305 |
+
github/__pycache__/RepositoryDiscussionComment.cpython-310.pyc,,
|
| 306 |
+
github/__pycache__/RepositoryKey.cpython-310.pyc,,
|
| 307 |
+
github/__pycache__/RepositoryPreferences.cpython-310.pyc,,
|
| 308 |
+
github/__pycache__/Requester.cpython-310.pyc,,
|
| 309 |
+
github/__pycache__/RequiredPullRequestReviews.cpython-310.pyc,,
|
| 310 |
+
github/__pycache__/RequiredStatusChecks.cpython-310.pyc,,
|
| 311 |
+
github/__pycache__/Secret.cpython-310.pyc,,
|
| 312 |
+
github/__pycache__/SecurityAndAnalysis.cpython-310.pyc,,
|
| 313 |
+
github/__pycache__/SecurityAndAnalysisFeature.cpython-310.pyc,,
|
| 314 |
+
github/__pycache__/SelfHostedActionsRunner.cpython-310.pyc,,
|
| 315 |
+
github/__pycache__/SourceImport.cpython-310.pyc,,
|
| 316 |
+
github/__pycache__/Stargazer.cpython-310.pyc,,
|
| 317 |
+
github/__pycache__/StatsCodeFrequency.cpython-310.pyc,,
|
| 318 |
+
github/__pycache__/StatsCommitActivity.cpython-310.pyc,,
|
| 319 |
+
github/__pycache__/StatsContributor.cpython-310.pyc,,
|
| 320 |
+
github/__pycache__/StatsParticipation.cpython-310.pyc,,
|
| 321 |
+
github/__pycache__/StatsPunchCard.cpython-310.pyc,,
|
| 322 |
+
github/__pycache__/Tag.cpython-310.pyc,,
|
| 323 |
+
github/__pycache__/Team.cpython-310.pyc,,
|
| 324 |
+
github/__pycache__/TeamDiscussion.cpython-310.pyc,,
|
| 325 |
+
github/__pycache__/TimelineEvent.cpython-310.pyc,,
|
| 326 |
+
github/__pycache__/TimelineEventSource.cpython-310.pyc,,
|
| 327 |
+
github/__pycache__/Topic.cpython-310.pyc,,
|
| 328 |
+
github/__pycache__/Traffic.cpython-310.pyc,,
|
| 329 |
+
github/__pycache__/UserKey.cpython-310.pyc,,
|
| 330 |
+
github/__pycache__/Variable.cpython-310.pyc,,
|
| 331 |
+
github/__pycache__/View.cpython-310.pyc,,
|
| 332 |
+
github/__pycache__/Workflow.cpython-310.pyc,,
|
| 333 |
+
github/__pycache__/WorkflowJob.cpython-310.pyc,,
|
| 334 |
+
github/__pycache__/WorkflowRun.cpython-310.pyc,,
|
| 335 |
+
github/__pycache__/WorkflowStep.cpython-310.pyc,,
|
| 336 |
+
github/__pycache__/__init__.cpython-310.pyc,,
|
| 337 |
+
github/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/REQUESTED
ADDED
|
File without changes
|
venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: setuptools (75.8.0)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
| 5 |
+
|
venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
github
|
venv/lib/python3.10/site-packages/_soundfile.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# auto-generated file
|
| 2 |
+
import _cffi_backend
|
| 3 |
+
|
| 4 |
+
ffi = _cffi_backend.FFI('_soundfile',
|
| 5 |
+
_version = 0x2601,
|
| 6 |
+
_types = b'\x00\x00\x12\x0D\x00\x00\x68\x03\x00\x00\x07\x01\x00\x00\x67\x03\x00\x00\x75\x03\x00\x00\x00\x0F\x00\x00\x12\x0D\x00\x00\x6A\x03\x00\x00\x07\x01\x00\x00\x03\x11\x00\x00\x00\x0F\x00\x00\x12\x0D\x00\x00\x07\x01\x00\x00\x07\x01\x00\x00\x03\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x07\x0D\x00\x00\x69\x03\x00\x00\x00\x0F\x00\x00\x07\x0D\x00\x00\x12\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x07\x0D\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x07\x0D\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x67\x03\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x12\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x12\x11\x00\x00\x6A\x03\x00\x00\x1C\x01\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x12\x11\x00\x00\x07\x01\x00\x00\x07\x11\x00\x00\x00\x0F\x00\x00\x02\x0D\x00\x00\x12\x11\x00\x00\x07\x01\x00\x00\x04\x11\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x12\x11\x00\x00\x6B\x03\x00\x00\x17\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x12\x11\x00\x00\x6F\x03\x00\x00\x17\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x12\x11\x00\x00\x02\x03\x00\x00\x17\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x12\x11\x00\x00\x17\x01\x00\x00\x07\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x12\x11\x00\x00\x74\x03\x00\x00\x17\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x12\x11\x00\x00\x04\x11\x00\x00\x17\x01\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x17\x01\x00\x00\x07\x01\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x04\x11\x00\x00\x17\x01\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x36\x0D\x00\x00\x75\x03\x00\x00\x17\x01\x00\x00\x04\x11\x00\x00\x00\x0F\x00\x00\x75\x0D\x00\x00\x12\x11\x00\x00\x00\x0F\x00\x00\x00\x09\x00\x00\x01\x09\x00\x00\x02\x09\x00\x00\x03\x09\x00\x00\x02\x01\x00\x00\x0E\x01\x00\x00\x00\x0B\x00\x00\x01\x0B\x00\x00\x02\x0B\x00\x00\x0D\x01\x00\x00\x51\x03\x00\x00\x56\x03\x00\x00\x59\x03\x00\x00\x5E\x03\x00\x00\x05\x01\x00\x00\x00\x01',
|
| 7 |
+
_globals = (b'\xFF\xFF\xFF\x0BSFC_FILE_TRUNCATE',4224,b'\xFF\xFF\xFF\x0BSFC_GET_FORMAT_INFO',4136,b'\xFF\xFF\xFF\x0BSFC_GET_FORMAT_MAJOR',4145,b'\xFF\xFF\xFF\x0BSFC_GET_FORMAT_MAJOR_COUNT',4144,b'\xFF\xFF\xFF\x0BSFC_GET_FORMAT_SUBTYPE',4147,b'\xFF\xFF\xFF\x0BSFC_GET_FORMAT_SUBTYPE_COUNT',4146,b'\xFF\xFF\xFF\x0BSFC_GET_LIB_VERSION',4096,b'\xFF\xFF\xFF\x0BSFC_GET_LOG_INFO',4097,b'\xFF\xFF\xFF\x0BSFC_SET_BITRATE_MODE',4869,b'\xFF\xFF\xFF\x0BSFC_SET_CLIPPING',4288,b'\xFF\xFF\xFF\x0BSFC_SET_COMPRESSION_LEVEL',4865,b'\xFF\xFF\xFF\x0BSFC_SET_SCALE_FLOAT_INT_READ',4116,b'\xFF\xFF\xFF\x0BSFC_SET_SCALE_INT_FLOAT_WRITE',4117,b'\xFF\xFF\xFF\x0BSFM_RDWR',48,b'\xFF\xFF\xFF\x0BSFM_READ',16,b'\xFF\xFF\xFF\x0BSFM_WRITE',32,b'\xFF\xFF\xFF\x0BSF_BITRATE_MODE_AVERAGE',1,b'\xFF\xFF\xFF\x0BSF_BITRATE_MODE_CONSTANT',0,b'\xFF\xFF\xFF\x0BSF_BITRATE_MODE_VARIABLE',2,b'\xFF\xFF\xFF\x0BSF_FALSE',0,b'\xFF\xFF\xFF\x0BSF_FORMAT_ENDMASK',805306368,b'\xFF\xFF\xFF\x0BSF_FORMAT_SUBMASK',65535,b'\xFF\xFF\xFF\x0BSF_FORMAT_TYPEMASK',268369920,b'\xFF\xFF\xFF\x0BSF_TRUE',1,b'\x00\x00\x20\x23sf_close',0,b'\x00\x00\x2D\x23sf_command',0,b'\x00\x00\x20\x23sf_error',0,b'\x00\x00\x18\x23sf_error_number',0,b'\x00\x00\x23\x23sf_error_str',0,b'\x00\x00\x1D\x23sf_format_check',0,b'\x00\x00\x14\x23sf_get_string',0,b'\x00\x00\x06\x23sf_open',0,b'\x00\x00\x0B\x23sf_open_fd',0,b'\x00\x00\x00\x23sf_open_virtual',0,b'\x00\x00\x20\x23sf_perror',0,b'\x00\x00\x33\x23sf_read_double',0,b'\x00\x00\x38\x23sf_read_float',0,b'\x00\x00\x3D\x23sf_read_int',0,b'\x00\x00\x4C\x23sf_read_raw',0,b'\x00\x00\x47\x23sf_read_short',0,b'\x00\x00\x4C\x23sf_readf_double',0,b'\x00\x00\x4C\x23sf_readf_float',0,b'\x00\x00\x4C\x23sf_readf_int',0,b'\x00\x00\x4C\x23sf_readf_short',0,b'\x00\x00\x42\x23sf_seek',0,b'\x00\x00\x28\x23sf_set_string',0,b'\x00\x00\x11\x23sf_strerror',0,b'\x00\x00\x1B\x23sf_version_string',0,b'\x00\x00\x33\x23sf_write_double',0,b'\x00\x00\x38\x23sf_write_float',0,b'\x00\x00\x3D\x23sf_write_int',0,b'\x00\x00\x4C\x23sf_write_raw',0,b'\x00\x00\x47\x23sf_write_short',0,b'\x00\x00\x63\x23sf_write_sync',0,b'\x00\x00\x4C\x23sf_writef_double',0,b'\x00\x00\x4C\x23sf_writef_float',0,b'\x00\x00\x4C\x23sf_writef_int',0,b'\x00\x00\x4C\x23sf_writef_short',0),
|
| 8 |
+
_struct_unions = ((b'\x00\x00\x00\x66\x00\x00\x00\x02SF_FORMAT_INFO',b'\x00\x00\x02\x11format',b'\x00\x00\x07\x11name',b'\x00\x00\x07\x11extension'),(b'\x00\x00\x00\x67\x00\x00\x00\x02SF_INFO',b'\x00\x00\x36\x11frames',b'\x00\x00\x02\x11samplerate',b'\x00\x00\x02\x11channels',b'\x00\x00\x02\x11format',b'\x00\x00\x02\x11sections',b'\x00\x00\x02\x11seekable'),(b'\x00\x00\x00\x68\x00\x00\x00\x02SF_VIRTUAL_IO',b'\x00\x00\x71\x11get_filelen',b'\x00\x00\x70\x11seek',b'\x00\x00\x72\x11read',b'\x00\x00\x73\x11write',b'\x00\x00\x71\x11tell'),(b'\x00\x00\x00\x69\x00\x00\x00\x10SNDFILE_tag',)),
|
| 9 |
+
_enums = (b'\x00\x00\x00\x6C\x00\x00\x00\x16$1\x00SF_FORMAT_SUBMASK,SF_FORMAT_TYPEMASK,SF_FORMAT_ENDMASK',b'\x00\x00\x00\x6D\x00\x00\x00\x16$2\x00SFC_GET_LIB_VERSION,SFC_GET_LOG_INFO,SFC_GET_FORMAT_INFO,SFC_GET_FORMAT_MAJOR_COUNT,SFC_GET_FORMAT_MAJOR,SFC_GET_FORMAT_SUBTYPE_COUNT,SFC_GET_FORMAT_SUBTYPE,SFC_FILE_TRUNCATE,SFC_SET_CLIPPING,SFC_SET_SCALE_FLOAT_INT_READ,SFC_SET_SCALE_INT_FLOAT_WRITE,SFC_SET_COMPRESSION_LEVEL,SFC_SET_BITRATE_MODE',b'\x00\x00\x00\x6E\x00\x00\x00\x16$3\x00SF_FALSE,SF_TRUE,SFM_READ,SFM_WRITE,SFM_RDWR,SF_BITRATE_MODE_CONSTANT,SF_BITRATE_MODE_AVERAGE,SF_BITRATE_MODE_VARIABLE'),
|
| 10 |
+
_typenames = (b'\x00\x00\x00\x66SF_FORMAT_INFO',b'\x00\x00\x00\x67SF_INFO',b'\x00\x00\x00\x68SF_VIRTUAL_IO',b'\x00\x00\x00\x69SNDFILE',b'\x00\x00\x00\x36sf_count_t',b'\x00\x00\x00\x71sf_vio_get_filelen',b'\x00\x00\x00\x72sf_vio_read',b'\x00\x00\x00\x70sf_vio_seek',b'\x00\x00\x00\x71sf_vio_tell',b'\x00\x00\x00\x73sf_vio_write'),
|
| 11 |
+
)
|
venv/lib/python3.10/site-packages/cython.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
|
| 3 |
+
#
|
| 4 |
+
# Cython -- Main Program, generic
|
| 5 |
+
#
|
| 6 |
+
|
| 7 |
+
try:
|
| 8 |
+
from typing import TYPE_CHECKING
|
| 9 |
+
except ImportError:
|
| 10 |
+
TYPE_CHECKING = False
|
| 11 |
+
|
| 12 |
+
if not TYPE_CHECKING and __name__ == '__main__':
|
| 13 |
+
|
| 14 |
+
import os
|
| 15 |
+
import sys
|
| 16 |
+
|
| 17 |
+
# Make sure we import the right Cython
|
| 18 |
+
cythonpath, _ = os.path.split(os.path.realpath(__file__))
|
| 19 |
+
sys.path.insert(0, cythonpath)
|
| 20 |
+
|
| 21 |
+
from Cython.Compiler.Main import main
|
| 22 |
+
main(command_line = 1)
|
| 23 |
+
|
| 24 |
+
else:
|
| 25 |
+
# Void cython.* directives.
|
| 26 |
+
from Cython.Shadow import *
|
| 27 |
+
## and bring in the __version__
|
| 28 |
+
from Cython import __version__
|
| 29 |
+
from Cython import load_ipython_extension
|
venv/lib/python3.10/site-packages/github/WorkflowStep.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
############################ Copyrights and license ############################
|
| 2 |
+
# #
|
| 3 |
+
# Copyright 2012 Vincent Jacques <vincent@vincent-jacques.net> #
|
| 4 |
+
# Copyright 2012 Zearin <zearin@gonk.net> #
|
| 5 |
+
# Copyright 2013 AKFish <akfish@gmail.com> #
|
| 6 |
+
# Copyright 2013 Vincent Jacques <vincent@vincent-jacques.net> #
|
| 7 |
+
# Copyright 2014 Vincent Jacques <vincent@vincent-jacques.net> #
|
| 8 |
+
# Copyright 2016 Jannis Gebauer <ja.geb@me.com> #
|
| 9 |
+
# Copyright 2016 Peter Buckley <dx-pbuckley@users.noreply.github.com> #
|
| 10 |
+
# Copyright 2018 Wan Liuyang <tsfdye@gmail.com> #
|
| 11 |
+
# Copyright 2018 sfdye <tsfdye@gmail.com> #
|
| 12 |
+
# Copyright 2019 Steve Kowalik <steven@wedontsleep.org> #
|
| 13 |
+
# Copyright 2019 Wan Liuyang <tsfdye@gmail.com> #
|
| 14 |
+
# Copyright 2020 Steve Kowalik <steven@wedontsleep.org> #
|
| 15 |
+
# Copyright 2020 Victor Zeng <zacker150@users.noreply.github.com> #
|
| 16 |
+
# Copyright 2023 Enrico Minack <github@enrico.minack.dev> #
|
| 17 |
+
# Copyright 2023 Jeppe Fihl-Pearson <tenzer@tenzer.dk> #
|
| 18 |
+
# Copyright 2023 Jirka Borovec <6035284+Borda@users.noreply.github.com> #
|
| 19 |
+
# Copyright 2023 Trim21 <trim21.me@gmail.com> #
|
| 20 |
+
# Copyright 2024 Enrico Minack <github@enrico.minack.dev> #
|
| 21 |
+
# Copyright 2024 Jirka Borovec <6035284+Borda@users.noreply.github.com> #
|
| 22 |
+
# #
|
| 23 |
+
# This file is part of PyGithub. #
|
| 24 |
+
# http://pygithub.readthedocs.io/ #
|
| 25 |
+
# #
|
| 26 |
+
# PyGithub is free software: you can redistribute it and/or modify it under #
|
| 27 |
+
# the terms of the GNU Lesser General Public License as published by the Free #
|
| 28 |
+
# Software Foundation, either version 3 of the License, or (at your option) #
|
| 29 |
+
# any later version. #
|
| 30 |
+
# #
|
| 31 |
+
# PyGithub is distributed in the hope that it will be useful, but WITHOUT ANY #
|
| 32 |
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS #
|
| 33 |
+
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more #
|
| 34 |
+
# details. #
|
| 35 |
+
# #
|
| 36 |
+
# You should have received a copy of the GNU Lesser General Public License #
|
| 37 |
+
# along with PyGithub. If not, see <http://www.gnu.org/licenses/>. #
|
| 38 |
+
# #
|
| 39 |
+
################################################################################
|
| 40 |
+
|
| 41 |
+
from datetime import datetime
|
| 42 |
+
from typing import Any, Dict
|
| 43 |
+
|
| 44 |
+
from github.GithubObject import Attribute, CompletableGithubObject, NotSet
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class WorkflowStep(CompletableGithubObject):
|
| 48 |
+
"""
|
| 49 |
+
This class represents steps in a Workflow Job.
|
| 50 |
+
|
| 51 |
+
The reference can be found here
|
| 52 |
+
https://docs.github.com/en/rest/reference/actions#workflow-jobs
|
| 53 |
+
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
def _initAttributes(self) -> None:
|
| 57 |
+
self._completed_at: Attribute[datetime] = NotSet
|
| 58 |
+
self._conclusion: Attribute[str] = NotSet
|
| 59 |
+
self._name: Attribute[str] = NotSet
|
| 60 |
+
self._number: Attribute[int] = NotSet
|
| 61 |
+
self._started_at: Attribute[datetime] = NotSet
|
| 62 |
+
self._status: Attribute[str] = NotSet
|
| 63 |
+
|
| 64 |
+
def __repr__(self) -> str:
|
| 65 |
+
return self.get__repr__({"number": self._number.value, "name": self._name.value})
|
| 66 |
+
|
| 67 |
+
@property
|
| 68 |
+
def completed_at(self) -> datetime:
|
| 69 |
+
self._completeIfNotSet(self._completed_at)
|
| 70 |
+
return self._completed_at.value
|
| 71 |
+
|
| 72 |
+
@property
|
| 73 |
+
def conclusion(self) -> str:
|
| 74 |
+
self._completeIfNotSet(self._conclusion)
|
| 75 |
+
return self._conclusion.value
|
| 76 |
+
|
| 77 |
+
@property
|
| 78 |
+
def name(self) -> str:
|
| 79 |
+
self._completeIfNotSet(self._name)
|
| 80 |
+
return self._name.value
|
| 81 |
+
|
| 82 |
+
@property
|
| 83 |
+
def number(self) -> int:
|
| 84 |
+
self._completeIfNotSet(self._number)
|
| 85 |
+
return self._number.value
|
| 86 |
+
|
| 87 |
+
@property
|
| 88 |
+
def started_at(self) -> datetime:
|
| 89 |
+
self._completeIfNotSet(self._started_at)
|
| 90 |
+
return self._started_at.value
|
| 91 |
+
|
| 92 |
+
@property
|
| 93 |
+
def status(self) -> str:
|
| 94 |
+
self._completeIfNotSet(self._status)
|
| 95 |
+
return self._status.value
|
| 96 |
+
|
| 97 |
+
def _useAttributes(self, attributes: Dict[str, Any]) -> None:
|
| 98 |
+
if "completed_at" in attributes: # pragma no branch
|
| 99 |
+
self._completed_at = self._makeDatetimeAttribute(attributes["completed_at"])
|
| 100 |
+
if "conclusion" in attributes: # pragma no branch
|
| 101 |
+
self._conclusion = self._makeStringAttribute(attributes["conclusion"])
|
| 102 |
+
if "name" in attributes: # pragma no branch
|
| 103 |
+
self._name = self._makeStringAttribute(attributes["name"])
|
| 104 |
+
if "number" in attributes: # pragma no branch
|
| 105 |
+
self._number = self._makeIntAttribute(attributes["number"])
|
| 106 |
+
if "started_at" in attributes: # pragma no branch
|
| 107 |
+
self._started_at = self._makeDatetimeAttribute(attributes["started_at"])
|
| 108 |
+
if "status" in attributes: # pragma no branch
|
| 109 |
+
self._status = self._makeStringAttribute(attributes["status"])
|
venv/lib/python3.10/site-packages/isympy.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Python shell for SymPy.
|
| 3 |
+
|
| 4 |
+
This is just a normal Python shell (IPython shell if you have the
|
| 5 |
+
IPython package installed), that executes the following commands for
|
| 6 |
+
the user:
|
| 7 |
+
|
| 8 |
+
>>> from __future__ import division
|
| 9 |
+
>>> from sympy import *
|
| 10 |
+
>>> x, y, z, t = symbols('x y z t')
|
| 11 |
+
>>> k, m, n = symbols('k m n', integer=True)
|
| 12 |
+
>>> f, g, h = symbols('f g h', cls=Function)
|
| 13 |
+
>>> init_printing()
|
| 14 |
+
|
| 15 |
+
So starting 'isympy' is equivalent to starting Python (or IPython) and
|
| 16 |
+
executing the above commands by hand. It is intended for easy and quick
|
| 17 |
+
experimentation with SymPy. isympy is a good way to use SymPy as an
|
| 18 |
+
interactive calculator. If you have IPython and Matplotlib installed, then
|
| 19 |
+
interactive plotting is enabled by default.
|
| 20 |
+
|
| 21 |
+
COMMAND LINE OPTIONS
|
| 22 |
+
--------------------
|
| 23 |
+
|
| 24 |
+
-c CONSOLE, --console=CONSOLE
|
| 25 |
+
|
| 26 |
+
Use the specified shell (Python or IPython) shell as the console
|
| 27 |
+
backend instead of the default one (IPython if present, Python
|
| 28 |
+
otherwise), e.g.:
|
| 29 |
+
|
| 30 |
+
$isympy -c python
|
| 31 |
+
|
| 32 |
+
CONSOLE must be one of 'ipython' or 'python'
|
| 33 |
+
|
| 34 |
+
-p PRETTY, --pretty PRETTY
|
| 35 |
+
|
| 36 |
+
Setup pretty-printing in SymPy. When pretty-printing is enabled,
|
| 37 |
+
expressions can be printed with Unicode or ASCII. The default is
|
| 38 |
+
to use pretty-printing (with Unicode if the terminal supports it).
|
| 39 |
+
When this option is 'no', expressions will not be pretty-printed
|
| 40 |
+
and ASCII will be used:
|
| 41 |
+
|
| 42 |
+
$isympy -p no
|
| 43 |
+
|
| 44 |
+
PRETTY must be one of 'unicode', 'ascii', or 'no'
|
| 45 |
+
|
| 46 |
+
-t TYPES, --types=TYPES
|
| 47 |
+
|
| 48 |
+
Setup the ground types for the polys. By default, gmpy ground types
|
| 49 |
+
are used if gmpy2 or gmpy is installed, otherwise it falls back to python
|
| 50 |
+
ground types, which are a little bit slower. You can manually
|
| 51 |
+
choose python ground types even if gmpy is installed (e.g., for
|
| 52 |
+
testing purposes):
|
| 53 |
+
|
| 54 |
+
$isympy -t python
|
| 55 |
+
|
| 56 |
+
TYPES must be one of 'gmpy', 'gmpy1' or 'python'
|
| 57 |
+
|
| 58 |
+
Note that the ground type gmpy1 is primarily intended for testing; it
|
| 59 |
+
forces the use of gmpy version 1 even if gmpy2 is available.
|
| 60 |
+
|
| 61 |
+
This is the same as setting the environment variable
|
| 62 |
+
SYMPY_GROUND_TYPES to the given ground type (e.g.,
|
| 63 |
+
SYMPY_GROUND_TYPES='gmpy')
|
| 64 |
+
|
| 65 |
+
The ground types can be determined interactively from the variable
|
| 66 |
+
sympy.polys.domains.GROUND_TYPES.
|
| 67 |
+
|
| 68 |
+
-o ORDER, --order ORDER
|
| 69 |
+
|
| 70 |
+
Setup the ordering of terms for printing. The default is lex, which
|
| 71 |
+
orders terms lexicographically (e.g., x**2 + x + 1). You can choose
|
| 72 |
+
other orderings, such as rev-lex, which will use reverse
|
| 73 |
+
lexicographic ordering (e.g., 1 + x + x**2):
|
| 74 |
+
|
| 75 |
+
$isympy -o rev-lex
|
| 76 |
+
|
| 77 |
+
ORDER must be one of 'lex', 'rev-lex', 'grlex', 'rev-grlex',
|
| 78 |
+
'grevlex', 'rev-grevlex', 'old', or 'none'.
|
| 79 |
+
|
| 80 |
+
Note that for very large expressions, ORDER='none' may speed up
|
| 81 |
+
printing considerably but the terms will have no canonical order.
|
| 82 |
+
|
| 83 |
+
-q, --quiet
|
| 84 |
+
|
| 85 |
+
Print only Python's and SymPy's versions to stdout at startup.
|
| 86 |
+
|
| 87 |
+
-d, --doctest
|
| 88 |
+
|
| 89 |
+
Use the same format that should be used for doctests. This is
|
| 90 |
+
equivalent to -c python -p no.
|
| 91 |
+
|
| 92 |
+
-C, --no-cache
|
| 93 |
+
|
| 94 |
+
Disable the caching mechanism. Disabling the cache may slow certain
|
| 95 |
+
operations down considerably. This is useful for testing the cache,
|
| 96 |
+
or for benchmarking, as the cache can result in deceptive timings.
|
| 97 |
+
|
| 98 |
+
This is equivalent to setting the environment variable
|
| 99 |
+
SYMPY_USE_CACHE to 'no'.
|
| 100 |
+
|
| 101 |
+
-a, --auto-symbols (requires at least IPython 0.11)
|
| 102 |
+
|
| 103 |
+
Automatically create missing symbols. Normally, typing a name of a
|
| 104 |
+
Symbol that has not been instantiated first would raise NameError,
|
| 105 |
+
but with this option enabled, any undefined name will be
|
| 106 |
+
automatically created as a Symbol.
|
| 107 |
+
|
| 108 |
+
Note that this is intended only for interactive, calculator style
|
| 109 |
+
usage. In a script that uses SymPy, Symbols should be instantiated
|
| 110 |
+
at the top, so that it's clear what they are.
|
| 111 |
+
|
| 112 |
+
This will not override any names that are already defined, which
|
| 113 |
+
includes the single character letters represented by the mnemonic
|
| 114 |
+
QCOSINE (see the "Gotchas and Pitfalls" document in the
|
| 115 |
+
documentation). You can delete existing names by executing "del
|
| 116 |
+
name". If a name is defined, typing "'name' in dir()" will return True.
|
| 117 |
+
|
| 118 |
+
The Symbols that are created using this have default assumptions.
|
| 119 |
+
If you want to place assumptions on symbols, you should create them
|
| 120 |
+
using symbols() or var().
|
| 121 |
+
|
| 122 |
+
Finally, this only works in the top level namespace. So, for
|
| 123 |
+
example, if you define a function in isympy with an undefined
|
| 124 |
+
Symbol, it will not work.
|
| 125 |
+
|
| 126 |
+
See also the -i and -I options.
|
| 127 |
+
|
| 128 |
+
-i, --int-to-Integer (requires at least IPython 0.11)
|
| 129 |
+
|
| 130 |
+
Automatically wrap int literals with Integer. This makes it so that
|
| 131 |
+
things like 1/2 will come out as Rational(1, 2), rather than 0.5. This
|
| 132 |
+
works by preprocessing the source and wrapping all int literals with
|
| 133 |
+
Integer. Note that this will not change the behavior of int literals
|
| 134 |
+
assigned to variables, and it also won't change the behavior of functions
|
| 135 |
+
that return int literals.
|
| 136 |
+
|
| 137 |
+
If you want an int, you can wrap the literal in int(), e.g. int(3)/int(2)
|
| 138 |
+
gives 1.5 (with division imported from __future__).
|
| 139 |
+
|
| 140 |
+
-I, --interactive (requires at least IPython 0.11)
|
| 141 |
+
|
| 142 |
+
This is equivalent to --auto-symbols --int-to-Integer. Future options
|
| 143 |
+
designed for ease of interactive use may be added to this.
|
| 144 |
+
|
| 145 |
+
-D, --debug
|
| 146 |
+
|
| 147 |
+
Enable debugging output. This is the same as setting the
|
| 148 |
+
environment variable SYMPY_DEBUG to 'True'. The debug status is set
|
| 149 |
+
in the variable SYMPY_DEBUG within isympy.
|
| 150 |
+
|
| 151 |
+
-- IPython options
|
| 152 |
+
|
| 153 |
+
Additionally you can pass command line options directly to the IPython
|
| 154 |
+
interpreter (the standard Python shell is not supported). However you
|
| 155 |
+
need to add the '--' separator between two types of options, e.g the
|
| 156 |
+
startup banner option and the colors option. You need to enter the
|
| 157 |
+
options as required by the version of IPython that you are using, too:
|
| 158 |
+
|
| 159 |
+
in IPython 0.11,
|
| 160 |
+
|
| 161 |
+
$isympy -q -- --colors=NoColor
|
| 162 |
+
|
| 163 |
+
or older versions of IPython,
|
| 164 |
+
|
| 165 |
+
$isympy -q -- -colors NoColor
|
| 166 |
+
|
| 167 |
+
See also isympy --help.
|
| 168 |
+
"""
|
| 169 |
+
|
| 170 |
+
import os
|
| 171 |
+
import sys
|
| 172 |
+
|
| 173 |
+
# DO NOT IMPORT SYMPY HERE! Or the setting of the sympy environment variables
|
| 174 |
+
# by the command line will break.
|
| 175 |
+
|
| 176 |
+
def main() -> None:
|
| 177 |
+
from argparse import ArgumentParser, RawDescriptionHelpFormatter
|
| 178 |
+
|
| 179 |
+
VERSION = None
|
| 180 |
+
if '--version' in sys.argv:
|
| 181 |
+
# We cannot import sympy before this is run, because flags like -C and
|
| 182 |
+
# -t set environment variables that must be set before SymPy is
|
| 183 |
+
# imported. The only thing we need to import it for is to get the
|
| 184 |
+
# version, which only matters with the --version flag.
|
| 185 |
+
import sympy
|
| 186 |
+
VERSION = sympy.__version__
|
| 187 |
+
|
| 188 |
+
usage = 'isympy [options] -- [ipython options]'
|
| 189 |
+
parser = ArgumentParser(
|
| 190 |
+
usage=usage,
|
| 191 |
+
description=__doc__,
|
| 192 |
+
formatter_class=RawDescriptionHelpFormatter,
|
| 193 |
+
)
|
| 194 |
+
|
| 195 |
+
parser.add_argument('--version', action='version', version=VERSION)
|
| 196 |
+
|
| 197 |
+
parser.add_argument(
|
| 198 |
+
'-c', '--console',
|
| 199 |
+
dest='console',
|
| 200 |
+
action='store',
|
| 201 |
+
default=None,
|
| 202 |
+
choices=['ipython', 'python'],
|
| 203 |
+
metavar='CONSOLE',
|
| 204 |
+
help='select type of interactive session: ipython | python; defaults '
|
| 205 |
+
'to ipython if IPython is installed, otherwise python')
|
| 206 |
+
|
| 207 |
+
parser.add_argument(
|
| 208 |
+
'-p', '--pretty',
|
| 209 |
+
dest='pretty',
|
| 210 |
+
action='store',
|
| 211 |
+
default=None,
|
| 212 |
+
metavar='PRETTY',
|
| 213 |
+
choices=['unicode', 'ascii', 'no'],
|
| 214 |
+
help='setup pretty printing: unicode | ascii | no; defaults to '
|
| 215 |
+
'unicode printing if the terminal supports it, otherwise ascii')
|
| 216 |
+
|
| 217 |
+
parser.add_argument(
|
| 218 |
+
'-t', '--types',
|
| 219 |
+
dest='types',
|
| 220 |
+
action='store',
|
| 221 |
+
default=None,
|
| 222 |
+
metavar='TYPES',
|
| 223 |
+
choices=['gmpy', 'gmpy1', 'python'],
|
| 224 |
+
help='setup ground types: gmpy | gmpy1 | python; defaults to gmpy if gmpy2 '
|
| 225 |
+
'or gmpy is installed, otherwise python')
|
| 226 |
+
|
| 227 |
+
parser.add_argument(
|
| 228 |
+
'-o', '--order',
|
| 229 |
+
dest='order',
|
| 230 |
+
action='store',
|
| 231 |
+
default=None,
|
| 232 |
+
metavar='ORDER',
|
| 233 |
+
choices=['lex', 'grlex', 'grevlex', 'rev-lex', 'rev-grlex', 'rev-grevlex', 'old', 'none'],
|
| 234 |
+
help='setup ordering of terms: [rev-]lex | [rev-]grlex | [rev-]grevlex | old | none; defaults to lex')
|
| 235 |
+
|
| 236 |
+
parser.add_argument(
|
| 237 |
+
'-q', '--quiet',
|
| 238 |
+
dest='quiet',
|
| 239 |
+
action='store_true',
|
| 240 |
+
default=False,
|
| 241 |
+
help='print only version information at startup')
|
| 242 |
+
|
| 243 |
+
parser.add_argument(
|
| 244 |
+
'-d', '--doctest',
|
| 245 |
+
dest='doctest',
|
| 246 |
+
action='store_true',
|
| 247 |
+
default=False,
|
| 248 |
+
help='use the doctest format for output (you can just copy and paste it)')
|
| 249 |
+
|
| 250 |
+
parser.add_argument(
|
| 251 |
+
'-C', '--no-cache',
|
| 252 |
+
dest='cache',
|
| 253 |
+
action='store_false',
|
| 254 |
+
default=True,
|
| 255 |
+
help='disable caching mechanism')
|
| 256 |
+
|
| 257 |
+
parser.add_argument(
|
| 258 |
+
'-a', '--auto-symbols',
|
| 259 |
+
dest='auto_symbols',
|
| 260 |
+
action='store_true',
|
| 261 |
+
default=False,
|
| 262 |
+
help='automatically construct missing symbols')
|
| 263 |
+
|
| 264 |
+
parser.add_argument(
|
| 265 |
+
'-i', '--int-to-Integer',
|
| 266 |
+
dest='auto_int_to_Integer',
|
| 267 |
+
action='store_true',
|
| 268 |
+
default=False,
|
| 269 |
+
help="automatically wrap int literals with Integer")
|
| 270 |
+
|
| 271 |
+
parser.add_argument(
|
| 272 |
+
'-I', '--interactive',
|
| 273 |
+
dest='interactive',
|
| 274 |
+
action='store_true',
|
| 275 |
+
default=False,
|
| 276 |
+
help="equivalent to -a -i")
|
| 277 |
+
|
| 278 |
+
parser.add_argument(
|
| 279 |
+
'-D', '--debug',
|
| 280 |
+
dest='debug',
|
| 281 |
+
action='store_true',
|
| 282 |
+
default=False,
|
| 283 |
+
help='enable debugging output')
|
| 284 |
+
|
| 285 |
+
(options, ipy_args) = parser.parse_known_args()
|
| 286 |
+
if '--' in ipy_args:
|
| 287 |
+
ipy_args.remove('--')
|
| 288 |
+
|
| 289 |
+
if not options.cache:
|
| 290 |
+
os.environ['SYMPY_USE_CACHE'] = 'no'
|
| 291 |
+
|
| 292 |
+
if options.types:
|
| 293 |
+
os.environ['SYMPY_GROUND_TYPES'] = options.types
|
| 294 |
+
|
| 295 |
+
if options.debug:
|
| 296 |
+
os.environ['SYMPY_DEBUG'] = str(options.debug)
|
| 297 |
+
|
| 298 |
+
if options.doctest:
|
| 299 |
+
options.pretty = 'no'
|
| 300 |
+
options.console = 'python'
|
| 301 |
+
|
| 302 |
+
session = options.console
|
| 303 |
+
|
| 304 |
+
if session is not None:
|
| 305 |
+
ipython = session == 'ipython'
|
| 306 |
+
else:
|
| 307 |
+
try:
|
| 308 |
+
import IPython # noqa: F401
|
| 309 |
+
ipython = True
|
| 310 |
+
except ImportError:
|
| 311 |
+
if not options.quiet:
|
| 312 |
+
from sympy.interactive.session import no_ipython
|
| 313 |
+
print(no_ipython)
|
| 314 |
+
ipython = False
|
| 315 |
+
|
| 316 |
+
args = {
|
| 317 |
+
'pretty_print': True,
|
| 318 |
+
'use_unicode': None,
|
| 319 |
+
'use_latex': None,
|
| 320 |
+
'order': None,
|
| 321 |
+
'argv': ipy_args,
|
| 322 |
+
}
|
| 323 |
+
|
| 324 |
+
if options.pretty == 'unicode':
|
| 325 |
+
args['use_unicode'] = True
|
| 326 |
+
elif options.pretty == 'ascii':
|
| 327 |
+
args['use_unicode'] = False
|
| 328 |
+
elif options.pretty == 'no':
|
| 329 |
+
args['pretty_print'] = False
|
| 330 |
+
|
| 331 |
+
if options.order is not None:
|
| 332 |
+
args['order'] = options.order
|
| 333 |
+
|
| 334 |
+
args['quiet'] = options.quiet
|
| 335 |
+
args['auto_symbols'] = options.auto_symbols or options.interactive
|
| 336 |
+
args['auto_int_to_Integer'] = options.auto_int_to_Integer or options.interactive
|
| 337 |
+
|
| 338 |
+
from sympy.interactive import init_session
|
| 339 |
+
init_session(ipython, **args)
|
| 340 |
+
|
| 341 |
+
if __name__ == "__main__":
|
| 342 |
+
main()
|
venv/lib/python3.10/site-packages/pylab.py
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from matplotlib.pylab import * # noqa: F401, F403
|
| 2 |
+
import matplotlib.pylab
|
| 3 |
+
__doc__ = matplotlib.pylab.__doc__
|
venv/lib/python3.10/site-packages/six.py
ADDED
|
@@ -0,0 +1,1003 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) 2010-2024 Benjamin Peterson
|
| 2 |
+
#
|
| 3 |
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 4 |
+
# of this software and associated documentation files (the "Software"), to deal
|
| 5 |
+
# in the Software without restriction, including without limitation the rights
|
| 6 |
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 7 |
+
# copies of the Software, and to permit persons to whom the Software is
|
| 8 |
+
# furnished to do so, subject to the following conditions:
|
| 9 |
+
#
|
| 10 |
+
# The above copyright notice and this permission notice shall be included in all
|
| 11 |
+
# copies or substantial portions of the Software.
|
| 12 |
+
#
|
| 13 |
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 14 |
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 15 |
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 16 |
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 17 |
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 18 |
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 19 |
+
# SOFTWARE.
|
| 20 |
+
|
| 21 |
+
"""Utilities for writing code that runs on Python 2 and 3"""
|
| 22 |
+
|
| 23 |
+
from __future__ import absolute_import
|
| 24 |
+
|
| 25 |
+
import functools
|
| 26 |
+
import itertools
|
| 27 |
+
import operator
|
| 28 |
+
import sys
|
| 29 |
+
import types
|
| 30 |
+
|
| 31 |
+
__author__ = "Benjamin Peterson <benjamin@python.org>"
|
| 32 |
+
__version__ = "1.17.0"
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# Useful for very coarse version differentiation.
|
| 36 |
+
PY2 = sys.version_info[0] == 2
|
| 37 |
+
PY3 = sys.version_info[0] == 3
|
| 38 |
+
PY34 = sys.version_info[0:2] >= (3, 4)
|
| 39 |
+
|
| 40 |
+
if PY3:
|
| 41 |
+
string_types = str,
|
| 42 |
+
integer_types = int,
|
| 43 |
+
class_types = type,
|
| 44 |
+
text_type = str
|
| 45 |
+
binary_type = bytes
|
| 46 |
+
|
| 47 |
+
MAXSIZE = sys.maxsize
|
| 48 |
+
else:
|
| 49 |
+
string_types = basestring,
|
| 50 |
+
integer_types = (int, long)
|
| 51 |
+
class_types = (type, types.ClassType)
|
| 52 |
+
text_type = unicode
|
| 53 |
+
binary_type = str
|
| 54 |
+
|
| 55 |
+
if sys.platform.startswith("java"):
|
| 56 |
+
# Jython always uses 32 bits.
|
| 57 |
+
MAXSIZE = int((1 << 31) - 1)
|
| 58 |
+
else:
|
| 59 |
+
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
|
| 60 |
+
class X(object):
|
| 61 |
+
|
| 62 |
+
def __len__(self):
|
| 63 |
+
return 1 << 31
|
| 64 |
+
try:
|
| 65 |
+
len(X())
|
| 66 |
+
except OverflowError:
|
| 67 |
+
# 32-bit
|
| 68 |
+
MAXSIZE = int((1 << 31) - 1)
|
| 69 |
+
else:
|
| 70 |
+
# 64-bit
|
| 71 |
+
MAXSIZE = int((1 << 63) - 1)
|
| 72 |
+
del X
|
| 73 |
+
|
| 74 |
+
if PY34:
|
| 75 |
+
from importlib.util import spec_from_loader
|
| 76 |
+
else:
|
| 77 |
+
spec_from_loader = None
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def _add_doc(func, doc):
|
| 81 |
+
"""Add documentation to a function."""
|
| 82 |
+
func.__doc__ = doc
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def _import_module(name):
|
| 86 |
+
"""Import module, returning the module after the last dot."""
|
| 87 |
+
__import__(name)
|
| 88 |
+
return sys.modules[name]
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
class _LazyDescr(object):
|
| 92 |
+
|
| 93 |
+
def __init__(self, name):
|
| 94 |
+
self.name = name
|
| 95 |
+
|
| 96 |
+
def __get__(self, obj, tp):
|
| 97 |
+
result = self._resolve()
|
| 98 |
+
setattr(obj, self.name, result) # Invokes __set__.
|
| 99 |
+
try:
|
| 100 |
+
# This is a bit ugly, but it avoids running this again by
|
| 101 |
+
# removing this descriptor.
|
| 102 |
+
delattr(obj.__class__, self.name)
|
| 103 |
+
except AttributeError:
|
| 104 |
+
pass
|
| 105 |
+
return result
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
class MovedModule(_LazyDescr):
|
| 109 |
+
|
| 110 |
+
def __init__(self, name, old, new=None):
|
| 111 |
+
super(MovedModule, self).__init__(name)
|
| 112 |
+
if PY3:
|
| 113 |
+
if new is None:
|
| 114 |
+
new = name
|
| 115 |
+
self.mod = new
|
| 116 |
+
else:
|
| 117 |
+
self.mod = old
|
| 118 |
+
|
| 119 |
+
def _resolve(self):
|
| 120 |
+
return _import_module(self.mod)
|
| 121 |
+
|
| 122 |
+
def __getattr__(self, attr):
|
| 123 |
+
_module = self._resolve()
|
| 124 |
+
value = getattr(_module, attr)
|
| 125 |
+
setattr(self, attr, value)
|
| 126 |
+
return value
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
class _LazyModule(types.ModuleType):
|
| 130 |
+
|
| 131 |
+
def __init__(self, name):
|
| 132 |
+
super(_LazyModule, self).__init__(name)
|
| 133 |
+
self.__doc__ = self.__class__.__doc__
|
| 134 |
+
|
| 135 |
+
def __dir__(self):
|
| 136 |
+
attrs = ["__doc__", "__name__"]
|
| 137 |
+
attrs += [attr.name for attr in self._moved_attributes]
|
| 138 |
+
return attrs
|
| 139 |
+
|
| 140 |
+
# Subclasses should override this
|
| 141 |
+
_moved_attributes = []
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
class MovedAttribute(_LazyDescr):
|
| 145 |
+
|
| 146 |
+
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
|
| 147 |
+
super(MovedAttribute, self).__init__(name)
|
| 148 |
+
if PY3:
|
| 149 |
+
if new_mod is None:
|
| 150 |
+
new_mod = name
|
| 151 |
+
self.mod = new_mod
|
| 152 |
+
if new_attr is None:
|
| 153 |
+
if old_attr is None:
|
| 154 |
+
new_attr = name
|
| 155 |
+
else:
|
| 156 |
+
new_attr = old_attr
|
| 157 |
+
self.attr = new_attr
|
| 158 |
+
else:
|
| 159 |
+
self.mod = old_mod
|
| 160 |
+
if old_attr is None:
|
| 161 |
+
old_attr = name
|
| 162 |
+
self.attr = old_attr
|
| 163 |
+
|
| 164 |
+
def _resolve(self):
|
| 165 |
+
module = _import_module(self.mod)
|
| 166 |
+
return getattr(module, self.attr)
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
class _SixMetaPathImporter(object):
|
| 170 |
+
|
| 171 |
+
"""
|
| 172 |
+
A meta path importer to import six.moves and its submodules.
|
| 173 |
+
|
| 174 |
+
This class implements a PEP302 finder and loader. It should be compatible
|
| 175 |
+
with Python 2.5 and all existing versions of Python3
|
| 176 |
+
"""
|
| 177 |
+
|
| 178 |
+
def __init__(self, six_module_name):
|
| 179 |
+
self.name = six_module_name
|
| 180 |
+
self.known_modules = {}
|
| 181 |
+
|
| 182 |
+
def _add_module(self, mod, *fullnames):
|
| 183 |
+
for fullname in fullnames:
|
| 184 |
+
self.known_modules[self.name + "." + fullname] = mod
|
| 185 |
+
|
| 186 |
+
def _get_module(self, fullname):
|
| 187 |
+
return self.known_modules[self.name + "." + fullname]
|
| 188 |
+
|
| 189 |
+
def find_module(self, fullname, path=None):
|
| 190 |
+
if fullname in self.known_modules:
|
| 191 |
+
return self
|
| 192 |
+
return None
|
| 193 |
+
|
| 194 |
+
def find_spec(self, fullname, path, target=None):
|
| 195 |
+
if fullname in self.known_modules:
|
| 196 |
+
return spec_from_loader(fullname, self)
|
| 197 |
+
return None
|
| 198 |
+
|
| 199 |
+
def __get_module(self, fullname):
|
| 200 |
+
try:
|
| 201 |
+
return self.known_modules[fullname]
|
| 202 |
+
except KeyError:
|
| 203 |
+
raise ImportError("This loader does not know module " + fullname)
|
| 204 |
+
|
| 205 |
+
def load_module(self, fullname):
|
| 206 |
+
try:
|
| 207 |
+
# in case of a reload
|
| 208 |
+
return sys.modules[fullname]
|
| 209 |
+
except KeyError:
|
| 210 |
+
pass
|
| 211 |
+
mod = self.__get_module(fullname)
|
| 212 |
+
if isinstance(mod, MovedModule):
|
| 213 |
+
mod = mod._resolve()
|
| 214 |
+
else:
|
| 215 |
+
mod.__loader__ = self
|
| 216 |
+
sys.modules[fullname] = mod
|
| 217 |
+
return mod
|
| 218 |
+
|
| 219 |
+
def is_package(self, fullname):
|
| 220 |
+
"""
|
| 221 |
+
Return true, if the named module is a package.
|
| 222 |
+
|
| 223 |
+
We need this method to get correct spec objects with
|
| 224 |
+
Python 3.4 (see PEP451)
|
| 225 |
+
"""
|
| 226 |
+
return hasattr(self.__get_module(fullname), "__path__")
|
| 227 |
+
|
| 228 |
+
def get_code(self, fullname):
|
| 229 |
+
"""Return None
|
| 230 |
+
|
| 231 |
+
Required, if is_package is implemented"""
|
| 232 |
+
self.__get_module(fullname) # eventually raises ImportError
|
| 233 |
+
return None
|
| 234 |
+
get_source = get_code # same as get_code
|
| 235 |
+
|
| 236 |
+
def create_module(self, spec):
|
| 237 |
+
return self.load_module(spec.name)
|
| 238 |
+
|
| 239 |
+
def exec_module(self, module):
|
| 240 |
+
pass
|
| 241 |
+
|
| 242 |
+
_importer = _SixMetaPathImporter(__name__)
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
class _MovedItems(_LazyModule):
|
| 246 |
+
|
| 247 |
+
"""Lazy loading of moved objects"""
|
| 248 |
+
__path__ = [] # mark as package
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
_moved_attributes = [
|
| 252 |
+
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
|
| 253 |
+
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
|
| 254 |
+
MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
|
| 255 |
+
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
|
| 256 |
+
MovedAttribute("intern", "__builtin__", "sys"),
|
| 257 |
+
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
|
| 258 |
+
MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
|
| 259 |
+
MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
|
| 260 |
+
MovedAttribute("getoutput", "commands", "subprocess"),
|
| 261 |
+
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
|
| 262 |
+
MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
|
| 263 |
+
MovedAttribute("reduce", "__builtin__", "functools"),
|
| 264 |
+
MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
|
| 265 |
+
MovedAttribute("StringIO", "StringIO", "io"),
|
| 266 |
+
MovedAttribute("UserDict", "UserDict", "collections", "IterableUserDict", "UserDict"),
|
| 267 |
+
MovedAttribute("UserList", "UserList", "collections"),
|
| 268 |
+
MovedAttribute("UserString", "UserString", "collections"),
|
| 269 |
+
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
|
| 270 |
+
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
|
| 271 |
+
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
|
| 272 |
+
MovedModule("builtins", "__builtin__"),
|
| 273 |
+
MovedModule("configparser", "ConfigParser"),
|
| 274 |
+
MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"),
|
| 275 |
+
MovedModule("copyreg", "copy_reg"),
|
| 276 |
+
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
|
| 277 |
+
MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"),
|
| 278 |
+
MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"),
|
| 279 |
+
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
|
| 280 |
+
MovedModule("http_cookies", "Cookie", "http.cookies"),
|
| 281 |
+
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
|
| 282 |
+
MovedModule("html_parser", "HTMLParser", "html.parser"),
|
| 283 |
+
MovedModule("http_client", "httplib", "http.client"),
|
| 284 |
+
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
|
| 285 |
+
MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
|
| 286 |
+
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
|
| 287 |
+
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
|
| 288 |
+
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
|
| 289 |
+
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
|
| 290 |
+
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
|
| 291 |
+
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
|
| 292 |
+
MovedModule("cPickle", "cPickle", "pickle"),
|
| 293 |
+
MovedModule("queue", "Queue"),
|
| 294 |
+
MovedModule("reprlib", "repr"),
|
| 295 |
+
MovedModule("socketserver", "SocketServer"),
|
| 296 |
+
MovedModule("_thread", "thread", "_thread"),
|
| 297 |
+
MovedModule("tkinter", "Tkinter"),
|
| 298 |
+
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
|
| 299 |
+
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
|
| 300 |
+
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
|
| 301 |
+
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
|
| 302 |
+
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
|
| 303 |
+
MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
|
| 304 |
+
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
|
| 305 |
+
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
|
| 306 |
+
MovedModule("tkinter_colorchooser", "tkColorChooser",
|
| 307 |
+
"tkinter.colorchooser"),
|
| 308 |
+
MovedModule("tkinter_commondialog", "tkCommonDialog",
|
| 309 |
+
"tkinter.commondialog"),
|
| 310 |
+
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
|
| 311 |
+
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
|
| 312 |
+
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
|
| 313 |
+
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
|
| 314 |
+
"tkinter.simpledialog"),
|
| 315 |
+
MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
|
| 316 |
+
MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
|
| 317 |
+
MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
|
| 318 |
+
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
|
| 319 |
+
MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
|
| 320 |
+
MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
|
| 321 |
+
]
|
| 322 |
+
# Add windows specific modules.
|
| 323 |
+
if sys.platform == "win32":
|
| 324 |
+
_moved_attributes += [
|
| 325 |
+
MovedModule("winreg", "_winreg"),
|
| 326 |
+
]
|
| 327 |
+
|
| 328 |
+
for attr in _moved_attributes:
|
| 329 |
+
setattr(_MovedItems, attr.name, attr)
|
| 330 |
+
if isinstance(attr, MovedModule):
|
| 331 |
+
_importer._add_module(attr, "moves." + attr.name)
|
| 332 |
+
del attr
|
| 333 |
+
|
| 334 |
+
_MovedItems._moved_attributes = _moved_attributes
|
| 335 |
+
|
| 336 |
+
moves = _MovedItems(__name__ + ".moves")
|
| 337 |
+
_importer._add_module(moves, "moves")
|
| 338 |
+
|
| 339 |
+
|
| 340 |
+
class Module_six_moves_urllib_parse(_LazyModule):
|
| 341 |
+
|
| 342 |
+
"""Lazy loading of moved objects in six.moves.urllib_parse"""
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
_urllib_parse_moved_attributes = [
|
| 346 |
+
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
|
| 347 |
+
MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
|
| 348 |
+
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
|
| 349 |
+
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
|
| 350 |
+
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
|
| 351 |
+
MovedAttribute("urljoin", "urlparse", "urllib.parse"),
|
| 352 |
+
MovedAttribute("urlparse", "urlparse", "urllib.parse"),
|
| 353 |
+
MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
|
| 354 |
+
MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
|
| 355 |
+
MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
|
| 356 |
+
MovedAttribute("quote", "urllib", "urllib.parse"),
|
| 357 |
+
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
|
| 358 |
+
MovedAttribute("unquote", "urllib", "urllib.parse"),
|
| 359 |
+
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
|
| 360 |
+
MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
|
| 361 |
+
MovedAttribute("urlencode", "urllib", "urllib.parse"),
|
| 362 |
+
MovedAttribute("splitquery", "urllib", "urllib.parse"),
|
| 363 |
+
MovedAttribute("splittag", "urllib", "urllib.parse"),
|
| 364 |
+
MovedAttribute("splituser", "urllib", "urllib.parse"),
|
| 365 |
+
MovedAttribute("splitvalue", "urllib", "urllib.parse"),
|
| 366 |
+
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
|
| 367 |
+
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
|
| 368 |
+
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
|
| 369 |
+
MovedAttribute("uses_query", "urlparse", "urllib.parse"),
|
| 370 |
+
MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
|
| 371 |
+
]
|
| 372 |
+
for attr in _urllib_parse_moved_attributes:
|
| 373 |
+
setattr(Module_six_moves_urllib_parse, attr.name, attr)
|
| 374 |
+
del attr
|
| 375 |
+
|
| 376 |
+
Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
|
| 377 |
+
|
| 378 |
+
_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
|
| 379 |
+
"moves.urllib_parse", "moves.urllib.parse")
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
class Module_six_moves_urllib_error(_LazyModule):
|
| 383 |
+
|
| 384 |
+
"""Lazy loading of moved objects in six.moves.urllib_error"""
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
_urllib_error_moved_attributes = [
|
| 388 |
+
MovedAttribute("URLError", "urllib2", "urllib.error"),
|
| 389 |
+
MovedAttribute("HTTPError", "urllib2", "urllib.error"),
|
| 390 |
+
MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
|
| 391 |
+
]
|
| 392 |
+
for attr in _urllib_error_moved_attributes:
|
| 393 |
+
setattr(Module_six_moves_urllib_error, attr.name, attr)
|
| 394 |
+
del attr
|
| 395 |
+
|
| 396 |
+
Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
|
| 397 |
+
|
| 398 |
+
_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
|
| 399 |
+
"moves.urllib_error", "moves.urllib.error")
|
| 400 |
+
|
| 401 |
+
|
| 402 |
+
class Module_six_moves_urllib_request(_LazyModule):
|
| 403 |
+
|
| 404 |
+
"""Lazy loading of moved objects in six.moves.urllib_request"""
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
_urllib_request_moved_attributes = [
|
| 408 |
+
MovedAttribute("urlopen", "urllib2", "urllib.request"),
|
| 409 |
+
MovedAttribute("install_opener", "urllib2", "urllib.request"),
|
| 410 |
+
MovedAttribute("build_opener", "urllib2", "urllib.request"),
|
| 411 |
+
MovedAttribute("pathname2url", "urllib", "urllib.request"),
|
| 412 |
+
MovedAttribute("url2pathname", "urllib", "urllib.request"),
|
| 413 |
+
MovedAttribute("getproxies", "urllib", "urllib.request"),
|
| 414 |
+
MovedAttribute("Request", "urllib2", "urllib.request"),
|
| 415 |
+
MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
|
| 416 |
+
MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
|
| 417 |
+
MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
|
| 418 |
+
MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
|
| 419 |
+
MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
|
| 420 |
+
MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
|
| 421 |
+
MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
|
| 422 |
+
MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
|
| 423 |
+
MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
|
| 424 |
+
MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
|
| 425 |
+
MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
|
| 426 |
+
MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
|
| 427 |
+
MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
|
| 428 |
+
MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
|
| 429 |
+
MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
|
| 430 |
+
MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
|
| 431 |
+
MovedAttribute("FileHandler", "urllib2", "urllib.request"),
|
| 432 |
+
MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
|
| 433 |
+
MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
|
| 434 |
+
MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
|
| 435 |
+
MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
|
| 436 |
+
MovedAttribute("urlretrieve", "urllib", "urllib.request"),
|
| 437 |
+
MovedAttribute("urlcleanup", "urllib", "urllib.request"),
|
| 438 |
+
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
|
| 439 |
+
MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
|
| 440 |
+
MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
|
| 441 |
+
]
|
| 442 |
+
if sys.version_info[:2] < (3, 14):
|
| 443 |
+
_urllib_request_moved_attributes.extend(
|
| 444 |
+
[
|
| 445 |
+
MovedAttribute("URLopener", "urllib", "urllib.request"),
|
| 446 |
+
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
|
| 447 |
+
]
|
| 448 |
+
)
|
| 449 |
+
for attr in _urllib_request_moved_attributes:
|
| 450 |
+
setattr(Module_six_moves_urllib_request, attr.name, attr)
|
| 451 |
+
del attr
|
| 452 |
+
|
| 453 |
+
Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
|
| 454 |
+
|
| 455 |
+
_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
|
| 456 |
+
"moves.urllib_request", "moves.urllib.request")
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
class Module_six_moves_urllib_response(_LazyModule):
|
| 460 |
+
|
| 461 |
+
"""Lazy loading of moved objects in six.moves.urllib_response"""
|
| 462 |
+
|
| 463 |
+
|
| 464 |
+
_urllib_response_moved_attributes = [
|
| 465 |
+
MovedAttribute("addbase", "urllib", "urllib.response"),
|
| 466 |
+
MovedAttribute("addclosehook", "urllib", "urllib.response"),
|
| 467 |
+
MovedAttribute("addinfo", "urllib", "urllib.response"),
|
| 468 |
+
MovedAttribute("addinfourl", "urllib", "urllib.response"),
|
| 469 |
+
]
|
| 470 |
+
for attr in _urllib_response_moved_attributes:
|
| 471 |
+
setattr(Module_six_moves_urllib_response, attr.name, attr)
|
| 472 |
+
del attr
|
| 473 |
+
|
| 474 |
+
Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
|
| 475 |
+
|
| 476 |
+
_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
|
| 477 |
+
"moves.urllib_response", "moves.urllib.response")
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
class Module_six_moves_urllib_robotparser(_LazyModule):
|
| 481 |
+
|
| 482 |
+
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
_urllib_robotparser_moved_attributes = [
|
| 486 |
+
MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
|
| 487 |
+
]
|
| 488 |
+
for attr in _urllib_robotparser_moved_attributes:
|
| 489 |
+
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
|
| 490 |
+
del attr
|
| 491 |
+
|
| 492 |
+
Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
|
| 493 |
+
|
| 494 |
+
_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
|
| 495 |
+
"moves.urllib_robotparser", "moves.urllib.robotparser")
|
| 496 |
+
|
| 497 |
+
|
| 498 |
+
class Module_six_moves_urllib(types.ModuleType):
|
| 499 |
+
|
| 500 |
+
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
|
| 501 |
+
__path__ = [] # mark as package
|
| 502 |
+
parse = _importer._get_module("moves.urllib_parse")
|
| 503 |
+
error = _importer._get_module("moves.urllib_error")
|
| 504 |
+
request = _importer._get_module("moves.urllib_request")
|
| 505 |
+
response = _importer._get_module("moves.urllib_response")
|
| 506 |
+
robotparser = _importer._get_module("moves.urllib_robotparser")
|
| 507 |
+
|
| 508 |
+
def __dir__(self):
|
| 509 |
+
return ['parse', 'error', 'request', 'response', 'robotparser']
|
| 510 |
+
|
| 511 |
+
_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
|
| 512 |
+
"moves.urllib")
|
| 513 |
+
|
| 514 |
+
|
| 515 |
+
def add_move(move):
|
| 516 |
+
"""Add an item to six.moves."""
|
| 517 |
+
setattr(_MovedItems, move.name, move)
|
| 518 |
+
|
| 519 |
+
|
| 520 |
+
def remove_move(name):
|
| 521 |
+
"""Remove item from six.moves."""
|
| 522 |
+
try:
|
| 523 |
+
delattr(_MovedItems, name)
|
| 524 |
+
except AttributeError:
|
| 525 |
+
try:
|
| 526 |
+
del moves.__dict__[name]
|
| 527 |
+
except KeyError:
|
| 528 |
+
raise AttributeError("no such move, %r" % (name,))
|
| 529 |
+
|
| 530 |
+
|
| 531 |
+
if PY3:
|
| 532 |
+
_meth_func = "__func__"
|
| 533 |
+
_meth_self = "__self__"
|
| 534 |
+
|
| 535 |
+
_func_closure = "__closure__"
|
| 536 |
+
_func_code = "__code__"
|
| 537 |
+
_func_defaults = "__defaults__"
|
| 538 |
+
_func_globals = "__globals__"
|
| 539 |
+
else:
|
| 540 |
+
_meth_func = "im_func"
|
| 541 |
+
_meth_self = "im_self"
|
| 542 |
+
|
| 543 |
+
_func_closure = "func_closure"
|
| 544 |
+
_func_code = "func_code"
|
| 545 |
+
_func_defaults = "func_defaults"
|
| 546 |
+
_func_globals = "func_globals"
|
| 547 |
+
|
| 548 |
+
|
| 549 |
+
try:
|
| 550 |
+
advance_iterator = next
|
| 551 |
+
except NameError:
|
| 552 |
+
def advance_iterator(it):
|
| 553 |
+
return it.next()
|
| 554 |
+
next = advance_iterator
|
| 555 |
+
|
| 556 |
+
|
| 557 |
+
try:
|
| 558 |
+
callable = callable
|
| 559 |
+
except NameError:
|
| 560 |
+
def callable(obj):
|
| 561 |
+
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
if PY3:
|
| 565 |
+
def get_unbound_function(unbound):
|
| 566 |
+
return unbound
|
| 567 |
+
|
| 568 |
+
create_bound_method = types.MethodType
|
| 569 |
+
|
| 570 |
+
def create_unbound_method(func, cls):
|
| 571 |
+
return func
|
| 572 |
+
|
| 573 |
+
Iterator = object
|
| 574 |
+
else:
|
| 575 |
+
def get_unbound_function(unbound):
|
| 576 |
+
return unbound.im_func
|
| 577 |
+
|
| 578 |
+
def create_bound_method(func, obj):
|
| 579 |
+
return types.MethodType(func, obj, obj.__class__)
|
| 580 |
+
|
| 581 |
+
def create_unbound_method(func, cls):
|
| 582 |
+
return types.MethodType(func, None, cls)
|
| 583 |
+
|
| 584 |
+
class Iterator(object):
|
| 585 |
+
|
| 586 |
+
def next(self):
|
| 587 |
+
return type(self).__next__(self)
|
| 588 |
+
|
| 589 |
+
callable = callable
|
| 590 |
+
_add_doc(get_unbound_function,
|
| 591 |
+
"""Get the function out of a possibly unbound function""")
|
| 592 |
+
|
| 593 |
+
|
| 594 |
+
get_method_function = operator.attrgetter(_meth_func)
|
| 595 |
+
get_method_self = operator.attrgetter(_meth_self)
|
| 596 |
+
get_function_closure = operator.attrgetter(_func_closure)
|
| 597 |
+
get_function_code = operator.attrgetter(_func_code)
|
| 598 |
+
get_function_defaults = operator.attrgetter(_func_defaults)
|
| 599 |
+
get_function_globals = operator.attrgetter(_func_globals)
|
| 600 |
+
|
| 601 |
+
|
| 602 |
+
if PY3:
|
| 603 |
+
def iterkeys(d, **kw):
|
| 604 |
+
return iter(d.keys(**kw))
|
| 605 |
+
|
| 606 |
+
def itervalues(d, **kw):
|
| 607 |
+
return iter(d.values(**kw))
|
| 608 |
+
|
| 609 |
+
def iteritems(d, **kw):
|
| 610 |
+
return iter(d.items(**kw))
|
| 611 |
+
|
| 612 |
+
def iterlists(d, **kw):
|
| 613 |
+
return iter(d.lists(**kw))
|
| 614 |
+
|
| 615 |
+
viewkeys = operator.methodcaller("keys")
|
| 616 |
+
|
| 617 |
+
viewvalues = operator.methodcaller("values")
|
| 618 |
+
|
| 619 |
+
viewitems = operator.methodcaller("items")
|
| 620 |
+
else:
|
| 621 |
+
def iterkeys(d, **kw):
|
| 622 |
+
return d.iterkeys(**kw)
|
| 623 |
+
|
| 624 |
+
def itervalues(d, **kw):
|
| 625 |
+
return d.itervalues(**kw)
|
| 626 |
+
|
| 627 |
+
def iteritems(d, **kw):
|
| 628 |
+
return d.iteritems(**kw)
|
| 629 |
+
|
| 630 |
+
def iterlists(d, **kw):
|
| 631 |
+
return d.iterlists(**kw)
|
| 632 |
+
|
| 633 |
+
viewkeys = operator.methodcaller("viewkeys")
|
| 634 |
+
|
| 635 |
+
viewvalues = operator.methodcaller("viewvalues")
|
| 636 |
+
|
| 637 |
+
viewitems = operator.methodcaller("viewitems")
|
| 638 |
+
|
| 639 |
+
_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
|
| 640 |
+
_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
|
| 641 |
+
_add_doc(iteritems,
|
| 642 |
+
"Return an iterator over the (key, value) pairs of a dictionary.")
|
| 643 |
+
_add_doc(iterlists,
|
| 644 |
+
"Return an iterator over the (key, [values]) pairs of a dictionary.")
|
| 645 |
+
|
| 646 |
+
|
| 647 |
+
if PY3:
|
| 648 |
+
def b(s):
|
| 649 |
+
return s.encode("latin-1")
|
| 650 |
+
|
| 651 |
+
def u(s):
|
| 652 |
+
return s
|
| 653 |
+
unichr = chr
|
| 654 |
+
import struct
|
| 655 |
+
int2byte = struct.Struct(">B").pack
|
| 656 |
+
del struct
|
| 657 |
+
byte2int = operator.itemgetter(0)
|
| 658 |
+
indexbytes = operator.getitem
|
| 659 |
+
iterbytes = iter
|
| 660 |
+
import io
|
| 661 |
+
StringIO = io.StringIO
|
| 662 |
+
BytesIO = io.BytesIO
|
| 663 |
+
del io
|
| 664 |
+
_assertCountEqual = "assertCountEqual"
|
| 665 |
+
if sys.version_info[1] <= 1:
|
| 666 |
+
_assertRaisesRegex = "assertRaisesRegexp"
|
| 667 |
+
_assertRegex = "assertRegexpMatches"
|
| 668 |
+
_assertNotRegex = "assertNotRegexpMatches"
|
| 669 |
+
else:
|
| 670 |
+
_assertRaisesRegex = "assertRaisesRegex"
|
| 671 |
+
_assertRegex = "assertRegex"
|
| 672 |
+
_assertNotRegex = "assertNotRegex"
|
| 673 |
+
else:
|
| 674 |
+
def b(s):
|
| 675 |
+
return s
|
| 676 |
+
# Workaround for standalone backslash
|
| 677 |
+
|
| 678 |
+
def u(s):
|
| 679 |
+
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
|
| 680 |
+
unichr = unichr
|
| 681 |
+
int2byte = chr
|
| 682 |
+
|
| 683 |
+
def byte2int(bs):
|
| 684 |
+
return ord(bs[0])
|
| 685 |
+
|
| 686 |
+
def indexbytes(buf, i):
|
| 687 |
+
return ord(buf[i])
|
| 688 |
+
iterbytes = functools.partial(itertools.imap, ord)
|
| 689 |
+
import StringIO
|
| 690 |
+
StringIO = BytesIO = StringIO.StringIO
|
| 691 |
+
_assertCountEqual = "assertItemsEqual"
|
| 692 |
+
_assertRaisesRegex = "assertRaisesRegexp"
|
| 693 |
+
_assertRegex = "assertRegexpMatches"
|
| 694 |
+
_assertNotRegex = "assertNotRegexpMatches"
|
| 695 |
+
_add_doc(b, """Byte literal""")
|
| 696 |
+
_add_doc(u, """Text literal""")
|
| 697 |
+
|
| 698 |
+
|
| 699 |
+
def assertCountEqual(self, *args, **kwargs):
|
| 700 |
+
return getattr(self, _assertCountEqual)(*args, **kwargs)
|
| 701 |
+
|
| 702 |
+
|
| 703 |
+
def assertRaisesRegex(self, *args, **kwargs):
|
| 704 |
+
return getattr(self, _assertRaisesRegex)(*args, **kwargs)
|
| 705 |
+
|
| 706 |
+
|
| 707 |
+
def assertRegex(self, *args, **kwargs):
|
| 708 |
+
return getattr(self, _assertRegex)(*args, **kwargs)
|
| 709 |
+
|
| 710 |
+
|
| 711 |
+
def assertNotRegex(self, *args, **kwargs):
|
| 712 |
+
return getattr(self, _assertNotRegex)(*args, **kwargs)
|
| 713 |
+
|
| 714 |
+
|
| 715 |
+
if PY3:
|
| 716 |
+
exec_ = getattr(moves.builtins, "exec")
|
| 717 |
+
|
| 718 |
+
def reraise(tp, value, tb=None):
|
| 719 |
+
try:
|
| 720 |
+
if value is None:
|
| 721 |
+
value = tp()
|
| 722 |
+
if value.__traceback__ is not tb:
|
| 723 |
+
raise value.with_traceback(tb)
|
| 724 |
+
raise value
|
| 725 |
+
finally:
|
| 726 |
+
value = None
|
| 727 |
+
tb = None
|
| 728 |
+
|
| 729 |
+
else:
|
| 730 |
+
def exec_(_code_, _globs_=None, _locs_=None):
|
| 731 |
+
"""Execute code in a namespace."""
|
| 732 |
+
if _globs_ is None:
|
| 733 |
+
frame = sys._getframe(1)
|
| 734 |
+
_globs_ = frame.f_globals
|
| 735 |
+
if _locs_ is None:
|
| 736 |
+
_locs_ = frame.f_locals
|
| 737 |
+
del frame
|
| 738 |
+
elif _locs_ is None:
|
| 739 |
+
_locs_ = _globs_
|
| 740 |
+
exec("""exec _code_ in _globs_, _locs_""")
|
| 741 |
+
|
| 742 |
+
exec_("""def reraise(tp, value, tb=None):
|
| 743 |
+
try:
|
| 744 |
+
raise tp, value, tb
|
| 745 |
+
finally:
|
| 746 |
+
tb = None
|
| 747 |
+
""")
|
| 748 |
+
|
| 749 |
+
|
| 750 |
+
if sys.version_info[:2] > (3,):
|
| 751 |
+
exec_("""def raise_from(value, from_value):
|
| 752 |
+
try:
|
| 753 |
+
raise value from from_value
|
| 754 |
+
finally:
|
| 755 |
+
value = None
|
| 756 |
+
""")
|
| 757 |
+
else:
|
| 758 |
+
def raise_from(value, from_value):
|
| 759 |
+
raise value
|
| 760 |
+
|
| 761 |
+
|
| 762 |
+
print_ = getattr(moves.builtins, "print", None)
|
| 763 |
+
if print_ is None:
|
| 764 |
+
def print_(*args, **kwargs):
|
| 765 |
+
"""The new-style print function for Python 2.4 and 2.5."""
|
| 766 |
+
fp = kwargs.pop("file", sys.stdout)
|
| 767 |
+
if fp is None:
|
| 768 |
+
return
|
| 769 |
+
|
| 770 |
+
def write(data):
|
| 771 |
+
if not isinstance(data, basestring):
|
| 772 |
+
data = str(data)
|
| 773 |
+
# If the file has an encoding, encode unicode with it.
|
| 774 |
+
if (isinstance(fp, file) and
|
| 775 |
+
isinstance(data, unicode) and
|
| 776 |
+
fp.encoding is not None):
|
| 777 |
+
errors = getattr(fp, "errors", None)
|
| 778 |
+
if errors is None:
|
| 779 |
+
errors = "strict"
|
| 780 |
+
data = data.encode(fp.encoding, errors)
|
| 781 |
+
fp.write(data)
|
| 782 |
+
want_unicode = False
|
| 783 |
+
sep = kwargs.pop("sep", None)
|
| 784 |
+
if sep is not None:
|
| 785 |
+
if isinstance(sep, unicode):
|
| 786 |
+
want_unicode = True
|
| 787 |
+
elif not isinstance(sep, str):
|
| 788 |
+
raise TypeError("sep must be None or a string")
|
| 789 |
+
end = kwargs.pop("end", None)
|
| 790 |
+
if end is not None:
|
| 791 |
+
if isinstance(end, unicode):
|
| 792 |
+
want_unicode = True
|
| 793 |
+
elif not isinstance(end, str):
|
| 794 |
+
raise TypeError("end must be None or a string")
|
| 795 |
+
if kwargs:
|
| 796 |
+
raise TypeError("invalid keyword arguments to print()")
|
| 797 |
+
if not want_unicode:
|
| 798 |
+
for arg in args:
|
| 799 |
+
if isinstance(arg, unicode):
|
| 800 |
+
want_unicode = True
|
| 801 |
+
break
|
| 802 |
+
if want_unicode:
|
| 803 |
+
newline = unicode("\n")
|
| 804 |
+
space = unicode(" ")
|
| 805 |
+
else:
|
| 806 |
+
newline = "\n"
|
| 807 |
+
space = " "
|
| 808 |
+
if sep is None:
|
| 809 |
+
sep = space
|
| 810 |
+
if end is None:
|
| 811 |
+
end = newline
|
| 812 |
+
for i, arg in enumerate(args):
|
| 813 |
+
if i:
|
| 814 |
+
write(sep)
|
| 815 |
+
write(arg)
|
| 816 |
+
write(end)
|
| 817 |
+
if sys.version_info[:2] < (3, 3):
|
| 818 |
+
_print = print_
|
| 819 |
+
|
| 820 |
+
def print_(*args, **kwargs):
|
| 821 |
+
fp = kwargs.get("file", sys.stdout)
|
| 822 |
+
flush = kwargs.pop("flush", False)
|
| 823 |
+
_print(*args, **kwargs)
|
| 824 |
+
if flush and fp is not None:
|
| 825 |
+
fp.flush()
|
| 826 |
+
|
| 827 |
+
_add_doc(reraise, """Reraise an exception.""")
|
| 828 |
+
|
| 829 |
+
if sys.version_info[0:2] < (3, 4):
|
| 830 |
+
# This does exactly the same what the :func:`py3:functools.update_wrapper`
|
| 831 |
+
# function does on Python versions after 3.2. It sets the ``__wrapped__``
|
| 832 |
+
# attribute on ``wrapper`` object and it doesn't raise an error if any of
|
| 833 |
+
# the attributes mentioned in ``assigned`` and ``updated`` are missing on
|
| 834 |
+
# ``wrapped`` object.
|
| 835 |
+
def _update_wrapper(wrapper, wrapped,
|
| 836 |
+
assigned=functools.WRAPPER_ASSIGNMENTS,
|
| 837 |
+
updated=functools.WRAPPER_UPDATES):
|
| 838 |
+
for attr in assigned:
|
| 839 |
+
try:
|
| 840 |
+
value = getattr(wrapped, attr)
|
| 841 |
+
except AttributeError:
|
| 842 |
+
continue
|
| 843 |
+
else:
|
| 844 |
+
setattr(wrapper, attr, value)
|
| 845 |
+
for attr in updated:
|
| 846 |
+
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
|
| 847 |
+
wrapper.__wrapped__ = wrapped
|
| 848 |
+
return wrapper
|
| 849 |
+
_update_wrapper.__doc__ = functools.update_wrapper.__doc__
|
| 850 |
+
|
| 851 |
+
def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
|
| 852 |
+
updated=functools.WRAPPER_UPDATES):
|
| 853 |
+
return functools.partial(_update_wrapper, wrapped=wrapped,
|
| 854 |
+
assigned=assigned, updated=updated)
|
| 855 |
+
wraps.__doc__ = functools.wraps.__doc__
|
| 856 |
+
|
| 857 |
+
else:
|
| 858 |
+
wraps = functools.wraps
|
| 859 |
+
|
| 860 |
+
|
| 861 |
+
def with_metaclass(meta, *bases):
|
| 862 |
+
"""Create a base class with a metaclass."""
|
| 863 |
+
# This requires a bit of explanation: the basic idea is to make a dummy
|
| 864 |
+
# metaclass for one level of class instantiation that replaces itself with
|
| 865 |
+
# the actual metaclass.
|
| 866 |
+
class metaclass(type):
|
| 867 |
+
|
| 868 |
+
def __new__(cls, name, this_bases, d):
|
| 869 |
+
if sys.version_info[:2] >= (3, 7):
|
| 870 |
+
# This version introduced PEP 560 that requires a bit
|
| 871 |
+
# of extra care (we mimic what is done by __build_class__).
|
| 872 |
+
resolved_bases = types.resolve_bases(bases)
|
| 873 |
+
if resolved_bases is not bases:
|
| 874 |
+
d['__orig_bases__'] = bases
|
| 875 |
+
else:
|
| 876 |
+
resolved_bases = bases
|
| 877 |
+
return meta(name, resolved_bases, d)
|
| 878 |
+
|
| 879 |
+
@classmethod
|
| 880 |
+
def __prepare__(cls, name, this_bases):
|
| 881 |
+
return meta.__prepare__(name, bases)
|
| 882 |
+
return type.__new__(metaclass, 'temporary_class', (), {})
|
| 883 |
+
|
| 884 |
+
|
| 885 |
+
def add_metaclass(metaclass):
|
| 886 |
+
"""Class decorator for creating a class with a metaclass."""
|
| 887 |
+
def wrapper(cls):
|
| 888 |
+
orig_vars = cls.__dict__.copy()
|
| 889 |
+
slots = orig_vars.get('__slots__')
|
| 890 |
+
if slots is not None:
|
| 891 |
+
if isinstance(slots, str):
|
| 892 |
+
slots = [slots]
|
| 893 |
+
for slots_var in slots:
|
| 894 |
+
orig_vars.pop(slots_var)
|
| 895 |
+
orig_vars.pop('__dict__', None)
|
| 896 |
+
orig_vars.pop('__weakref__', None)
|
| 897 |
+
if hasattr(cls, '__qualname__'):
|
| 898 |
+
orig_vars['__qualname__'] = cls.__qualname__
|
| 899 |
+
return metaclass(cls.__name__, cls.__bases__, orig_vars)
|
| 900 |
+
return wrapper
|
| 901 |
+
|
| 902 |
+
|
| 903 |
+
def ensure_binary(s, encoding='utf-8', errors='strict'):
|
| 904 |
+
"""Coerce **s** to six.binary_type.
|
| 905 |
+
|
| 906 |
+
For Python 2:
|
| 907 |
+
- `unicode` -> encoded to `str`
|
| 908 |
+
- `str` -> `str`
|
| 909 |
+
|
| 910 |
+
For Python 3:
|
| 911 |
+
- `str` -> encoded to `bytes`
|
| 912 |
+
- `bytes` -> `bytes`
|
| 913 |
+
"""
|
| 914 |
+
if isinstance(s, binary_type):
|
| 915 |
+
return s
|
| 916 |
+
if isinstance(s, text_type):
|
| 917 |
+
return s.encode(encoding, errors)
|
| 918 |
+
raise TypeError("not expecting type '%s'" % type(s))
|
| 919 |
+
|
| 920 |
+
|
| 921 |
+
def ensure_str(s, encoding='utf-8', errors='strict'):
|
| 922 |
+
"""Coerce *s* to `str`.
|
| 923 |
+
|
| 924 |
+
For Python 2:
|
| 925 |
+
- `unicode` -> encoded to `str`
|
| 926 |
+
- `str` -> `str`
|
| 927 |
+
|
| 928 |
+
For Python 3:
|
| 929 |
+
- `str` -> `str`
|
| 930 |
+
- `bytes` -> decoded to `str`
|
| 931 |
+
"""
|
| 932 |
+
# Optimization: Fast return for the common case.
|
| 933 |
+
if type(s) is str:
|
| 934 |
+
return s
|
| 935 |
+
if PY2 and isinstance(s, text_type):
|
| 936 |
+
return s.encode(encoding, errors)
|
| 937 |
+
elif PY3 and isinstance(s, binary_type):
|
| 938 |
+
return s.decode(encoding, errors)
|
| 939 |
+
elif not isinstance(s, (text_type, binary_type)):
|
| 940 |
+
raise TypeError("not expecting type '%s'" % type(s))
|
| 941 |
+
return s
|
| 942 |
+
|
| 943 |
+
|
| 944 |
+
def ensure_text(s, encoding='utf-8', errors='strict'):
|
| 945 |
+
"""Coerce *s* to six.text_type.
|
| 946 |
+
|
| 947 |
+
For Python 2:
|
| 948 |
+
- `unicode` -> `unicode`
|
| 949 |
+
- `str` -> `unicode`
|
| 950 |
+
|
| 951 |
+
For Python 3:
|
| 952 |
+
- `str` -> `str`
|
| 953 |
+
- `bytes` -> decoded to `str`
|
| 954 |
+
"""
|
| 955 |
+
if isinstance(s, binary_type):
|
| 956 |
+
return s.decode(encoding, errors)
|
| 957 |
+
elif isinstance(s, text_type):
|
| 958 |
+
return s
|
| 959 |
+
else:
|
| 960 |
+
raise TypeError("not expecting type '%s'" % type(s))
|
| 961 |
+
|
| 962 |
+
|
| 963 |
+
def python_2_unicode_compatible(klass):
|
| 964 |
+
"""
|
| 965 |
+
A class decorator that defines __unicode__ and __str__ methods under Python 2.
|
| 966 |
+
Under Python 3 it does nothing.
|
| 967 |
+
|
| 968 |
+
To support Python 2 and 3 with a single code base, define a __str__ method
|
| 969 |
+
returning text and apply this decorator to the class.
|
| 970 |
+
"""
|
| 971 |
+
if PY2:
|
| 972 |
+
if '__str__' not in klass.__dict__:
|
| 973 |
+
raise ValueError("@python_2_unicode_compatible cannot be applied "
|
| 974 |
+
"to %s because it doesn't define __str__()." %
|
| 975 |
+
klass.__name__)
|
| 976 |
+
klass.__unicode__ = klass.__str__
|
| 977 |
+
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
|
| 978 |
+
return klass
|
| 979 |
+
|
| 980 |
+
|
| 981 |
+
# Complete the moves implementation.
|
| 982 |
+
# This code is at the end of this module to speed up module loading.
|
| 983 |
+
# Turn this module into a package.
|
| 984 |
+
__path__ = [] # required for PEP 302 and PEP 451
|
| 985 |
+
__package__ = __name__ # see PEP 366 @ReservedAssignment
|
| 986 |
+
if globals().get("__spec__") is not None:
|
| 987 |
+
__spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
|
| 988 |
+
# Remove other six meta path importers, since they cause problems. This can
|
| 989 |
+
# happen if six is removed from sys.modules and then reloaded. (Setuptools does
|
| 990 |
+
# this for some reason.)
|
| 991 |
+
if sys.meta_path:
|
| 992 |
+
for i, importer in enumerate(sys.meta_path):
|
| 993 |
+
# Here's some real nastiness: Another "instance" of the six module might
|
| 994 |
+
# be floating around. Therefore, we can't use isinstance() to check for
|
| 995 |
+
# the six meta path importer, since the other six instance will have
|
| 996 |
+
# inserted an importer with different class.
|
| 997 |
+
if (type(importer).__name__ == "_SixMetaPathImporter" and
|
| 998 |
+
importer.name == __name__):
|
| 999 |
+
del sys.meta_path[i]
|
| 1000 |
+
break
|
| 1001 |
+
del i, importer
|
| 1002 |
+
# Finally, add the importer to the meta path import hook.
|
| 1003 |
+
sys.meta_path.append(_importer)
|
venv/lib/python3.10/site-packages/soundfile.py
ADDED
|
@@ -0,0 +1,1674 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""python-soundfile is an audio library based on libsndfile, CFFI and NumPy.
|
| 2 |
+
|
| 3 |
+
Sound files can be read or written directly using the functions
|
| 4 |
+
`read()` and `write()`.
|
| 5 |
+
To read a sound file in a block-wise fashion, use `blocks()`.
|
| 6 |
+
Alternatively, sound files can be opened as `SoundFile` objects.
|
| 7 |
+
|
| 8 |
+
For further information, see https://python-soundfile.readthedocs.io/.
|
| 9 |
+
|
| 10 |
+
"""
|
| 11 |
+
__version__ = "0.13.1"
|
| 12 |
+
|
| 13 |
+
import os as _os
|
| 14 |
+
import sys as _sys
|
| 15 |
+
from os import SEEK_SET, SEEK_CUR, SEEK_END
|
| 16 |
+
from ctypes.util import find_library as _find_library
|
| 17 |
+
from _soundfile import ffi as _ffi
|
| 18 |
+
|
| 19 |
+
try:
|
| 20 |
+
_unicode = unicode # doesn't exist in Python 3.x
|
| 21 |
+
except NameError:
|
| 22 |
+
_unicode = str
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
_str_types = {
|
| 26 |
+
'title': 0x01,
|
| 27 |
+
'copyright': 0x02,
|
| 28 |
+
'software': 0x03,
|
| 29 |
+
'artist': 0x04,
|
| 30 |
+
'comment': 0x05,
|
| 31 |
+
'date': 0x06,
|
| 32 |
+
'album': 0x07,
|
| 33 |
+
'license': 0x08,
|
| 34 |
+
'tracknumber': 0x09,
|
| 35 |
+
'genre': 0x10,
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
_formats = {
|
| 39 |
+
'WAV': 0x010000, # Microsoft WAV format (little endian default).
|
| 40 |
+
'AIFF': 0x020000, # Apple/SGI AIFF format (big endian).
|
| 41 |
+
'AU': 0x030000, # Sun/NeXT AU format (big endian).
|
| 42 |
+
'RAW': 0x040000, # RAW PCM data.
|
| 43 |
+
'PAF': 0x050000, # Ensoniq PARIS file format.
|
| 44 |
+
'SVX': 0x060000, # Amiga IFF / SVX8 / SV16 format.
|
| 45 |
+
'NIST': 0x070000, # Sphere NIST format.
|
| 46 |
+
'VOC': 0x080000, # VOC files.
|
| 47 |
+
'IRCAM': 0x0A0000, # Berkeley/IRCAM/CARL
|
| 48 |
+
'W64': 0x0B0000, # Sonic Foundry's 64 bit RIFF/WAV
|
| 49 |
+
'MAT4': 0x0C0000, # Matlab (tm) V4.2 / GNU Octave 2.0
|
| 50 |
+
'MAT5': 0x0D0000, # Matlab (tm) V5.0 / GNU Octave 2.1
|
| 51 |
+
'PVF': 0x0E0000, # Portable Voice Format
|
| 52 |
+
'XI': 0x0F0000, # Fasttracker 2 Extended Instrument
|
| 53 |
+
'HTK': 0x100000, # HMM Tool Kit format
|
| 54 |
+
'SDS': 0x110000, # Midi Sample Dump Standard
|
| 55 |
+
'AVR': 0x120000, # Audio Visual Research
|
| 56 |
+
'WAVEX': 0x130000, # MS WAVE with WAVEFORMATEX
|
| 57 |
+
'SD2': 0x160000, # Sound Designer 2
|
| 58 |
+
'FLAC': 0x170000, # FLAC lossless file format
|
| 59 |
+
'CAF': 0x180000, # Core Audio File format
|
| 60 |
+
'WVE': 0x190000, # Psion WVE format
|
| 61 |
+
'OGG': 0x200000, # Xiph OGG container
|
| 62 |
+
'MPC2K': 0x210000, # Akai MPC 2000 sampler
|
| 63 |
+
'RF64': 0x220000, # RF64 WAV file
|
| 64 |
+
'MP3': 0x230000, # MPEG-1/2 audio stream
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
_subtypes = {
|
| 68 |
+
'PCM_S8': 0x0001, # Signed 8 bit data
|
| 69 |
+
'PCM_16': 0x0002, # Signed 16 bit data
|
| 70 |
+
'PCM_24': 0x0003, # Signed 24 bit data
|
| 71 |
+
'PCM_32': 0x0004, # Signed 32 bit data
|
| 72 |
+
'PCM_U8': 0x0005, # Unsigned 8 bit data (WAV and RAW only)
|
| 73 |
+
'FLOAT': 0x0006, # 32 bit float data
|
| 74 |
+
'DOUBLE': 0x0007, # 64 bit float data
|
| 75 |
+
'ULAW': 0x0010, # U-Law encoded.
|
| 76 |
+
'ALAW': 0x0011, # A-Law encoded.
|
| 77 |
+
'IMA_ADPCM': 0x0012, # IMA ADPCM.
|
| 78 |
+
'MS_ADPCM': 0x0013, # Microsoft ADPCM.
|
| 79 |
+
'GSM610': 0x0020, # GSM 6.10 encoding.
|
| 80 |
+
'VOX_ADPCM': 0x0021, # OKI / Dialogix ADPCM
|
| 81 |
+
'NMS_ADPCM_16': 0x0022, # 16kbs NMS G721-variant encoding.
|
| 82 |
+
'NMS_ADPCM_24': 0x0023, # 24kbs NMS G721-variant encoding.
|
| 83 |
+
'NMS_ADPCM_32': 0x0024, # 32kbs NMS G721-variant encoding.
|
| 84 |
+
'G721_32': 0x0030, # 32kbs G721 ADPCM encoding.
|
| 85 |
+
'G723_24': 0x0031, # 24kbs G723 ADPCM encoding.
|
| 86 |
+
'G723_40': 0x0032, # 40kbs G723 ADPCM encoding.
|
| 87 |
+
'DWVW_12': 0x0040, # 12 bit Delta Width Variable Word encoding.
|
| 88 |
+
'DWVW_16': 0x0041, # 16 bit Delta Width Variable Word encoding.
|
| 89 |
+
'DWVW_24': 0x0042, # 24 bit Delta Width Variable Word encoding.
|
| 90 |
+
'DWVW_N': 0x0043, # N bit Delta Width Variable Word encoding.
|
| 91 |
+
'DPCM_8': 0x0050, # 8 bit differential PCM (XI only)
|
| 92 |
+
'DPCM_16': 0x0051, # 16 bit differential PCM (XI only)
|
| 93 |
+
'VORBIS': 0x0060, # Xiph Vorbis encoding.
|
| 94 |
+
'OPUS': 0x0064, # Xiph/Skype Opus encoding.
|
| 95 |
+
'ALAC_16': 0x0070, # Apple Lossless Audio Codec (16 bit).
|
| 96 |
+
'ALAC_20': 0x0071, # Apple Lossless Audio Codec (20 bit).
|
| 97 |
+
'ALAC_24': 0x0072, # Apple Lossless Audio Codec (24 bit).
|
| 98 |
+
'ALAC_32': 0x0073, # Apple Lossless Audio Codec (32 bit).
|
| 99 |
+
'MPEG_LAYER_I': 0x0080, # MPEG-1 Audio Layer I.
|
| 100 |
+
'MPEG_LAYER_II': 0x0081, # MPEG-1 Audio Layer II.
|
| 101 |
+
'MPEG_LAYER_III': 0x0082, # MPEG-2 Audio Layer III.
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
_endians = {
|
| 105 |
+
'FILE': 0x00000000, # Default file endian-ness.
|
| 106 |
+
'LITTLE': 0x10000000, # Force little endian-ness.
|
| 107 |
+
'BIG': 0x20000000, # Force big endian-ness.
|
| 108 |
+
'CPU': 0x30000000, # Force CPU endian-ness.
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
# libsndfile doesn't specify default subtypes, these are somehow arbitrary:
|
| 112 |
+
_default_subtypes = {
|
| 113 |
+
'WAV': 'PCM_16',
|
| 114 |
+
'AIFF': 'PCM_16',
|
| 115 |
+
'AU': 'PCM_16',
|
| 116 |
+
# 'RAW': # subtype must be explicit!
|
| 117 |
+
'PAF': 'PCM_16',
|
| 118 |
+
'SVX': 'PCM_16',
|
| 119 |
+
'NIST': 'PCM_16',
|
| 120 |
+
'VOC': 'PCM_16',
|
| 121 |
+
'IRCAM': 'PCM_16',
|
| 122 |
+
'W64': 'PCM_16',
|
| 123 |
+
'MAT4': 'DOUBLE',
|
| 124 |
+
'MAT5': 'DOUBLE',
|
| 125 |
+
'PVF': 'PCM_16',
|
| 126 |
+
'XI': 'DPCM_16',
|
| 127 |
+
'HTK': 'PCM_16',
|
| 128 |
+
'SDS': 'PCM_16',
|
| 129 |
+
'AVR': 'PCM_16',
|
| 130 |
+
'WAVEX': 'PCM_16',
|
| 131 |
+
'SD2': 'PCM_16',
|
| 132 |
+
'FLAC': 'PCM_16',
|
| 133 |
+
'CAF': 'PCM_16',
|
| 134 |
+
'WVE': 'ALAW',
|
| 135 |
+
'OGG': 'VORBIS',
|
| 136 |
+
'MPC2K': 'PCM_16',
|
| 137 |
+
'RF64': 'PCM_16',
|
| 138 |
+
'MP3': 'MPEG_LAYER_III',
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
_ffi_types = {
|
| 142 |
+
'float64': 'double',
|
| 143 |
+
'float32': 'float',
|
| 144 |
+
'int32': 'int',
|
| 145 |
+
'int16': 'short'
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
_bitrate_modes = {
|
| 149 |
+
'CONSTANT': 0,
|
| 150 |
+
'AVERAGE': 1,
|
| 151 |
+
'VARIABLE': 2,
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
try: # packaged lib (in _soundfile_data which should be on python path)
|
| 155 |
+
if _sys.platform == 'darwin':
|
| 156 |
+
from platform import machine as _machine
|
| 157 |
+
_packaged_libname = 'libsndfile_' + _machine() + '.dylib'
|
| 158 |
+
elif _sys.platform == 'win32':
|
| 159 |
+
from platform import architecture as _architecture
|
| 160 |
+
from platform import machine as _machine
|
| 161 |
+
# this check can not be completed correctly: for x64 binaries running on
|
| 162 |
+
# arm64 Windows report the same values as arm64 binaries. For now, neither
|
| 163 |
+
# numpy nor cffi are available for arm64, so we can safely assume we're
|
| 164 |
+
# in x86 land:
|
| 165 |
+
if _architecture()[0] == '64bit':
|
| 166 |
+
_packaged_libname = 'libsndfile_x64.dll'
|
| 167 |
+
elif _architecture()[0] == '32bit':
|
| 168 |
+
_packaged_libname = 'libsndfile_x86.dll'
|
| 169 |
+
else:
|
| 170 |
+
raise OSError('no packaged library for Windows {} {}'
|
| 171 |
+
.format(_architecture(), _machine()))
|
| 172 |
+
elif _sys.platform == 'linux':
|
| 173 |
+
from platform import machine as _machine
|
| 174 |
+
if _machine() in ["aarch64", "aarch64_be", "armv8b", "armv8l"]:
|
| 175 |
+
_packaged_libname = 'libsndfile_arm64.so'
|
| 176 |
+
else:
|
| 177 |
+
_packaged_libname = 'libsndfile_' + _machine() + '.so'
|
| 178 |
+
else:
|
| 179 |
+
raise OSError('no packaged library for this platform')
|
| 180 |
+
|
| 181 |
+
import _soundfile_data # ImportError if this doesn't exist
|
| 182 |
+
_path = _os.path.dirname(_soundfile_data.__file__) # TypeError if __file__ is None
|
| 183 |
+
_full_path = _os.path.join(_path, _packaged_libname)
|
| 184 |
+
_snd = _ffi.dlopen(_full_path) # OSError if file doesn't exist or can't be loaded
|
| 185 |
+
|
| 186 |
+
except (OSError, ImportError, TypeError):
|
| 187 |
+
try: # system-wide libsndfile:
|
| 188 |
+
_libname = _find_library('sndfile')
|
| 189 |
+
if _libname is None:
|
| 190 |
+
raise OSError('sndfile library not found using ctypes.util.find_library')
|
| 191 |
+
_snd = _ffi.dlopen(_libname)
|
| 192 |
+
|
| 193 |
+
except OSError:
|
| 194 |
+
# Try explicit file name, if the general does not work (e.g. on nixos)
|
| 195 |
+
if _sys.platform == 'darwin':
|
| 196 |
+
_explicit_libname = 'libsndfile.dylib'
|
| 197 |
+
elif _sys.platform == 'win32':
|
| 198 |
+
_explicit_libname = 'libsndfile.dll'
|
| 199 |
+
elif _sys.platform == 'linux':
|
| 200 |
+
_explicit_libname = 'libsndfile.so'
|
| 201 |
+
else:
|
| 202 |
+
raise
|
| 203 |
+
|
| 204 |
+
# Homebrew on Apple M1 uses a `/opt/homebrew/lib` instead of
|
| 205 |
+
# `/usr/local/lib`. We are making sure we pick that up.
|
| 206 |
+
from platform import machine as _machine
|
| 207 |
+
if _sys.platform == 'darwin' and _machine() == 'arm64':
|
| 208 |
+
_hbrew_path = '/opt/homebrew/lib/' if _os.path.isdir('/opt/homebrew/lib/') \
|
| 209 |
+
else '/usr/local/lib/'
|
| 210 |
+
_snd = _ffi.dlopen(_os.path.join(_hbrew_path, _explicit_libname))
|
| 211 |
+
else:
|
| 212 |
+
_snd = _ffi.dlopen(_explicit_libname)
|
| 213 |
+
|
| 214 |
+
__libsndfile_version__ = _ffi.string(_snd.sf_version_string()).decode('utf-8', 'replace')
|
| 215 |
+
if __libsndfile_version__.startswith('libsndfile-'):
|
| 216 |
+
__libsndfile_version__ = __libsndfile_version__[len('libsndfile-'):]
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def read(file, frames=-1, start=0, stop=None, dtype='float64', always_2d=False,
|
| 220 |
+
fill_value=None, out=None, samplerate=None, channels=None,
|
| 221 |
+
format=None, subtype=None, endian=None, closefd=True):
|
| 222 |
+
"""Provide audio data from a sound file as NumPy array.
|
| 223 |
+
|
| 224 |
+
By default, the whole file is read from the beginning, but the
|
| 225 |
+
position to start reading can be specified with *start* and the
|
| 226 |
+
number of frames to read can be specified with *frames*.
|
| 227 |
+
Alternatively, a range can be specified with *start* and *stop*.
|
| 228 |
+
|
| 229 |
+
If there is less data left in the file than requested, the rest of
|
| 230 |
+
the frames are filled with *fill_value*.
|
| 231 |
+
If no *fill_value* is specified, a smaller array is returned.
|
| 232 |
+
|
| 233 |
+
Parameters
|
| 234 |
+
----------
|
| 235 |
+
file : str or int or file-like object
|
| 236 |
+
The file to read from. See `SoundFile` for details.
|
| 237 |
+
frames : int, optional
|
| 238 |
+
The number of frames to read. If *frames* is negative, the whole
|
| 239 |
+
rest of the file is read. Not allowed if *stop* is given.
|
| 240 |
+
start : int, optional
|
| 241 |
+
Where to start reading. A negative value counts from the end.
|
| 242 |
+
stop : int, optional
|
| 243 |
+
The index after the last frame to be read. A negative value
|
| 244 |
+
counts from the end. Not allowed if *frames* is given.
|
| 245 |
+
dtype : {'float64', 'float32', 'int32', 'int16'}, optional
|
| 246 |
+
Data type of the returned array, by default ``'float64'``.
|
| 247 |
+
Floating point audio data is typically in the range from
|
| 248 |
+
``-1.0`` to ``1.0``. Integer data is in the range from
|
| 249 |
+
``-2**15`` to ``2**15-1`` for ``'int16'`` and from ``-2**31`` to
|
| 250 |
+
``2**31-1`` for ``'int32'``.
|
| 251 |
+
|
| 252 |
+
.. note:: Reading int values from a float file will *not*
|
| 253 |
+
scale the data to [-1.0, 1.0). If the file contains
|
| 254 |
+
``np.array([42.6], dtype='float32')``, you will read
|
| 255 |
+
``np.array([43], dtype='int32')`` for ``dtype='int32'``.
|
| 256 |
+
|
| 257 |
+
Returns
|
| 258 |
+
-------
|
| 259 |
+
audiodata : `numpy.ndarray` or type(out)
|
| 260 |
+
A two-dimensional (frames x channels) NumPy array is returned.
|
| 261 |
+
If the sound file has only one channel, a one-dimensional array
|
| 262 |
+
is returned. Use ``always_2d=True`` to return a two-dimensional
|
| 263 |
+
array anyway.
|
| 264 |
+
|
| 265 |
+
If *out* was specified, it is returned. If *out* has more
|
| 266 |
+
frames than available in the file (or if *frames* is smaller
|
| 267 |
+
than the length of *out*) and no *fill_value* is given, then
|
| 268 |
+
only a part of *out* is overwritten and a view containing all
|
| 269 |
+
valid frames is returned.
|
| 270 |
+
samplerate : int
|
| 271 |
+
The sample rate of the audio file.
|
| 272 |
+
|
| 273 |
+
Other Parameters
|
| 274 |
+
----------------
|
| 275 |
+
always_2d : bool, optional
|
| 276 |
+
By default, reading a mono sound file will return a
|
| 277 |
+
one-dimensional array. With ``always_2d=True``, audio data is
|
| 278 |
+
always returned as a two-dimensional array, even if the audio
|
| 279 |
+
file has only one channel.
|
| 280 |
+
fill_value : float, optional
|
| 281 |
+
If more frames are requested than available in the file, the
|
| 282 |
+
rest of the output is be filled with *fill_value*. If
|
| 283 |
+
*fill_value* is not specified, a smaller array is returned.
|
| 284 |
+
out : `numpy.ndarray` or subclass, optional
|
| 285 |
+
If *out* is specified, the data is written into the given array
|
| 286 |
+
instead of creating a new array. In this case, the arguments
|
| 287 |
+
*dtype* and *always_2d* are silently ignored! If *frames* is
|
| 288 |
+
not given, it is obtained from the length of *out*.
|
| 289 |
+
samplerate, channels, format, subtype, endian, closefd
|
| 290 |
+
See `SoundFile`.
|
| 291 |
+
|
| 292 |
+
Examples
|
| 293 |
+
--------
|
| 294 |
+
>>> import soundfile as sf
|
| 295 |
+
>>> data, samplerate = sf.read('stereo_file.wav')
|
| 296 |
+
>>> data
|
| 297 |
+
array([[ 0.71329652, 0.06294799],
|
| 298 |
+
[-0.26450912, -0.38874483],
|
| 299 |
+
...
|
| 300 |
+
[ 0.67398441, -0.11516333]])
|
| 301 |
+
>>> samplerate
|
| 302 |
+
44100
|
| 303 |
+
|
| 304 |
+
"""
|
| 305 |
+
with SoundFile(file, 'r', samplerate, channels,
|
| 306 |
+
subtype, endian, format, closefd) as f:
|
| 307 |
+
frames = f._prepare_read(start, stop, frames)
|
| 308 |
+
data = f.read(frames, dtype, always_2d, fill_value, out)
|
| 309 |
+
return data, f.samplerate
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
def write(file, data, samplerate, subtype=None, endian=None, format=None,
|
| 313 |
+
closefd=True, compression_level=None, bitrate_mode=None):
|
| 314 |
+
"""Write data to a sound file.
|
| 315 |
+
|
| 316 |
+
.. note:: If *file* exists, it will be truncated and overwritten!
|
| 317 |
+
|
| 318 |
+
Parameters
|
| 319 |
+
----------
|
| 320 |
+
file : str or int or file-like object
|
| 321 |
+
The file to write to. See `SoundFile` for details.
|
| 322 |
+
data : array_like
|
| 323 |
+
The data to write. Usually two-dimensional (frames x channels),
|
| 324 |
+
but one-dimensional *data* can be used for mono files.
|
| 325 |
+
Only the data types ``'float64'``, ``'float32'``, ``'int32'``
|
| 326 |
+
and ``'int16'`` are supported.
|
| 327 |
+
|
| 328 |
+
.. note:: The data type of *data* does **not** select the data
|
| 329 |
+
type of the written file. Audio data will be
|
| 330 |
+
converted to the given *subtype*. Writing int values
|
| 331 |
+
to a float file will *not* scale the values to
|
| 332 |
+
[-1.0, 1.0). If you write the value ``np.array([42],
|
| 333 |
+
dtype='int32')``, to a ``subtype='FLOAT'`` file, the
|
| 334 |
+
file will then contain ``np.array([42.],
|
| 335 |
+
dtype='float32')``.
|
| 336 |
+
|
| 337 |
+
samplerate : int
|
| 338 |
+
The sample rate of the audio data.
|
| 339 |
+
subtype : str, optional
|
| 340 |
+
See `default_subtype()` for the default value and
|
| 341 |
+
`available_subtypes()` for all possible values.
|
| 342 |
+
|
| 343 |
+
Other Parameters
|
| 344 |
+
----------------
|
| 345 |
+
format, endian, closefd, compression_level, bitrate_mode
|
| 346 |
+
See `SoundFile`.
|
| 347 |
+
|
| 348 |
+
Examples
|
| 349 |
+
--------
|
| 350 |
+
Write 10 frames of random data to a new file:
|
| 351 |
+
|
| 352 |
+
>>> import numpy as np
|
| 353 |
+
>>> import soundfile as sf
|
| 354 |
+
>>> sf.write('stereo_file.wav', np.random.randn(10, 2), 44100, 'PCM_24')
|
| 355 |
+
|
| 356 |
+
"""
|
| 357 |
+
import numpy as np
|
| 358 |
+
data = np.asarray(data)
|
| 359 |
+
if data.ndim == 1:
|
| 360 |
+
channels = 1
|
| 361 |
+
else:
|
| 362 |
+
channels = data.shape[1]
|
| 363 |
+
with SoundFile(file, 'w', samplerate, channels,
|
| 364 |
+
subtype, endian, format, closefd,
|
| 365 |
+
compression_level, bitrate_mode) as f:
|
| 366 |
+
f.write(data)
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
def blocks(file, blocksize=None, overlap=0, frames=-1, start=0, stop=None,
|
| 370 |
+
dtype='float64', always_2d=False, fill_value=None, out=None,
|
| 371 |
+
samplerate=None, channels=None,
|
| 372 |
+
format=None, subtype=None, endian=None, closefd=True):
|
| 373 |
+
"""Return a generator for block-wise reading.
|
| 374 |
+
|
| 375 |
+
By default, iteration starts at the beginning and stops at the end
|
| 376 |
+
of the file. Use *start* to start at a later position and *frames*
|
| 377 |
+
or *stop* to stop earlier.
|
| 378 |
+
|
| 379 |
+
If you stop iterating over the generator before it's exhausted,
|
| 380 |
+
the sound file is not closed. This is normally not a problem
|
| 381 |
+
because the file is opened in read-only mode. To close the file
|
| 382 |
+
properly, the generator's ``close()`` method can be called.
|
| 383 |
+
|
| 384 |
+
Parameters
|
| 385 |
+
----------
|
| 386 |
+
file : str or int or file-like object
|
| 387 |
+
The file to read from. See `SoundFile` for details.
|
| 388 |
+
blocksize : int
|
| 389 |
+
The number of frames to read per block.
|
| 390 |
+
Either this or *out* must be given.
|
| 391 |
+
overlap : int, optional
|
| 392 |
+
The number of frames to rewind between each block.
|
| 393 |
+
|
| 394 |
+
Yields
|
| 395 |
+
------
|
| 396 |
+
`numpy.ndarray` or type(out)
|
| 397 |
+
Blocks of audio data.
|
| 398 |
+
If *out* was given, and the requested frames are not an integer
|
| 399 |
+
multiple of the length of *out*, and no *fill_value* was given,
|
| 400 |
+
the last block will be a smaller view into *out*.
|
| 401 |
+
|
| 402 |
+
Other Parameters
|
| 403 |
+
----------------
|
| 404 |
+
frames, start, stop
|
| 405 |
+
See `read()`.
|
| 406 |
+
dtype : {'float64', 'float32', 'int32', 'int16'}, optional
|
| 407 |
+
See `read()`.
|
| 408 |
+
always_2d, fill_value, out
|
| 409 |
+
See `read()`.
|
| 410 |
+
samplerate, channels, format, subtype, endian, closefd
|
| 411 |
+
See `SoundFile`.
|
| 412 |
+
|
| 413 |
+
Examples
|
| 414 |
+
--------
|
| 415 |
+
>>> import soundfile as sf
|
| 416 |
+
>>> for block in sf.blocks('stereo_file.wav', blocksize=1024):
|
| 417 |
+
>>> pass # do something with 'block'
|
| 418 |
+
|
| 419 |
+
"""
|
| 420 |
+
with SoundFile(file, 'r', samplerate, channels,
|
| 421 |
+
subtype, endian, format, closefd) as f:
|
| 422 |
+
frames = f._prepare_read(start, stop, frames)
|
| 423 |
+
for block in f.blocks(blocksize, overlap, frames,
|
| 424 |
+
dtype, always_2d, fill_value, out):
|
| 425 |
+
yield block
|
| 426 |
+
|
| 427 |
+
|
| 428 |
+
class _SoundFileInfo(object):
|
| 429 |
+
"""Information about a SoundFile"""
|
| 430 |
+
|
| 431 |
+
def __init__(self, file, verbose):
|
| 432 |
+
self.verbose = verbose
|
| 433 |
+
with SoundFile(file) as f:
|
| 434 |
+
self.name = f.name
|
| 435 |
+
self.samplerate = f.samplerate
|
| 436 |
+
self.channels = f.channels
|
| 437 |
+
self.frames = f.frames
|
| 438 |
+
self.duration = float(self.frames)/f.samplerate
|
| 439 |
+
self.format = f.format
|
| 440 |
+
self.subtype = f.subtype
|
| 441 |
+
self.endian = f.endian
|
| 442 |
+
self.format_info = f.format_info
|
| 443 |
+
self.subtype_info = f.subtype_info
|
| 444 |
+
self.sections = f.sections
|
| 445 |
+
self.extra_info = f.extra_info
|
| 446 |
+
|
| 447 |
+
@property
|
| 448 |
+
def _duration_str(self):
|
| 449 |
+
hours, rest = divmod(self.duration, 3600)
|
| 450 |
+
minutes, seconds = divmod(rest, 60)
|
| 451 |
+
if hours >= 1:
|
| 452 |
+
duration = "{0:.0g}:{1:02.0g}:{2:05.3f} h".format(hours, minutes, seconds)
|
| 453 |
+
elif minutes >= 1:
|
| 454 |
+
duration = "{0:02.0g}:{1:05.3f} min".format(minutes, seconds)
|
| 455 |
+
elif seconds <= 1:
|
| 456 |
+
duration = "{0:d} samples".format(self.frames)
|
| 457 |
+
else:
|
| 458 |
+
duration = "{0:.3f} s".format(seconds)
|
| 459 |
+
return duration
|
| 460 |
+
|
| 461 |
+
def __repr__(self):
|
| 462 |
+
info = "\n".join(
|
| 463 |
+
["{0.name}",
|
| 464 |
+
"samplerate: {0.samplerate} Hz",
|
| 465 |
+
"channels: {0.channels}",
|
| 466 |
+
"duration: {0._duration_str}",
|
| 467 |
+
"format: {0.format_info} [{0.format}]",
|
| 468 |
+
"subtype: {0.subtype_info} [{0.subtype}]"])
|
| 469 |
+
if self.verbose:
|
| 470 |
+
info += "\n".join(
|
| 471 |
+
["\nendian: {0.endian}",
|
| 472 |
+
"sections: {0.sections}",
|
| 473 |
+
"frames: {0.frames}",
|
| 474 |
+
'extra_info: """',
|
| 475 |
+
' {1}"""'])
|
| 476 |
+
indented_extra_info = ("\n"+" "*4).join(self.extra_info.split("\n"))
|
| 477 |
+
return info.format(self, indented_extra_info)
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
def info(file, verbose=False):
|
| 481 |
+
"""Returns an object with information about a `SoundFile`.
|
| 482 |
+
|
| 483 |
+
Parameters
|
| 484 |
+
----------
|
| 485 |
+
verbose : bool
|
| 486 |
+
Whether to print additional information.
|
| 487 |
+
"""
|
| 488 |
+
return _SoundFileInfo(file, verbose)
|
| 489 |
+
|
| 490 |
+
|
| 491 |
+
def available_formats():
|
| 492 |
+
"""Return a dictionary of available major formats.
|
| 493 |
+
|
| 494 |
+
Examples
|
| 495 |
+
--------
|
| 496 |
+
>>> import soundfile as sf
|
| 497 |
+
>>> sf.available_formats()
|
| 498 |
+
{'FLAC': 'FLAC (FLAC Lossless Audio Codec)',
|
| 499 |
+
'OGG': 'OGG (OGG Container format)',
|
| 500 |
+
'WAV': 'WAV (Microsoft)',
|
| 501 |
+
'AIFF': 'AIFF (Apple/SGI)',
|
| 502 |
+
...
|
| 503 |
+
'WAVEX': 'WAVEX (Microsoft)',
|
| 504 |
+
'RAW': 'RAW (header-less)',
|
| 505 |
+
'MAT5': 'MAT5 (GNU Octave 2.1 / Matlab 5.0)'}
|
| 506 |
+
|
| 507 |
+
"""
|
| 508 |
+
return dict(_available_formats_helper(_snd.SFC_GET_FORMAT_MAJOR_COUNT,
|
| 509 |
+
_snd.SFC_GET_FORMAT_MAJOR))
|
| 510 |
+
|
| 511 |
+
|
| 512 |
+
def available_subtypes(format=None):
|
| 513 |
+
"""Return a dictionary of available subtypes.
|
| 514 |
+
|
| 515 |
+
Parameters
|
| 516 |
+
----------
|
| 517 |
+
format : str
|
| 518 |
+
If given, only compatible subtypes are returned.
|
| 519 |
+
|
| 520 |
+
Examples
|
| 521 |
+
--------
|
| 522 |
+
>>> import soundfile as sf
|
| 523 |
+
>>> sf.available_subtypes('FLAC')
|
| 524 |
+
{'PCM_24': 'Signed 24 bit PCM',
|
| 525 |
+
'PCM_16': 'Signed 16 bit PCM',
|
| 526 |
+
'PCM_S8': 'Signed 8 bit PCM'}
|
| 527 |
+
|
| 528 |
+
"""
|
| 529 |
+
subtypes = _available_formats_helper(_snd.SFC_GET_FORMAT_SUBTYPE_COUNT,
|
| 530 |
+
_snd.SFC_GET_FORMAT_SUBTYPE)
|
| 531 |
+
return dict((subtype, name) for subtype, name in subtypes
|
| 532 |
+
if format is None or check_format(format, subtype))
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
def check_format(format, subtype=None, endian=None):
|
| 536 |
+
"""Check if the combination of format/subtype/endian is valid.
|
| 537 |
+
|
| 538 |
+
Examples
|
| 539 |
+
--------
|
| 540 |
+
>>> import soundfile as sf
|
| 541 |
+
>>> sf.check_format('WAV', 'PCM_24')
|
| 542 |
+
True
|
| 543 |
+
>>> sf.check_format('FLAC', 'VORBIS')
|
| 544 |
+
False
|
| 545 |
+
|
| 546 |
+
"""
|
| 547 |
+
try:
|
| 548 |
+
return bool(_format_int(format, subtype, endian))
|
| 549 |
+
except (ValueError, TypeError):
|
| 550 |
+
return False
|
| 551 |
+
|
| 552 |
+
|
| 553 |
+
def default_subtype(format):
|
| 554 |
+
"""Return the default subtype for a given format.
|
| 555 |
+
|
| 556 |
+
Examples
|
| 557 |
+
--------
|
| 558 |
+
>>> import soundfile as sf
|
| 559 |
+
>>> sf.default_subtype('WAV')
|
| 560 |
+
'PCM_16'
|
| 561 |
+
>>> sf.default_subtype('MAT5')
|
| 562 |
+
'DOUBLE'
|
| 563 |
+
|
| 564 |
+
"""
|
| 565 |
+
_check_format(format)
|
| 566 |
+
return _default_subtypes.get(format.upper())
|
| 567 |
+
|
| 568 |
+
|
| 569 |
+
class SoundFile(object):
|
| 570 |
+
"""A sound file.
|
| 571 |
+
|
| 572 |
+
For more documentation see the __init__() docstring (which is also
|
| 573 |
+
used for the online documentation (https://python-soundfile.readthedocs.io/).
|
| 574 |
+
|
| 575 |
+
"""
|
| 576 |
+
|
| 577 |
+
def __init__(self, file, mode='r', samplerate=None, channels=None,
|
| 578 |
+
subtype=None, endian=None, format=None, closefd=True,
|
| 579 |
+
compression_level=None, bitrate_mode=None):
|
| 580 |
+
"""Open a sound file.
|
| 581 |
+
|
| 582 |
+
If a file is opened with `mode` ``'r'`` (the default) or
|
| 583 |
+
``'r+'``, no sample rate, channels or file format need to be
|
| 584 |
+
given because the information is obtained from the file. An
|
| 585 |
+
exception is the ``'RAW'`` data format, which always requires
|
| 586 |
+
these data points.
|
| 587 |
+
|
| 588 |
+
File formats consist of three case-insensitive strings:
|
| 589 |
+
|
| 590 |
+
* a *major format* which is by default obtained from the
|
| 591 |
+
extension of the file name (if known) and which can be
|
| 592 |
+
forced with the format argument (e.g. ``format='WAVEX'``).
|
| 593 |
+
* a *subtype*, e.g. ``'PCM_24'``. Most major formats have a
|
| 594 |
+
default subtype which is used if no subtype is specified.
|
| 595 |
+
* an *endian-ness*, which doesn't have to be specified at all in
|
| 596 |
+
most cases.
|
| 597 |
+
|
| 598 |
+
A `SoundFile` object is a *context manager*, which means
|
| 599 |
+
if used in a "with" statement, `close()` is automatically
|
| 600 |
+
called when reaching the end of the code block inside the "with"
|
| 601 |
+
statement.
|
| 602 |
+
|
| 603 |
+
Parameters
|
| 604 |
+
----------
|
| 605 |
+
file : str or int or file-like object
|
| 606 |
+
The file to open. This can be a file name, a file
|
| 607 |
+
descriptor or a Python file object (or a similar object with
|
| 608 |
+
the methods ``read()``/``readinto()``, ``write()``,
|
| 609 |
+
``seek()`` and ``tell()``).
|
| 610 |
+
mode : {'r', 'r+', 'w', 'w+', 'x', 'x+'}, optional
|
| 611 |
+
Open mode. Has to begin with one of these three characters:
|
| 612 |
+
``'r'`` for reading, ``'w'`` for writing (truncates *file*)
|
| 613 |
+
or ``'x'`` for writing (raises an error if *file* already
|
| 614 |
+
exists). Additionally, it may contain ``'+'`` to open
|
| 615 |
+
*file* for both reading and writing.
|
| 616 |
+
The character ``'b'`` for *binary mode* is implied because
|
| 617 |
+
all sound files have to be opened in this mode.
|
| 618 |
+
If *file* is a file descriptor or a file-like object,
|
| 619 |
+
``'w'`` doesn't truncate and ``'x'`` doesn't raise an error.
|
| 620 |
+
samplerate : int
|
| 621 |
+
The sample rate of the file. If `mode` contains ``'r'``,
|
| 622 |
+
this is obtained from the file (except for ``'RAW'`` files).
|
| 623 |
+
channels : int
|
| 624 |
+
The number of channels of the file.
|
| 625 |
+
If `mode` contains ``'r'``, this is obtained from the file
|
| 626 |
+
(except for ``'RAW'`` files).
|
| 627 |
+
subtype : str, sometimes optional
|
| 628 |
+
The subtype of the sound file. If `mode` contains ``'r'``,
|
| 629 |
+
this is obtained from the file (except for ``'RAW'``
|
| 630 |
+
files), if not, the default value depends on the selected
|
| 631 |
+
`format` (see `default_subtype()`).
|
| 632 |
+
See `available_subtypes()` for all possible subtypes for
|
| 633 |
+
a given `format`.
|
| 634 |
+
endian : {'FILE', 'LITTLE', 'BIG', 'CPU'}, sometimes optional
|
| 635 |
+
The endian-ness of the sound file. If `mode` contains
|
| 636 |
+
``'r'``, this is obtained from the file (except for
|
| 637 |
+
``'RAW'`` files), if not, the default value is ``'FILE'``,
|
| 638 |
+
which is correct in most cases.
|
| 639 |
+
format : str, sometimes optional
|
| 640 |
+
The major format of the sound file. If `mode` contains
|
| 641 |
+
``'r'``, this is obtained from the file (except for
|
| 642 |
+
``'RAW'`` files), if not, the default value is determined
|
| 643 |
+
from the file extension. See `available_formats()` for
|
| 644 |
+
all possible values.
|
| 645 |
+
closefd : bool, optional
|
| 646 |
+
Whether to close the file descriptor on `close()`. Only
|
| 647 |
+
applicable if the *file* argument is a file descriptor.
|
| 648 |
+
compression_level : float, optional
|
| 649 |
+
The compression level on 'write()'. The compression level
|
| 650 |
+
should be between 0.0 (minimum compression level) and 1.0
|
| 651 |
+
(highest compression level).
|
| 652 |
+
See `libsndfile document <https://github.com/libsndfile/libsndfile/blob/c81375f070f3c6764969a738eacded64f53a076e/docs/command.md>`__.
|
| 653 |
+
bitrate_mode : {'CONSTANT', 'AVERAGE', 'VARIABLE'}, optional
|
| 654 |
+
The bitrate mode on 'write()'.
|
| 655 |
+
See `libsndfile document <https://github.com/libsndfile/libsndfile/blob/c81375f070f3c6764969a738eacded64f53a076e/docs/command.md>`__.
|
| 656 |
+
|
| 657 |
+
Examples
|
| 658 |
+
--------
|
| 659 |
+
>>> from soundfile import SoundFile
|
| 660 |
+
|
| 661 |
+
Open an existing file for reading:
|
| 662 |
+
|
| 663 |
+
>>> myfile = SoundFile('existing_file.wav')
|
| 664 |
+
>>> # do something with myfile
|
| 665 |
+
>>> myfile.close()
|
| 666 |
+
|
| 667 |
+
Create a new sound file for reading and writing using a with
|
| 668 |
+
statement:
|
| 669 |
+
|
| 670 |
+
>>> with SoundFile('new_file.wav', 'x+', 44100, 2) as myfile:
|
| 671 |
+
>>> # do something with myfile
|
| 672 |
+
>>> # ...
|
| 673 |
+
>>> assert not myfile.closed
|
| 674 |
+
>>> # myfile.close() is called automatically at the end
|
| 675 |
+
>>> assert myfile.closed
|
| 676 |
+
|
| 677 |
+
"""
|
| 678 |
+
# resolve PathLike objects (see PEP519 for details):
|
| 679 |
+
# can be replaced with _os.fspath(file) for Python >= 3.6
|
| 680 |
+
file = file.__fspath__() if hasattr(file, '__fspath__') else file
|
| 681 |
+
self._name = file
|
| 682 |
+
if mode is None:
|
| 683 |
+
mode = getattr(file, 'mode', None)
|
| 684 |
+
mode_int = _check_mode(mode)
|
| 685 |
+
self._mode = mode
|
| 686 |
+
self._compression_level = compression_level
|
| 687 |
+
self._bitrate_mode = bitrate_mode
|
| 688 |
+
self._info = _create_info_struct(file, mode, samplerate, channels,
|
| 689 |
+
format, subtype, endian)
|
| 690 |
+
self._file = self._open(file, mode_int, closefd)
|
| 691 |
+
if set(mode).issuperset('r+') and self.seekable():
|
| 692 |
+
# Move write position to 0 (like in Python file objects)
|
| 693 |
+
self.seek(0)
|
| 694 |
+
_snd.sf_command(self._file, _snd.SFC_SET_CLIPPING, _ffi.NULL,
|
| 695 |
+
_snd.SF_TRUE)
|
| 696 |
+
|
| 697 |
+
# set compression setting
|
| 698 |
+
if self._compression_level is not None:
|
| 699 |
+
# needs to be called before set_bitrate_mode
|
| 700 |
+
self._set_compression_level(self._compression_level)
|
| 701 |
+
if self._bitrate_mode is not None:
|
| 702 |
+
self._set_bitrate_mode(self._bitrate_mode)
|
| 703 |
+
|
| 704 |
+
name = property(lambda self: self._name)
|
| 705 |
+
"""The file name of the sound file."""
|
| 706 |
+
mode = property(lambda self: self._mode)
|
| 707 |
+
"""The open mode the sound file was opened with."""
|
| 708 |
+
samplerate = property(lambda self: self._info.samplerate)
|
| 709 |
+
"""The sample rate of the sound file."""
|
| 710 |
+
frames = property(lambda self: self._info.frames)
|
| 711 |
+
"""The number of frames in the sound file."""
|
| 712 |
+
channels = property(lambda self: self._info.channels)
|
| 713 |
+
"""The number of channels in the sound file."""
|
| 714 |
+
format = property(
|
| 715 |
+
lambda self: _format_str(self._info.format & _snd.SF_FORMAT_TYPEMASK))
|
| 716 |
+
"""The major format of the sound file."""
|
| 717 |
+
subtype = property(
|
| 718 |
+
lambda self: _format_str(self._info.format & _snd.SF_FORMAT_SUBMASK))
|
| 719 |
+
"""The subtype of data in the the sound file."""
|
| 720 |
+
endian = property(
|
| 721 |
+
lambda self: _format_str(self._info.format & _snd.SF_FORMAT_ENDMASK))
|
| 722 |
+
"""The endian-ness of the data in the sound file."""
|
| 723 |
+
format_info = property(
|
| 724 |
+
lambda self: _format_info(self._info.format &
|
| 725 |
+
_snd.SF_FORMAT_TYPEMASK)[1])
|
| 726 |
+
"""A description of the major format of the sound file."""
|
| 727 |
+
subtype_info = property(
|
| 728 |
+
lambda self: _format_info(self._info.format &
|
| 729 |
+
_snd.SF_FORMAT_SUBMASK)[1])
|
| 730 |
+
"""A description of the subtype of the sound file."""
|
| 731 |
+
sections = property(lambda self: self._info.sections)
|
| 732 |
+
"""The number of sections of the sound file."""
|
| 733 |
+
closed = property(lambda self: self._file is None)
|
| 734 |
+
"""Whether the sound file is closed or not."""
|
| 735 |
+
_errorcode = property(lambda self: _snd.sf_error(self._file))
|
| 736 |
+
"""A pending sndfile error code."""
|
| 737 |
+
compression_level = property(lambda self: self._compression_level)
|
| 738 |
+
"""The compression level on 'write()'"""
|
| 739 |
+
bitrate_mode = property(lambda self: self._bitrate_mode)
|
| 740 |
+
"""The bitrate mode on 'write()'"""
|
| 741 |
+
|
| 742 |
+
@property
|
| 743 |
+
def extra_info(self):
|
| 744 |
+
"""Retrieve the log string generated when opening the file."""
|
| 745 |
+
info = _ffi.new("char[]", 2**14)
|
| 746 |
+
_snd.sf_command(self._file, _snd.SFC_GET_LOG_INFO,
|
| 747 |
+
info, _ffi.sizeof(info))
|
| 748 |
+
return _ffi.string(info).decode('utf-8', 'replace')
|
| 749 |
+
|
| 750 |
+
# avoid confusion if something goes wrong before assigning self._file:
|
| 751 |
+
_file = None
|
| 752 |
+
|
| 753 |
+
def __repr__(self):
|
| 754 |
+
compression_setting = (", compression_level={0}".format(self.compression_level)
|
| 755 |
+
if self.compression_level is not None else "")
|
| 756 |
+
compression_setting += (", bitrate_mode='{0}'".format(self.bitrate_mode)
|
| 757 |
+
if self.bitrate_mode is not None else "")
|
| 758 |
+
return ("SoundFile({0.name!r}, mode={0.mode!r}, "
|
| 759 |
+
"samplerate={0.samplerate}, channels={0.channels}, "
|
| 760 |
+
"format={0.format!r}, subtype={0.subtype!r}, "
|
| 761 |
+
"endian={0.endian!r}{1})".format(self, compression_setting))
|
| 762 |
+
|
| 763 |
+
def __del__(self):
|
| 764 |
+
self.close()
|
| 765 |
+
|
| 766 |
+
def __enter__(self):
|
| 767 |
+
return self
|
| 768 |
+
|
| 769 |
+
def __exit__(self, *args):
|
| 770 |
+
self.close()
|
| 771 |
+
|
| 772 |
+
def __setattr__(self, name, value):
|
| 773 |
+
"""Write text meta-data in the sound file through properties."""
|
| 774 |
+
if name in _str_types:
|
| 775 |
+
self._check_if_closed()
|
| 776 |
+
err = _snd.sf_set_string(self._file, _str_types[name],
|
| 777 |
+
value.encode())
|
| 778 |
+
_error_check(err)
|
| 779 |
+
else:
|
| 780 |
+
object.__setattr__(self, name, value)
|
| 781 |
+
|
| 782 |
+
def __getattr__(self, name):
|
| 783 |
+
"""Read text meta-data in the sound file through properties."""
|
| 784 |
+
if name in _str_types:
|
| 785 |
+
self._check_if_closed()
|
| 786 |
+
data = _snd.sf_get_string(self._file, _str_types[name])
|
| 787 |
+
return _ffi.string(data).decode('utf-8', 'replace') if data else ""
|
| 788 |
+
else:
|
| 789 |
+
raise AttributeError(
|
| 790 |
+
"'SoundFile' object has no attribute {0!r}".format(name))
|
| 791 |
+
|
| 792 |
+
def __len__(self):
|
| 793 |
+
# Note: This is deprecated and will be removed at some point,
|
| 794 |
+
# see https://github.com/bastibe/python-soundfile/issues/199
|
| 795 |
+
return self._info.frames
|
| 796 |
+
|
| 797 |
+
def __bool__(self):
|
| 798 |
+
# Note: This is temporary until __len__ is removed, afterwards it
|
| 799 |
+
# can (and should) be removed without change of behavior
|
| 800 |
+
return True
|
| 801 |
+
|
| 802 |
+
def __nonzero__(self):
|
| 803 |
+
# Note: This is only for compatibility with Python 2 and it shall be
|
| 804 |
+
# removed at the same time as __bool__().
|
| 805 |
+
return self.__bool__()
|
| 806 |
+
|
| 807 |
+
def seekable(self):
|
| 808 |
+
"""Return True if the file supports seeking."""
|
| 809 |
+
return self._info.seekable == _snd.SF_TRUE
|
| 810 |
+
|
| 811 |
+
def seek(self, frames, whence=SEEK_SET):
|
| 812 |
+
"""Set the read/write position.
|
| 813 |
+
|
| 814 |
+
Parameters
|
| 815 |
+
----------
|
| 816 |
+
frames : int
|
| 817 |
+
The frame index or offset to seek.
|
| 818 |
+
whence : {SEEK_SET, SEEK_CUR, SEEK_END}, optional
|
| 819 |
+
By default (``whence=SEEK_SET``), *frames* are counted from
|
| 820 |
+
the beginning of the file.
|
| 821 |
+
``whence=SEEK_CUR`` seeks from the current position
|
| 822 |
+
(positive and negative values are allowed for *frames*).
|
| 823 |
+
``whence=SEEK_END`` seeks from the end (use negative value
|
| 824 |
+
for *frames*).
|
| 825 |
+
|
| 826 |
+
Returns
|
| 827 |
+
-------
|
| 828 |
+
int
|
| 829 |
+
The new absolute read/write position in frames.
|
| 830 |
+
|
| 831 |
+
Examples
|
| 832 |
+
--------
|
| 833 |
+
>>> from soundfile import SoundFile, SEEK_END
|
| 834 |
+
>>> myfile = SoundFile('stereo_file.wav')
|
| 835 |
+
|
| 836 |
+
Seek to the beginning of the file:
|
| 837 |
+
|
| 838 |
+
>>> myfile.seek(0)
|
| 839 |
+
0
|
| 840 |
+
|
| 841 |
+
Seek to the end of the file:
|
| 842 |
+
|
| 843 |
+
>>> myfile.seek(0, SEEK_END)
|
| 844 |
+
44100 # this is the file length
|
| 845 |
+
|
| 846 |
+
"""
|
| 847 |
+
self._check_if_closed()
|
| 848 |
+
position = _snd.sf_seek(self._file, frames, whence)
|
| 849 |
+
_error_check(self._errorcode)
|
| 850 |
+
return position
|
| 851 |
+
|
| 852 |
+
def tell(self):
|
| 853 |
+
"""Return the current read/write position."""
|
| 854 |
+
return self.seek(0, SEEK_CUR)
|
| 855 |
+
|
| 856 |
+
def read(self, frames=-1, dtype='float64', always_2d=False,
|
| 857 |
+
fill_value=None, out=None):
|
| 858 |
+
"""Read from the file and return data as NumPy array.
|
| 859 |
+
|
| 860 |
+
Reads the given number of frames in the given data format
|
| 861 |
+
starting at the current read/write position. This advances the
|
| 862 |
+
read/write position by the same number of frames.
|
| 863 |
+
By default, all frames from the current read/write position to
|
| 864 |
+
the end of the file are returned.
|
| 865 |
+
Use `seek()` to move the current read/write position.
|
| 866 |
+
|
| 867 |
+
Parameters
|
| 868 |
+
----------
|
| 869 |
+
frames : int, optional
|
| 870 |
+
The number of frames to read. If ``frames < 0``, the whole
|
| 871 |
+
rest of the file is read.
|
| 872 |
+
dtype : {'float64', 'float32', 'int32', 'int16'}, optional
|
| 873 |
+
Data type of the returned array, by default ``'float64'``.
|
| 874 |
+
Floating point audio data is typically in the range from
|
| 875 |
+
``-1.0`` to ``1.0``. Integer data is in the range from
|
| 876 |
+
``-2**15`` to ``2**15-1`` for ``'int16'`` and from
|
| 877 |
+
``-2**31`` to ``2**31-1`` for ``'int32'``.
|
| 878 |
+
|
| 879 |
+
.. note:: Reading int values from a float file will *not*
|
| 880 |
+
scale the data to [-1.0, 1.0). If the file contains
|
| 881 |
+
``np.array([42.6], dtype='float32')``, you will read
|
| 882 |
+
``np.array([43], dtype='int32')`` for
|
| 883 |
+
``dtype='int32'``.
|
| 884 |
+
|
| 885 |
+
Returns
|
| 886 |
+
-------
|
| 887 |
+
audiodata : `numpy.ndarray` or type(out)
|
| 888 |
+
A two-dimensional NumPy (frames x channels) array is
|
| 889 |
+
returned. If the sound file has only one channel, a
|
| 890 |
+
one-dimensional array is returned. Use ``always_2d=True``
|
| 891 |
+
to return a two-dimensional array anyway.
|
| 892 |
+
|
| 893 |
+
If *out* was specified, it is returned. If *out* has more
|
| 894 |
+
frames than available in the file (or if *frames* is
|
| 895 |
+
smaller than the length of *out*) and no *fill_value* is
|
| 896 |
+
given, then only a part of *out* is overwritten and a view
|
| 897 |
+
containing all valid frames is returned.
|
| 898 |
+
|
| 899 |
+
Other Parameters
|
| 900 |
+
----------------
|
| 901 |
+
always_2d : bool, optional
|
| 902 |
+
By default, reading a mono sound file will return a
|
| 903 |
+
one-dimensional array. With ``always_2d=True``, audio data
|
| 904 |
+
is always returned as a two-dimensional array, even if the
|
| 905 |
+
audio file has only one channel.
|
| 906 |
+
fill_value : float, optional
|
| 907 |
+
If more frames are requested than available in the file,
|
| 908 |
+
the rest of the output is be filled with *fill_value*. If
|
| 909 |
+
*fill_value* is not specified, a smaller array is
|
| 910 |
+
returned.
|
| 911 |
+
out : `numpy.ndarray` or subclass, optional
|
| 912 |
+
If *out* is specified, the data is written into the given
|
| 913 |
+
array instead of creating a new array. In this case, the
|
| 914 |
+
arguments *dtype* and *always_2d* are silently ignored! If
|
| 915 |
+
*frames* is not given, it is obtained from the length of
|
| 916 |
+
*out*.
|
| 917 |
+
|
| 918 |
+
Examples
|
| 919 |
+
--------
|
| 920 |
+
>>> from soundfile import SoundFile
|
| 921 |
+
>>> myfile = SoundFile('stereo_file.wav')
|
| 922 |
+
|
| 923 |
+
Reading 3 frames from a stereo file:
|
| 924 |
+
|
| 925 |
+
>>> myfile.read(3)
|
| 926 |
+
array([[ 0.71329652, 0.06294799],
|
| 927 |
+
[-0.26450912, -0.38874483],
|
| 928 |
+
[ 0.67398441, -0.11516333]])
|
| 929 |
+
>>> myfile.close()
|
| 930 |
+
|
| 931 |
+
See Also
|
| 932 |
+
--------
|
| 933 |
+
buffer_read, .write
|
| 934 |
+
|
| 935 |
+
"""
|
| 936 |
+
if out is None:
|
| 937 |
+
frames = self._check_frames(frames, fill_value)
|
| 938 |
+
out = self._create_empty_array(frames, always_2d, dtype)
|
| 939 |
+
else:
|
| 940 |
+
if frames < 0 or frames > len(out):
|
| 941 |
+
frames = len(out)
|
| 942 |
+
frames = self._array_io('read', out, frames)
|
| 943 |
+
if len(out) > frames:
|
| 944 |
+
if fill_value is None:
|
| 945 |
+
out = out[:frames]
|
| 946 |
+
else:
|
| 947 |
+
out[frames:] = fill_value
|
| 948 |
+
return out
|
| 949 |
+
|
| 950 |
+
def buffer_read(self, frames=-1, dtype=None):
|
| 951 |
+
"""Read from the file and return data as buffer object.
|
| 952 |
+
|
| 953 |
+
Reads the given number of *frames* in the given data format
|
| 954 |
+
starting at the current read/write position. This advances the
|
| 955 |
+
read/write position by the same number of frames.
|
| 956 |
+
By default, all frames from the current read/write position to
|
| 957 |
+
the end of the file are returned.
|
| 958 |
+
Use `seek()` to move the current read/write position.
|
| 959 |
+
|
| 960 |
+
Parameters
|
| 961 |
+
----------
|
| 962 |
+
frames : int, optional
|
| 963 |
+
The number of frames to read. If ``frames < 0``, the whole
|
| 964 |
+
rest of the file is read.
|
| 965 |
+
dtype : {'float64', 'float32', 'int32', 'int16'}
|
| 966 |
+
Audio data will be converted to the given data type.
|
| 967 |
+
|
| 968 |
+
Returns
|
| 969 |
+
-------
|
| 970 |
+
buffer
|
| 971 |
+
A buffer containing the read data.
|
| 972 |
+
|
| 973 |
+
See Also
|
| 974 |
+
--------
|
| 975 |
+
buffer_read_into, .read, buffer_write
|
| 976 |
+
|
| 977 |
+
"""
|
| 978 |
+
frames = self._check_frames(frames, fill_value=None)
|
| 979 |
+
ctype = self._check_dtype(dtype)
|
| 980 |
+
cdata = _ffi.new(ctype + '[]', frames * self.channels)
|
| 981 |
+
read_frames = self._cdata_io('read', cdata, ctype, frames)
|
| 982 |
+
assert read_frames == frames
|
| 983 |
+
return _ffi.buffer(cdata)
|
| 984 |
+
|
| 985 |
+
def buffer_read_into(self, buffer, dtype):
|
| 986 |
+
"""Read from the file into a given buffer object.
|
| 987 |
+
|
| 988 |
+
Fills the given *buffer* with frames in the given data format
|
| 989 |
+
starting at the current read/write position (which can be
|
| 990 |
+
changed with `seek()`) until the buffer is full or the end
|
| 991 |
+
of the file is reached. This advances the read/write position
|
| 992 |
+
by the number of frames that were read.
|
| 993 |
+
|
| 994 |
+
Parameters
|
| 995 |
+
----------
|
| 996 |
+
buffer : writable buffer
|
| 997 |
+
Audio frames from the file are written to this buffer.
|
| 998 |
+
dtype : {'float64', 'float32', 'int32', 'int16'}
|
| 999 |
+
The data type of *buffer*.
|
| 1000 |
+
|
| 1001 |
+
Returns
|
| 1002 |
+
-------
|
| 1003 |
+
int
|
| 1004 |
+
The number of frames that were read from the file.
|
| 1005 |
+
This can be less than the size of *buffer*.
|
| 1006 |
+
The rest of the buffer is not filled with meaningful data.
|
| 1007 |
+
|
| 1008 |
+
See Also
|
| 1009 |
+
--------
|
| 1010 |
+
buffer_read, .read
|
| 1011 |
+
|
| 1012 |
+
"""
|
| 1013 |
+
ctype = self._check_dtype(dtype)
|
| 1014 |
+
cdata, frames = self._check_buffer(buffer, ctype)
|
| 1015 |
+
frames = self._cdata_io('read', cdata, ctype, frames)
|
| 1016 |
+
return frames
|
| 1017 |
+
|
| 1018 |
+
def write(self, data):
|
| 1019 |
+
"""Write audio data from a NumPy array to the file.
|
| 1020 |
+
|
| 1021 |
+
Writes a number of frames at the read/write position to the
|
| 1022 |
+
file. This also advances the read/write position by the same
|
| 1023 |
+
number of frames and enlarges the file if necessary.
|
| 1024 |
+
|
| 1025 |
+
Note that writing int values to a float file will *not* scale
|
| 1026 |
+
the values to [-1.0, 1.0). If you write the value
|
| 1027 |
+
``np.array([42], dtype='int32')``, to a ``subtype='FLOAT'``
|
| 1028 |
+
file, the file will then contain ``np.array([42.],
|
| 1029 |
+
dtype='float32')``.
|
| 1030 |
+
|
| 1031 |
+
Parameters
|
| 1032 |
+
----------
|
| 1033 |
+
data : array_like
|
| 1034 |
+
The data to write. Usually two-dimensional (frames x
|
| 1035 |
+
channels), but one-dimensional *data* can be used for mono
|
| 1036 |
+
files. Only the data types ``'float64'``, ``'float32'``,
|
| 1037 |
+
``'int32'`` and ``'int16'`` are supported.
|
| 1038 |
+
|
| 1039 |
+
.. note:: The data type of *data* does **not** select the
|
| 1040 |
+
data type of the written file. Audio data will be
|
| 1041 |
+
converted to the given *subtype*. Writing int values
|
| 1042 |
+
to a float file will *not* scale the values to
|
| 1043 |
+
[-1.0, 1.0). If you write the value ``np.array([42],
|
| 1044 |
+
dtype='int32')``, to a ``subtype='FLOAT'`` file, the
|
| 1045 |
+
file will then contain ``np.array([42.],
|
| 1046 |
+
dtype='float32')``.
|
| 1047 |
+
|
| 1048 |
+
Examples
|
| 1049 |
+
--------
|
| 1050 |
+
>>> import numpy as np
|
| 1051 |
+
>>> from soundfile import SoundFile
|
| 1052 |
+
>>> myfile = SoundFile('stereo_file.wav')
|
| 1053 |
+
|
| 1054 |
+
Write 10 frames of random data to a new file:
|
| 1055 |
+
|
| 1056 |
+
>>> with SoundFile('stereo_file.wav', 'w', 44100, 2, 'PCM_24') as f:
|
| 1057 |
+
>>> f.write(np.random.randn(10, 2))
|
| 1058 |
+
|
| 1059 |
+
See Also
|
| 1060 |
+
--------
|
| 1061 |
+
buffer_write, .read
|
| 1062 |
+
|
| 1063 |
+
"""
|
| 1064 |
+
import numpy as np
|
| 1065 |
+
|
| 1066 |
+
# no copy is made if data has already the correct memory layout:
|
| 1067 |
+
data = np.ascontiguousarray(data)
|
| 1068 |
+
written = self._array_io('write', data, len(data))
|
| 1069 |
+
assert written == len(data)
|
| 1070 |
+
self._update_frames(written)
|
| 1071 |
+
|
| 1072 |
+
def buffer_write(self, data, dtype):
|
| 1073 |
+
"""Write audio data from a buffer/bytes object to the file.
|
| 1074 |
+
|
| 1075 |
+
Writes the contents of *data* to the file at the current
|
| 1076 |
+
read/write position.
|
| 1077 |
+
This also advances the read/write position by the number of
|
| 1078 |
+
frames that were written and enlarges the file if necessary.
|
| 1079 |
+
|
| 1080 |
+
Parameters
|
| 1081 |
+
----------
|
| 1082 |
+
data : buffer or bytes
|
| 1083 |
+
A buffer or bytes object containing the audio data to be
|
| 1084 |
+
written.
|
| 1085 |
+
dtype : {'float64', 'float32', 'int32', 'int16'}
|
| 1086 |
+
The data type of the audio data stored in *data*.
|
| 1087 |
+
|
| 1088 |
+
See Also
|
| 1089 |
+
--------
|
| 1090 |
+
.write, buffer_read
|
| 1091 |
+
|
| 1092 |
+
"""
|
| 1093 |
+
ctype = self._check_dtype(dtype)
|
| 1094 |
+
cdata, frames = self._check_buffer(data, ctype)
|
| 1095 |
+
written = self._cdata_io('write', cdata, ctype, frames)
|
| 1096 |
+
assert written == frames
|
| 1097 |
+
self._update_frames(written)
|
| 1098 |
+
|
| 1099 |
+
def blocks(self, blocksize=None, overlap=0, frames=-1, dtype='float64',
|
| 1100 |
+
always_2d=False, fill_value=None, out=None):
|
| 1101 |
+
"""Return a generator for block-wise reading.
|
| 1102 |
+
|
| 1103 |
+
By default, the generator yields blocks of the given
|
| 1104 |
+
*blocksize* (using a given *overlap*) until the end of the file
|
| 1105 |
+
is reached; *frames* can be used to stop earlier.
|
| 1106 |
+
|
| 1107 |
+
Parameters
|
| 1108 |
+
----------
|
| 1109 |
+
blocksize : int
|
| 1110 |
+
The number of frames to read per block. Either this or *out*
|
| 1111 |
+
must be given.
|
| 1112 |
+
overlap : int, optional
|
| 1113 |
+
The number of frames to rewind between each block.
|
| 1114 |
+
frames : int, optional
|
| 1115 |
+
The number of frames to read.
|
| 1116 |
+
If ``frames < 0``, the file is read until the end.
|
| 1117 |
+
dtype : {'float64', 'float32', 'int32', 'int16'}, optional
|
| 1118 |
+
See `read()`.
|
| 1119 |
+
|
| 1120 |
+
Yields
|
| 1121 |
+
------
|
| 1122 |
+
`numpy.ndarray` or type(out)
|
| 1123 |
+
Blocks of audio data.
|
| 1124 |
+
If *out* was given, and the requested frames are not an
|
| 1125 |
+
integer multiple of the length of *out*, and no
|
| 1126 |
+
*fill_value* was given, the last block will be a smaller
|
| 1127 |
+
view into *out*.
|
| 1128 |
+
|
| 1129 |
+
|
| 1130 |
+
Other Parameters
|
| 1131 |
+
----------------
|
| 1132 |
+
always_2d, fill_value, out
|
| 1133 |
+
See `read()`.
|
| 1134 |
+
fill_value : float, optional
|
| 1135 |
+
See `read()`.
|
| 1136 |
+
out : `numpy.ndarray` or subclass, optional
|
| 1137 |
+
If *out* is specified, the data is written into the given
|
| 1138 |
+
array instead of creating a new array. In this case, the
|
| 1139 |
+
arguments *dtype* and *always_2d* are silently ignored!
|
| 1140 |
+
|
| 1141 |
+
Examples
|
| 1142 |
+
--------
|
| 1143 |
+
>>> from soundfile import SoundFile
|
| 1144 |
+
>>> with SoundFile('stereo_file.wav') as f:
|
| 1145 |
+
>>> for block in f.blocks(blocksize=1024):
|
| 1146 |
+
>>> pass # do something with 'block'
|
| 1147 |
+
|
| 1148 |
+
"""
|
| 1149 |
+
import numpy as np
|
| 1150 |
+
|
| 1151 |
+
if 'r' not in self.mode and '+' not in self.mode:
|
| 1152 |
+
raise SoundFileRuntimeError("blocks() is not allowed in write-only mode")
|
| 1153 |
+
|
| 1154 |
+
frames = self._check_frames(frames, fill_value)
|
| 1155 |
+
if out is None:
|
| 1156 |
+
if blocksize is None:
|
| 1157 |
+
raise TypeError("One of {blocksize, out} must be specified")
|
| 1158 |
+
out_size = blocksize if fill_value is not None else min(blocksize, frames)
|
| 1159 |
+
out = self._create_empty_array(out_size, always_2d, dtype)
|
| 1160 |
+
copy_out = True
|
| 1161 |
+
else:
|
| 1162 |
+
if blocksize is not None:
|
| 1163 |
+
raise TypeError(
|
| 1164 |
+
"Only one of {blocksize, out} may be specified")
|
| 1165 |
+
blocksize = len(out)
|
| 1166 |
+
copy_out = False
|
| 1167 |
+
|
| 1168 |
+
overlap_memory = None
|
| 1169 |
+
while frames > 0:
|
| 1170 |
+
if overlap_memory is None:
|
| 1171 |
+
output_offset = 0
|
| 1172 |
+
else:
|
| 1173 |
+
output_offset = len(overlap_memory)
|
| 1174 |
+
out[:output_offset] = overlap_memory
|
| 1175 |
+
|
| 1176 |
+
toread = min(blocksize - output_offset, frames)
|
| 1177 |
+
self.read(toread, dtype, always_2d, fill_value, out[output_offset:])
|
| 1178 |
+
|
| 1179 |
+
if overlap:
|
| 1180 |
+
if overlap_memory is None:
|
| 1181 |
+
overlap_memory = np.copy(out[-overlap:])
|
| 1182 |
+
else:
|
| 1183 |
+
overlap_memory[:] = out[-overlap:]
|
| 1184 |
+
|
| 1185 |
+
if blocksize > frames + overlap and fill_value is None:
|
| 1186 |
+
block = out[:frames + overlap]
|
| 1187 |
+
else:
|
| 1188 |
+
block = out
|
| 1189 |
+
yield np.copy(block) if copy_out else block
|
| 1190 |
+
frames -= toread
|
| 1191 |
+
|
| 1192 |
+
def truncate(self, frames=None):
|
| 1193 |
+
"""Truncate the file to a given number of frames.
|
| 1194 |
+
|
| 1195 |
+
After this command, the read/write position will be at the new
|
| 1196 |
+
end of the file.
|
| 1197 |
+
|
| 1198 |
+
Parameters
|
| 1199 |
+
----------
|
| 1200 |
+
frames : int, optional
|
| 1201 |
+
Only the data before *frames* is kept, the rest is deleted.
|
| 1202 |
+
If not specified, the current read/write position is used.
|
| 1203 |
+
|
| 1204 |
+
"""
|
| 1205 |
+
if frames is None:
|
| 1206 |
+
frames = self.tell()
|
| 1207 |
+
err = _snd.sf_command(self._file, _snd.SFC_FILE_TRUNCATE,
|
| 1208 |
+
_ffi.new("sf_count_t*", frames),
|
| 1209 |
+
_ffi.sizeof("sf_count_t"))
|
| 1210 |
+
if err:
|
| 1211 |
+
# get the actual error code
|
| 1212 |
+
err = _snd.sf_error(self._file)
|
| 1213 |
+
raise LibsndfileError(err, "Error truncating the file")
|
| 1214 |
+
self._info.frames = frames
|
| 1215 |
+
|
| 1216 |
+
def flush(self):
|
| 1217 |
+
"""Write unwritten data to the file system.
|
| 1218 |
+
|
| 1219 |
+
Data written with `write()` is not immediately written to
|
| 1220 |
+
the file system but buffered in memory to be written at a later
|
| 1221 |
+
time. Calling `flush()` makes sure that all changes are
|
| 1222 |
+
actually written to the file system.
|
| 1223 |
+
|
| 1224 |
+
This has no effect on files opened in read-only mode.
|
| 1225 |
+
|
| 1226 |
+
"""
|
| 1227 |
+
self._check_if_closed()
|
| 1228 |
+
_snd.sf_write_sync(self._file)
|
| 1229 |
+
|
| 1230 |
+
def close(self):
|
| 1231 |
+
"""Close the file. Can be called multiple times."""
|
| 1232 |
+
if not self.closed:
|
| 1233 |
+
# be sure to flush data to disk before closing the file
|
| 1234 |
+
self.flush()
|
| 1235 |
+
err = _snd.sf_close(self._file)
|
| 1236 |
+
self._file = None
|
| 1237 |
+
_error_check(err)
|
| 1238 |
+
|
| 1239 |
+
def _open(self, file, mode_int, closefd):
|
| 1240 |
+
"""Call the appropriate sf_open*() function from libsndfile."""
|
| 1241 |
+
if isinstance(file, (_unicode, bytes)):
|
| 1242 |
+
if _os.path.isfile(file):
|
| 1243 |
+
if 'x' in self.mode:
|
| 1244 |
+
raise OSError("File exists: {0!r}".format(self.name))
|
| 1245 |
+
elif set(self.mode).issuperset('w+'):
|
| 1246 |
+
# truncate the file, because SFM_RDWR doesn't:
|
| 1247 |
+
_os.close(_os.open(file, _os.O_WRONLY | _os.O_TRUNC))
|
| 1248 |
+
openfunction = _snd.sf_open
|
| 1249 |
+
if isinstance(file, _unicode):
|
| 1250 |
+
if _sys.platform == 'win32':
|
| 1251 |
+
openfunction = _snd.sf_wchar_open
|
| 1252 |
+
else:
|
| 1253 |
+
file = file.encode(_sys.getfilesystemencoding())
|
| 1254 |
+
file_ptr = openfunction(file, mode_int, self._info)
|
| 1255 |
+
elif isinstance(file, int):
|
| 1256 |
+
file_ptr = _snd.sf_open_fd(file, mode_int, self._info, closefd)
|
| 1257 |
+
elif _has_virtual_io_attrs(file, mode_int):
|
| 1258 |
+
file_ptr = _snd.sf_open_virtual(self._init_virtual_io(file),
|
| 1259 |
+
mode_int, self._info, _ffi.NULL)
|
| 1260 |
+
else:
|
| 1261 |
+
raise TypeError("Invalid file: {0!r}".format(self.name))
|
| 1262 |
+
if file_ptr == _ffi.NULL:
|
| 1263 |
+
# get the actual error code
|
| 1264 |
+
err = _snd.sf_error(file_ptr)
|
| 1265 |
+
raise LibsndfileError(err, prefix="Error opening {0!r}: ".format(self.name))
|
| 1266 |
+
if mode_int == _snd.SFM_WRITE:
|
| 1267 |
+
# Due to a bug in libsndfile version <= 1.0.25, frames != 0
|
| 1268 |
+
# when opening a named pipe in SFM_WRITE mode.
|
| 1269 |
+
# See http://github.com/erikd/libsndfile/issues/77.
|
| 1270 |
+
self._info.frames = 0
|
| 1271 |
+
# This is not necessary for "normal" files (because
|
| 1272 |
+
# frames == 0 in this case), but it doesn't hurt, either.
|
| 1273 |
+
return file_ptr
|
| 1274 |
+
|
| 1275 |
+
def _init_virtual_io(self, file):
|
| 1276 |
+
"""Initialize callback functions for sf_open_virtual()."""
|
| 1277 |
+
@_ffi.callback("sf_vio_get_filelen")
|
| 1278 |
+
def vio_get_filelen(user_data):
|
| 1279 |
+
curr = file.tell()
|
| 1280 |
+
file.seek(0, SEEK_END)
|
| 1281 |
+
size = file.tell()
|
| 1282 |
+
file.seek(curr, SEEK_SET)
|
| 1283 |
+
return size
|
| 1284 |
+
|
| 1285 |
+
@_ffi.callback("sf_vio_seek")
|
| 1286 |
+
def vio_seek(offset, whence, user_data):
|
| 1287 |
+
file.seek(offset, whence)
|
| 1288 |
+
return file.tell()
|
| 1289 |
+
|
| 1290 |
+
@_ffi.callback("sf_vio_read")
|
| 1291 |
+
def vio_read(ptr, count, user_data):
|
| 1292 |
+
# first try readinto(), if not available fall back to read()
|
| 1293 |
+
try:
|
| 1294 |
+
buf = _ffi.buffer(ptr, count)
|
| 1295 |
+
data_read = file.readinto(buf)
|
| 1296 |
+
except AttributeError:
|
| 1297 |
+
data = file.read(count)
|
| 1298 |
+
data_read = len(data)
|
| 1299 |
+
buf = _ffi.buffer(ptr, data_read)
|
| 1300 |
+
buf[0:data_read] = data
|
| 1301 |
+
return data_read
|
| 1302 |
+
|
| 1303 |
+
@_ffi.callback("sf_vio_write")
|
| 1304 |
+
def vio_write(ptr, count, user_data):
|
| 1305 |
+
buf = _ffi.buffer(ptr, count)
|
| 1306 |
+
data = buf[:]
|
| 1307 |
+
written = file.write(data)
|
| 1308 |
+
# write() returns None for file objects in Python <= 2.7:
|
| 1309 |
+
if written is None:
|
| 1310 |
+
written = count
|
| 1311 |
+
return written
|
| 1312 |
+
|
| 1313 |
+
@_ffi.callback("sf_vio_tell")
|
| 1314 |
+
def vio_tell(user_data):
|
| 1315 |
+
return file.tell()
|
| 1316 |
+
|
| 1317 |
+
# Note: the callback functions must be kept alive!
|
| 1318 |
+
self._virtual_io = {'get_filelen': vio_get_filelen,
|
| 1319 |
+
'seek': vio_seek,
|
| 1320 |
+
'read': vio_read,
|
| 1321 |
+
'write': vio_write,
|
| 1322 |
+
'tell': vio_tell}
|
| 1323 |
+
|
| 1324 |
+
return _ffi.new("SF_VIRTUAL_IO*", self._virtual_io)
|
| 1325 |
+
|
| 1326 |
+
def _getAttributeNames(self):
|
| 1327 |
+
"""Return all attributes used in __setattr__ and __getattr__.
|
| 1328 |
+
|
| 1329 |
+
This is useful for auto-completion (e.g. IPython).
|
| 1330 |
+
|
| 1331 |
+
"""
|
| 1332 |
+
return _str_types
|
| 1333 |
+
|
| 1334 |
+
def _check_if_closed(self):
|
| 1335 |
+
"""Check if the file is closed and raise an error if it is.
|
| 1336 |
+
|
| 1337 |
+
This should be used in every method that uses self._file.
|
| 1338 |
+
|
| 1339 |
+
"""
|
| 1340 |
+
if self.closed:
|
| 1341 |
+
raise SoundFileRuntimeError("I/O operation on closed file")
|
| 1342 |
+
|
| 1343 |
+
def _check_frames(self, frames, fill_value):
|
| 1344 |
+
"""Reduce frames to no more than are available in the file."""
|
| 1345 |
+
if self.seekable():
|
| 1346 |
+
remaining_frames = self.frames - self.tell()
|
| 1347 |
+
if frames < 0 or (frames > remaining_frames and
|
| 1348 |
+
fill_value is None):
|
| 1349 |
+
frames = remaining_frames
|
| 1350 |
+
elif frames < 0:
|
| 1351 |
+
raise ValueError("frames must be specified for non-seekable files")
|
| 1352 |
+
return frames
|
| 1353 |
+
|
| 1354 |
+
def _check_buffer(self, data, ctype):
|
| 1355 |
+
"""Convert buffer to cdata and check for valid size."""
|
| 1356 |
+
assert ctype in _ffi_types.values()
|
| 1357 |
+
if not isinstance(data, bytes):
|
| 1358 |
+
data = _ffi.from_buffer(data)
|
| 1359 |
+
frames, remainder = divmod(len(data),
|
| 1360 |
+
self.channels * _ffi.sizeof(ctype))
|
| 1361 |
+
if remainder:
|
| 1362 |
+
raise ValueError("Data size must be a multiple of frame size")
|
| 1363 |
+
return data, frames
|
| 1364 |
+
|
| 1365 |
+
def _create_empty_array(self, frames, always_2d, dtype):
|
| 1366 |
+
"""Create an empty array with appropriate shape."""
|
| 1367 |
+
import numpy as np
|
| 1368 |
+
if always_2d or self.channels > 1:
|
| 1369 |
+
shape = frames, self.channels
|
| 1370 |
+
else:
|
| 1371 |
+
shape = frames,
|
| 1372 |
+
return np.empty(shape, dtype, order='C')
|
| 1373 |
+
|
| 1374 |
+
def _check_dtype(self, dtype):
|
| 1375 |
+
"""Check if dtype string is valid and return ctype string."""
|
| 1376 |
+
try:
|
| 1377 |
+
return _ffi_types[dtype]
|
| 1378 |
+
except KeyError:
|
| 1379 |
+
raise ValueError("dtype must be one of {0!r} and not {1!r}".format(
|
| 1380 |
+
sorted(_ffi_types.keys()), dtype))
|
| 1381 |
+
|
| 1382 |
+
def _array_io(self, action, array, frames):
|
| 1383 |
+
"""Check array and call low-level IO function."""
|
| 1384 |
+
if array.ndim not in (1,2):
|
| 1385 |
+
raise ValueError("Invalid shape: {0!r} ({1})".format(array.shape, "0 dimensions not supported" if array.ndim < 1 else "too many dimensions"))
|
| 1386 |
+
array_channels = 1 if array.ndim == 1 else array.shape[1]
|
| 1387 |
+
if array_channels != self.channels:
|
| 1388 |
+
raise ValueError("Invalid shape: {0!r} (Expected {1} channels, got {2})".format(array.shape, self.channels, array_channels))
|
| 1389 |
+
if not array.flags.c_contiguous:
|
| 1390 |
+
raise ValueError("Data must be C-contiguous")
|
| 1391 |
+
ctype = self._check_dtype(array.dtype.name)
|
| 1392 |
+
assert array.dtype.itemsize == _ffi.sizeof(ctype)
|
| 1393 |
+
cdata = _ffi.cast(ctype + '*', array.__array_interface__['data'][0])
|
| 1394 |
+
return self._cdata_io(action, cdata, ctype, frames)
|
| 1395 |
+
|
| 1396 |
+
def _cdata_io(self, action, data, ctype, frames):
|
| 1397 |
+
"""Call one of libsndfile's read/write functions."""
|
| 1398 |
+
assert ctype in _ffi_types.values()
|
| 1399 |
+
self._check_if_closed()
|
| 1400 |
+
if self.seekable():
|
| 1401 |
+
curr = self.tell()
|
| 1402 |
+
func = getattr(_snd, 'sf_' + action + 'f_' + ctype)
|
| 1403 |
+
frames = func(self._file, data, frames)
|
| 1404 |
+
_error_check(self._errorcode)
|
| 1405 |
+
if self.seekable():
|
| 1406 |
+
self.seek(curr + frames, SEEK_SET) # Update read & write position
|
| 1407 |
+
return frames
|
| 1408 |
+
|
| 1409 |
+
def _update_frames(self, written):
|
| 1410 |
+
"""Update self.frames after writing."""
|
| 1411 |
+
if self.seekable():
|
| 1412 |
+
curr = self.tell()
|
| 1413 |
+
self._info.frames = self.seek(0, SEEK_END)
|
| 1414 |
+
self.seek(curr, SEEK_SET)
|
| 1415 |
+
else:
|
| 1416 |
+
self._info.frames += written
|
| 1417 |
+
|
| 1418 |
+
def _prepare_read(self, start, stop, frames):
|
| 1419 |
+
"""Seek to start frame and calculate length."""
|
| 1420 |
+
if start != 0 and not self.seekable():
|
| 1421 |
+
raise ValueError("start is only allowed for seekable files")
|
| 1422 |
+
if frames >= 0 and stop is not None:
|
| 1423 |
+
raise TypeError("Only one of {frames, stop} may be used")
|
| 1424 |
+
|
| 1425 |
+
start, stop, _ = slice(start, stop).indices(self.frames)
|
| 1426 |
+
if stop < start:
|
| 1427 |
+
stop = start
|
| 1428 |
+
if frames < 0:
|
| 1429 |
+
frames = stop - start
|
| 1430 |
+
if self.seekable():
|
| 1431 |
+
self.seek(start, SEEK_SET)
|
| 1432 |
+
return frames
|
| 1433 |
+
|
| 1434 |
+
def copy_metadata(self):
|
| 1435 |
+
"""Get all metadata present in this SoundFile
|
| 1436 |
+
|
| 1437 |
+
Returns
|
| 1438 |
+
-------
|
| 1439 |
+
|
| 1440 |
+
metadata: dict[str, str]
|
| 1441 |
+
A dict with all metadata. Possible keys are: 'title', 'copyright',
|
| 1442 |
+
'software', 'artist', 'comment', 'date', 'album', 'license',
|
| 1443 |
+
'tracknumber' and 'genre'.
|
| 1444 |
+
"""
|
| 1445 |
+
strs = {}
|
| 1446 |
+
for strtype, strid in _str_types.items():
|
| 1447 |
+
data = _snd.sf_get_string(self._file, strid)
|
| 1448 |
+
if data:
|
| 1449 |
+
strs[strtype] = _ffi.string(data).decode('utf-8', 'replace')
|
| 1450 |
+
return strs
|
| 1451 |
+
|
| 1452 |
+
def _set_bitrate_mode(self, bitrate_mode):
|
| 1453 |
+
"""Call libsndfile's set bitrate mode function."""
|
| 1454 |
+
assert bitrate_mode in _bitrate_modes
|
| 1455 |
+
|
| 1456 |
+
pointer_bitrate_mode = _ffi.new("int[1]")
|
| 1457 |
+
pointer_bitrate_mode[0] = _bitrate_modes[bitrate_mode]
|
| 1458 |
+
err = _snd.sf_command(self._file, _snd.SFC_SET_BITRATE_MODE, pointer_bitrate_mode, _ffi.sizeof(pointer_bitrate_mode))
|
| 1459 |
+
if err != _snd.SF_TRUE:
|
| 1460 |
+
err = _snd.sf_error(self._file)
|
| 1461 |
+
raise LibsndfileError(err, f"Error set bitrate mode {bitrate_mode}")
|
| 1462 |
+
|
| 1463 |
+
|
| 1464 |
+
def _set_compression_level(self, compression_level):
|
| 1465 |
+
"""Call libsndfile's set compression level function."""
|
| 1466 |
+
if not (0 <= compression_level <= 1):
|
| 1467 |
+
raise ValueError("Compression level must be in range [0..1]")
|
| 1468 |
+
|
| 1469 |
+
pointer_compression_level = _ffi.new("double[1]")
|
| 1470 |
+
pointer_compression_level[0] = compression_level
|
| 1471 |
+
err = _snd.sf_command(self._file, _snd.SFC_SET_COMPRESSION_LEVEL, pointer_compression_level, _ffi.sizeof(pointer_compression_level))
|
| 1472 |
+
if err != _snd.SF_TRUE:
|
| 1473 |
+
err = _snd.sf_error(self._file)
|
| 1474 |
+
raise LibsndfileError(err, f"Error set compression level {compression_level}")
|
| 1475 |
+
|
| 1476 |
+
|
| 1477 |
+
def _error_check(err, prefix=""):
|
| 1478 |
+
"""Raise LibsndfileError if there is an error."""
|
| 1479 |
+
if err != 0:
|
| 1480 |
+
raise LibsndfileError(err, prefix=prefix)
|
| 1481 |
+
|
| 1482 |
+
|
| 1483 |
+
def _format_int(format, subtype, endian):
|
| 1484 |
+
"""Return numeric ID for given format|subtype|endian combo."""
|
| 1485 |
+
result = _check_format(format)
|
| 1486 |
+
if subtype is None:
|
| 1487 |
+
subtype = default_subtype(format)
|
| 1488 |
+
if subtype is None:
|
| 1489 |
+
raise TypeError(
|
| 1490 |
+
"No default subtype for major format {0!r}".format(format))
|
| 1491 |
+
elif not isinstance(subtype, (_unicode, str)):
|
| 1492 |
+
raise TypeError("Invalid subtype: {0!r}".format(subtype))
|
| 1493 |
+
try:
|
| 1494 |
+
result |= _subtypes[subtype.upper()]
|
| 1495 |
+
except KeyError:
|
| 1496 |
+
raise ValueError("Unknown subtype: {0!r}".format(subtype))
|
| 1497 |
+
if endian is None:
|
| 1498 |
+
endian = 'FILE'
|
| 1499 |
+
elif not isinstance(endian, (_unicode, str)):
|
| 1500 |
+
raise TypeError("Invalid endian-ness: {0!r}".format(endian))
|
| 1501 |
+
try:
|
| 1502 |
+
result |= _endians[endian.upper()]
|
| 1503 |
+
except KeyError:
|
| 1504 |
+
raise ValueError("Unknown endian-ness: {0!r}".format(endian))
|
| 1505 |
+
|
| 1506 |
+
info = _ffi.new("SF_INFO*")
|
| 1507 |
+
info.format = result
|
| 1508 |
+
info.channels = 1
|
| 1509 |
+
if _snd.sf_format_check(info) == _snd.SF_FALSE:
|
| 1510 |
+
raise ValueError(
|
| 1511 |
+
"Invalid combination of format, subtype and endian")
|
| 1512 |
+
return result
|
| 1513 |
+
|
| 1514 |
+
|
| 1515 |
+
def _check_mode(mode):
|
| 1516 |
+
"""Check if mode is valid and return its integer representation."""
|
| 1517 |
+
if not isinstance(mode, (_unicode, str)):
|
| 1518 |
+
raise TypeError("Invalid mode: {0!r}".format(mode))
|
| 1519 |
+
mode_set = set(mode)
|
| 1520 |
+
if mode_set.difference('xrwb+') or len(mode) > len(mode_set):
|
| 1521 |
+
raise ValueError("Invalid mode: {0!r}".format(mode))
|
| 1522 |
+
if len(mode_set.intersection('xrw')) != 1:
|
| 1523 |
+
raise ValueError("mode must contain exactly one of 'xrw'")
|
| 1524 |
+
|
| 1525 |
+
if '+' in mode_set:
|
| 1526 |
+
mode_int = _snd.SFM_RDWR
|
| 1527 |
+
elif 'r' in mode_set:
|
| 1528 |
+
mode_int = _snd.SFM_READ
|
| 1529 |
+
else:
|
| 1530 |
+
mode_int = _snd.SFM_WRITE
|
| 1531 |
+
return mode_int
|
| 1532 |
+
|
| 1533 |
+
|
| 1534 |
+
def _create_info_struct(file, mode, samplerate, channels,
|
| 1535 |
+
format, subtype, endian):
|
| 1536 |
+
"""Check arguments and create SF_INFO struct."""
|
| 1537 |
+
original_format = format
|
| 1538 |
+
if format is None:
|
| 1539 |
+
format = _get_format_from_filename(file, mode)
|
| 1540 |
+
assert isinstance(format, (_unicode, str))
|
| 1541 |
+
else:
|
| 1542 |
+
_check_format(format)
|
| 1543 |
+
|
| 1544 |
+
info = _ffi.new("SF_INFO*")
|
| 1545 |
+
if 'r' not in mode or format.upper() == 'RAW':
|
| 1546 |
+
if samplerate is None:
|
| 1547 |
+
raise TypeError("samplerate must be specified")
|
| 1548 |
+
info.samplerate = samplerate
|
| 1549 |
+
if channels is None:
|
| 1550 |
+
raise TypeError("channels must be specified")
|
| 1551 |
+
info.channels = channels
|
| 1552 |
+
info.format = _format_int(format, subtype, endian)
|
| 1553 |
+
else:
|
| 1554 |
+
if any(arg is not None for arg in (
|
| 1555 |
+
samplerate, channels, original_format, subtype, endian)):
|
| 1556 |
+
raise TypeError("Not allowed for existing files (except 'RAW'): "
|
| 1557 |
+
"samplerate, channels, format, subtype, endian")
|
| 1558 |
+
return info
|
| 1559 |
+
|
| 1560 |
+
|
| 1561 |
+
def _get_format_from_filename(file, mode):
|
| 1562 |
+
"""Return a format string obtained from file (or file.name).
|
| 1563 |
+
|
| 1564 |
+
If file already exists (= read mode), an empty string is returned on
|
| 1565 |
+
error. If not, an exception is raised.
|
| 1566 |
+
The return type will always be str or unicode (even if
|
| 1567 |
+
file/file.name is a bytes object).
|
| 1568 |
+
|
| 1569 |
+
"""
|
| 1570 |
+
format = ''
|
| 1571 |
+
file = getattr(file, 'name', file)
|
| 1572 |
+
try:
|
| 1573 |
+
# This raises an exception if file is not a (Unicode/byte) string:
|
| 1574 |
+
format = _os.path.splitext(file)[-1][1:]
|
| 1575 |
+
# Convert bytes to unicode (raises AttributeError on Python 3 str):
|
| 1576 |
+
format = format.decode('utf-8', 'replace')
|
| 1577 |
+
except Exception:
|
| 1578 |
+
pass
|
| 1579 |
+
if format.upper() not in _formats and 'r' not in mode:
|
| 1580 |
+
raise TypeError("No format specified and unable to get format from "
|
| 1581 |
+
"file extension: {0!r}".format(file))
|
| 1582 |
+
return format
|
| 1583 |
+
|
| 1584 |
+
|
| 1585 |
+
def _format_str(format_int):
|
| 1586 |
+
"""Return the string representation of a given numeric format."""
|
| 1587 |
+
for dictionary in _formats, _subtypes, _endians:
|
| 1588 |
+
for k, v in dictionary.items():
|
| 1589 |
+
if v == format_int:
|
| 1590 |
+
return k
|
| 1591 |
+
else:
|
| 1592 |
+
return 'n/a'
|
| 1593 |
+
|
| 1594 |
+
|
| 1595 |
+
def _format_info(format_int, format_flag=_snd.SFC_GET_FORMAT_INFO):
|
| 1596 |
+
"""Return the ID and short description of a given format."""
|
| 1597 |
+
format_info = _ffi.new("SF_FORMAT_INFO*")
|
| 1598 |
+
format_info.format = format_int
|
| 1599 |
+
_snd.sf_command(_ffi.NULL, format_flag, format_info,
|
| 1600 |
+
_ffi.sizeof("SF_FORMAT_INFO"))
|
| 1601 |
+
name = format_info.name
|
| 1602 |
+
return (_format_str(format_info.format),
|
| 1603 |
+
_ffi.string(name).decode('utf-8', 'replace') if name else "")
|
| 1604 |
+
|
| 1605 |
+
|
| 1606 |
+
def _available_formats_helper(count_flag, format_flag):
|
| 1607 |
+
"""Helper for available_formats() and available_subtypes()."""
|
| 1608 |
+
count = _ffi.new("int*")
|
| 1609 |
+
_snd.sf_command(_ffi.NULL, count_flag, count, _ffi.sizeof("int"))
|
| 1610 |
+
for format_int in range(count[0]):
|
| 1611 |
+
yield _format_info(format_int, format_flag)
|
| 1612 |
+
|
| 1613 |
+
|
| 1614 |
+
def _check_format(format_str):
|
| 1615 |
+
"""Check if `format_str` is valid and return format ID."""
|
| 1616 |
+
if not isinstance(format_str, (_unicode, str)):
|
| 1617 |
+
raise TypeError("Invalid format: {0!r}".format(format_str))
|
| 1618 |
+
try:
|
| 1619 |
+
format_int = _formats[format_str.upper()]
|
| 1620 |
+
except KeyError:
|
| 1621 |
+
raise ValueError("Unknown format: {0!r}".format(format_str))
|
| 1622 |
+
return format_int
|
| 1623 |
+
|
| 1624 |
+
|
| 1625 |
+
def _has_virtual_io_attrs(file, mode_int):
|
| 1626 |
+
"""Check if file has all the necessary attributes for virtual IO."""
|
| 1627 |
+
readonly = mode_int == _snd.SFM_READ
|
| 1628 |
+
writeonly = mode_int == _snd.SFM_WRITE
|
| 1629 |
+
return all([
|
| 1630 |
+
hasattr(file, 'seek'),
|
| 1631 |
+
hasattr(file, 'tell'),
|
| 1632 |
+
hasattr(file, 'write') or readonly,
|
| 1633 |
+
hasattr(file, 'read') or hasattr(file, 'readinto') or writeonly,
|
| 1634 |
+
])
|
| 1635 |
+
|
| 1636 |
+
|
| 1637 |
+
class SoundFileError(Exception):
|
| 1638 |
+
"""Base class for all soundfile-specific errors."""
|
| 1639 |
+
pass
|
| 1640 |
+
|
| 1641 |
+
class SoundFileRuntimeError(SoundFileError, RuntimeError):
|
| 1642 |
+
"""soundfile module runtime error.
|
| 1643 |
+
|
| 1644 |
+
Errors that used to be `RuntimeError`."""
|
| 1645 |
+
pass
|
| 1646 |
+
|
| 1647 |
+
class LibsndfileError(SoundFileRuntimeError):
|
| 1648 |
+
"""libsndfile errors.
|
| 1649 |
+
|
| 1650 |
+
|
| 1651 |
+
Attributes
|
| 1652 |
+
----------
|
| 1653 |
+
code
|
| 1654 |
+
libsndfile internal error number.
|
| 1655 |
+
"""
|
| 1656 |
+
def __init__(self, code, prefix=""):
|
| 1657 |
+
SoundFileRuntimeError.__init__(self, code, prefix)
|
| 1658 |
+
self.code = code
|
| 1659 |
+
self.prefix = prefix
|
| 1660 |
+
|
| 1661 |
+
@property
|
| 1662 |
+
def error_string(self):
|
| 1663 |
+
"""Raw libsndfile error message."""
|
| 1664 |
+
if self.code:
|
| 1665 |
+
err_str = _snd.sf_error_number(self.code)
|
| 1666 |
+
return _ffi.string(err_str).decode('utf-8', 'replace')
|
| 1667 |
+
else:
|
| 1668 |
+
# Due to race conditions, if used concurrently, sf_error() may
|
| 1669 |
+
# return 0 (= no error) even if an error has happened.
|
| 1670 |
+
# See https://github.com/erikd/libsndfile/issues/610 for details.
|
| 1671 |
+
return "(Garbled error message from libsndfile)"
|
| 1672 |
+
|
| 1673 |
+
def __str__(self):
|
| 1674 |
+
return self.prefix + self.error_string
|
venv/lib/python3.10/site-packages/threadpoolctl.py
ADDED
|
@@ -0,0 +1,1292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""threadpoolctl
|
| 2 |
+
|
| 3 |
+
This module provides utilities to introspect native libraries that relies on
|
| 4 |
+
thread pools (notably BLAS and OpenMP implementations) and dynamically set the
|
| 5 |
+
maximal number of threads they can use.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
# License: BSD 3-Clause
|
| 9 |
+
|
| 10 |
+
# The code to introspect dynamically loaded libraries on POSIX systems is
|
| 11 |
+
# adapted from code by Intel developer @anton-malakhov available at
|
| 12 |
+
# https://github.com/IntelPython/smp (Copyright (c) 2017, Intel Corporation)
|
| 13 |
+
# and also published under the BSD 3-Clause license
|
| 14 |
+
import os
|
| 15 |
+
import re
|
| 16 |
+
import sys
|
| 17 |
+
import ctypes
|
| 18 |
+
import itertools
|
| 19 |
+
import textwrap
|
| 20 |
+
from typing import final
|
| 21 |
+
import warnings
|
| 22 |
+
from ctypes.util import find_library
|
| 23 |
+
from abc import ABC, abstractmethod
|
| 24 |
+
from functools import lru_cache
|
| 25 |
+
from contextlib import ContextDecorator
|
| 26 |
+
|
| 27 |
+
__version__ = "3.6.0"
|
| 28 |
+
__all__ = [
|
| 29 |
+
"threadpool_limits",
|
| 30 |
+
"threadpool_info",
|
| 31 |
+
"ThreadpoolController",
|
| 32 |
+
"LibController",
|
| 33 |
+
"register",
|
| 34 |
+
]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
# One can get runtime errors or even segfaults due to multiple OpenMP libraries
|
| 38 |
+
# loaded simultaneously which can happen easily in Python when importing and
|
| 39 |
+
# using compiled extensions built with different compilers and therefore
|
| 40 |
+
# different OpenMP runtimes in the same program. In particular libiomp (used by
|
| 41 |
+
# Intel ICC) and libomp used by clang/llvm tend to crash. This can happen for
|
| 42 |
+
# instance when calling BLAS inside a prange. Setting the following environment
|
| 43 |
+
# variable allows multiple OpenMP libraries to be loaded. It should not degrade
|
| 44 |
+
# performances since we manually take care of potential over-subscription
|
| 45 |
+
# performance issues, in sections of the code where nested OpenMP loops can
|
| 46 |
+
# happen, by dynamically reconfiguring the inner OpenMP runtime to temporarily
|
| 47 |
+
# disable it while under the scope of the outer OpenMP parallel section.
|
| 48 |
+
os.environ.setdefault("KMP_DUPLICATE_LIB_OK", "True")
|
| 49 |
+
|
| 50 |
+
# Structure to cast the info on dynamically loaded library. See
|
| 51 |
+
# https://linux.die.net/man/3/dl_iterate_phdr for more details.
|
| 52 |
+
_SYSTEM_UINT = ctypes.c_uint64 if sys.maxsize > 2**32 else ctypes.c_uint32
|
| 53 |
+
_SYSTEM_UINT_HALF = ctypes.c_uint32 if sys.maxsize > 2**32 else ctypes.c_uint16
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class _dl_phdr_info(ctypes.Structure):
|
| 57 |
+
_fields_ = [
|
| 58 |
+
("dlpi_addr", _SYSTEM_UINT), # Base address of object
|
| 59 |
+
("dlpi_name", ctypes.c_char_p), # path to the library
|
| 60 |
+
("dlpi_phdr", ctypes.c_void_p), # pointer on dlpi_headers
|
| 61 |
+
("dlpi_phnum", _SYSTEM_UINT_HALF), # number of elements in dlpi_phdr
|
| 62 |
+
]
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
# The RTLD_NOLOAD flag for loading shared libraries is not defined on Windows.
|
| 66 |
+
try:
|
| 67 |
+
_RTLD_NOLOAD = os.RTLD_NOLOAD
|
| 68 |
+
except AttributeError:
|
| 69 |
+
_RTLD_NOLOAD = ctypes.DEFAULT_MODE
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class LibController(ABC):
|
| 73 |
+
"""Abstract base class for the individual library controllers
|
| 74 |
+
|
| 75 |
+
A library controller must expose the following class attributes:
|
| 76 |
+
- user_api : str
|
| 77 |
+
Usually the name of the library or generic specification the library
|
| 78 |
+
implements, e.g. "blas" is a specification with different implementations.
|
| 79 |
+
- internal_api : str
|
| 80 |
+
Usually the name of the library or concrete implementation of some
|
| 81 |
+
specification, e.g. "openblas" is an implementation of the "blas"
|
| 82 |
+
specification.
|
| 83 |
+
- filename_prefixes : tuple
|
| 84 |
+
Possible prefixes of the shared library's filename that allow to
|
| 85 |
+
identify the library. e.g. "libopenblas" for libopenblas.so.
|
| 86 |
+
|
| 87 |
+
and implement the following methods: `get_num_threads`, `set_num_threads` and
|
| 88 |
+
`get_version`.
|
| 89 |
+
|
| 90 |
+
Threadpoolctl loops through all the loaded shared libraries and tries to match
|
| 91 |
+
the filename of each library with the `filename_prefixes`. If a match is found, a
|
| 92 |
+
controller is instantiated and a handler to the library is stored in the `dynlib`
|
| 93 |
+
attribute as a `ctypes.CDLL` object. It can be used to access the necessary symbols
|
| 94 |
+
of the shared library to implement the above methods.
|
| 95 |
+
|
| 96 |
+
The following information will be exposed in the info dictionary:
|
| 97 |
+
- user_api : standardized API, if any, or a copy of internal_api.
|
| 98 |
+
- internal_api : implementation-specific API.
|
| 99 |
+
- num_threads : the current thread limit.
|
| 100 |
+
- prefix : prefix of the shared library's filename.
|
| 101 |
+
- filepath : path to the loaded shared library.
|
| 102 |
+
- version : version of the library (if available).
|
| 103 |
+
|
| 104 |
+
In addition, each library controller may expose internal API specific entries. They
|
| 105 |
+
must be set as attributes in the `set_additional_attributes` method.
|
| 106 |
+
"""
|
| 107 |
+
|
| 108 |
+
@final
|
| 109 |
+
def __init__(self, *, filepath=None, prefix=None, parent=None):
|
| 110 |
+
"""This is not meant to be overriden by subclasses."""
|
| 111 |
+
self.parent = parent
|
| 112 |
+
self.prefix = prefix
|
| 113 |
+
self.filepath = filepath
|
| 114 |
+
self.dynlib = ctypes.CDLL(filepath, mode=_RTLD_NOLOAD)
|
| 115 |
+
self._symbol_prefix, self._symbol_suffix = self._find_affixes()
|
| 116 |
+
self.version = self.get_version()
|
| 117 |
+
self.set_additional_attributes()
|
| 118 |
+
|
| 119 |
+
def info(self):
|
| 120 |
+
"""Return relevant info wrapped in a dict"""
|
| 121 |
+
hidden_attrs = ("dynlib", "parent", "_symbol_prefix", "_symbol_suffix")
|
| 122 |
+
return {
|
| 123 |
+
"user_api": self.user_api,
|
| 124 |
+
"internal_api": self.internal_api,
|
| 125 |
+
"num_threads": self.num_threads,
|
| 126 |
+
**{k: v for k, v in vars(self).items() if k not in hidden_attrs},
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
def set_additional_attributes(self):
|
| 130 |
+
"""Set additional attributes meant to be exposed in the info dict"""
|
| 131 |
+
|
| 132 |
+
@property
|
| 133 |
+
def num_threads(self):
|
| 134 |
+
"""Exposes the current thread limit as a dynamic property
|
| 135 |
+
|
| 136 |
+
This is not meant to be used or overriden by subclasses.
|
| 137 |
+
"""
|
| 138 |
+
return self.get_num_threads()
|
| 139 |
+
|
| 140 |
+
@abstractmethod
|
| 141 |
+
def get_num_threads(self):
|
| 142 |
+
"""Return the maximum number of threads available to use"""
|
| 143 |
+
|
| 144 |
+
@abstractmethod
|
| 145 |
+
def set_num_threads(self, num_threads):
|
| 146 |
+
"""Set the maximum number of threads to use"""
|
| 147 |
+
|
| 148 |
+
@abstractmethod
|
| 149 |
+
def get_version(self):
|
| 150 |
+
"""Return the version of the shared library"""
|
| 151 |
+
|
| 152 |
+
def _find_affixes(self):
|
| 153 |
+
"""Return the affixes for the symbols of the shared library"""
|
| 154 |
+
return "", ""
|
| 155 |
+
|
| 156 |
+
def _get_symbol(self, name):
|
| 157 |
+
"""Return the symbol of the shared library accounding for the affixes"""
|
| 158 |
+
return getattr(
|
| 159 |
+
self.dynlib, f"{self._symbol_prefix}{name}{self._symbol_suffix}", None
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
class OpenBLASController(LibController):
|
| 164 |
+
"""Controller class for OpenBLAS"""
|
| 165 |
+
|
| 166 |
+
user_api = "blas"
|
| 167 |
+
internal_api = "openblas"
|
| 168 |
+
filename_prefixes = ("libopenblas", "libblas", "libscipy_openblas")
|
| 169 |
+
|
| 170 |
+
_symbol_prefixes = ("", "scipy_")
|
| 171 |
+
_symbol_suffixes = ("", "64_", "_64")
|
| 172 |
+
|
| 173 |
+
# All variations of "openblas_get_num_threads", accounting for the affixes
|
| 174 |
+
check_symbols = tuple(
|
| 175 |
+
f"{prefix}openblas_get_num_threads{suffix}"
|
| 176 |
+
for prefix, suffix in itertools.product(_symbol_prefixes, _symbol_suffixes)
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
def _find_affixes(self):
|
| 180 |
+
for prefix, suffix in itertools.product(
|
| 181 |
+
self._symbol_prefixes, self._symbol_suffixes
|
| 182 |
+
):
|
| 183 |
+
if hasattr(self.dynlib, f"{prefix}openblas_get_num_threads{suffix}"):
|
| 184 |
+
return prefix, suffix
|
| 185 |
+
|
| 186 |
+
def set_additional_attributes(self):
|
| 187 |
+
self.threading_layer = self._get_threading_layer()
|
| 188 |
+
self.architecture = self._get_architecture()
|
| 189 |
+
|
| 190 |
+
def get_num_threads(self):
|
| 191 |
+
get_num_threads_func = self._get_symbol("openblas_get_num_threads")
|
| 192 |
+
if get_num_threads_func is not None:
|
| 193 |
+
return get_num_threads_func()
|
| 194 |
+
return None
|
| 195 |
+
|
| 196 |
+
def set_num_threads(self, num_threads):
|
| 197 |
+
set_num_threads_func = self._get_symbol("openblas_set_num_threads")
|
| 198 |
+
if set_num_threads_func is not None:
|
| 199 |
+
return set_num_threads_func(num_threads)
|
| 200 |
+
return None
|
| 201 |
+
|
| 202 |
+
def get_version(self):
|
| 203 |
+
# None means OpenBLAS is not loaded or version < 0.3.4, since OpenBLAS
|
| 204 |
+
# did not expose its version before that.
|
| 205 |
+
get_version_func = self._get_symbol("openblas_get_config")
|
| 206 |
+
if get_version_func is not None:
|
| 207 |
+
get_version_func.restype = ctypes.c_char_p
|
| 208 |
+
config = get_version_func().split()
|
| 209 |
+
if config[0] == b"OpenBLAS":
|
| 210 |
+
return config[1].decode("utf-8")
|
| 211 |
+
return None
|
| 212 |
+
return None
|
| 213 |
+
|
| 214 |
+
def _get_threading_layer(self):
|
| 215 |
+
"""Return the threading layer of OpenBLAS"""
|
| 216 |
+
get_threading_layer_func = self._get_symbol("openblas_get_parallel")
|
| 217 |
+
if get_threading_layer_func is not None:
|
| 218 |
+
threading_layer = get_threading_layer_func()
|
| 219 |
+
if threading_layer == 2:
|
| 220 |
+
return "openmp"
|
| 221 |
+
elif threading_layer == 1:
|
| 222 |
+
return "pthreads"
|
| 223 |
+
return "disabled"
|
| 224 |
+
return "unknown"
|
| 225 |
+
|
| 226 |
+
def _get_architecture(self):
|
| 227 |
+
"""Return the architecture detected by OpenBLAS"""
|
| 228 |
+
get_architecture_func = self._get_symbol("openblas_get_corename")
|
| 229 |
+
if get_architecture_func is not None:
|
| 230 |
+
get_architecture_func.restype = ctypes.c_char_p
|
| 231 |
+
return get_architecture_func().decode("utf-8")
|
| 232 |
+
return None
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
class BLISController(LibController):
|
| 236 |
+
"""Controller class for BLIS"""
|
| 237 |
+
|
| 238 |
+
user_api = "blas"
|
| 239 |
+
internal_api = "blis"
|
| 240 |
+
filename_prefixes = ("libblis", "libblas")
|
| 241 |
+
check_symbols = (
|
| 242 |
+
"bli_thread_get_num_threads",
|
| 243 |
+
"bli_thread_set_num_threads",
|
| 244 |
+
"bli_info_get_version_str",
|
| 245 |
+
"bli_info_get_enable_openmp",
|
| 246 |
+
"bli_info_get_enable_pthreads",
|
| 247 |
+
"bli_arch_query_id",
|
| 248 |
+
"bli_arch_string",
|
| 249 |
+
)
|
| 250 |
+
|
| 251 |
+
def set_additional_attributes(self):
|
| 252 |
+
self.threading_layer = self._get_threading_layer()
|
| 253 |
+
self.architecture = self._get_architecture()
|
| 254 |
+
|
| 255 |
+
def get_num_threads(self):
|
| 256 |
+
get_func = getattr(self.dynlib, "bli_thread_get_num_threads", lambda: None)
|
| 257 |
+
num_threads = get_func()
|
| 258 |
+
# by default BLIS is single-threaded and get_num_threads
|
| 259 |
+
# returns -1. We map it to 1 for consistency with other libraries.
|
| 260 |
+
return 1 if num_threads == -1 else num_threads
|
| 261 |
+
|
| 262 |
+
def set_num_threads(self, num_threads):
|
| 263 |
+
set_func = getattr(
|
| 264 |
+
self.dynlib, "bli_thread_set_num_threads", lambda num_threads: None
|
| 265 |
+
)
|
| 266 |
+
return set_func(num_threads)
|
| 267 |
+
|
| 268 |
+
def get_version(self):
|
| 269 |
+
get_version_ = getattr(self.dynlib, "bli_info_get_version_str", None)
|
| 270 |
+
if get_version_ is None:
|
| 271 |
+
return None
|
| 272 |
+
|
| 273 |
+
get_version_.restype = ctypes.c_char_p
|
| 274 |
+
return get_version_().decode("utf-8")
|
| 275 |
+
|
| 276 |
+
def _get_threading_layer(self):
|
| 277 |
+
"""Return the threading layer of BLIS"""
|
| 278 |
+
if getattr(self.dynlib, "bli_info_get_enable_openmp", lambda: False)():
|
| 279 |
+
return "openmp"
|
| 280 |
+
elif getattr(self.dynlib, "bli_info_get_enable_pthreads", lambda: False)():
|
| 281 |
+
return "pthreads"
|
| 282 |
+
return "disabled"
|
| 283 |
+
|
| 284 |
+
def _get_architecture(self):
|
| 285 |
+
"""Return the architecture detected by BLIS"""
|
| 286 |
+
bli_arch_query_id = getattr(self.dynlib, "bli_arch_query_id", None)
|
| 287 |
+
bli_arch_string = getattr(self.dynlib, "bli_arch_string", None)
|
| 288 |
+
if bli_arch_query_id is None or bli_arch_string is None:
|
| 289 |
+
return None
|
| 290 |
+
|
| 291 |
+
# the true restype should be BLIS' arch_t (enum) but int should work
|
| 292 |
+
# for us:
|
| 293 |
+
bli_arch_query_id.restype = ctypes.c_int
|
| 294 |
+
bli_arch_string.restype = ctypes.c_char_p
|
| 295 |
+
return bli_arch_string(bli_arch_query_id()).decode("utf-8")
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
class FlexiBLASController(LibController):
|
| 299 |
+
"""Controller class for FlexiBLAS"""
|
| 300 |
+
|
| 301 |
+
user_api = "blas"
|
| 302 |
+
internal_api = "flexiblas"
|
| 303 |
+
filename_prefixes = ("libflexiblas",)
|
| 304 |
+
check_symbols = (
|
| 305 |
+
"flexiblas_get_num_threads",
|
| 306 |
+
"flexiblas_set_num_threads",
|
| 307 |
+
"flexiblas_get_version",
|
| 308 |
+
"flexiblas_list",
|
| 309 |
+
"flexiblas_list_loaded",
|
| 310 |
+
"flexiblas_current_backend",
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
@property
|
| 314 |
+
def loaded_backends(self):
|
| 315 |
+
return self._get_backend_list(loaded=True)
|
| 316 |
+
|
| 317 |
+
@property
|
| 318 |
+
def current_backend(self):
|
| 319 |
+
return self._get_current_backend()
|
| 320 |
+
|
| 321 |
+
def info(self):
|
| 322 |
+
"""Return relevant info wrapped in a dict"""
|
| 323 |
+
# We override the info method because the loaded and current backends
|
| 324 |
+
# are dynamic properties
|
| 325 |
+
exposed_attrs = super().info()
|
| 326 |
+
exposed_attrs["loaded_backends"] = self.loaded_backends
|
| 327 |
+
exposed_attrs["current_backend"] = self.current_backend
|
| 328 |
+
|
| 329 |
+
return exposed_attrs
|
| 330 |
+
|
| 331 |
+
def set_additional_attributes(self):
|
| 332 |
+
self.available_backends = self._get_backend_list(loaded=False)
|
| 333 |
+
|
| 334 |
+
def get_num_threads(self):
|
| 335 |
+
get_func = getattr(self.dynlib, "flexiblas_get_num_threads", lambda: None)
|
| 336 |
+
num_threads = get_func()
|
| 337 |
+
# by default BLIS is single-threaded and get_num_threads
|
| 338 |
+
# returns -1. We map it to 1 for consistency with other libraries.
|
| 339 |
+
return 1 if num_threads == -1 else num_threads
|
| 340 |
+
|
| 341 |
+
def set_num_threads(self, num_threads):
|
| 342 |
+
set_func = getattr(
|
| 343 |
+
self.dynlib, "flexiblas_set_num_threads", lambda num_threads: None
|
| 344 |
+
)
|
| 345 |
+
return set_func(num_threads)
|
| 346 |
+
|
| 347 |
+
def get_version(self):
|
| 348 |
+
get_version_ = getattr(self.dynlib, "flexiblas_get_version", None)
|
| 349 |
+
if get_version_ is None:
|
| 350 |
+
return None
|
| 351 |
+
|
| 352 |
+
major = ctypes.c_int()
|
| 353 |
+
minor = ctypes.c_int()
|
| 354 |
+
patch = ctypes.c_int()
|
| 355 |
+
get_version_(ctypes.byref(major), ctypes.byref(minor), ctypes.byref(patch))
|
| 356 |
+
return f"{major.value}.{minor.value}.{patch.value}"
|
| 357 |
+
|
| 358 |
+
def _get_backend_list(self, loaded=False):
|
| 359 |
+
"""Return the list of available backends for FlexiBLAS.
|
| 360 |
+
|
| 361 |
+
If loaded is False, return the list of available backends from the FlexiBLAS
|
| 362 |
+
configuration. If loaded is True, return the list of actually loaded backends.
|
| 363 |
+
"""
|
| 364 |
+
func_name = f"flexiblas_list{'_loaded' if loaded else ''}"
|
| 365 |
+
get_backend_list_ = getattr(self.dynlib, func_name, None)
|
| 366 |
+
if get_backend_list_ is None:
|
| 367 |
+
return None
|
| 368 |
+
|
| 369 |
+
n_backends = get_backend_list_(None, 0, 0)
|
| 370 |
+
|
| 371 |
+
backends = []
|
| 372 |
+
for i in range(n_backends):
|
| 373 |
+
backend_name = ctypes.create_string_buffer(1024)
|
| 374 |
+
get_backend_list_(backend_name, 1024, i)
|
| 375 |
+
if backend_name.value.decode("utf-8") != "__FALLBACK__":
|
| 376 |
+
# We don't know when to expect __FALLBACK__ but it is not a real
|
| 377 |
+
# backend and does not show up when running flexiblas list.
|
| 378 |
+
backends.append(backend_name.value.decode("utf-8"))
|
| 379 |
+
return backends
|
| 380 |
+
|
| 381 |
+
def _get_current_backend(self):
|
| 382 |
+
"""Return the backend of FlexiBLAS"""
|
| 383 |
+
get_backend_ = getattr(self.dynlib, "flexiblas_current_backend", None)
|
| 384 |
+
if get_backend_ is None:
|
| 385 |
+
return None
|
| 386 |
+
|
| 387 |
+
backend = ctypes.create_string_buffer(1024)
|
| 388 |
+
get_backend_(backend, ctypes.sizeof(backend))
|
| 389 |
+
return backend.value.decode("utf-8")
|
| 390 |
+
|
| 391 |
+
def switch_backend(self, backend):
|
| 392 |
+
"""Switch the backend of FlexiBLAS
|
| 393 |
+
|
| 394 |
+
Parameters
|
| 395 |
+
----------
|
| 396 |
+
backend : str
|
| 397 |
+
The name or the path to the shared library of the backend to switch to. If
|
| 398 |
+
the backend is not already loaded, it will be loaded first.
|
| 399 |
+
"""
|
| 400 |
+
if backend not in self.loaded_backends:
|
| 401 |
+
if backend in self.available_backends:
|
| 402 |
+
load_func = getattr(self.dynlib, "flexiblas_load_backend", lambda _: -1)
|
| 403 |
+
else: # assume backend is a path to a shared library
|
| 404 |
+
load_func = getattr(
|
| 405 |
+
self.dynlib, "flexiblas_load_backend_library", lambda _: -1
|
| 406 |
+
)
|
| 407 |
+
res = load_func(str(backend).encode("utf-8"))
|
| 408 |
+
if res == -1:
|
| 409 |
+
raise RuntimeError(
|
| 410 |
+
f"Failed to load backend {backend!r}. It must either be the name of"
|
| 411 |
+
" a backend available in the FlexiBLAS configuration "
|
| 412 |
+
f"{self.available_backends} or the path to a valid shared library."
|
| 413 |
+
)
|
| 414 |
+
|
| 415 |
+
# Trigger a new search of loaded shared libraries since loading a new
|
| 416 |
+
# backend caused a dlopen.
|
| 417 |
+
self.parent._load_libraries()
|
| 418 |
+
|
| 419 |
+
switch_func = getattr(self.dynlib, "flexiblas_switch", lambda _: -1)
|
| 420 |
+
idx = self.loaded_backends.index(backend)
|
| 421 |
+
res = switch_func(idx)
|
| 422 |
+
if res == -1:
|
| 423 |
+
raise RuntimeError(f"Failed to switch to backend {backend!r}.")
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
class MKLController(LibController):
|
| 427 |
+
"""Controller class for MKL"""
|
| 428 |
+
|
| 429 |
+
user_api = "blas"
|
| 430 |
+
internal_api = "mkl"
|
| 431 |
+
filename_prefixes = ("libmkl_rt", "mkl_rt", "libblas")
|
| 432 |
+
check_symbols = (
|
| 433 |
+
"MKL_Get_Max_Threads",
|
| 434 |
+
"MKL_Set_Num_Threads",
|
| 435 |
+
"MKL_Get_Version_String",
|
| 436 |
+
"MKL_Set_Threading_Layer",
|
| 437 |
+
)
|
| 438 |
+
|
| 439 |
+
def set_additional_attributes(self):
|
| 440 |
+
self.threading_layer = self._get_threading_layer()
|
| 441 |
+
|
| 442 |
+
def get_num_threads(self):
|
| 443 |
+
get_func = getattr(self.dynlib, "MKL_Get_Max_Threads", lambda: None)
|
| 444 |
+
return get_func()
|
| 445 |
+
|
| 446 |
+
def set_num_threads(self, num_threads):
|
| 447 |
+
set_func = getattr(self.dynlib, "MKL_Set_Num_Threads", lambda num_threads: None)
|
| 448 |
+
return set_func(num_threads)
|
| 449 |
+
|
| 450 |
+
def get_version(self):
|
| 451 |
+
if not hasattr(self.dynlib, "MKL_Get_Version_String"):
|
| 452 |
+
return None
|
| 453 |
+
|
| 454 |
+
res = ctypes.create_string_buffer(200)
|
| 455 |
+
self.dynlib.MKL_Get_Version_String(res, 200)
|
| 456 |
+
|
| 457 |
+
version = res.value.decode("utf-8")
|
| 458 |
+
group = re.search(r"Version ([^ ]+) ", version)
|
| 459 |
+
if group is not None:
|
| 460 |
+
version = group.groups()[0]
|
| 461 |
+
return version.strip()
|
| 462 |
+
|
| 463 |
+
def _get_threading_layer(self):
|
| 464 |
+
"""Return the threading layer of MKL"""
|
| 465 |
+
# The function mkl_set_threading_layer returns the current threading
|
| 466 |
+
# layer. Calling it with an invalid threading layer allows us to safely
|
| 467 |
+
# get the threading layer
|
| 468 |
+
set_threading_layer = getattr(
|
| 469 |
+
self.dynlib, "MKL_Set_Threading_Layer", lambda layer: -1
|
| 470 |
+
)
|
| 471 |
+
layer_map = {
|
| 472 |
+
0: "intel",
|
| 473 |
+
1: "sequential",
|
| 474 |
+
2: "pgi",
|
| 475 |
+
3: "gnu",
|
| 476 |
+
4: "tbb",
|
| 477 |
+
-1: "not specified",
|
| 478 |
+
}
|
| 479 |
+
return layer_map[set_threading_layer(-1)]
|
| 480 |
+
|
| 481 |
+
|
| 482 |
+
class OpenMPController(LibController):
|
| 483 |
+
"""Controller class for OpenMP"""
|
| 484 |
+
|
| 485 |
+
user_api = "openmp"
|
| 486 |
+
internal_api = "openmp"
|
| 487 |
+
filename_prefixes = ("libiomp", "libgomp", "libomp", "vcomp")
|
| 488 |
+
check_symbols = (
|
| 489 |
+
"omp_get_max_threads",
|
| 490 |
+
"omp_get_num_threads",
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
def get_num_threads(self):
|
| 494 |
+
get_func = getattr(self.dynlib, "omp_get_max_threads", lambda: None)
|
| 495 |
+
return get_func()
|
| 496 |
+
|
| 497 |
+
def set_num_threads(self, num_threads):
|
| 498 |
+
set_func = getattr(self.dynlib, "omp_set_num_threads", lambda num_threads: None)
|
| 499 |
+
return set_func(num_threads)
|
| 500 |
+
|
| 501 |
+
def get_version(self):
|
| 502 |
+
# There is no way to get the version number programmatically in OpenMP.
|
| 503 |
+
return None
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
# Controllers for the libraries that we'll look for in the loaded libraries.
|
| 507 |
+
# Third party libraries can register their own controllers.
|
| 508 |
+
_ALL_CONTROLLERS = [
|
| 509 |
+
OpenBLASController,
|
| 510 |
+
BLISController,
|
| 511 |
+
MKLController,
|
| 512 |
+
OpenMPController,
|
| 513 |
+
FlexiBLASController,
|
| 514 |
+
]
|
| 515 |
+
|
| 516 |
+
# Helpers for the doc and test names
|
| 517 |
+
_ALL_USER_APIS = list(set(lib.user_api for lib in _ALL_CONTROLLERS))
|
| 518 |
+
_ALL_INTERNAL_APIS = [lib.internal_api for lib in _ALL_CONTROLLERS]
|
| 519 |
+
_ALL_PREFIXES = list(
|
| 520 |
+
set(prefix for lib in _ALL_CONTROLLERS for prefix in lib.filename_prefixes)
|
| 521 |
+
)
|
| 522 |
+
_ALL_BLAS_LIBRARIES = [
|
| 523 |
+
lib.internal_api for lib in _ALL_CONTROLLERS if lib.user_api == "blas"
|
| 524 |
+
]
|
| 525 |
+
_ALL_OPENMP_LIBRARIES = OpenMPController.filename_prefixes
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
def register(controller):
|
| 529 |
+
"""Register a new controller"""
|
| 530 |
+
_ALL_CONTROLLERS.append(controller)
|
| 531 |
+
_ALL_USER_APIS.append(controller.user_api)
|
| 532 |
+
_ALL_INTERNAL_APIS.append(controller.internal_api)
|
| 533 |
+
_ALL_PREFIXES.extend(controller.filename_prefixes)
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
def _format_docstring(*args, **kwargs):
|
| 537 |
+
def decorator(o):
|
| 538 |
+
if o.__doc__ is not None:
|
| 539 |
+
o.__doc__ = o.__doc__.format(*args, **kwargs)
|
| 540 |
+
return o
|
| 541 |
+
|
| 542 |
+
return decorator
|
| 543 |
+
|
| 544 |
+
|
| 545 |
+
@lru_cache(maxsize=10000)
|
| 546 |
+
def _realpath(filepath):
|
| 547 |
+
"""Small caching wrapper around os.path.realpath to limit system calls"""
|
| 548 |
+
return os.path.realpath(filepath)
|
| 549 |
+
|
| 550 |
+
|
| 551 |
+
@_format_docstring(USER_APIS=list(_ALL_USER_APIS), INTERNAL_APIS=_ALL_INTERNAL_APIS)
|
| 552 |
+
def threadpool_info():
|
| 553 |
+
"""Return the maximal number of threads for each detected library.
|
| 554 |
+
|
| 555 |
+
Return a list with all the supported libraries that have been found. Each
|
| 556 |
+
library is represented by a dict with the following information:
|
| 557 |
+
|
| 558 |
+
- "user_api" : user API. Possible values are {USER_APIS}.
|
| 559 |
+
- "internal_api": internal API. Possible values are {INTERNAL_APIS}.
|
| 560 |
+
- "prefix" : filename prefix of the specific implementation.
|
| 561 |
+
- "filepath": path to the loaded library.
|
| 562 |
+
- "version": version of the library (if available).
|
| 563 |
+
- "num_threads": the current thread limit.
|
| 564 |
+
|
| 565 |
+
In addition, each library may contain internal_api specific entries.
|
| 566 |
+
"""
|
| 567 |
+
return ThreadpoolController().info()
|
| 568 |
+
|
| 569 |
+
|
| 570 |
+
class _ThreadpoolLimiter:
|
| 571 |
+
"""The guts of ThreadpoolController.limit
|
| 572 |
+
|
| 573 |
+
Refer to the docstring of ThreadpoolController.limit for more details.
|
| 574 |
+
|
| 575 |
+
It will only act on the library controllers held by the provided `controller`.
|
| 576 |
+
Using the default constructor sets the limits right away such that it can be used as
|
| 577 |
+
a callable. Setting the limits can be delayed by using the `wrap` class method such
|
| 578 |
+
that it can be used as a decorator.
|
| 579 |
+
"""
|
| 580 |
+
|
| 581 |
+
def __init__(self, controller, *, limits=None, user_api=None):
|
| 582 |
+
self._controller = controller
|
| 583 |
+
self._limits, self._user_api, self._prefixes = self._check_params(
|
| 584 |
+
limits, user_api
|
| 585 |
+
)
|
| 586 |
+
self._original_info = self._controller.info()
|
| 587 |
+
self._set_threadpool_limits()
|
| 588 |
+
|
| 589 |
+
def __enter__(self):
|
| 590 |
+
return self
|
| 591 |
+
|
| 592 |
+
def __exit__(self, type, value, traceback):
|
| 593 |
+
self.restore_original_limits()
|
| 594 |
+
|
| 595 |
+
@classmethod
|
| 596 |
+
def wrap(cls, controller, *, limits=None, user_api=None):
|
| 597 |
+
"""Return an instance of this class that can be used as a decorator"""
|
| 598 |
+
return _ThreadpoolLimiterDecorator(
|
| 599 |
+
controller=controller, limits=limits, user_api=user_api
|
| 600 |
+
)
|
| 601 |
+
|
| 602 |
+
def restore_original_limits(self):
|
| 603 |
+
"""Set the limits back to their original values"""
|
| 604 |
+
for lib_controller, original_info in zip(
|
| 605 |
+
self._controller.lib_controllers, self._original_info
|
| 606 |
+
):
|
| 607 |
+
lib_controller.set_num_threads(original_info["num_threads"])
|
| 608 |
+
|
| 609 |
+
# Alias of `restore_original_limits` for backward compatibility
|
| 610 |
+
unregister = restore_original_limits
|
| 611 |
+
|
| 612 |
+
def get_original_num_threads(self):
|
| 613 |
+
"""Original num_threads from before calling threadpool_limits
|
| 614 |
+
|
| 615 |
+
Return a dict `{user_api: num_threads}`.
|
| 616 |
+
"""
|
| 617 |
+
num_threads = {}
|
| 618 |
+
warning_apis = []
|
| 619 |
+
|
| 620 |
+
for user_api in self._user_api:
|
| 621 |
+
limits = [
|
| 622 |
+
lib_info["num_threads"]
|
| 623 |
+
for lib_info in self._original_info
|
| 624 |
+
if lib_info["user_api"] == user_api
|
| 625 |
+
]
|
| 626 |
+
limits = set(limits)
|
| 627 |
+
n_limits = len(limits)
|
| 628 |
+
|
| 629 |
+
if n_limits == 1:
|
| 630 |
+
limit = limits.pop()
|
| 631 |
+
elif n_limits == 0:
|
| 632 |
+
limit = None
|
| 633 |
+
else:
|
| 634 |
+
limit = min(limits)
|
| 635 |
+
warning_apis.append(user_api)
|
| 636 |
+
|
| 637 |
+
num_threads[user_api] = limit
|
| 638 |
+
|
| 639 |
+
if warning_apis:
|
| 640 |
+
warnings.warn(
|
| 641 |
+
"Multiple value possible for following user apis: "
|
| 642 |
+
+ ", ".join(warning_apis)
|
| 643 |
+
+ ". Returning the minimum."
|
| 644 |
+
)
|
| 645 |
+
|
| 646 |
+
return num_threads
|
| 647 |
+
|
| 648 |
+
def _check_params(self, limits, user_api):
|
| 649 |
+
"""Suitable values for the _limits, _user_api and _prefixes attributes"""
|
| 650 |
+
|
| 651 |
+
if isinstance(limits, str) and limits == "sequential_blas_under_openmp":
|
| 652 |
+
(
|
| 653 |
+
limits,
|
| 654 |
+
user_api,
|
| 655 |
+
) = self._controller._get_params_for_sequential_blas_under_openmp().values()
|
| 656 |
+
|
| 657 |
+
if limits is None or isinstance(limits, int):
|
| 658 |
+
if user_api is None:
|
| 659 |
+
user_api = _ALL_USER_APIS
|
| 660 |
+
elif user_api in _ALL_USER_APIS:
|
| 661 |
+
user_api = [user_api]
|
| 662 |
+
else:
|
| 663 |
+
raise ValueError(
|
| 664 |
+
f"user_api must be either in {_ALL_USER_APIS} or None. Got "
|
| 665 |
+
f"{user_api} instead."
|
| 666 |
+
)
|
| 667 |
+
|
| 668 |
+
if limits is not None:
|
| 669 |
+
limits = {api: limits for api in user_api}
|
| 670 |
+
prefixes = []
|
| 671 |
+
else:
|
| 672 |
+
if isinstance(limits, list):
|
| 673 |
+
# This should be a list of dicts of library info, for
|
| 674 |
+
# compatibility with the result from threadpool_info.
|
| 675 |
+
limits = {
|
| 676 |
+
lib_info["prefix"]: lib_info["num_threads"] for lib_info in limits
|
| 677 |
+
}
|
| 678 |
+
elif isinstance(limits, ThreadpoolController):
|
| 679 |
+
# To set the limits from the library controllers of a
|
| 680 |
+
# ThreadpoolController object.
|
| 681 |
+
limits = {
|
| 682 |
+
lib_controller.prefix: lib_controller.num_threads
|
| 683 |
+
for lib_controller in limits.lib_controllers
|
| 684 |
+
}
|
| 685 |
+
|
| 686 |
+
if not isinstance(limits, dict):
|
| 687 |
+
raise TypeError(
|
| 688 |
+
"limits must either be an int, a list, a dict, or "
|
| 689 |
+
f"'sequential_blas_under_openmp'. Got {type(limits)} instead"
|
| 690 |
+
)
|
| 691 |
+
|
| 692 |
+
# With a dictionary, can set both specific limit for given
|
| 693 |
+
# libraries and global limit for user_api. Fetch each separately.
|
| 694 |
+
prefixes = [prefix for prefix in limits if prefix in _ALL_PREFIXES]
|
| 695 |
+
user_api = [api for api in limits if api in _ALL_USER_APIS]
|
| 696 |
+
|
| 697 |
+
return limits, user_api, prefixes
|
| 698 |
+
|
| 699 |
+
def _set_threadpool_limits(self):
|
| 700 |
+
"""Change the maximal number of threads in selected thread pools.
|
| 701 |
+
|
| 702 |
+
Return a list with all the supported libraries that have been found
|
| 703 |
+
matching `self._prefixes` and `self._user_api`.
|
| 704 |
+
"""
|
| 705 |
+
if self._limits is None:
|
| 706 |
+
return
|
| 707 |
+
|
| 708 |
+
for lib_controller in self._controller.lib_controllers:
|
| 709 |
+
# self._limits is a dict {key: num_threads} where key is either
|
| 710 |
+
# a prefix or a user_api. If a library matches both, the limit
|
| 711 |
+
# corresponding to the prefix is chosen.
|
| 712 |
+
if lib_controller.prefix in self._limits:
|
| 713 |
+
num_threads = self._limits[lib_controller.prefix]
|
| 714 |
+
elif lib_controller.user_api in self._limits:
|
| 715 |
+
num_threads = self._limits[lib_controller.user_api]
|
| 716 |
+
else:
|
| 717 |
+
continue
|
| 718 |
+
|
| 719 |
+
if num_threads is not None:
|
| 720 |
+
lib_controller.set_num_threads(num_threads)
|
| 721 |
+
|
| 722 |
+
|
| 723 |
+
class _ThreadpoolLimiterDecorator(_ThreadpoolLimiter, ContextDecorator):
|
| 724 |
+
"""Same as _ThreadpoolLimiter but to be used as a decorator"""
|
| 725 |
+
|
| 726 |
+
def __init__(self, controller, *, limits=None, user_api=None):
|
| 727 |
+
self._limits, self._user_api, self._prefixes = self._check_params(
|
| 728 |
+
limits, user_api
|
| 729 |
+
)
|
| 730 |
+
self._controller = controller
|
| 731 |
+
|
| 732 |
+
def __enter__(self):
|
| 733 |
+
# we need to set the limits here and not in the __init__ because we want the
|
| 734 |
+
# limits to be set when calling the decorated function, not when creating the
|
| 735 |
+
# decorator.
|
| 736 |
+
self._original_info = self._controller.info()
|
| 737 |
+
self._set_threadpool_limits()
|
| 738 |
+
return self
|
| 739 |
+
|
| 740 |
+
|
| 741 |
+
@_format_docstring(
|
| 742 |
+
USER_APIS=", ".join(f'"{api}"' for api in _ALL_USER_APIS),
|
| 743 |
+
BLAS_LIBS=", ".join(_ALL_BLAS_LIBRARIES),
|
| 744 |
+
OPENMP_LIBS=", ".join(_ALL_OPENMP_LIBRARIES),
|
| 745 |
+
)
|
| 746 |
+
class threadpool_limits(_ThreadpoolLimiter):
|
| 747 |
+
"""Change the maximal number of threads that can be used in thread pools.
|
| 748 |
+
|
| 749 |
+
This object can be used either as a callable (the construction of this object
|
| 750 |
+
limits the number of threads), as a context manager in a `with` block to
|
| 751 |
+
automatically restore the original state of the controlled libraries when exiting
|
| 752 |
+
the block, or as a decorator through its `wrap` method.
|
| 753 |
+
|
| 754 |
+
Set the maximal number of threads that can be used in thread pools used in
|
| 755 |
+
the supported libraries to `limit`. This function works for libraries that
|
| 756 |
+
are already loaded in the interpreter and can be changed dynamically.
|
| 757 |
+
|
| 758 |
+
This effect is global and impacts the whole Python process. There is no thread level
|
| 759 |
+
isolation as these libraries do not offer thread-local APIs to configure the number
|
| 760 |
+
of threads to use in nested parallel calls.
|
| 761 |
+
|
| 762 |
+
Parameters
|
| 763 |
+
----------
|
| 764 |
+
limits : int, dict, 'sequential_blas_under_openmp' or None (default=None)
|
| 765 |
+
The maximal number of threads that can be used in thread pools
|
| 766 |
+
|
| 767 |
+
- If int, sets the maximum number of threads to `limits` for each
|
| 768 |
+
library selected by `user_api`.
|
| 769 |
+
|
| 770 |
+
- If it is a dictionary `{{key: max_threads}}`, this function sets a
|
| 771 |
+
custom maximum number of threads for each `key` which can be either a
|
| 772 |
+
`user_api` or a `prefix` for a specific library.
|
| 773 |
+
|
| 774 |
+
- If 'sequential_blas_under_openmp', it will chose the appropriate `limits`
|
| 775 |
+
and `user_api` parameters for the specific use case of sequential BLAS
|
| 776 |
+
calls within an OpenMP parallel region. The `user_api` parameter is
|
| 777 |
+
ignored.
|
| 778 |
+
|
| 779 |
+
- If None, this function does not do anything.
|
| 780 |
+
|
| 781 |
+
user_api : {USER_APIS} or None (default=None)
|
| 782 |
+
APIs of libraries to limit. Used only if `limits` is an int.
|
| 783 |
+
|
| 784 |
+
- If "blas", it will only limit BLAS supported libraries ({BLAS_LIBS}).
|
| 785 |
+
|
| 786 |
+
- If "openmp", it will only limit OpenMP supported libraries
|
| 787 |
+
({OPENMP_LIBS}). Note that it can affect the number of threads used
|
| 788 |
+
by the BLAS libraries if they rely on OpenMP.
|
| 789 |
+
|
| 790 |
+
- If None, this function will apply to all supported libraries.
|
| 791 |
+
"""
|
| 792 |
+
|
| 793 |
+
def __init__(self, limits=None, user_api=None):
|
| 794 |
+
super().__init__(ThreadpoolController(), limits=limits, user_api=user_api)
|
| 795 |
+
|
| 796 |
+
@classmethod
|
| 797 |
+
def wrap(cls, limits=None, user_api=None):
|
| 798 |
+
return super().wrap(ThreadpoolController(), limits=limits, user_api=user_api)
|
| 799 |
+
|
| 800 |
+
|
| 801 |
+
class ThreadpoolController:
|
| 802 |
+
"""Collection of LibController objects for all loaded supported libraries
|
| 803 |
+
|
| 804 |
+
Attributes
|
| 805 |
+
----------
|
| 806 |
+
lib_controllers : list of `LibController` objects
|
| 807 |
+
The list of library controllers of all loaded supported libraries.
|
| 808 |
+
"""
|
| 809 |
+
|
| 810 |
+
# Cache for libc under POSIX and a few system libraries under Windows.
|
| 811 |
+
# We use a class level cache instead of an instance level cache because
|
| 812 |
+
# it's very unlikely that a shared library will be unloaded and reloaded
|
| 813 |
+
# during the lifetime of a program.
|
| 814 |
+
_system_libraries = dict()
|
| 815 |
+
|
| 816 |
+
def __init__(self):
|
| 817 |
+
self.lib_controllers = []
|
| 818 |
+
self._load_libraries()
|
| 819 |
+
self._warn_if_incompatible_openmp()
|
| 820 |
+
|
| 821 |
+
@classmethod
|
| 822 |
+
def _from_controllers(cls, lib_controllers):
|
| 823 |
+
new_controller = cls.__new__(cls)
|
| 824 |
+
new_controller.lib_controllers = lib_controllers
|
| 825 |
+
return new_controller
|
| 826 |
+
|
| 827 |
+
def info(self):
|
| 828 |
+
"""Return lib_controllers info as a list of dicts"""
|
| 829 |
+
return [lib_controller.info() for lib_controller in self.lib_controllers]
|
| 830 |
+
|
| 831 |
+
def select(self, **kwargs):
|
| 832 |
+
"""Return a ThreadpoolController containing a subset of its current
|
| 833 |
+
library controllers
|
| 834 |
+
|
| 835 |
+
It will select all libraries matching at least one pair (key, value) from kwargs
|
| 836 |
+
where key is an entry of the library info dict (like "user_api", "internal_api",
|
| 837 |
+
"prefix", ...) and value is the value or a list of acceptable values for that
|
| 838 |
+
entry.
|
| 839 |
+
|
| 840 |
+
For instance, `ThreadpoolController().select(internal_api=["blis", "openblas"])`
|
| 841 |
+
will select all library controllers whose internal_api is either "blis" or
|
| 842 |
+
"openblas".
|
| 843 |
+
"""
|
| 844 |
+
for key, vals in kwargs.items():
|
| 845 |
+
kwargs[key] = [vals] if not isinstance(vals, list) else vals
|
| 846 |
+
|
| 847 |
+
lib_controllers = [
|
| 848 |
+
lib_controller
|
| 849 |
+
for lib_controller in self.lib_controllers
|
| 850 |
+
if any(
|
| 851 |
+
getattr(lib_controller, key, None) in vals
|
| 852 |
+
for key, vals in kwargs.items()
|
| 853 |
+
)
|
| 854 |
+
]
|
| 855 |
+
|
| 856 |
+
return ThreadpoolController._from_controllers(lib_controllers)
|
| 857 |
+
|
| 858 |
+
def _get_params_for_sequential_blas_under_openmp(self):
|
| 859 |
+
"""Return appropriate params to use for a sequential BLAS call in an OpenMP loop
|
| 860 |
+
|
| 861 |
+
This function takes into account the unexpected behavior of OpenBLAS with the
|
| 862 |
+
OpenMP threading layer.
|
| 863 |
+
"""
|
| 864 |
+
if self.select(
|
| 865 |
+
internal_api="openblas", threading_layer="openmp"
|
| 866 |
+
).lib_controllers:
|
| 867 |
+
return {"limits": None, "user_api": None}
|
| 868 |
+
return {"limits": 1, "user_api": "blas"}
|
| 869 |
+
|
| 870 |
+
@_format_docstring(
|
| 871 |
+
USER_APIS=", ".join('"{}"'.format(api) for api in _ALL_USER_APIS),
|
| 872 |
+
BLAS_LIBS=", ".join(_ALL_BLAS_LIBRARIES),
|
| 873 |
+
OPENMP_LIBS=", ".join(_ALL_OPENMP_LIBRARIES),
|
| 874 |
+
)
|
| 875 |
+
def limit(self, *, limits=None, user_api=None):
|
| 876 |
+
"""Change the maximal number of threads that can be used in thread pools.
|
| 877 |
+
|
| 878 |
+
This function returns an object that can be used either as a callable (the
|
| 879 |
+
construction of this object limits the number of threads) or as a context
|
| 880 |
+
manager, in a `with` block to automatically restore the original state of the
|
| 881 |
+
controlled libraries when exiting the block.
|
| 882 |
+
|
| 883 |
+
Set the maximal number of threads that can be used in thread pools used in
|
| 884 |
+
the supported libraries to `limits`. This function works for libraries that
|
| 885 |
+
are already loaded in the interpreter and can be changed dynamically.
|
| 886 |
+
|
| 887 |
+
This effect is global and impacts the whole Python process. There is no thread
|
| 888 |
+
level isolation as these libraries do not offer thread-local APIs to configure
|
| 889 |
+
the number of threads to use in nested parallel calls.
|
| 890 |
+
|
| 891 |
+
Parameters
|
| 892 |
+
----------
|
| 893 |
+
limits : int, dict, 'sequential_blas_under_openmp' or None (default=None)
|
| 894 |
+
The maximal number of threads that can be used in thread pools
|
| 895 |
+
|
| 896 |
+
- If int, sets the maximum number of threads to `limits` for each
|
| 897 |
+
library selected by `user_api`.
|
| 898 |
+
|
| 899 |
+
- If it is a dictionary `{{key: max_threads}}`, this function sets a
|
| 900 |
+
custom maximum number of threads for each `key` which can be either a
|
| 901 |
+
`user_api` or a `prefix` for a specific library.
|
| 902 |
+
|
| 903 |
+
- If 'sequential_blas_under_openmp', it will chose the appropriate `limits`
|
| 904 |
+
and `user_api` parameters for the specific use case of sequential BLAS
|
| 905 |
+
calls within an OpenMP parallel region. The `user_api` parameter is
|
| 906 |
+
ignored.
|
| 907 |
+
|
| 908 |
+
- If None, this function does not do anything.
|
| 909 |
+
|
| 910 |
+
user_api : {USER_APIS} or None (default=None)
|
| 911 |
+
APIs of libraries to limit. Used only if `limits` is an int.
|
| 912 |
+
|
| 913 |
+
- If "blas", it will only limit BLAS supported libraries ({BLAS_LIBS}).
|
| 914 |
+
|
| 915 |
+
- If "openmp", it will only limit OpenMP supported libraries
|
| 916 |
+
({OPENMP_LIBS}). Note that it can affect the number of threads used
|
| 917 |
+
by the BLAS libraries if they rely on OpenMP.
|
| 918 |
+
|
| 919 |
+
- If None, this function will apply to all supported libraries.
|
| 920 |
+
"""
|
| 921 |
+
return _ThreadpoolLimiter(self, limits=limits, user_api=user_api)
|
| 922 |
+
|
| 923 |
+
@_format_docstring(
|
| 924 |
+
USER_APIS=", ".join('"{}"'.format(api) for api in _ALL_USER_APIS),
|
| 925 |
+
BLAS_LIBS=", ".join(_ALL_BLAS_LIBRARIES),
|
| 926 |
+
OPENMP_LIBS=", ".join(_ALL_OPENMP_LIBRARIES),
|
| 927 |
+
)
|
| 928 |
+
def wrap(self, *, limits=None, user_api=None):
|
| 929 |
+
"""Change the maximal number of threads that can be used in thread pools.
|
| 930 |
+
|
| 931 |
+
This function returns an object that can be used as a decorator.
|
| 932 |
+
|
| 933 |
+
Set the maximal number of threads that can be used in thread pools used in
|
| 934 |
+
the supported libraries to `limits`. This function works for libraries that
|
| 935 |
+
are already loaded in the interpreter and can be changed dynamically.
|
| 936 |
+
|
| 937 |
+
Parameters
|
| 938 |
+
----------
|
| 939 |
+
limits : int, dict or None (default=None)
|
| 940 |
+
The maximal number of threads that can be used in thread pools
|
| 941 |
+
|
| 942 |
+
- If int, sets the maximum number of threads to `limits` for each
|
| 943 |
+
library selected by `user_api`.
|
| 944 |
+
|
| 945 |
+
- If it is a dictionary `{{key: max_threads}}`, this function sets a
|
| 946 |
+
custom maximum number of threads for each `key` which can be either a
|
| 947 |
+
`user_api` or a `prefix` for a specific library.
|
| 948 |
+
|
| 949 |
+
- If None, this function does not do anything.
|
| 950 |
+
|
| 951 |
+
user_api : {USER_APIS} or None (default=None)
|
| 952 |
+
APIs of libraries to limit. Used only if `limits` is an int.
|
| 953 |
+
|
| 954 |
+
- If "blas", it will only limit BLAS supported libraries ({BLAS_LIBS}).
|
| 955 |
+
|
| 956 |
+
- If "openmp", it will only limit OpenMP supported libraries
|
| 957 |
+
({OPENMP_LIBS}). Note that it can affect the number of threads used
|
| 958 |
+
by the BLAS libraries if they rely on OpenMP.
|
| 959 |
+
|
| 960 |
+
- If None, this function will apply to all supported libraries.
|
| 961 |
+
"""
|
| 962 |
+
return _ThreadpoolLimiter.wrap(self, limits=limits, user_api=user_api)
|
| 963 |
+
|
| 964 |
+
def __len__(self):
|
| 965 |
+
return len(self.lib_controllers)
|
| 966 |
+
|
| 967 |
+
def _load_libraries(self):
|
| 968 |
+
"""Loop through loaded shared libraries and store the supported ones"""
|
| 969 |
+
if sys.platform == "darwin":
|
| 970 |
+
self._find_libraries_with_dyld()
|
| 971 |
+
elif sys.platform == "win32":
|
| 972 |
+
self._find_libraries_with_enum_process_module_ex()
|
| 973 |
+
elif "pyodide" in sys.modules:
|
| 974 |
+
self._find_libraries_pyodide()
|
| 975 |
+
else:
|
| 976 |
+
self._find_libraries_with_dl_iterate_phdr()
|
| 977 |
+
|
| 978 |
+
def _find_libraries_with_dl_iterate_phdr(self):
|
| 979 |
+
"""Loop through loaded libraries and return binders on supported ones
|
| 980 |
+
|
| 981 |
+
This function is expected to work on POSIX system only.
|
| 982 |
+
This code is adapted from code by Intel developer @anton-malakhov
|
| 983 |
+
available at https://github.com/IntelPython/smp
|
| 984 |
+
|
| 985 |
+
Copyright (c) 2017, Intel Corporation published under the BSD 3-Clause
|
| 986 |
+
license
|
| 987 |
+
"""
|
| 988 |
+
libc = self._get_libc()
|
| 989 |
+
if not hasattr(libc, "dl_iterate_phdr"): # pragma: no cover
|
| 990 |
+
warnings.warn(
|
| 991 |
+
"Could not find dl_iterate_phdr in the C standard library.",
|
| 992 |
+
RuntimeWarning,
|
| 993 |
+
)
|
| 994 |
+
return []
|
| 995 |
+
|
| 996 |
+
# Callback function for `dl_iterate_phdr` which is called for every
|
| 997 |
+
# library loaded in the current process until it returns 1.
|
| 998 |
+
def match_library_callback(info, size, data):
|
| 999 |
+
# Get the path of the current library
|
| 1000 |
+
filepath = info.contents.dlpi_name
|
| 1001 |
+
if filepath:
|
| 1002 |
+
filepath = filepath.decode("utf-8")
|
| 1003 |
+
|
| 1004 |
+
# Store the library controller if it is supported and selected
|
| 1005 |
+
self._make_controller_from_path(filepath)
|
| 1006 |
+
return 0
|
| 1007 |
+
|
| 1008 |
+
c_func_signature = ctypes.CFUNCTYPE(
|
| 1009 |
+
ctypes.c_int, # Return type
|
| 1010 |
+
ctypes.POINTER(_dl_phdr_info),
|
| 1011 |
+
ctypes.c_size_t,
|
| 1012 |
+
ctypes.c_char_p,
|
| 1013 |
+
)
|
| 1014 |
+
c_match_library_callback = c_func_signature(match_library_callback)
|
| 1015 |
+
|
| 1016 |
+
data = ctypes.c_char_p(b"")
|
| 1017 |
+
libc.dl_iterate_phdr(c_match_library_callback, data)
|
| 1018 |
+
|
| 1019 |
+
def _find_libraries_with_dyld(self):
|
| 1020 |
+
"""Loop through loaded libraries and return binders on supported ones
|
| 1021 |
+
|
| 1022 |
+
This function is expected to work on OSX system only
|
| 1023 |
+
"""
|
| 1024 |
+
libc = self._get_libc()
|
| 1025 |
+
if not hasattr(libc, "_dyld_image_count"): # pragma: no cover
|
| 1026 |
+
warnings.warn(
|
| 1027 |
+
"Could not find _dyld_image_count in the C standard library.",
|
| 1028 |
+
RuntimeWarning,
|
| 1029 |
+
)
|
| 1030 |
+
return []
|
| 1031 |
+
|
| 1032 |
+
n_dyld = libc._dyld_image_count()
|
| 1033 |
+
libc._dyld_get_image_name.restype = ctypes.c_char_p
|
| 1034 |
+
|
| 1035 |
+
for i in range(n_dyld):
|
| 1036 |
+
filepath = ctypes.string_at(libc._dyld_get_image_name(i))
|
| 1037 |
+
filepath = filepath.decode("utf-8")
|
| 1038 |
+
|
| 1039 |
+
# Store the library controller if it is supported and selected
|
| 1040 |
+
self._make_controller_from_path(filepath)
|
| 1041 |
+
|
| 1042 |
+
def _find_libraries_with_enum_process_module_ex(self):
|
| 1043 |
+
"""Loop through loaded libraries and return binders on supported ones
|
| 1044 |
+
|
| 1045 |
+
This function is expected to work on windows system only.
|
| 1046 |
+
This code is adapted from code by Philipp Hagemeister @phihag available
|
| 1047 |
+
at https://stackoverflow.com/questions/17474574
|
| 1048 |
+
"""
|
| 1049 |
+
from ctypes.wintypes import DWORD, HMODULE, MAX_PATH
|
| 1050 |
+
|
| 1051 |
+
PROCESS_QUERY_INFORMATION = 0x0400
|
| 1052 |
+
PROCESS_VM_READ = 0x0010
|
| 1053 |
+
|
| 1054 |
+
LIST_LIBRARIES_ALL = 0x03
|
| 1055 |
+
|
| 1056 |
+
ps_api = self._get_windll("Psapi")
|
| 1057 |
+
kernel_32 = self._get_windll("kernel32")
|
| 1058 |
+
|
| 1059 |
+
h_process = kernel_32.OpenProcess(
|
| 1060 |
+
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, os.getpid()
|
| 1061 |
+
)
|
| 1062 |
+
if not h_process: # pragma: no cover
|
| 1063 |
+
raise OSError(f"Could not open PID {os.getpid()}")
|
| 1064 |
+
|
| 1065 |
+
try:
|
| 1066 |
+
buf_count = 256
|
| 1067 |
+
needed = DWORD()
|
| 1068 |
+
# Grow the buffer until it becomes large enough to hold all the
|
| 1069 |
+
# module headers
|
| 1070 |
+
while True:
|
| 1071 |
+
buf = (HMODULE * buf_count)()
|
| 1072 |
+
buf_size = ctypes.sizeof(buf)
|
| 1073 |
+
if not ps_api.EnumProcessModulesEx(
|
| 1074 |
+
h_process,
|
| 1075 |
+
ctypes.byref(buf),
|
| 1076 |
+
buf_size,
|
| 1077 |
+
ctypes.byref(needed),
|
| 1078 |
+
LIST_LIBRARIES_ALL,
|
| 1079 |
+
):
|
| 1080 |
+
raise OSError("EnumProcessModulesEx failed")
|
| 1081 |
+
if buf_size >= needed.value:
|
| 1082 |
+
break
|
| 1083 |
+
buf_count = needed.value // (buf_size // buf_count)
|
| 1084 |
+
|
| 1085 |
+
count = needed.value // (buf_size // buf_count)
|
| 1086 |
+
h_modules = map(HMODULE, buf[:count])
|
| 1087 |
+
|
| 1088 |
+
# Loop through all the module headers and get the library path
|
| 1089 |
+
# Allocate a buffer for the path 10 times the size of MAX_PATH to take
|
| 1090 |
+
# into account long path names.
|
| 1091 |
+
max_path = 10 * MAX_PATH
|
| 1092 |
+
buf = ctypes.create_unicode_buffer(max_path)
|
| 1093 |
+
n_size = DWORD()
|
| 1094 |
+
for h_module in h_modules:
|
| 1095 |
+
# Get the path of the current module
|
| 1096 |
+
if not ps_api.GetModuleFileNameExW(
|
| 1097 |
+
h_process, h_module, ctypes.byref(buf), ctypes.byref(n_size)
|
| 1098 |
+
):
|
| 1099 |
+
raise OSError("GetModuleFileNameEx failed")
|
| 1100 |
+
filepath = buf.value
|
| 1101 |
+
|
| 1102 |
+
if len(filepath) == max_path: # pragma: no cover
|
| 1103 |
+
warnings.warn(
|
| 1104 |
+
"Could not get the full path of a dynamic library (path too "
|
| 1105 |
+
"long). This library will be ignored and threadpoolctl might "
|
| 1106 |
+
"not be able to control or display information about all "
|
| 1107 |
+
f"loaded libraries. Here's the truncated path: {filepath!r}",
|
| 1108 |
+
RuntimeWarning,
|
| 1109 |
+
)
|
| 1110 |
+
else:
|
| 1111 |
+
# Store the library controller if it is supported and selected
|
| 1112 |
+
self._make_controller_from_path(filepath)
|
| 1113 |
+
finally:
|
| 1114 |
+
kernel_32.CloseHandle(h_process)
|
| 1115 |
+
|
| 1116 |
+
def _find_libraries_pyodide(self):
|
| 1117 |
+
"""Pyodide specific implementation for finding loaded libraries.
|
| 1118 |
+
|
| 1119 |
+
Adapted from suggestion in https://github.com/joblib/threadpoolctl/pull/169#issuecomment-1946696449.
|
| 1120 |
+
|
| 1121 |
+
One day, we may have a simpler solution. libc dl_iterate_phdr needs to
|
| 1122 |
+
be implemented in Emscripten and exposed in Pyodide, see
|
| 1123 |
+
https://github.com/emscripten-core/emscripten/issues/21354 for more
|
| 1124 |
+
details.
|
| 1125 |
+
"""
|
| 1126 |
+
try:
|
| 1127 |
+
from pyodide_js._module import LDSO
|
| 1128 |
+
except ImportError:
|
| 1129 |
+
warnings.warn(
|
| 1130 |
+
"Unable to import LDSO from pyodide_js._module. This should never "
|
| 1131 |
+
"happen."
|
| 1132 |
+
)
|
| 1133 |
+
return
|
| 1134 |
+
|
| 1135 |
+
for filepath in LDSO.loadedLibsByName.as_object_map():
|
| 1136 |
+
# Some libraries are duplicated by Pyodide and do not exist in the
|
| 1137 |
+
# filesystem, so we first check for the existence of the file. For
|
| 1138 |
+
# more details, see
|
| 1139 |
+
# https://github.com/joblib/threadpoolctl/pull/169#issuecomment-1947946728
|
| 1140 |
+
if os.path.exists(filepath):
|
| 1141 |
+
self._make_controller_from_path(filepath)
|
| 1142 |
+
|
| 1143 |
+
def _make_controller_from_path(self, filepath):
|
| 1144 |
+
"""Store a library controller if it is supported and selected"""
|
| 1145 |
+
# Required to resolve symlinks
|
| 1146 |
+
filepath = _realpath(filepath)
|
| 1147 |
+
# `lower` required to take account of OpenMP dll case on Windows
|
| 1148 |
+
# (vcomp, VCOMP, Vcomp, ...)
|
| 1149 |
+
filename = os.path.basename(filepath).lower()
|
| 1150 |
+
|
| 1151 |
+
# Loop through supported libraries to find if this filename corresponds
|
| 1152 |
+
# to a supported one.
|
| 1153 |
+
for controller_class in _ALL_CONTROLLERS:
|
| 1154 |
+
# check if filename matches a supported prefix
|
| 1155 |
+
prefix = self._check_prefix(filename, controller_class.filename_prefixes)
|
| 1156 |
+
|
| 1157 |
+
# filename does not match any of the prefixes of the candidate
|
| 1158 |
+
# library. move to next library.
|
| 1159 |
+
if prefix is None:
|
| 1160 |
+
continue
|
| 1161 |
+
|
| 1162 |
+
# workaround for BLAS libraries packaged by conda-forge on windows, which
|
| 1163 |
+
# are all renamed "libblas.dll". We thus have to check to which BLAS
|
| 1164 |
+
# implementation it actually corresponds looking for implementation
|
| 1165 |
+
# specific symbols.
|
| 1166 |
+
if prefix == "libblas":
|
| 1167 |
+
if filename.endswith(".dll"):
|
| 1168 |
+
libblas = ctypes.CDLL(filepath, _RTLD_NOLOAD)
|
| 1169 |
+
if not any(
|
| 1170 |
+
hasattr(libblas, func)
|
| 1171 |
+
for func in controller_class.check_symbols
|
| 1172 |
+
):
|
| 1173 |
+
continue
|
| 1174 |
+
else:
|
| 1175 |
+
# We ignore libblas on other platforms than windows because there
|
| 1176 |
+
# might be a libblas dso comming with openblas for instance that
|
| 1177 |
+
# can't be used to instantiate a pertinent LibController (many
|
| 1178 |
+
# symbols are missing) and would create confusion by making a
|
| 1179 |
+
# duplicate entry in threadpool_info.
|
| 1180 |
+
continue
|
| 1181 |
+
|
| 1182 |
+
# filename matches a prefix. Now we check if the library has the symbols we
|
| 1183 |
+
# are looking for. If none of the symbols exists, it's very likely not the
|
| 1184 |
+
# expected library (e.g. a library having a common prefix with one of the
|
| 1185 |
+
# our supported libraries). Otherwise, create and store the library
|
| 1186 |
+
# controller.
|
| 1187 |
+
lib_controller = controller_class(
|
| 1188 |
+
filepath=filepath, prefix=prefix, parent=self
|
| 1189 |
+
)
|
| 1190 |
+
|
| 1191 |
+
if filepath in (lib.filepath for lib in self.lib_controllers):
|
| 1192 |
+
# We already have a controller for this library.
|
| 1193 |
+
continue
|
| 1194 |
+
|
| 1195 |
+
if not hasattr(controller_class, "check_symbols") or any(
|
| 1196 |
+
hasattr(lib_controller.dynlib, func)
|
| 1197 |
+
for func in controller_class.check_symbols
|
| 1198 |
+
):
|
| 1199 |
+
self.lib_controllers.append(lib_controller)
|
| 1200 |
+
|
| 1201 |
+
def _check_prefix(self, library_basename, filename_prefixes):
|
| 1202 |
+
"""Return the prefix library_basename starts with
|
| 1203 |
+
|
| 1204 |
+
Return None if none matches.
|
| 1205 |
+
"""
|
| 1206 |
+
for prefix in filename_prefixes:
|
| 1207 |
+
if library_basename.startswith(prefix):
|
| 1208 |
+
return prefix
|
| 1209 |
+
return None
|
| 1210 |
+
|
| 1211 |
+
def _warn_if_incompatible_openmp(self):
|
| 1212 |
+
"""Raise a warning if llvm-OpenMP and intel-OpenMP are both loaded"""
|
| 1213 |
+
prefixes = [lib_controller.prefix for lib_controller in self.lib_controllers]
|
| 1214 |
+
msg = textwrap.dedent(
|
| 1215 |
+
"""
|
| 1216 |
+
Found Intel OpenMP ('libiomp') and LLVM OpenMP ('libomp') loaded at
|
| 1217 |
+
the same time. Both libraries are known to be incompatible and this
|
| 1218 |
+
can cause random crashes or deadlocks on Linux when loaded in the
|
| 1219 |
+
same Python program.
|
| 1220 |
+
Using threadpoolctl may cause crashes or deadlocks. For more
|
| 1221 |
+
information and possible workarounds, please see
|
| 1222 |
+
https://github.com/joblib/threadpoolctl/blob/master/multiple_openmp.md
|
| 1223 |
+
"""
|
| 1224 |
+
)
|
| 1225 |
+
if "libomp" in prefixes and "libiomp" in prefixes:
|
| 1226 |
+
warnings.warn(msg, RuntimeWarning)
|
| 1227 |
+
|
| 1228 |
+
@classmethod
|
| 1229 |
+
def _get_libc(cls):
|
| 1230 |
+
"""Load the lib-C for unix systems."""
|
| 1231 |
+
libc = cls._system_libraries.get("libc")
|
| 1232 |
+
if libc is None:
|
| 1233 |
+
# Remark: If libc is statically linked or if Python is linked against an
|
| 1234 |
+
# alternative implementation of libc like musl, find_library will return
|
| 1235 |
+
# None and CDLL will load the main program itself which should contain the
|
| 1236 |
+
# libc symbols. We still name it libc for convenience.
|
| 1237 |
+
# If the main program does not contain the libc symbols, it's ok because
|
| 1238 |
+
# we check their presence later anyway.
|
| 1239 |
+
libc = ctypes.CDLL(find_library("c"), mode=_RTLD_NOLOAD)
|
| 1240 |
+
cls._system_libraries["libc"] = libc
|
| 1241 |
+
return libc
|
| 1242 |
+
|
| 1243 |
+
@classmethod
|
| 1244 |
+
def _get_windll(cls, dll_name):
|
| 1245 |
+
"""Load a windows DLL"""
|
| 1246 |
+
dll = cls._system_libraries.get(dll_name)
|
| 1247 |
+
if dll is None:
|
| 1248 |
+
dll = ctypes.WinDLL(f"{dll_name}.dll")
|
| 1249 |
+
cls._system_libraries[dll_name] = dll
|
| 1250 |
+
return dll
|
| 1251 |
+
|
| 1252 |
+
|
| 1253 |
+
def _main():
|
| 1254 |
+
"""Commandline interface to display thread-pool information and exit."""
|
| 1255 |
+
import argparse
|
| 1256 |
+
import importlib
|
| 1257 |
+
import json
|
| 1258 |
+
import sys
|
| 1259 |
+
|
| 1260 |
+
parser = argparse.ArgumentParser(
|
| 1261 |
+
usage="python -m threadpoolctl -i numpy scipy.linalg xgboost",
|
| 1262 |
+
description="Display thread-pool information and exit.",
|
| 1263 |
+
)
|
| 1264 |
+
parser.add_argument(
|
| 1265 |
+
"-i",
|
| 1266 |
+
"--import",
|
| 1267 |
+
dest="modules",
|
| 1268 |
+
nargs="*",
|
| 1269 |
+
default=(),
|
| 1270 |
+
help="Python modules to import before introspecting thread-pools.",
|
| 1271 |
+
)
|
| 1272 |
+
parser.add_argument(
|
| 1273 |
+
"-c",
|
| 1274 |
+
"--command",
|
| 1275 |
+
help="a Python statement to execute before introspecting thread-pools.",
|
| 1276 |
+
)
|
| 1277 |
+
|
| 1278 |
+
options = parser.parse_args(sys.argv[1:])
|
| 1279 |
+
for module in options.modules:
|
| 1280 |
+
try:
|
| 1281 |
+
importlib.import_module(module, package=None)
|
| 1282 |
+
except ImportError:
|
| 1283 |
+
print("WARNING: could not import", module, file=sys.stderr)
|
| 1284 |
+
|
| 1285 |
+
if options.command:
|
| 1286 |
+
exec(options.command)
|
| 1287 |
+
|
| 1288 |
+
print(json.dumps(threadpool_info(), indent=2))
|
| 1289 |
+
|
| 1290 |
+
|
| 1291 |
+
if __name__ == "__main__":
|
| 1292 |
+
_main()
|
venv/lib/python3.10/site-packages/typing_extensions.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|