zhmiscellany 6.2.4__py3-none-any.whl → 6.2.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,5 @@
1
1
  # these lines are purposefully the first thing to run when zhmiscellany is imported
2
- import threading, logging, os, inspect, tempfile, shutil
3
- from itertools import chain
4
- import zhmiscellany.fileio
5
- from io import StringIO
6
- import sys
7
- import io
8
- from unittest.mock import patch
2
+ import sys # cannot be moved
9
3
 
10
4
  # Ray availability check
11
5
  if sys.platform == "win32" or True:
@@ -13,8 +7,13 @@ if sys.platform == "win32" or True:
13
7
  else:
14
8
  RAY_AVAILABLE = False
15
9
 
10
+ import os # needed for module-level log clearing and cause detection
16
11
 
17
12
  def clear_logs():
13
+ import tempfile
14
+ import os
15
+ import shutil
16
+ import zhmiscellany.fileio
18
17
  ray_dir = tempfile.gettempdir()
19
18
  ray_dir = os.path.join(ray_dir, 'ray')
20
19
 
@@ -46,6 +45,7 @@ if 'ray_logs_cleared' not in os.environ:
46
45
 
47
46
 
48
47
  def safe_open_log(path, unbuffered=False, **kwargs):
48
+ import os
49
49
  try:
50
50
  kwargs.setdefault("buffering", 1)
51
51
  kwargs.setdefault("mode", "a")
@@ -65,6 +65,8 @@ def safe_open_log(path, unbuffered=False, **kwargs):
65
65
 
66
66
 
67
67
  def ray_init(auto=False):
68
+ import threading
69
+ import os
68
70
  if not RAY_AVAILABLE:
69
71
  print("ray_init() only supports Windows! Functionality disabled")
70
72
  return
@@ -96,6 +98,10 @@ def _ray_init():
96
98
 
97
99
  try:
98
100
  def safe_ray_init():
101
+ import sys
102
+ import io
103
+ import ray
104
+
99
105
  def ensure_valid_handles():
100
106
  """Ensure stdout and stderr are valid file-like objects"""
101
107
  if not hasattr(sys.stdout, 'write') or sys.stdout.closed:
@@ -136,6 +142,7 @@ def _ray_init():
136
142
 
137
143
 
138
144
  def get_import_chain():
145
+ import inspect
139
146
  frame = inspect.currentframe()
140
147
  chain = []
141
148
  while frame:
@@ -149,6 +156,8 @@ def get_import_chain():
149
156
  frame = frame.f_back
150
157
  return chain[::-1]
151
158
 
