stouputils 1.3.2__tar.gz → 1.3.4__tar.gz

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.
Files changed (107) hide show
  1. {stouputils-1.3.2 → stouputils-1.3.4}/PKG-INFO +2 -1
  2. {stouputils-1.3.2 → stouputils-1.3.4}/pyproject.toml +2 -1
  3. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras_utils/visualizations.py +16 -13
  4. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/scripts/exhaustive_process.py +6 -1
  5. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/io.py +60 -53
  6. {stouputils-1.3.2 → stouputils-1.3.4}/.gitignore +0 -0
  7. {stouputils-1.3.2 → stouputils-1.3.4}/LICENSE +0 -0
  8. {stouputils-1.3.2 → stouputils-1.3.4}/README.md +0 -0
  9. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/__init__.py +0 -0
  10. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/all_doctests.py +0 -0
  11. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/applications/__init__.py +0 -0
  12. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/applications/automatic_docs.py +0 -0
  13. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/applications/upscaler/__init__.py +0 -0
  14. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/applications/upscaler/config.py +0 -0
  15. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/applications/upscaler/image.py +0 -0
  16. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/applications/upscaler/video.py +0 -0
  17. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/archive.py +0 -0
  18. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/backup.py +0 -0
  19. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/collections.py +0 -0
  20. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/continuous_delivery/__init__.py +0 -0
  21. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/continuous_delivery/cd_utils.py +0 -0
  22. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/continuous_delivery/github.py +0 -0
  23. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/continuous_delivery/pypi.py +0 -0
  24. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/continuous_delivery/pyproject.py +0 -0
  25. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/ctx.py +0 -0
  26. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/config/get.py +0 -0
  27. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/config/set.py +0 -0
  28. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/__init__.py +0 -0
  29. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/auto_contrast.py +0 -0
  30. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/axis_flip.py +0 -0
  31. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/bias_field_correction.py +0 -0
  32. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/binary_threshold.py +0 -0
  33. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/blur.py +0 -0
  34. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/brightness.py +0 -0
  35. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/canny.py +0 -0
  36. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/clahe.py +0 -0
  37. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/common.py +0 -0
  38. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/contrast.py +0 -0
  39. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/curvature_flow_filter.py +0 -0
  40. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/denoise.py +0 -0
  41. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/histogram_equalization.py +0 -0
  42. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/invert.py +0 -0
  43. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/laplacian.py +0 -0
  44. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/median_blur.py +0 -0
  45. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/noise.py +0 -0
  46. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/normalize.py +0 -0
  47. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/random_erase.py +0 -0
  48. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/resize.py +0 -0
  49. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/rotation.py +0 -0
  50. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/salt_pepper.py +0 -0
  51. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/sharpening.py +0 -0
  52. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/shearing.py +0 -0
  53. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/threshold.py +0 -0
  54. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/translation.py +0 -0
  55. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image/zoom.py +0 -0
  56. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image_augmentation.py +0 -0
  57. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/image_preprocess.py +0 -0
  58. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/prosthesis_detection.py +0 -0
  59. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/data_processing/technique.py +0 -0
  60. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/dataset/__init__.py +0 -0
  61. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/dataset/dataset.py +0 -0
  62. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/dataset/dataset_loader.py +0 -0
  63. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/dataset/grouping_strategy.py +0 -0
  64. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/dataset/image_loader.py +0 -0
  65. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/dataset/xy_tuple.py +0 -0
  66. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/metric_dictionnary.py +0 -0
  67. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/metric_utils.py +0 -0
  68. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/mlflow_utils.py +0 -0
  69. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/abstract_model.py +0 -0
  70. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/all.py +0 -0
  71. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/base_keras.py +0 -0
  72. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras/all.py +0 -0
  73. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras/convnext.py +0 -0
  74. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras/densenet.py +0 -0
  75. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras/efficientnet.py +0 -0
  76. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras/mobilenet.py +0 -0
  77. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras/resnet.py +0 -0
  78. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras/squeezenet.py +0 -0
  79. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras/vgg.py +0 -0
  80. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras/xception.py +0 -0
  81. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras_utils/callbacks/__init__.py +0 -0
  82. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras_utils/callbacks/colored_progress_bar.py +0 -0
  83. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras_utils/callbacks/learning_rate_finder.py +0 -0
  84. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras_utils/callbacks/model_checkpoint_v2.py +0 -0
  85. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras_utils/callbacks/progressive_unfreezing.py +0 -0
  86. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras_utils/callbacks/warmup_scheduler.py +0 -0
  87. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras_utils/losses/__init__.py +0 -0
  88. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/keras_utils/losses/next_generation_loss.py +0 -0
  89. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/model_interface.py +0 -0
  90. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/models/sandbox.py +0 -0
  91. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/range_tuple.py +0 -0
  92. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/scripts/augment_dataset.py +0 -0
  93. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/scripts/preprocess_dataset.py +0 -0
  94. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/scripts/routine.py +0 -0
  95. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/data_science/utils.py +0 -0
  96. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/decorators.py +0 -0
  97. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/dont_look/zip_file_override.py +0 -0
  98. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/image.py +0 -0
  99. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/installer/__init__.py +0 -0
  100. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/installer/common.py +0 -0
  101. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/installer/downloader.py +0 -0
  102. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/installer/linux.py +0 -0
  103. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/installer/main.py +0 -0
  104. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/installer/windows.py +0 -0
  105. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/parallel.py +0 -0
  106. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/print.py +0 -0
  107. {stouputils-1.3.2 → stouputils-1.3.4}/stouputils/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stouputils
