oafuncs 0.0.98.9__py3-none-any.whl → 0.0.98.11__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.
@@ -148,7 +148,7 @@ class ColorProgressBar:
148
148
  description: str = "Working ...",
149
149
  total: Optional[float] = None,
150
150
  completed: float = 0,
151
- color: Any = "cyan",
151
+ color: Any = "green",
152
152
  cmap: Union[str, List[str]] = None,
153
153
  update_interval: float = 0.1,
154
154
  bar_length: int = None,
@@ -1,4 +1,3 @@
1
- import atexit
2
1
  import logging
3
2
  import multiprocessing as mp
4
3
  import platform
@@ -30,6 +29,7 @@ class ParallelExecutor:
30
29
  self.running = True
31
30
  self.task_history = []
32
31
  self._executor = None
32
+ self._shutdown_called = False
33
33
 
34
34
  self.mode, default_workers = self._determine_optimal_settings()
35
35
  self.max_workers = max_workers or default_workers
@@ -37,7 +37,6 @@ class ParallelExecutor:
37
37
 
38
38
  self._init_platform_settings()
39
39
  self._start_resource_monitor()
40
- atexit.register(self.shutdown)
41
40
 
42
41
  logging.info(f"Initialized {self.__class__.__name__} on {self.platform} (mode={self.mode}, workers={self.max_workers})")
43
42
 
@@ -99,16 +98,26 @@ class ParallelExecutor:
99
98
 
100
99
  def run(self, func: Callable, params: List[Tuple], chunk_size: Optional[int] = None) -> List[Any]:
101
100
  chunk_size = chunk_size or self.chunk_size
102
- for retry in range(self.max_retries + 1):
103
- try:
104
- start_time = time.monotonic()
105
- results = self._execute_batch(func, params, chunk_size)
106
- self._update_settings(time.monotonic() - start_time, len(params))
107
- return results
108
- except Exception as e:
109
- logging.error(f"Attempt {retry + 1} failed: {e}")
110
- self._handle_failure()
111
- raise RuntimeError(f"Failed after {self.max_retries} retries")
101
+ try:
102
+ for retry in range(self.max_retries + 1):
103
+ try:
104
+ start_time = time.monotonic()
105
+ results = self._execute_batch(func, params, chunk_size)
106
+ self._update_settings(time.monotonic() - start_time, len(params))
107
+ return results
108
+ except Exception as e:
109
+ logging.error(f"Attempt {retry + 1} failed: {e}")
110
+ self._handle_failure()
111
+ raise RuntimeError(f"Failed after {self.max_retries} retries")
112
+ finally:
113
+ # 仅关闭当前 executor,保留资源监控等运行状态
114
+ if self._executor:
115
+ try:
116
+ self._executor.shutdown(wait=True)
117
+ except Exception as e:
118
+ logging.error(f"Executor shutdown error: {e}")
119
+ finally:
120
+ self._executor = None
112
121
 
113
122
  def _execute_batch(self, func: Callable, params: List[Tuple], chunk_size: int) -> List[Any]:
114
123
  if not params:
@@ -168,10 +177,14 @@ class ParallelExecutor:
168
177
  self._restart_executor()
169
178
 
170
179
  def shutdown(self):
180
+ if self._shutdown_called:
181
+ return
182
+ self._shutdown_called = True
171
183
  self.running = False
184
+ # 基类不再打印日志,由子类统一处理
172
185
  if self._executor:
173
186
  try:
174
- self._executor.shutdown(wait=False)
187
+ self._executor.shutdown(wait=True)
175
188
  except Exception as e:
176
189
  logging.error(f"Shutdown error: {e}")
177
190
  finally:
