SuperRealCo commited on
Commit
b495482
·
verified ·
1 Parent(s): ef23a31

Add files using upload-large-folder tool

Browse files
Files changed (50) hide show
  1. server.py +975 -0
  2. venv/bin/Activate.ps1 +247 -0
  3. venv/bin/activate +69 -0
  4. venv/bin/activate.csh +26 -0
  5. venv/bin/activate.fish +69 -0
  6. venv/bin/alembic +8 -0
  7. venv/bin/dotenv +8 -0
  8. venv/bin/ds +6 -0
  9. venv/bin/fonttools +8 -0
  10. venv/bin/huggingface-cli +8 -0
  11. venv/bin/imageio_download_bin +8 -0
  12. venv/bin/imageio_remove_bin +8 -0
  13. venv/bin/insightface-cli +8 -0
  14. venv/bin/isympy +8 -0
  15. venv/bin/lsm2bin +8 -0
  16. venv/bin/mako-render +8 -0
  17. venv/bin/normalizer +8 -0
  18. venv/bin/pip +8 -0
  19. venv/bin/pip3 +8 -0
  20. venv/bin/pip3.10 +8 -0
  21. venv/bin/proton +8 -0
  22. venv/bin/proton-viewer +8 -0
  23. venv/bin/pyav +8 -0
  24. venv/bin/pyftmerge +8 -0
  25. venv/bin/sz_split +8 -0
  26. venv/bin/sz_wc +8 -0
  27. venv/bin/tiff2fsspec +8 -0
  28. venv/bin/tiffcomment +8 -0
  29. venv/bin/tifffile +8 -0
  30. venv/bin/tiny-agents +8 -0
  31. venv/bin/torchfrtrace +8 -0
  32. venv/bin/torchrun +8 -0
  33. venv/bin/tqdm +8 -0
  34. venv/bin/transformers +8 -0
  35. venv/bin/transformers-cli +8 -0
  36. venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/INSTALLER +1 -0
  37. venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/METADATA +103 -0
  38. venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/RECORD +337 -0
  39. venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/REQUESTED +0 -0
  40. venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/WHEEL +5 -0
  41. venv/lib/python3.10/site-packages/PyGithub-2.6.1.dist-info/top_level.txt +1 -0
  42. venv/lib/python3.10/site-packages/_soundfile.py +11 -0
  43. venv/lib/python3.10/site-packages/cython.py +29 -0
  44. venv/lib/python3.10/site-packages/github/WorkflowStep.py +109 -0
  45. venv/lib/python3.10/site-packages/isympy.py +342 -0
  46. venv/lib/python3.10/site-packages/pylab.py +3 -0
  47. venv/lib/python3.10/site-packages/six.py +1003 -0
  48. venv/lib/python3.10/site-packages/soundfile.py +1674 -0
  49. venv/lib/python3.10/site-packages/threadpoolctl.py +1292 -0
  50. 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
+ [![PyPI](https://img.shields.io/pypi/v/PyGithub.svg)](https://pypi.python.org/pypi/PyGithub)
38
+ ![CI](https://github.com/PyGithub/PyGithub/workflows/CI/badge.svg)
39
+ [![readthedocs](https://img.shields.io/badge/docs-stable-brightgreen.svg?style=flat)](https://pygithub.readthedocs.io/en/stable/?badge=stable)
40
+ [![License](https://img.shields.io/badge/license-LGPL-blue.svg)](https://en.wikipedia.org/wiki/GNU_Lesser_General_Public_License)
41
+ [![Slack](https://img.shields.io/badge/Slack%20channel-%20%20-blue.svg)](https://join.slack.com/t/pygithub-project/shared_invite/zt-duj89xtx-uKFZtgAg209o6Vweqm8xeQ)
42
+ [![Open Source Helpers](https://www.codetriage.com/pygithub/pygithub/badges/users.svg)](https://www.codetriage.com/pygithub/pygithub)
43
+ [![codecov](https://codecov.io/gh/PyGithub/PyGithub/branch/master/graph/badge.svg)](https://codecov.io/gh/PyGithub/PyGithub)
44
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](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