3
- Version: 1.3.2
3
+ Version: 1.3.4
4
4
  Summary: Stouputils is a collection of utility modules designed to simplify and enhance the development process. It includes a range of tools for tasks such as execution of doctests, display utilities, decorators, as well as context managers, and many more.
5
5
  Project-URL: Homepage, https://github.com/Stoupy51/stouputils
6
6
  Project-URL: Issues, https://github.com/Stoupy51/stouputils/issues
@@ -14,6 +14,7 @@ Requires-Dist: numpy
14
14
  Requires-Dist: opencv-python>=4.0.0
15
15
  Requires-Dist: orjson>=3.0.0
16
16
  Requires-Dist: pillow>=11.0.0
17
+ Requires-Dist: pyfastcopy>=1.0.0
17
18
  Requires-Dist: pyyaml>=6.0.0
18
19
  Requires-Dist: requests>=2.20.0
19
20
  Requires-Dist: toml>=0.10.0
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
5
5
 
6
6
  [project]
7
7
  name = "stouputils"
8
- version = "1.3.2"
8
+ version = "1.3.4"
9
9
  description = "Stouputils is a collection of utility modules designed to simplify and enhance the development process. It includes a range of tools for tasks such as execution of doctests, display utilities, decorators, as well as context managers, and many more."
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.10"
@@ -23,6 +23,7 @@ dependencies = [
23
23
  "numpy",
24
24
  "opencv-python>=4.0.0",
25
25
  "orjson>=3.0.0",
26
+ "pyfastcopy>=1.0.0",
26
27
  ]
27
28
  [[project.authors]]
28
29
  name = "Stoupy51"
@@ -4,6 +4,7 @@
4
4
  # pyright: reportUnknownVariableType=false
5
5
  # pyright: reportUnknownArgumentType=false
6
6
  # pyright: reportMissingTypeStubs=false
7
+ # pyright: reportIndexIssue=false
7
8
 
8
9
  # Imports
9
10
  import os