oafuncs/oa_data.py CHANGED
@@ -121,7 +121,26 @@ def _interp_single_worker(*args):
121
121
  """
122
122
  data_slice, origin_points, target_points, interpolation_method, target_shape = args
123
123
 
124
- return griddata(origin_points, data_slice.ravel(), target_points, method=interpolation_method).reshape(target_shape)
124
+ # 过滤掉包含 NaN 的点
125
+ valid_mask = ~np.isnan(data_slice.ravel())
126
+ valid_data = data_slice.ravel()[valid_mask]
127
+ valid_points = origin_points[valid_mask]
128
+
129
+ if len(valid_data) < 10: # 如果有效数据太少,用均值填充
130
+ return np.full(target_shape, np.nanmean(data_slice))
131
+
132
+ # 使用有效数据进行插值
133
+ result = griddata(valid_points, valid_data, target_points, method=interpolation_method)
134
+ result = result.reshape(target_shape)
135
+
136
+ # 检查插值结果中是否仍有 NaN,如果有,用最近邻插值填充
137
+ if np.any(np.isnan(result)):
138
+ # 使用最近邻方法填充剩余的 NaN 值
139
+ nan_mask = np.isnan(result)
140
+ result_nn = griddata(valid_points, valid_data, target_points[nan_mask.ravel()], method="nearest")
141
+ result.ravel()[nan_mask.ravel()] = result_nn
142
+
143
+ return result
125
144
 
126
145
 
127
146
  def interp_2d(
@@ -130,7 +149,7 @@ def interp_2d(
130
149
  source_x_coordinates: Union[np.ndarray, List[float]],
131
150
  source_y_coordinates: Union[np.ndarray, List[float]],
132
151
  source_data: np.ndarray,
133
- interpolation_method: str = "linear",
152
+ interpolation_method: str = "cubic",
134
153
  ) -> np.ndarray:
135
154
  """
136
155
  Perform 2D interpolation on the last two dimensions of a multi-dimensional array.