159
+
160
+ # Cause detection for auto-initializing ray
152
161
  cause_strings = [
153
162
  'processing.multiprocess(',
154
163
  'processing.batch_multiprocess(',
@@ -171,6 +180,7 @@ for file in cause_files:
171
180
  cause = True
172
181
  break
173
182
 
183
+ import threading
174
184
  _ray_init_thread = threading.Thread() # initialize variable to completed thread
175
185
  _ray_init_thread.start()
176
186
 
@@ -194,6 +204,8 @@ class ThreadWithResult(threading.Thread):
194
204
 
195
205
 
196
206
  def batch_multiprocess(targets_and_args, max_retries=0, expect_crashes=False, disable_warning=False, flatten=False):
207
+ import logging
208
+ from itertools import chain
197
209
  if not RAY_AVAILABLE:
198
210
  print("batch_multiprocess() only supports Windows! Returning empty list")
199
211
  return []
@@ -213,6 +225,7 @@ from zhmiscellany._processing_supportfuncs import _ray_init_thread; _ray_init_th
213
225
  _ray_init_thread.join()
214
226
 
215
227
  if not expect_crashes:
228
+ import ray
216
229
  @ray.remote(max_retries=max_retries, num_cpus=0)
217
230
  def worker(func, *args):
218
231
  return func(*args)
@@ -223,12 +236,14 @@ from zhmiscellany._processing_supportfuncs import _ray_init_thread; _ray_init_th
223
236
  results = list(chain.from_iterable(results))
224
237
  return results
225
238
  else:
239
+ import ray
226
240
  def wrap_exception(task, disable_warning, max_retries):
227
241
  try:
228
242
  result = multiprocess(*task, disable_warning=disable_warning, max_retries=max_retries)
229
243
  return result
230
244
  except ray.exceptions.WorkerCrashedError:
231
245
  return None
246
+ import threading # this import is explicitly in the original code
232
247
  threads = []
233
248
  for task in targets_and_args:
234
249
  t = ThreadWithResult(
@@ -244,6 +259,7 @@ from zhmiscellany._processing_supportfuncs import _ray_init_thread; _ray_init_th
244
259
  results = list(chain.from_iterable(results))
245
260
  return results
246
261
 
262
+
247
263
  def multiprocess(target, args=(), max_retries=0, disable_warning=False):
248
264
  if not RAY_AVAILABLE:
249
265
  print("multiprocess() only supports Windows! Returning None")
@@ -255,10 +271,12 @@ class RayActorWrapper:
255
271
  def __init__(self, actor_instance):
256
272
  self._actor = actor_instance
257
273
 
274
+ import ray
258
275
  ray.get(self._actor._ready.remote())
259
276
 
260
277
  def __getattr__(self, name):
261
278
  # When you access an attribute, assume it's a remote method.
279
+ import ray
262
280
  remote_method = getattr(self._actor, name)
263
281
  if not callable(remote_method):
264
282
  # If it's not callable, try to get its value.
@@ -274,6 +292,8 @@ class RayActorWrapper:
274
292
 
275
293
 
276
294
  def synchronous_class_multiprocess(cls, *args, disable_warning=False, **kwargs):
295
+ import logging
296
+ import ray
277
297
  if not RAY_AVAILABLE:
278
298
  print("synchronous_class_multiprocess() only supports Windows! Returning None")
279
299
  return None
@@ -299,4 +319,4 @@ from zhmiscellany._processing_supportfuncs import _ray_init_thread; _ray_init_th
299
319
 
300
320
  remote_cls = ray.remote(num_cpus=0)(cls)
301
321
  actor_instance = remote_cls.remote(*args, **kwargs)
302
- return RayActorWrapper(actor_instance)
322
+ return RayActorWrapper(actor_instance)
@@ -1,5 +1,5 @@
1
- import os, zlib
2
1
  def gen():
2
+ import os, zlib
3
3
  os.makedirs('resources', exist_ok=True)
4
4
  os.makedirs('resources\\random_header_generator', exist_ok=True)
5
5
  os.makedirs('resources\\random_header_generator\\data', exist_ok=True)
zhmiscellany/dict.py CHANGED
@@ -1,5 +1,3 @@
1
- import json
2
-
3
-
4
1
  def print_dict(ldict):
2
+ import json
5
3
  print(json.dumps(ldict, indent=4))
zhmiscellany/discord.py CHANGED
@@ -1,32 +1,16 @@
1
- import time
2
- import sys
3
- import requests
4
- import copy
5
- import zhmiscellany.fileio
6
- import zhmiscellany.netio
7
- import zhmiscellany.processing
1
+ import sys # cannot be touched because it's needed
8
2
  from ._discord_supportfuncs import scrape_guild
9
3
 
10
- import base64
11
- import os
12
- import json
13
- import re
14
-
15
4
  # Windows-specific imports
16
5
  if sys.platform == "win32":
17
- try:
18
- import win32crypt
19
- from Crypto.Cipher import AES
20
- WIN32_AVAILABLE = True
21
- except ImportError:
22
- WIN32_AVAILABLE = False
23
- print("Warning: Windows modules not available - local Discord user detection disabled")
6
+ WIN32_AVAILABLE = True
24
7
  else:
25
8
  WIN32_AVAILABLE = False
26
9
 
27
10
 
28
11
  def add_reactions_to_message(user_token, emojis, channel_id, message_id):
29
-
12
+ import time
13
+ import requests
30
14
  for emoji in emojis:
31
15
  url = f'https://discord.com/api/v9/channels/{channel_id}/messages/{message_id}/reactions/{emoji}/@me'
32
16
  #headers = {**zhmiscellany.netio.generate_headers(url), 'Authorization': user_token}
@@ -54,7 +38,9 @@ def get_channel_messages(user_token, channel_id, limit=0, use_cache=True, show_p
54
38
  '''
55
39
  Function to get all client messages in a specific channel. Script by @z_h_ on discord.
56
40
  '''
57
-
41
+ import requests
42
+ import zhmiscellany.fileio
43
+ import os
58
44
  if use_cache:
59
45
  cache_folder = 'zhmiscellany_cache'
60
46
  zhmiscellany.fileio.create_folder(cache_folder)
@@ -118,10 +104,18 @@ def get_channel_messages(user_token, channel_id, limit=0, use_cache=True, show_p
118
104
 
119
105
 
120
106
  def get_local_discord_user(show_output=False):
107
+ import requests
108
+ import os
109
+ import json
110
+ import re
111
+ import base64
112
+ import win32crypt
113
+ from Crypto.Cipher import AES
114
+ import zhmiscellany.netio
121
115
  if not WIN32_AVAILABLE:
122
116
  print("get_local_discord_user() only supports Windows! Returning None")
123
117
  return None
124
-
118
+
125
119
  global _cached_user_info
126
120
  try:
127
121
  a = _cached_user_info
@@ -241,6 +235,9 @@ def get_local_discord_user(show_output=False):
241
235
 
242
236
 
243
237
  def get_guild_channels(user_token, guild_id, use_cache=True):
238
+ import requests
239
+ import os
240
+ import zhmiscellany.netio
244
241
  if use_cache:
245
242
  potential_path = os.path.join('zhmiscellany_cache', f'{guild_id}_channels.json')
246
243
  if os.path.exists(potential_path):
@@ -260,12 +257,17 @@ def get_guild_channels(user_token, guild_id, use_cache=True):
260
257
 
261
258
 
262
259
  def send_type(user_token, channel_id): # after sending the typing post request, the account will be shown as "typing" in the given channel for 10 seconds, or until a message is sent.
260
+ import requests
261
+ import zhmiscellany.netio
263
262
  url = f'https://discord.com/api/v9/channels/{channel_id}/typing'
264
263
  headers = {**zhmiscellany.netio.generate_headers(url), 'Authorization': user_token}
265
264
  return requests.post(url, headers=headers)
266
265
 
267
266
 
268
267
  def send_message(user_token, text, channel_id, attachments=None, typing_time=0):
268
+ import time
269
+ import requests
270
+ import zhmiscellany.processing
269
271
  typing_time_increments = 9.5 # not set to 10 because then every 10 seconds the typing would stop very briefly
270
272
  while typing_time > 0:
271
273
  zhmiscellany.processing.start_daemon(target=send_type, args=(user_token, channel_id))
@@ -294,6 +296,8 @@ def send_message(user_token, text, channel_id, attachments=None, typing_time=0):
294
296
 
295
297
 
296
298
  def get_message(user_token, channel_id, message_id):
299
+ import requests
300
+ import zhmiscellany.netio
297
301
  message_url = f'https://discord.com/api/v9/channels/{channel_id}/messages?limit=1&around={message_id}'
298
302
  message = requests.get(message_url, headers={**zhmiscellany.netio.generate_headers(message_url), 'Authorization': user_token})
299
303
  message = message.json()
@@ -308,6 +312,7 @@ def ids_to_message_url(channel_id, message_id, guild_id=None):
308
312
 
309
313
 
310
314
  def message_url_to_ids(message_url):
315
+ import re
311
316
  # Regular expressions to extract IDs
312
317
  guild_channel_message_regex = r'https:\/\/discord\.com\/channels\/(\d+)\/(\d+)\/(\d+)'
313
318
  channel_message_regex = r'https:\/\/discord\.com\/channels\/(\d+)\/(\d+)'
@@ -336,6 +341,8 @@ def decode_user_id(user_token):
336
341
  padded_base64 = payload_base64 + '=' * (4 - len(payload_base64) % 4)
337
342
 
338
343
  # Decoding the base64 and converting to a JSON object
344
+ import base64
345
+ import json
339
346
  payload_json = base64.b64decode(padded_base64).decode('utf-8')
340
347
  user_id = json.loads(payload_json)
341
348
 
@@ -343,6 +350,9 @@ def decode_user_id(user_token):
343
350
 
344
351
 
345
352
  def get_guilds(user_token, use_cache=True):
353
+ import requests
354
+ import os
355
+ import zhmiscellany.netio
346
356
  if use_cache:
347
357
  potential_path = os.path.join('zhmiscellany_cache', f'{decode_user_id(user_token)}_guilds.json')
348
358
  if os.path.exists(potential_path):
@@ -361,6 +371,9 @@ def get_guilds(user_token, use_cache=True):
361
371
 
362
372
 
363
373
  def get_dm_channels(user_token, use_cache=True):
374
+ import requests
375
+ import os
376
+ import zhmiscellany.netio
364
377
  if use_cache:
365
378
  potential_path = os.path.join('zhmiscellany_cache', f'{decode_user_id(user_token)}_dm_channels.json')
366
379
  if os.path.exists(potential_path):
@@ -379,6 +392,9 @@ def get_dm_channels(user_token, use_cache=True):
379
392
 
380
393
 
381
394
  def get_invite_info(user_token, invite_code, use_cache=True):
395
+ import requests
396
+ import os
397
+ import zhmiscellany.netio
382
398
  if use_cache:
383
399
  potential_path = os.path.join('zhmiscellany_cache', f'{invite_code}_invite.json')
384
400
  if os.path.exists(potential_path):
@@ -397,6 +413,8 @@ def get_invite_info(user_token, invite_code, use_cache=True):
397
413
 
398
414
 
399
415
  def generate_server_invite(user_token, channel_id):
416
+ import zhmiscellany.netio
417
+ import requests
400
418
  url = f"https://discord.com/api/v9/channels/{channel_id}/invites"
401
419
  response = requests.get(url, headers={**zhmiscellany.netio.generate_headers(url), 'Authorization': user_token})
402
420
 
@@ -408,6 +426,8 @@ def generate_server_invite(user_token, channel_id):
408
426
 
409
427
 
410
428
  def get_approximate_member_count(user_token, channel_id, use_cache=True):
429
+ import os
430
+ import zhmiscellany.netio
411
431
  if use_cache:
412
432
  potential_path = os.path.join('zhmiscellany_cache', f'{channel_id}_member_count.json')
413
433
  if os.path.exists(potential_path):
@@ -434,6 +454,9 @@ def timestamp_to_id(timestamp):
434
454
 
435
455
 
436
456
  def get_user_avatar_url(user_token, user_id, use_cache=True):
457
+ import requests
458
+ import os
459
+ import zhmiscellany.netio
437
460
  url = f"https://discord.com/api/v10/users/{user_id}"
438
461
 
439
462
  if use_cache:
zhmiscellany/fileio.py CHANGED
@@ -1,22 +1,9 @@
1
- from ._fileio_supportfuncs import is_junction
2
- import json, os, shutil, dill, sys, pickle, base64, zlib
3
- import zhmiscellany.string
4
- import zhmiscellany.misc
5
- import hashlib
6
- from collections import defaultdict
7
- from itertools import chain
8
- import tempfile
9
- import random
10
- import string
11
- import orjson
12
- from datetime import datetime
13
- import inspect
14
-
15
-
16
1
  def read_json_file(file_path):
17
2
  """
18
3
  Reads JSON data from a file and returns it as a dictionary.
19
4
  """
5
+ import json
6
+ import os
20
7
  if os.path.exists(file_path):
21
8
  with open(file_path, 'r') as file:
22
9
  data = json.load(file)
@@ -31,61 +18,65 @@ def write_json_file(file_path, data):
31
18
  """
32
19
  Writes a dictionary to a JSON file.
33
20
  """
21
+ import json
34
22
  with open(file_path, 'w') as file:
35
23
  json.dump(data, file, indent=4)
36
24
 
37
25
 
38
26
  def create_folder(folder_name):
27
+ import os
39
28
  if not os.path.exists(folder_name):
40
29
  os.makedirs(folder_name)
41
30
 
42
31
 
43
32
  def remove_folder(folder_name):
33
+ import os
34
+ import shutil
44
35
  if os.path.exists(folder_name):
45
36
  shutil.rmtree(folder_name)
46
37
 
47
38
 
48
39
  def base_name_no_ext(file_path):
40
+ import os
49
41
  base_name = os.path.basename(file_path)
50
42
  base_name_without_extension, _ = os.path.splitext(base_name)
51
43
  return base_name_without_extension
52
44
 
53
45
 
54
46
  def convert_name_to_filename(name):
47
+ import zhmiscellany.string
55
48
  return zhmiscellany.string.multi_replace(name, [("/","["), (":","]"), (".","+")])
56
49
 
57
50
 
58
51
  def convert_filename_to_name(filename):
52
+ import zhmiscellany.string
59
53
  return zhmiscellany.string.multi_replace(filename, [("[","/"), ("]",":"), ("+",".")])
60
54
 
61
55
 
62
56
  def recursive_copy_files(source_dir, destination_dir, prints=False):
57
+ import os
58
+ import shutil
63
59
  if prints:
64
60
  print('Validating matching directory structure')
65
61
  for root, dirs, files in os.walk(source_dir):
66
62
  for dir in dirs:
67
63
  dir_path = os.path.join(root, dir)
68
64
  dest_dir_path = os.path.join(destination_dir, os.path.relpath(dir_path, source_dir))
69
-
70
65
  if not os.path.exists(dest_dir_path):
71
66
  print(f'Creating missing directory {dest_dir_path}')
72
67
  os.makedirs(dest_dir_path)
73
-
74
68
  if prints:
75
69
  print('Getting a list of files in the source directory')
76
70
  source_files = []
77
71
  for root, _, files in os.walk(source_dir):
78
72
  for file in files:
79
73
  source_files.append(os.path.join(root, file))
80
-
81
74
  if prints:
82
75
  print('Getting a list of files in the destination directory')
83
76
  dest_files = []
84
77
  for root, _, files in os.walk(destination_dir):
85
78
  for file in files:
86
79
  dest_files.append(os.path.join(root, file))
87
-
88
-
89
80
  if prints:
90
81
  print('Copying files from source to destination, skipping duplicates')
91
82
  for root, dirs, files in os.walk(source_dir):
@@ -93,7 +84,6 @@ def recursive_copy_files(source_dir, destination_dir, prints=False):
93
84
  source_file = os.path.join(root, file)
94
85
  rel_path = os.path.relpath(source_file, source_dir)
95
86
  dest_file = os.path.join(destination_dir, rel_path)
96
-
97
87
  if not os.path.exists(dest_file):
98
88
  if prints:
99
89
  print(f'Copying {source_file}')
@@ -105,10 +95,11 @@ def recursive_copy_files(source_dir, destination_dir, prints=False):
105
95
 
106
96
 
107
97
  def empty_directory(directory_path):
98
+ import os
99
+ import shutil
108
100
  # Iterate over all items in the directory
109
101
  for item in os.listdir(directory_path):
110
102
  item_path = os.path.join(directory_path, item)
111
-
112
103
  if os.path.isfile(item_path):
113
104
  # If it's a file, delete it
114
105
  os.unlink(item_path)
@@ -118,10 +109,12 @@ def empty_directory(directory_path):
118
109
 
119
110
 
120
111
  def abs_listdir(path):
112
+ import os
121
113
  return [os.path.join(path, file) for file in os.listdir(path)]
122
114
 
123
115
 
124
116
  def delete_ends_with(directory, string_endswith, avoid=[]):
117
+ import os
125
118
  files = abs_listdir(directory)
126
119
  for file in files:
127
120
  if file.endswith(string_endswith):
@@ -134,17 +127,20 @@ def read_bytes_section(file_path, section_start, section_end):
134
127
  file.seek(section_start) # Move the file pointer to the 'start' position
135
128
  bytes_to_read = section_end - section_start
136
129
  data = file.read(bytes_to_read) # Read 'bytes_to_read' number of bytes
137
-
138
130
  return data
139
131
 
140
132
 
141
133
  def copy_file_with_overwrite(src, dst):
134
+ import os
135
+ import shutil
142
136
  if os.path.exists(dst):
143
137
  os.remove(dst)
144
138
  shutil.copy2(src, dst)
145
139
 
146
140
 
147
141
  def fast_dill_dumps(object):
142
+ import pickle
143
+ import dill
148
144
  try:
149
145
  data = pickle.dumps(object, protocol=5) # pickle is much faster so at least attempt to use it at first
150
146
  except:
@@ -153,6 +149,8 @@ def fast_dill_dumps(object):
153
149
 
154
150
 
155
151
  def fast_dill_loads(data):
152
+ import pickle
153
+ import dill
156
154
  try:
157
155
  object = pickle.loads(data) # pickle is much faster so at least attempt to use it at first
158
156
  except:
@@ -161,6 +159,7 @@ def fast_dill_loads(data):
161
159
 
162
160
 
163
161
  def save_object_to_file(object, file_name, compressed=False):
162
+ import zlib
164
163
  with open(file_name, 'wb') as f:
165
164
  if compressed:
166
165
  f.write(zlib.compress(fast_dill_dumps(object)))
@@ -169,6 +168,7 @@ def save_object_to_file(object, file_name, compressed=False):
169
168
 
170
169
 
171
170
  def load_object_from_file(file_name, compressed=False):
171
+ import zlib
172
172
  with open(file_name, 'rb') as f:
173
173
  if compressed:
174
174
  return fast_dill_loads(zlib.decompress(f.read()))
@@ -178,26 +178,33 @@ def load_object_from_file(file_name, compressed=False):
178
178
 
179
179
  def pickle_and_encode(obj):
180
180
  """Pickles an object and URL-safe encodes it."""
181
+ import base64
182
+ import zlib
181
183
  pickled_data = zlib.compress(fast_dill_dumps(obj), 9) # Serialize the object
182
184
  encoded_data = base64.urlsafe_b64encode(pickled_data).decode() # Base64 encode
183
185
  return encoded_data
184
186
 
187
+
185
188
  def decode_and_unpickle(encoded_str):
186
189
  """Decodes a URL-safe encoded string and unpickles the object."""
190
+ import base64
191
+ import zlib
187
192
  pickled_data = base64.urlsafe_b64decode(encoded_str) # Decode from Base64
188
193
  obj = fast_dill_loads(zlib.decompress(pickled_data)) # Deserialize
189
194
  return obj
190
195
 
191
196
 
192
197
  def list_files_by_modified_time(directory):
198
+ import os
193
199
  files_with_times = [(file, os.path.getmtime(os.path.join(directory, file))) for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))]
194
200
  sorted_files = sorted(files_with_times, key=lambda x: x[1], reverse=True)
195
201
  sorted_file_names = [file for file, _ in sorted_files]
196
-
197
202
  return sorted_file_names
198
203
 
199
204
 
200
205
  def get_script_path():
206
+ """Returns the path to the current script or executable."""
207
+ import sys
201
208
  if getattr(sys, 'frozen', False):
202
209
  # Running as a standalone executable
203
210
  return sys.executable
@@ -207,10 +214,21 @@ def get_script_path():
207
214
 
208
215
 
209
216
  def chdir_to_script_dir():
217
+ import os
210
218
  os.chdir(os.path.dirname(get_script_path()))
211
219
 
212
220
 
213
221
  def cache(function, *args, **kwargs):
222
+ """
223
+ Caches the result of a function call to disk.
224
+ """
225
+ import os
226
+ import inspect
227
+ import orjson
228
+ import hashlib
229
+ from datetime import datetime
230
+ import zhmiscellany.fileio
231
+
214
232
  cache_folder = 'zhmiscellany_cache'
215
233
 
216
234
  def get_hash_orjson(data):
@@ -267,6 +285,10 @@ def cache(function, *args, **kwargs):
267
285
 
268
286
 
269
287
  def load_all_cached():
288
+ """
289
+ Loads all cached objects from the cache folder.
290
+ """
291
+ import os
270
292
  cache_folder = 'zhmiscellany_cache'
271
293
  if os.path.exists(cache_folder):
272
294
  files = abs_listdir(cache_folder)
@@ -280,6 +302,11 @@ def load_all_cached():
280
302
 
281
303
 
282
304
  def list_files_recursive(folder):
305
+ """
306
+ Recursively lists all files in a directory, excluding symlinks and junctions.
307
+ """
308
+ import os
309
+ from ._fileio_supportfuncs import is_junction
283
310
  files = []
284
311
  try:
285
312
  for entry in os.scandir(folder):
@@ -295,6 +322,9 @@ def list_files_recursive(folder):
295
322
 
296
323
 
297
324
  def list_files_recursive_multiprocessed(dir_path, return_folders=False):
325
+ import os
326
+ import zhmiscellany.processing
327
+
298
328
  def is_junction(entry):
299
329
  try:
300
330
  st = entry.stat(follow_symlinks=False)
@@ -342,6 +372,8 @@ def list_files_recursive_multiprocessed(dir_path, return_folders=False):
342
372
 
343
373
  def encode_safe_filename(s, max_length=16):
344
374
  """Encodes a string into a short, URL-safe, and file name-safe string."""
375
+ import base64
376
+ import hashlib
345
377
  encoded = base64.urlsafe_b64encode(s.encode()).decode().rstrip("=") # URL-safe encoding
346
378
  if len(encoded) > max_length: # Truncate if too long
347
379
  encoded = hashlib.md5(s.encode()).hexdigest()[:max_length] # Use a hash
@@ -349,6 +381,15 @@ def encode_safe_filename(s, max_length=16):
349
381
 
350
382
 
351
383
  def list_files_recursive_cache_optimised_multiprocessed(dir_path, show_timings=False, cache_in_temp=True):
384
+ import os
385
+ import zhmiscellany.processing
386
+ import zhmiscellany.fileio
387
+ import tempfile
388
+ from collections import defaultdict
389
+ import random
390
+ from itertools import chain
391
+ import zhmiscellany.misc
392
+
352
393
  def is_junction(entry):
353
394
  try:
354
395
  st = entry.stat(follow_symlinks=False)
@@ -501,7 +542,6 @@ def list_files_recursive_cache_optimised_multiprocessed(dir_path, show_timings=F
501
542
 
502
543
  groups = split_into_n_groups(changed_folders, scan_changed_folders_thread_group_count)
503
544
  tasks = [(atom, (group,)) for group in groups]
504
-
505
545
  if not tasks:
506
546
  results = []
507
547
  else:
@@ -516,19 +556,16 @@ def list_files_recursive_cache_optimised_multiprocessed(dir_path, show_timings=F
516
556
  if len(changed_folders) > fully_update_cache_threshold:
517
557
  new_folders.update(get_m_times(new_new_folders))
518
558
  if show_timings: zhmiscellany.misc.time_it(f'get m times of {len(new_new_folders)} new folders')
519
-
520
559
  zhmiscellany.fileio.save_object_to_file((files, new_folders), cache_file)
521
-
522
560
  if show_timings: zhmiscellany.misc.time_it(f'writing to cache')
523
561
 
524
562
  ret = list(chain.from_iterable(files.values()))
525
-
526
563
  if show_timings: zhmiscellany.misc.time_it('Everything together', 'lfrcomt')
527
-
528
564
  return ret
529
565
 
530
566
 
531
567
  def save_chunk(name, data):
568
+ import zhmiscellany.string
532
569
  create_folder(name)
533
570
  chunk_path = f'{name}/chunk_{zhmiscellany.string.get_universally_unique_string()}.pkl'
534
571
  save_object_to_file(data, chunk_path)
@@ -544,8 +581,12 @@ def load_chunks(name):
544
581
 
545
582
 
546
583
  def clear_chunks(name):
584
+ import os
547
585
  if os.path.exists(name):
548
586
  empty_directory(name)
549
587
 
588
+
550
589
  def list_drives():
590
+ import os
591
+ import string
551
592
  return [f"{d}:\\" for d in string.ascii_uppercase if os.path.exists(f"{d}:\\")]