@@ -67,7 +68,7 @@ def make_gradcam_heatmap(
67
68
  error("Last convolutional layer not found. Please provide the name of the last convolutional layer.")
68
69
 
69
70
  # Get the last convolutional layer
70
- last_layer: Model | list[Model] = model.get_layer(last_conv_layer_name)
71
+ last_layer: list[Model] | Any = model.get_layer(last_conv_layer_name)
71
72
  if isinstance(last_layer, list):
72
73
  last_layer = last_layer[0]
73
74
 
@@ -79,10 +80,11 @@ def make_gradcam_heatmap(
79
80
 
80
81
  # Record operations for automatic differentiation using GradientTape.
81
82
  with tf.GradientTape() as tape:
83
+
82
84
  # Forward pass: get the activations of the last conv layer and the predictions.
83
85
  conv_outputs: tf.Tensor
84
86
  predictions: tf.Tensor
85
- conv_outputs, predictions = grad_model(img)
87
+ conv_outputs, predictions = grad_model(img) # pyright: ignore [reportGeneralTypeIssues]
86
88
  # print("conv_outputs shape:", conv_outputs.shape)
87
89
  # print("predictions shape:", predictions.shape)
88
90
 
@@ -110,7 +112,7 @@ def make_gradcam_heatmap(
110
112
  activation_map: tf.Tensor = conv_outputs[:, :, :, i]
111
113
 
112
114
  # Weight the activation map by the gradient
113
- weighted_map: tf.Tensor = activation_map * pooled_grad
115
+ weighted_map: tf.Tensor = activation_map * pooled_grad # pyright: ignore [reportOperatorIssue]
114
116
 
115
117
  # Ensure that the heatmap has non-negative values and normalize it
116
118
  heatmap: tf.Tensor = tf.maximum(weighted_map, 0) / tf.math.reduce_max(weighted_map)
@@ -176,9 +178,9 @@ def make_saliency_map(
176
178
  # Record operations for automatic differentiation
177
179
  with tf.GradientTape() as tape:
178
180
  tape.watch(img_tensor)
179
- predictions = model(img_tensor)
181
+ predictions: tf.Tensor = model(img_tensor) # pyright: ignore [reportAssignmentType]
180
182
  # Use class index for positive class prediction
181
- loss = predictions[:, class_idx]
183
+ loss: tf.Tensor = predictions[:, class_idx]
182
184
 
183
185
  # Compute gradients of loss with respect to input image
184
186
  try:
@@ -214,7 +216,7 @@ def make_saliency_map(
214
216
  else:
215
217
  channel_saliency: tf.Tensor = channel_grads
216
218
 
217
- saliency_maps.append(channel_saliency.numpy().squeeze())
219
+ saliency_maps.append(channel_saliency.numpy().squeeze()) # type: ignore
218
220
  else:
219
221
  # Take absolute value of gradients
220
222
  abs_grads = tf.abs(grads)
@@ -324,6 +326,10 @@ def create_visualization_overlay(
324
326
  else:
325
327
  original_pil: Image.Image = original_img
326
328
 
329
+ # Force RGB mode
330
+ if original_pil.mode != "RGB":
331
+ original_pil = original_pil.convert("RGB")
332
+
327
333
  # Convert heatmap to PIL Image
328
334
  heatmap_pil: Image.Image = Image.fromarray(heatmap_rgb)
329
335
 
@@ -331,12 +337,9 @@ def create_visualization_overlay(
331
337
  if heatmap_pil.size != original_pil.size:
332
338
  heatmap_pil = heatmap_pil.resize(original_pil.size, Image.Resampling.LANCZOS)
333
339
 
334
- # Ensure both images have the same mode before blending
335
- if original_pil.mode != heatmap_pil.mode:
336
- if original_pil.mode == "RGB" and heatmap_pil.mode == "RGBA":
337
- heatmap_pil = heatmap_pil.convert("RGB")
338
- else:
339
- original_pil = original_pil.convert(heatmap_pil.mode)
340
+ # Ensure heatmap is also in RGB mode
341
+ if heatmap_pil.mode != "RGB":
342
+ heatmap_pil = heatmap_pil.convert("RGB")
340
343
 
341
344
  # Blend images
342
345
  overlaid_img: Image.Image = Image.blend(original_pil, heatmap_pil, alpha=alpha)
@@ -389,7 +392,7 @@ def all_visualizations_for_image(
389
392
  # Perform prediction to determine correctness
390
393
  # Ensure img is in batch format (add dimension if needed)
391
394
  img_batch: NDArray[Any] = np.expand_dims(img, axis=0) if img.ndim == 3 else img
392
- predicted_class_idx: int = int(np.argmax(model.predict(img_batch, verbose=0)[0])) # pyright: ignore [reportArgumentType]
395
+ predicted_class_idx: int = int(np.argmax(model.predict(img_batch, verbose=0)[0])) # type: ignore
393
396
  prediction_correct: bool = (predicted_class_idx == class_idx)
394
397
  status_suffix: str = "_correct" if prediction_correct else "_missed"
395
398
 
@@ -118,8 +118,13 @@ def exhaustive_process(
118
118
  commands.append(f"{base_cmd} {kfold_arg}")
119
119
 
120
120
  # Run all commands
121
+ def runner(cmd: str) -> None:
122
+ info(f"Executing command: '{cmd}'")
123
+ sys.stdout.flush()
124
+ sys.stderr.flush()
125
+ os.system(cmd)
121
126
  multithreading(
122
- os.system,
127
+ runner,
123
128
  commands,
124
129
  desc="Processing all datasets",
125
130
  max_workers=args.max_workers,
@@ -14,10 +14,12 @@ This module provides utilities for file management.
14
14
 
15
15
  # Imports
16
16
  import os
17
+ import re
17
18
  import shutil
18
19
  from typing import IO, Any
19
20
 
20
21
  import orjson
22
+ import pyfastcopy # type: ignore # noqa: F401
21
23
 
22
24
 
23
25
  # Function that replace the "~" by the user's home directory
@@ -167,25 +169,56 @@ def super_open(file_path: str, mode: str, encoding: str = "utf-8") -> IO[Any]:
167
169
 
168
170
 
169
171
  # For easy file copy
170
- def super_copy(src: str, dst: str, create_dir: bool = True) -> str:
172
+ def super_copy(src: str, dst: str, create_dir: bool = True, symlink: bool = False) -> str:
171
173
  """ Copy a file (or a folder) from the source to the destination
172
174
 
173
175
  Args:
174
176
  src (str): The source path
175
177
  dst (str): The destination path
176
178
  create_dir (bool): Whether to create the directory if it doesn't exist (default: True)
179
+ symlink (bool): Whether to create a symlink instead of copying (default: False)
177
180
  Returns:
178
181
  str: The destination path
179
182
  """
180
- # Make directory
183
+ # Create destination directory if needed
181
184
  if create_dir:
182
185
  os.makedirs(os.path.dirname(dst), exist_ok=True)
183
186
 
184
- # If source is a folder, copy it recursively
187
+ # Handle directory copying
185
188
  if os.path.isdir(src):
186
- return shutil.copytree(src, dst, dirs_exist_ok = True)
189
+ if symlink:
190
+
191
+ # Remove existing destination if it's different from source
192
+ if os.path.exists(dst):
193
+ if os.path.samefile(src, dst) is False:
194
+ if os.path.isdir(dst):
195
+ shutil.rmtree(dst)
196
+ else:
197
+ os.remove(dst)
198
+ return os.symlink(src, dst, target_is_directory=True) or dst
199
+ else:
200
+ return os.symlink(src, dst, target_is_directory=True) or dst
201
+
202
+ # Regular directory copy
203
+ else:
204
+ return shutil.copytree(src, dst, dirs_exist_ok = True)
205
+
206
+ # Handle file copying
187
207
  else:
188
- return shutil.copy(src, dst)
208
+ if symlink:
209
+
210
+ # Remove existing destination if it's different from source
211
+ if os.path.exists(dst):
212
+ if os.path.samefile(src, dst) is False:
213
+ os.remove(dst)
214
+ return os.symlink(src, dst, target_is_directory=False) or dst
215
+ else:
216
+ return os.symlink(src, dst, target_is_directory=False) or dst
217
+
218
+ # Regular file copy
219
+ else:
220
+ return shutil.copy(src, dst)
221
+ return ""
189
222
 
190
223
 
191
224
 
@@ -219,58 +252,32 @@ def super_json_dump(data: Any, file: IO[Any]|None = None, max_level: int = 2, in
219
252
  >>> super_json_dump({"a": [[1,2,3]], "b": 2}, max_level = 2)
220
253
  '{\\n\\t"a": [\\n\\t\\t[1,2,3]\\n\\t],\\n\\t"b": 2\\n}\\n'
221
254
  """
222
- # Dump content
223
- content: str = orjson.dumps(data, option=orjson.OPT_INDENT_2).decode("utf-8")
224
- if indent not in (2, " "):
225
- if isinstance(indent, str):
226
- content = content.replace(" ", indent)
227
- else:
228
- content = content.replace(" ", indent * ' ')
255
+ # Normalize indentation to string
256
+ if isinstance(indent, int):
257
+ indent = ' ' * indent
229
258
 
230
- # Fix indent level
259
+ # Dump content with 2-space indent and replace it with the desired indent
260
+ content: str = orjson.dumps(data, option=orjson.OPT_INDENT_2).decode("utf-8")
261
+ if indent != " ":
262
+ content = re.sub(
263
+ pattern=r'^(\s{2})+', # Match groups of 2 spaces at start of lines
264
+ repl=lambda match: indent * (len(match.group(0)) // 2), # Convert to desired indent
265
+ string=content,
266
+ flags=re.MULTILINE
267
+ )
268
+
269
+ # Limit max depth of indentation
231
270
  if max_level > -1:
232
-
233
- # Character-based indentation
234
- if isinstance(indent, str):
235
- longest_indentation: int = 0
236
- for line in content.split("\n"):
237
- indentation: int = 0
238
- for char in line:
239
- if char == indent:
240
- indentation += 1
241
- else:
242
- break
243
- longest_indentation = max(longest_indentation, indentation)
244
- for i in range(longest_indentation, max_level, -1):
245
- content = content.replace("\n" + indent * i, "")
246
-
247
- # Numeric indentation (spaces)
248
- else:
249
- longest_indentation: int = 0
250
- for line in content.split("\n"):
251
- indentation: int = 0
252
- for char in line:
253
- if char == " ":
254
- indentation += 1
255
- else:
256
- break
257
- longest_indentation = max(longest_indentation, indentation // indent)
258
- for i in range(longest_indentation, max_level, -1):
259
- content = content.replace("\n" + " " * (i * indent), "")
260
-
261
- # To finalyze, fix the last indentations
262
- finishes: tuple[str, str] = ('}', ']')
263
- for char in finishes:
264
- if isinstance(indent, str):
265
- to_replace: str = "\n" + indent * max_level + char
266
- else:
267
- to_replace: str = "\n" + " " * (max_level * indent) + char
268
- content = content.replace(to_replace, char)
269
-
270
- # Write file content and return it
271
+ escape: str = re.escape(indent)
272
+ pattern: re.Pattern[str] = re.compile(
273
+ r"\n" + escape + "{" + str(max_level + 1) + r",}(.*)"
274
+ r"|\n" + escape + "{" + str(max_level) + r"}([}\]])"
275
+ )
276
+ content = pattern.sub(r"\1\2", content)
277
+
278
+ # Final newline and write
271
279
  content += "\n"
272
280
  if file:
273
281
  file.write(content)
274
282
  return content
275
283
 
276
-
File without changes
File without changes
File without changes
File without changes