@@ -141,7 +160,8 @@ def interp_2d(
141
160
  source_x_coordinates (Union[np.ndarray, List[float]]): Original grid's x-coordinates.
142
161
  source_y_coordinates (Union[np.ndarray, List[float]]): Original grid's y-coordinates.
143
162
  source_data (np.ndarray): Multi-dimensional array with the last two dimensions as spatial.
144
- interpolation_method (str, optional): Interpolation method. Defaults to "linear".
163
+ interpolation_method (str, optional): Interpolation method. Defaults to "cubic".
164
+ >>> optional: 'linear', 'nearest', 'cubic', 'quintic', etc.
145
165
  use_parallel (bool, optional): Enable parallel processing. Defaults to True.
146
166
 
147
167
  Returns:
@@ -190,11 +210,12 @@ def interp_2d(
190
210
  for t_index in range(t):
191
211
  for z_index in range(z):
192
212
  paras.append((new_src_data[t_index, z_index], origin_points, target_points, interpolation_method, target_shape))
193
-
213
+
194
214
  with PEx() as excutor:
195
215
  result = excutor.run(_interp_single_worker, paras)
196
216
 
197
- return np.squeeze(np.array(result))
217
+ return np.squeeze(np.array(result).reshape(t, z, *target_shape))
218
+
198
219
 
199
220
 
200
221
  def mask_shapefile(
@@ -584,7 +584,7 @@ def _clear_existing_file(file_full_path):
584
584
 
585
585
  def _check_existing_file(file_full_path, avg_size):
586
586
  if os.path.exists(file_full_path):
587
- print(f"[bold #FFA54F]{file_full_path} exists")
587
+ print(f"[bold #FFA54F]{file_full_path} exists ...")
588
588
  fsize = file_size(file_full_path)
589
589
  delta_size_ratio = (fsize - avg_size) / avg_size
590
590
  if abs(delta_size_ratio) > 0.025:
@@ -796,7 +796,7 @@ def _download_file(target_url, store_path, file_name, cover=False):
796
796
  get_mean_size = _get_mean_size_move(same_file, save_path)
797
797
 
798
798
  if _check_existing_file(save_path, get_mean_size):
799
- print(f"[bold #FFA54F]{save_path} exists, skipping ...")
799
+ # print(f"[bold #FFA54F]{save_path} exists, skipping ...")
800
800
  count_dict["skip"] += 1
801
801
  return
802
802
 
@@ -901,7 +901,7 @@ def _prepare_url_to_download(var, lon_min=0, lon_max=359.92, lat_min=-80, lat_ma
901
901
  else:
902
902
  if download_time < "2024081012":
903
903
  varlist = [_ for _ in var]
904
- for key, value in pbar(var_group.items(), description=f"Var Group {download_time} ->", total=len(var_group), color="#d7feb9", next_line=True):
904
+ for key, value in pbar(var_group.items(), description=f"Var Group {download_time}", total=len(var_group), next_line=True):
905
905
  current_group = []
906
906
  for v in varlist:
907
907
  if v in value:
@@ -923,7 +923,7 @@ def _prepare_url_to_download(var, lon_min=0, lon_max=359.92, lat_min=-80, lat_ma
923
923
  file_name = f"HYCOM_{key}_{download_time}-{download_time_end}.nc"
924
924
  _download_file(submit_url, store_path, file_name, cover)
925
925
  else:
926
- for v in pbar(var, description=f"Var {download_time} ->", total=len(var), color="#d7feb9", next_line=True):
926
+ for v in pbar(var, description=f"Var {download_time}", total=len(var), next_line=True):
927
927
  submit_url = _get_submit_url_var(v, depth, level_num, lon_min, lon_max, lat_min, lat_max, dataset_name, version_name, download_time, download_time_end)
928
928
  file_name = f"HYCOM_{variable_info[v]['var_name']}_{download_time}.nc"
929
929
  if download_time_end is not None:
@@ -968,7 +968,7 @@ def _download_hourly_func(var, time_s, time_e, lon_min=0, lon_max=359.92, lat_mi
968
968
  # with Progress() as progress:
969
969
  # task = progress.add_task(f"[cyan]{bar_desc}", total=len(time_list))
970
970
  if num_workers is None or num_workers <= 1:
971
- for i, time_str in pbar(enumerate(time_list), description=f"{bar_desc}", total=len(time_list), cmap='colorful_1', next_line=True):
971
+ for i, time_str in pbar(enumerate(time_list), description=f"{bar_desc}", total=len(time_list), next_line=True):
972
972
  _prepare_url_to_download(var, lon_min, lon_max, lat_min, lat_max, time_str, None, depth, level, store_path, dataset_name, version_name, cover)
973
973
  # progress.update(task, advance=1, description=f"[cyan]{bar_desc} {i + 1}/{len(time_list)}")
974
974
  else:
@@ -976,7 +976,7 @@ def _download_hourly_func(var, time_s, time_e, lon_min=0, lon_max=359.92, lat_mi
976
976
  futures = [executor.submit(_download_task, var, time_str, None, lon_min, lon_max, lat_min, lat_max, depth, level, store_path, dataset_name, version_name, cover) for time_str in time_list]
977
977
  """ for feature in as_completed(futures):
978
978
  _done_callback(feature, progress, task, len(time_list), counter_lock) """
979
- for _ in pbar(as_completed(futures),description=f"{bar_desc}", total=len(futures),cmap='colorful_1',next_line=True):
979
+ for _ in pbar(as_completed(futures),description=f"{bar_desc}", total=len(futures),next_line=True):
980
980
  pass
981
981
  else:
982
982
  print("[bold red]Please ensure the time_s is no more than time_e")
@@ -1121,10 +1121,10 @@ def download(
1121
1121
  workers = 1
1122
1122
  given_idm_engine = idm_path
1123
1123
  idm_download_list = []
1124
- bar_desc = "Submitting to IDM ->"
1124
+ bar_desc = "Submitting to IDM"
1125
1125
  else:
1126
1126
  use_idm = False
1127
- bar_desc = "Downloading ->"
1127
+ bar_desc = "Downloading"
1128
1128
 
1129
1129
  global match_time
1130
1130
  match_time = validate_time
@@ -1136,7 +1136,7 @@ def download(
1136
1136
  workers = 1
1137
1137
  print("*" * mark_len)
1138
1138
  print("[bold red]Only checking the time of existing files.")
1139
- bar_desc = "Checking time ->"
1139
+ bar_desc = "Checking time"
1140
1140
 
1141
1141
  _download_hourly_func(
1142
1142
  variables,
@@ -1162,7 +1162,7 @@ def download(
1162
1162
  print("[bold #ecdbfe]*" * mark_len)
1163
1163
  if idm_download_list:
1164
1164
  remain_list = idm_download_list.copy()
1165
- for _ in pbar(range(len(idm_download_list)), cmap="diverging_1", description="Downloading ->"):
1165
+ for _ in pbar(range(len(idm_download_list)), description="Downloading"):
1166
1166
  success = False
1167
1167
  while not success:
1168
1168
  for f in remain_list:
oafuncs/oa_file.py CHANGED
@@ -287,7 +287,11 @@ def remove(target_pattern: str) -> None:
287
287
  (shutil.rmtree if os.path.isdir(file_path) else os.remove)(file_path)
288
288
  print(f"[green]Successfully deleted:[/green] [bold]{file_path}[/bold]")
289
289
  else:
290
- print(f"[yellow]Skipping unknown file type:[/yellow] [bold]{file_path}[/bold]")
290
+ if not os.path.exists(file_path):
291
+ # print(f"[yellow]File not found:[/yellow] [bold]{file_path}[/bold]")
292
+ pass
293
+ else:
294
+ print(f"[yellow]Skipping unknown file type:[/yellow] [bold]{file_path}[/bold]")
291
295
  except Exception as e:
292
296
  print(f"[red]Failed to delete:[/red] [bold]{file_path}[/bold]. Error: {e}")
293
297
 
oafuncs/oa_nc.py CHANGED
@@ -6,7 +6,7 @@ import numpy as np
6
6
  import xarray as xr
7
7
  from rich import print
8
8
 
9
- __all__ = ["save", "merge", "modify", "rename", "check", "convert_longitude", "isel", "draw"]
9
+ __all__ = ["save", "merge", "modify", "rename", "check", "convert_longitude", "isel", "draw", "compress_netcdf", "unpack_netcdf"]
10
10
 
11
11
 
12
12
  def save(
@@ -278,6 +278,48 @@ def draw(
278
278
  print("[red]No dataset or file provided.[/red]")
279
279
 
280
280
 
281
+ def compress_netcdf(src_path, dst_path=None):
282
+ """
283
+ 压缩 NetCDF 文件,使用 scale_factor/add_offset 压缩数据。
284
+ 若 dst_path 省略,则自动生成新文件名,写出后删除原文件并将新文件改回原名。
285
+ """
286
+ # 判断是否要替换原文件
287
+ delete_orig = dst_path is None
288
+ if delete_orig:
289
+ dst_path = src_path.replace(".nc", "_compress.nc")
290
+
291
+ ds = xr.open_dataset(src_path)
292
+ save(dst_path, ds)
293
+ ds.close()
294
+
295
+ if delete_orig:
296
+ os.remove(src_path)
297
+ os.rename(dst_path, src_path)
298
+ pass
299
+
300
+
301
+ def unpack_netcdf(src_path, dst_path=None):
302
+ """解码 NetCDF 并移除 scale_factor/add_offset,写出真实值。
303
+ 若 dst_path 省略,则自动生成新文件名,写出后删除原文件并将新文件改回原名。
304
+ """
305
+ # 判断是否要替换原文件
306
+ delete_orig = dst_path is None
307
+ if delete_orig:
308
+ dst_path = src_path.replace(".nc", "_unpacked.nc")
309
+
310
+ ds = xr.open_dataset(src_path, decode_cf=True)
311
+ for var in ds.data_vars:
312
+ ds[var].attrs.pop("scale_factor", None)
313
+ ds[var].attrs.pop("add_offset", None)
314
+ ds[var].encoding.clear()
315
+ ds.to_netcdf(dst_path, mode="w", format="NETCDF4", engine="netcdf4")
316
+ ds.close()
317
+
318
+ if delete_orig:
319
+ os.remove(src_path)
320
+ os.rename(dst_path, src_path)
321
+
322
+
281
323
  if __name__ == "__main__":
282
324
  data = np.random.rand(100, 50)
283
325
  save(r"test.nc", data, "data", {"time": np.linspace(0, 120, 100), "lev": np.linspace(0, 120, 50)}, "a")
oafuncs/oa_tool.py CHANGED
@@ -137,7 +137,7 @@ def pbar(
137
137
  description: str = "Working...",
138
138
  total: Optional[float] = None,
139
139
  completed: float = 0,
140
- color: Any = "cyan",
140
+ color: Any = "None",
141
141
  cmap: Union[str, List[str], None] = None,
142
142
  update_interval: float = 0.1,
143
143
  bar_length: Optional[int] = None,
@@ -173,8 +173,23 @@ def pbar(
173
173
  ... time.sleep(0.1)
174
174
  """
175
175
  from ._script.cprogressbar import ColorProgressBar
176
+ import random
176
177
 
177
- print(f"[blue]{description}[/blue]")
178
+ def _generate_random_color_hex():
179
+ """Generate a random color in hexadecimal format."""
180
+ r = random.randint(0, 255)
181
+ g = random.randint(0, 255)
182
+ b = random.randint(0, 255)
183
+ return '#{r:02x}{g:02x}{b:02x}'.format(r=r, g=g, b=b)
184
+
185
+ if color == 'None' and cmap is None:
186
+ color = _generate_random_color_hex()
187
+
188
+ style = f"bold {color if color != 'None' else 'green'}"
189
+ print(f"[{style}]~*^* {description} *^*~[/{style}]")
190
+
191
+ description=''
192
+
178
193
  return ColorProgressBar(
179
194
  iterable=iterable,
180
195
  description=description,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oafuncs
3
- Version: 0.0.98.9
3
+ Version: 0.0.98.11
4
4
  Summary: Oceanic and Atmospheric Functions
5
5
  Home-page: https://github.com/Industry-Pays/OAFuncs
6
6
  Author: Kun Liu
@@ -1,27 +1,27 @@
1
1
  oafuncs/__init__.py,sha256=T_-VtnWWllV3Q91twT5Yt2sUapeA051QbPNnBxmg9nw,1456
2
2
  oafuncs/oa_cmap.py,sha256=DimWT4Bg7uE5Lx8hSw1REp7whpsR2pFRStAwk1cowEM,11494
3
- oafuncs/oa_data.py,sha256=SqvG2mRVSTmr5j7H3Cn-ILgDo8iwSAl6W5NLpVpBx3c,10288
3
+ oafuncs/oa_data.py,sha256=PXn4EpSbLPHhYmNJXEStd8vIMwInl3a9O9sW2c9z-g0,11152
4
4
  oafuncs/oa_date.py,sha256=WhM6cyD4G3IeghjLTHhAMtlvJbA7kwQG2sHnxdTgyso,6303
5
5
  oafuncs/oa_draw.py,sha256=Wj2QBgyIPpV_dxaDrH10jqj_puK9ZM9rd-si-3VrsrE,17631
6
- oafuncs/oa_file.py,sha256=goF5iRXJFFCIKhIjlkCnYYt0EYlJb_4r8AeYNZ0-SOk,16209
6
+ oafuncs/oa_file.py,sha256=j9gXJgPOJsliu4IOUc4bc-luW4yBvQyNCEmMyDVjUwQ,16404
7
7
  oafuncs/oa_help.py,sha256=_4AZgRDq5Or0vauNvq5IDDHIBoBfdOQtzak-mG1wwAw,4537
8
- oafuncs/oa_nc.py,sha256=L1gqXxg93kIDsMOa87M0o-53KVmdqCipnXeF9XfzfY8,10513
8
+ oafuncs/oa_nc.py,sha256=lLEPjj4qgdEw1al0r1nKGZUnMP_ejT8A2NKD4lrs2kc,11936
9
9
  oafuncs/oa_python.py,sha256=NkopwkYFGSEuVljnTBvXCl6o2CeyRNBqRXSsUl3euEE,5192
10
- oafuncs/oa_tool.py,sha256=UNdiXKWfk860eb01vSGH8XN_Bd3CP7ihaspRrudday0,7983
10
+ oafuncs/oa_tool.py,sha256=EqOlGPq3Rx2ohqVnGuCZhMvr2o9_XgglrETMbAdEifM,8471
11
11
  oafuncs/_data/hycom.png,sha256=MadKs6Gyj5n9-TOu7L4atQfTXtF9dvN9w-tdU9IfygI,10945710
12
12
  oafuncs/_data/oafuncs.png,sha256=o3VD7wm-kwDea5E98JqxXl04_78cBX7VcdUt7uQXGiU,3679898
13
- oafuncs/_script/cprogressbar.py,sha256=wRU3SFPFtMI7ER26tTzg223kVKNo5RDWE9CzdIgUsuE,15771
13
+ oafuncs/_script/cprogressbar.py,sha256=UIgGcLFs-6IgWlITuBLaQqrpt4OAK3Mst5RlCiNfZdQ,15772
14
14
  oafuncs/_script/email.py,sha256=lL4HGKrr524-g0xLlgs-4u7x4-u7DtgNoD9AL8XJKj4,3058
15
15
  oafuncs/_script/netcdf_merge.py,sha256=ktmTOgGfLHBNdS4HBc6xFDfO8B7E4DT7d1e6Dtare9Y,5596
16
16
  oafuncs/_script/netcdf_modify.py,sha256=sGRUYNhfGgf9JV70rnBzw3bzuTRSXzBTL_RMDnDPeLQ,4552
17
17
  oafuncs/_script/netcdf_write.py,sha256=iO1Qv9bp6RLiw1D8Nrv7tX_8X-diUZaX3Nxhk6pJ5Nw,8556
18
- oafuncs/_script/parallel.py,sha256=dRT7w_rBnR3mZkUlO6v6j05SwBTQpTccOna5CXI5Msg,8196
18
+ oafuncs/_script/parallel.py,sha256=T9Aie-e4LcbKlFTLZ0l4lhEN3SBVa84jRcrAsIm8s0I,8767
19
19
  oafuncs/_script/parallel_test.py,sha256=0GBqZOX7IaCOKF2t1y8N8YYu53GJ33OkfsWgpvZNqM4,372
20
20
  oafuncs/_script/plot_dataset.py,sha256=zkSEnO_-biyagorwWXPoihts_cwuvripzEt-l9bHJ2E,13989
21
21
  oafuncs/_script/replace_file_content.py,sha256=eCFZjnZcwyRvy6b4mmIfBna-kylSZTyJRfgXd6DdCjk,5982
22
22
  oafuncs/oa_down/User_Agent-list.txt,sha256=pHaMlElMvZ8TG4vf4BqkZYKqe0JIGkr4kCN0lM1Y9FQ,514295
23
23
  oafuncs/oa_down/__init__.py,sha256=kRX5eTUCbAiz3zTaQM1501paOYS_3fizDN4Pa0mtNUA,585
24
- oafuncs/oa_down/hycom_3hourly.py,sha256=wWV14-OB9_LMmjUiZr3YXWBdKKwAyGXNa3Up7fSiWwk,55553
24
+ oafuncs/oa_down/hycom_3hourly.py,sha256=VYxG9DPAnS1wk8gEXyqSIww4IvNRWOYGC4D6PTh5t3A,55450
25
25
  oafuncs/oa_down/hycom_3hourly_proxy.py,sha256=1eaoJGI_m-7w4ZZ3n7NGxkZaeFdsm0d3U-hyw8RFNbc,54563
26
26
  oafuncs/oa_down/idm.py,sha256=4z5IvgfTyIKEI1kOtqXZwN7Jnfjwp6qDBOIoVyOLp0I,1823
27
27
  oafuncs/oa_down/literature.py,sha256=2bF9gSKQbzcci9LcKE81j8JEjIJwON7jbwQB3gDDA3E,11331
@@ -37,8 +37,8 @@ oafuncs/oa_sign/__init__.py,sha256=QKqTFrJDFK40C5uvk48GlRRbGFzO40rgkYwu6dYxatM,5
37
37
  oafuncs/oa_sign/meteorological.py,sha256=8091SHo2L8kl4dCFmmSH5NGVHDku5i5lSiLEG5DLnOQ,6489
38
38
  oafuncs/oa_sign/ocean.py,sha256=xrW-rWD7xBWsB5PuCyEwQ1Q_RDKq2KCLz-LOONHgldU,5932
39
39
  oafuncs/oa_sign/scientific.py,sha256=a4JxOBgm9vzNZKpJ_GQIQf7cokkraV5nh23HGbmTYKw,5064
40
- oafuncs-0.0.98.9.dist-info/licenses/LICENSE.txt,sha256=rMtLpVg8sKiSlwClfR9w_Dd_5WubTQgoOzE2PDFxzs4,1074
41
- oafuncs-0.0.98.9.dist-info/METADATA,sha256=Ns5ahXYSJiK2OmggCSbzoFC5XHMnLsFdC_KMnVrAnGA,4272
42
- oafuncs-0.0.98.9.dist-info/WHEEL,sha256=pxyMxgL8-pra_rKaQ4drOZAegBVuX-G_4nRHjjgWbmo,91
43
- oafuncs-0.0.98.9.dist-info/top_level.txt,sha256=bgC35QkXbN4EmPHEveg_xGIZ5i9NNPYWqtJqaKqTPsQ,8
44
- oafuncs-0.0.98.9.dist-info/RECORD,,
40
+ oafuncs-0.0.98.11.dist-info/licenses/LICENSE.txt,sha256=rMtLpVg8sKiSlwClfR9w_Dd_5WubTQgoOzE2PDFxzs4,1074
41
+ oafuncs-0.0.98.11.dist-info/METADATA,sha256=vbHI4zd7XEcXnbCI7VuGmyDEcnfv5hsVjR8XczfObTU,4273
42
+ oafuncs-0.0.98.11.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
43
+ oafuncs-0.0.98.11.dist-info/top_level.txt,sha256=bgC35QkXbN4EmPHEveg_xGIZ5i9NNPYWqtJqaKqTPsQ,8
44
+ oafuncs-0.0.98.11.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (79.0.0)
2
+ Generator: setuptools (79.0.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5