absfuyu 5.6.1__py3-none-any.whl → 6.1.2__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.

Potentially problematic release.


This version of absfuyu might be problematic. Click here for more details.

Files changed (102) hide show
  1. absfuyu/__init__.py +5 -3
  2. absfuyu/__main__.py +2 -2
  3. absfuyu/cli/__init__.py +13 -2
  4. absfuyu/cli/audio_group.py +98 -0
  5. absfuyu/cli/color.py +2 -2
  6. absfuyu/cli/config_group.py +2 -2
  7. absfuyu/cli/do_group.py +2 -2
  8. absfuyu/cli/game_group.py +20 -2
  9. absfuyu/cli/tool_group.py +68 -4
  10. absfuyu/config/__init__.py +3 -3
  11. absfuyu/core/__init__.py +10 -6
  12. absfuyu/core/baseclass.py +104 -34
  13. absfuyu/core/baseclass2.py +43 -2
  14. absfuyu/core/decorator.py +2 -2
  15. absfuyu/core/docstring.py +4 -2
  16. absfuyu/core/dummy_cli.py +3 -3
  17. absfuyu/core/dummy_func.py +2 -2
  18. absfuyu/dxt/__init__.py +2 -2
  19. absfuyu/dxt/base_type.py +93 -0
  20. absfuyu/dxt/dictext.py +188 -6
  21. absfuyu/dxt/dxt_support.py +2 -2
  22. absfuyu/dxt/intext.py +72 -4
  23. absfuyu/dxt/listext.py +495 -23
  24. absfuyu/dxt/strext.py +2 -2
  25. absfuyu/extra/__init__.py +2 -2
  26. absfuyu/extra/audio/__init__.py +8 -0
  27. absfuyu/extra/audio/_util.py +57 -0
  28. absfuyu/extra/audio/convert.py +192 -0
  29. absfuyu/extra/audio/lossless.py +281 -0
  30. absfuyu/extra/beautiful.py +2 -2
  31. absfuyu/extra/da/__init__.py +39 -3
  32. absfuyu/extra/da/dadf.py +436 -29
  33. absfuyu/extra/da/dadf_base.py +2 -2
  34. absfuyu/extra/da/df_func.py +89 -5
  35. absfuyu/extra/da/mplt.py +2 -2
  36. absfuyu/extra/ggapi/__init__.py +8 -0
  37. absfuyu/extra/ggapi/gdrive.py +223 -0
  38. absfuyu/extra/ggapi/glicense.py +148 -0
  39. absfuyu/extra/ggapi/glicense_df.py +186 -0
  40. absfuyu/extra/ggapi/gsheet.py +88 -0
  41. absfuyu/extra/img/__init__.py +30 -0
  42. absfuyu/extra/img/converter.py +402 -0
  43. absfuyu/extra/img/dup_check.py +291 -0
  44. absfuyu/extra/pdf.py +4 -6
  45. absfuyu/extra/rclone.py +253 -0
  46. absfuyu/extra/xml.py +90 -0
  47. absfuyu/fun/__init__.py +2 -20
  48. absfuyu/fun/rubik.py +2 -2
  49. absfuyu/fun/tarot.py +2 -2
  50. absfuyu/game/__init__.py +2 -2
  51. absfuyu/game/game_stat.py +2 -2
  52. absfuyu/game/schulte.py +78 -0
  53. absfuyu/game/sudoku.py +2 -2
  54. absfuyu/game/tictactoe.py +2 -2
  55. absfuyu/game/wordle.py +6 -4
  56. absfuyu/general/__init__.py +2 -2
  57. absfuyu/general/content.py +2 -2
  58. absfuyu/general/human.py +2 -2
  59. absfuyu/general/resrel.py +213 -0
  60. absfuyu/general/shape.py +3 -8
  61. absfuyu/general/tax.py +344 -0
  62. absfuyu/logger.py +806 -59
  63. absfuyu/numbers/__init__.py +13 -0
  64. absfuyu/numbers/number_to_word.py +321 -0
  65. absfuyu/numbers/shorten_number.py +303 -0
  66. absfuyu/numbers/time_duration.py +217 -0
  67. absfuyu/pkg_data/__init__.py +2 -2
  68. absfuyu/pkg_data/deprecated.py +2 -2
  69. absfuyu/pkg_data/logo.py +1462 -0
  70. absfuyu/sort.py +4 -4
  71. absfuyu/tools/__init__.py +2 -2
  72. absfuyu/tools/checksum.py +119 -4
  73. absfuyu/tools/converter.py +2 -2
  74. absfuyu/tools/generator.py +24 -7
  75. absfuyu/tools/inspector.py +2 -2
  76. absfuyu/tools/keygen.py +2 -2
  77. absfuyu/tools/obfuscator.py +2 -2
  78. absfuyu/tools/passwordlib.py +2 -2
  79. absfuyu/tools/shutdownizer.py +3 -8
  80. absfuyu/tools/sw.py +213 -10
  81. absfuyu/tools/web.py +10 -13
  82. absfuyu/typings.py +5 -8
  83. absfuyu/util/__init__.py +31 -2
  84. absfuyu/util/api.py +7 -4
  85. absfuyu/util/cli.py +119 -0
  86. absfuyu/util/gui.py +91 -0
  87. absfuyu/util/json_method.py +2 -2
  88. absfuyu/util/lunar.py +2 -2
  89. absfuyu/util/package.py +124 -0
  90. absfuyu/util/path.py +313 -4
  91. absfuyu/util/performance.py +2 -2
  92. absfuyu/util/shorten_number.py +206 -13
  93. absfuyu/util/text_table.py +2 -2
  94. absfuyu/util/zipped.py +2 -2
  95. absfuyu/version.py +22 -19
  96. {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/METADATA +37 -8
  97. absfuyu-6.1.2.dist-info/RECORD +105 -0
  98. {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
  99. absfuyu/extra/data_analysis.py +0 -21
  100. absfuyu-5.6.1.dist-info/RECORD +0 -79
  101. {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
  102. {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,192 @@
1
+ """
2
+ Absfuyu: Audio
3
+ --------------
4
+ Audio convert
5
+
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
9
+
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["DirectoryAudioConvertMixin"]
13
+
14
+
15
+ # Library
16
+ # ---------------------------------------------------------------------------
17
+ import json
18
+ import subprocess
19
+ from concurrent.futures import ProcessPoolExecutor, as_completed
20
+ from pathlib import Path
21
+ from typing import Literal
22
+
23
+ from absfuyu.core.dummy_func import tqdm
24
+ from absfuyu.extra.audio._util import ResultStatus as ConvertStatus
25
+ from absfuyu.extra.audio._util import StatusCode
26
+ from absfuyu.util import is_command_available
27
+ from absfuyu.util.path import DirectoryBase
28
+
29
+
30
+ # Class
31
+ # ---------------------------------------------------------------------------
32
+ class DirectoryAudioConvertMixin(DirectoryBase):
33
+ """
34
+ Directory - Audio convert to mp3
35
+
36
+ - convert_to_mp3
37
+ """
38
+
39
+ def __init__(self, source_path, create_if_not_exist=False):
40
+ super().__init__(source_path, create_if_not_exist)
41
+ is_command_available(["ffmpeg"], "ERROR: ffmpeg not installed to PATH")
42
+
43
+ def _read_metadata(self, audio_path: Path, /) -> dict:
44
+ """
45
+ Read audio metadata
46
+
47
+ Parameters
48
+ ----------
49
+ audio_path : Path
50
+ Path to audio
51
+
52
+ Returns
53
+ -------
54
+ dict
55
+ Audio's metadata
56
+ """
57
+ cmd = [
58
+ "ffprobe",
59
+ "-v",
60
+ "quiet",
61
+ "-print_format",
62
+ "json",
63
+ "-show_format",
64
+ str(audio_path.resolve()),
65
+ ]
66
+ result = subprocess.run(cmd, capture_output=True, text=True)
67
+ data = json.loads(result.stdout)
68
+
69
+ if "format" in data and "tags" in data["format"]:
70
+ return data["format"]["tags"]
71
+ return {}
72
+
73
+ def convert_one(self, audio_path: Path | str, bitrate: Literal["128k", "320k"] = "320k") -> ConvertStatus:
74
+ """
75
+ Convert audio to mp3
76
+
77
+ Parameters
78
+ ----------
79
+ audio_path : Path | str
80
+ Path to audio
81
+
82
+ bitrate : Literal["128k", "320k"], optional
83
+ Bitrate, by default "320k"
84
+
85
+ Returns
86
+ -------
87
+ ConvertStatus
88
+ Result
89
+ """
90
+ audio_path = Path(audio_path)
91
+ mp3_path = audio_path.with_suffix(".mp3")
92
+
93
+ if mp3_path.exists():
94
+ return ConvertStatus(StatusCode.SKIP, mp3_path)
95
+
96
+ metadata = self._read_metadata(audio_path)
97
+
98
+ ffmpeg_cmd = ["ffmpeg", "-y", "-i", str(audio_path.resolve())]
99
+
100
+ # Add metadata tags
101
+ for key, value in metadata.items():
102
+ ffmpeg_cmd.extend(["-metadata", f"{key}={value}"])
103
+
104
+ ffmpeg_cmd.extend(["-vn", "-codec:a", "libmp3lame", "-b:a", bitrate, str(mp3_path.resolve())])
105
+
106
+ subprocess.run(ffmpeg_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
107
+ return ConvertStatus(StatusCode.OK, mp3_path)
108
+
109
+ def convert_to_mp3_single_thread(
110
+ self,
111
+ from_format: str = ".flac",
112
+ recursive: bool = True,
113
+ bitrate: Literal["128k", "320k"] = "320k",
114
+ ) -> None:
115
+ """
116
+ Convert audios to .mp3 - Single thread
117
+
118
+ Parameters
119
+ ----------
120
+ from_format : str, optional
121
+ Audio format, by default ".flac"
122
+
123
+ recursive : bool, optional
124
+ Include audio in child folder, by default True
125
+
126
+ bitrate : Literal["128k", "320k"], optional
127
+ Bitrate, by default "320k"
128
+ """
129
+ audios = list(self.source_path.rglob(f"{'**/*'if recursive else '*'}{from_format}"))
130
+
131
+ if not audios:
132
+ print(f"No {from_format.upper()} files found.")
133
+ return None
134
+
135
+ print(f"Found {len(audios)} {from_format.upper()} files.")
136
+
137
+ results: list[ConvertStatus] = []
138
+ for x in tqdm(audios, desc="Converting", unit="file", unit_scale=True):
139
+ results.append(self.convert_one(x, bitrate=bitrate))
140
+
141
+ print("\n--- Summary ---")
142
+ for line in results:
143
+ line.print()
144
+
145
+ def convert_to_mp3(
146
+ self,
147
+ from_format: str = ".flac",
148
+ workers: int | None = None,
149
+ recursive: bool = True,
150
+ bitrate: Literal["128k", "320k"] = "320k",
151
+ ) -> None:
152
+ """
153
+ Convert audios to .mp3
154
+
155
+ Parameters
156
+ ----------
157
+ from_format : str, optional
158
+ Audio format, by default ".flac"
159
+
160
+ workers : int | None, optional
161
+ Number of parallel processing threads, by default None
162
+
163
+ recursive : bool, optional
164
+ Include audio in child folder, by default True
165
+
166
+ bitrate : Literal["128k", "320k"], optional
167
+ Bitrate, by default "320k"
168
+ """
169
+ audios = list(self.source_path.rglob(f"{'**/*'if recursive else '*'}{from_format}"))
170
+
171
+ if not audios:
172
+ print(f"No {from_format.upper()} files found.")
173
+ return None
174
+
175
+ print(f"Found {len(audios)} {from_format.upper()} files.")
176
+
177
+ results: list[ConvertStatus] = []
178
+ with ProcessPoolExecutor(max_workers=workers) as executor:
179
+ futures = {executor.submit(self.convert_one, x, bitrate): x for x in audios}
180
+
181
+ for fut in tqdm(
182
+ as_completed(futures),
183
+ total=len(futures),
184
+ desc="Converting",
185
+ unit="file",
186
+ unit_scale=True,
187
+ ):
188
+ results.append(fut.result())
189
+
190
+ print("\n--- Summary ---")
191
+ for line in results:
192
+ line.print()
@@ -0,0 +1,281 @@
1
+ """
2
+ Absfuyu: Audio
3
+ --------------
4
+ Audio lossless checker
5
+
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
9
+
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["AudioInfo", "DirectoryAudioLosslessCheckMixin"]
13
+
14
+
15
+ # Library
16
+ # ---------------------------------------------------------------------------
17
+ import json
18
+ import subprocess
19
+ from concurrent.futures import ProcessPoolExecutor, as_completed
20
+ from dataclasses import dataclass, field
21
+ from pathlib import Path
22
+ from typing import Any, NamedTuple
23
+
24
+ from absfuyu.core.dummy_func import tqdm
25
+ from absfuyu.extra.audio._util import ResultStatus, StatusCode
26
+ from absfuyu.util import is_command_available
27
+ from absfuyu.util.path import DirectoryBase
28
+ from absfuyu.util.shorten_number import Duration
29
+
30
+ try:
31
+ import numpy as np
32
+ except ImportError:
33
+ from subprocess import run
34
+
35
+ from absfuyu.config import ABSFUYU_CONFIG
36
+
37
+ if ABSFUYU_CONFIG._get_setting("auto-install-extra").value: # type: ignore
38
+ cmd = "python -m pip install -U absfuyu[extra]".split()
39
+ run(cmd)
40
+ else:
41
+ raise SystemExit("This feature is in absfuyu[extra] package") # noqa: B904
42
+
43
+
44
+ # Class
45
+ # ---------------------------------------------------------------------------
46
+ class FrequencyRange(NamedTuple):
47
+ """Audio frequency range"""
48
+
49
+ min: int | float
50
+ max: int | float
51
+
52
+
53
+ @dataclass
54
+ class AudioInfo:
55
+ """
56
+ Audio infomation
57
+
58
+ Parameters
59
+ ----------
60
+ sample_rate : int | None, optional
61
+ Sample rate (Hz)
62
+
63
+ bit_depth : int, optional
64
+ Bit depth
65
+
66
+ channels : int | None, optional
67
+ Number of channels
68
+
69
+ codec : str | None, optional
70
+ Audio codec
71
+
72
+ duration_raw : float, optional
73
+ Duration of audio (second)
74
+
75
+ bitrate : Any | None, optional
76
+ Bitrate
77
+
78
+ audio_path : Path | None, optional
79
+ Path to audio
80
+
81
+ Returns
82
+ -------
83
+ AudioInfo
84
+ Audio infomation
85
+ """
86
+
87
+ sample_rate: int | None = field(default=None, metadata={"unit": "Hz"})
88
+ bit_depth: int = field(default=0, metadata={"unit": "bit"})
89
+ channels: int | None = field(default=None)
90
+ codec: str | None = field(default=None)
91
+ duration_raw: float = field(default=0.0, repr=False, metadata={"unit": "second"})
92
+ bitrate: int | None = field(default=None)
93
+ audio_path: Path | None = field(default=None, repr=False) # hide path in repr
94
+
95
+ # computed fields
96
+ duration: float = field(init=False)
97
+ freq_range: FrequencyRange = field(init=False, metadata={"unit": "Hz"})
98
+
99
+ def __post_init__(self):
100
+ self.duration = Duration(self.duration_raw)
101
+
102
+ # Resource intensive method
103
+ # _, sampling_rate = librosa.load(audio_path, sr=None)
104
+ # freqs = librosa.fft_frequencies(sr=sampling_rate)
105
+ freqs = np.fft.rfftfreq(n=2048, d=1.0 / self.sample_rate)
106
+ self.freq_range = FrequencyRange(freqs[0], freqs[-1])
107
+
108
+ @property
109
+ def is_lossless(self) -> bool:
110
+ """
111
+ Audio is lossless when frequencies above 20,000 are not cut off
112
+
113
+ Returns
114
+ -------
115
+ bool
116
+ If audio is lossless
117
+ """
118
+ return self.freq_range[1] >= 20000
119
+
120
+ @property
121
+ def is_hi_res(self) -> bool:
122
+ """
123
+ Audio is HiRes when lossless and have sample rate >= 48,000Hz or bit rate >= 16 bit
124
+
125
+ Returns
126
+ -------
127
+ bool
128
+ If audio is lossless
129
+ """
130
+ bd = 0 if self.bit_depth is None else self.bit_depth
131
+ return all([self.sample_rate >= 48000, bd >= 24, self.is_lossless])
132
+
133
+
134
+ class DirectoryAudioLosslessCheckMixin(DirectoryBase):
135
+ """
136
+ Directory - Audio lossless checker
137
+
138
+ - lossless_check
139
+ """
140
+
141
+ def get_audio_info(self, audio_path: Path, /) -> AudioInfo:
142
+ """
143
+ Return audio info using ffprobe.
144
+
145
+ Parameters
146
+ ----------
147
+ audio_path : Path
148
+ Path to audio
149
+
150
+ Returns
151
+ -------
152
+ AudioInfo
153
+ Audio infomation
154
+ """
155
+
156
+ is_command_available(["ffmpeg"], "ERROR: ffmpeg not installed to PATH")
157
+
158
+ # Probe
159
+ cmd = [
160
+ "ffprobe",
161
+ "-v",
162
+ "error",
163
+ "-print_format",
164
+ "json",
165
+ "-show_streams",
166
+ "-select_streams",
167
+ "a:0",
168
+ str(audio_path.resolve()),
169
+ ]
170
+
171
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
172
+ data: dict[str, Any] = json.loads(result.stdout)["streams"][0]
173
+
174
+ bit_depth = data.get("bits_per_sample") or data.get("bits_per_raw_sample")
175
+ if bit_depth is not None:
176
+ bit_depth = int(bit_depth)
177
+
178
+ ai = AudioInfo(
179
+ sample_rate=int(data.get("sample_rate", 0)),
180
+ bit_depth=bit_depth,
181
+ channels=data.get("channels"),
182
+ codec=data.get("codec_name"),
183
+ duration_raw=float(data["duration"]) if "duration" in data else None,
184
+ bitrate=int(data["bit_rate"]) if "bit_rate" in data else None,
185
+ audio_path=audio_path,
186
+ )
187
+ return ai
188
+
189
+ def lossless_check_one(self, audio_path: Path) -> ResultStatus:
190
+ """
191
+ Check if audio is lossless
192
+
193
+ Parameters
194
+ ----------
195
+ audio_path : Path
196
+ Path to audio
197
+
198
+ Returns
199
+ -------
200
+ ResultStatus
201
+ If audio is lossless
202
+ """
203
+ res = self.get_audio_info(audio_path)
204
+ if res.is_hi_res:
205
+ return ResultStatus(StatusCode.HIRES, audio_path)
206
+ elif res.is_lossless:
207
+ return ResultStatus(StatusCode.LOSSLESS, audio_path)
208
+ return ResultStatus(StatusCode.NOT_LOSSLESS, audio_path)
209
+
210
+ def lossless_check_single_thread(self, from_format: str = ".flac", recursive: bool = True) -> None:
211
+ """
212
+ Check if audios in directory are lossless - single thread version
213
+
214
+ Parameters
215
+ ----------
216
+ from_format : str, optional
217
+ Audio format, by default ".flac"
218
+
219
+ recursive : bool, optional
220
+ Include audio in child folder, by default True
221
+ """
222
+ audios = list(self.source_path.rglob(f"{'**/*'if recursive else '*'}{from_format}"))
223
+
224
+ if not audios:
225
+ print(f"No {from_format.upper()} files found.")
226
+ return None
227
+
228
+ print(f"Found {len(audios)} {from_format.upper()} files.")
229
+
230
+ results: list[ResultStatus] = []
231
+ for x in tqdm(audios, desc="Checking", unit="file", unit_scale=True):
232
+ results.append(self.lossless_check_one(x))
233
+
234
+ print("\n--- Summary ---")
235
+ for line in results:
236
+ line.print()
237
+
238
+ def lossless_check(
239
+ self,
240
+ from_format: str = ".flac",
241
+ recursive: bool = True,
242
+ workers: int | None = None,
243
+ ) -> None:
244
+ """
245
+ Check if audios in directory are lossless
246
+
247
+ Parameters
248
+ ----------
249
+ from_format : str, optional
250
+ Audio format, by default ".flac"
251
+
252
+ recursive : bool, optional
253
+ Include audio in child folder, by default True
254
+
255
+ workers : int | None, optional
256
+ Number of parallel processing threads, by default None
257
+ """
258
+ audios = list(self.source_path.rglob(f"{'**/*'if recursive else '*'}{from_format}"))
259
+
260
+ if not audios:
261
+ print(f"No {from_format.upper()} files found.")
262
+ return None
263
+
264
+ print(f"Found {len(audios)} {from_format.upper()} files.")
265
+
266
+ results: list[ResultStatus] = []
267
+ with ProcessPoolExecutor(max_workers=workers) as executor:
268
+ futures = {executor.submit(self.lossless_check_one, x): x for x in audios}
269
+
270
+ for fut in tqdm(
271
+ as_completed(futures),
272
+ total=len(futures),
273
+ desc="Checking",
274
+ unit="file",
275
+ unit_scale=True,
276
+ ):
277
+ results.append(fut.result())
278
+
279
+ print("\n--- Summary ---")
280
+ for line in results:
281
+ line.print()
@@ -3,8 +3,8 @@ Absfuyu: Beautiful
3
3
  ------------------
4
4
  A decorator that makes output more beautiful
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
@@ -3,13 +3,19 @@ Absfuyu: Data Analysis
3
3
  ----------------------
4
4
  Data Analyst
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
11
11
  # ---------------------------------------------------------------------------
12
- __all__ = ["MatplotlibFormatString", "DADF"]
12
+ __all__ = [
13
+ "MatplotlibFormatString",
14
+ "DADF",
15
+ # Function
16
+ "custom_pandas_settings",
17
+ "reset_custom_pandas_settings",
18
+ ]
13
19
 
14
20
 
15
21
  # Library
@@ -18,7 +24,9 @@ DA_MODE = False
18
24
 
19
25
  try:
20
26
  import numpy as np
27
+ import openpyxl
21
28
  import pandas as pd
29
+ import xlsxwriter
22
30
  except ImportError:
23
31
  from subprocess import run
24
32
 
@@ -34,3 +42,31 @@ else:
34
42
 
35
43
  from absfuyu.extra.da.dadf import DADF
36
44
  from absfuyu.extra.da.mplt import MatplotlibFormatString
45
+
46
+
47
+ # Function
48
+ # ---------------------------------------------------------------------------
49
+ def custom_pandas_settings(*, show_all_rows: bool = False) -> None:
50
+ """
51
+ Custom pandas settings. Currently only show all cols/rows
52
+
53
+ Parameters
54
+ ----------
55
+ show_all_rows : bool, optional
56
+ Show all rows, by default False
57
+ """
58
+ # Shows all columns
59
+ pd.set_option("display.max_columns", None) # type: ignore
60
+
61
+ if show_all_rows:
62
+ # (optional) also show all rows if needed
63
+ pd.set_option("display.max_rows", None) # type: ignore
64
+
65
+
66
+ def reset_custom_pandas_settings() -> None:
67
+ """
68
+ Reset custom pandas settings
69
+ """
70
+ settings = ["display.max_columns", "display.max_rows"]
71
+ for x in settings:
72
+ pd.reset_option(x) # type: ignore