oafuncs 0.0.98.3__tar.gz → 0.0.98.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 (54) hide show
  1. {oafuncs-0.0.98.3/oafuncs.egg-info → oafuncs-0.0.98.4}/PKG-INFO +2 -1
  2. oafuncs-0.0.98.4/oafuncs/_script/parallel.py +214 -0
  3. oafuncs-0.0.98.4/oafuncs/_script/parallel_test.py +14 -0
  4. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_down/User_Agent-list.txt +1 -1611
  5. oafuncs-0.0.98.4/oafuncs/oa_down/hycom_3hourly.py +1216 -0
  6. oafuncs-0.0.98.3/oafuncs/oa_down/hycom_3hourly.py → oafuncs-0.0.98.4/oafuncs/oa_down/hycom_3hourly_20250416.py +16 -7
  7. oafuncs-0.0.98.4/oafuncs/oa_down/test_ua.py +40 -0
  8. oafuncs-0.0.98.4/oafuncs/oa_tool.py +207 -0
  9. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4/oafuncs.egg-info}/PKG-INFO +2 -1
  10. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs.egg-info/SOURCES.txt +2 -1
  11. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs.egg-info/requires.txt +1 -0
  12. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/setup.py +2 -1
  13. oafuncs-0.0.98.3/oafuncs/_script/parallel.py +0 -565
  14. oafuncs-0.0.98.3/oafuncs/_script/parallel_example_usage.py +0 -83
  15. oafuncs-0.0.98.3/oafuncs/oa_down/test_ua.py +0 -151
  16. oafuncs-0.0.98.3/oafuncs/oa_tool.py +0 -119
  17. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/LICENSE.txt +0 -0
  18. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/MANIFEST.in +0 -0
  19. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/README.md +0 -0
  20. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/__init__.py +0 -0
  21. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/_data/hycom.png +0 -0
  22. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/_data/oafuncs.png +0 -0
  23. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/_script/cprogressbar.py +0 -0
  24. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/_script/email.py +0 -0
  25. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/_script/netcdf_merge.py +0 -0
  26. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/_script/netcdf_modify.py +0 -0
  27. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/_script/netcdf_write.py +0 -0
  28. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/_script/plot_dataset.py +0 -0
  29. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/_script/replace_file_content.py +0 -0
  30. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_cmap.py +0 -0
  31. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_data.py +0 -0
  32. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_date.py +0 -0
  33. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_down/__init__.py +0 -0
  34. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_down/hycom_3hourly_20250407.py +0 -0
  35. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_down/idm.py +0 -0
  36. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_down/literature.py +0 -0
  37. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_down/user_agent.py +0 -0
  38. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_draw.py +0 -0
  39. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_file.py +0 -0
  40. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_help.py +0 -0
  41. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_model/__init__.py +0 -0
  42. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_model/roms/__init__.py +0 -0
  43. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_model/roms/test.py +0 -0
  44. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_model/wrf/__init__.py +0 -0
  45. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_model/wrf/little_r.py +0 -0
  46. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_nc.py +0 -0
  47. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_python.py +0 -0
  48. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_sign/__init__.py +0 -0
  49. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_sign/meteorological.py +0 -0
  50. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_sign/ocean.py +0 -0
  51. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs/oa_sign/scientific.py +0 -0
  52. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs.egg-info/dependency_links.txt +0 -0
  53. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/oafuncs.egg-info/top_level.txt +0 -0
  54. {oafuncs-0.0.98.3 → oafuncs-0.0.98.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oafuncs
3
- Version: 0.0.98.3
3
+ Version: 0.0.98.4
4
4
  Summary: Oceanic and Atmospheric Functions
5
5
  Home-page: https://github.com/Industry-Pays/OAFuncs
6
6
  Author: Kun Liu
@@ -25,6 +25,7 @@ Requires-Dist: rich
25
25
  Requires-Dist: pathlib
26
26
  Requires-Dist: requests
27
27
  Requires-Dist: bs4
28
+ Requires-Dist: httpx
28
29
  Requires-Dist: matplotlib
29
30
  Requires-Dist: netCDF4
30
31
  Requires-Dist: xlrd
@@ -0,0 +1,214 @@
1
+ import atexit
2
+ import logging
3
+ import multiprocessing as mp
4
+ import platform
5
+ import threading
6
+ import time
7
+ from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor, as_completed
8
+ from typing import Any, Callable, Dict, List, Optional, Tuple
9
+
10
+ import psutil
11
+
12
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
13
+
14
+ __all__ = ["ParallelExecutor"]
15
+
16
+
17
+ class ParallelExecutor:
18
+ def __init__(
19
+ self,
20
+ max_workers: Optional[int] = None,
21
+ chunk_size: Optional[int] = None,
22
+ mem_per_process: float = 1.0, # GB
23
+ timeout_per_task: int = 3600,
24
+ max_retries: int = 3,
25
+ ):
26
+ self.platform = self._detect_platform()
27
+ self.mem_per_process = mem_per_process
28
+ self.timeout_per_task = timeout_per_task
29
+ self.max_retries = max_retries
30
+ self.running = True
31
+ self.task_history = []
32
+ self._executor = None
33
+
34
+ self.mode, default_workers = self._determine_optimal_settings()
35
+ self.max_workers = max_workers or default_workers
36
+ self.chunk_size = chunk_size or self._get_default_chunk_size()
37
+
38
+ self._init_platform_settings()
39
+ self._start_resource_monitor()
40
+ atexit.register(self.shutdown)
41
+
42
+ logging.info(f"Initialized {self.__class__.__name__} on {self.platform} (mode={self.mode}, workers={self.max_workers})")
43
+
44
+ def _detect_platform(self) -> str:
45
+ system = platform.system().lower()
46
+ if system == "linux":
47
+ return "wsl" if "microsoft" in platform.release().lower() else "linux"
48
+ return system
49
+
50
+ def _init_platform_settings(self):
51
+ if self.platform in ["linux", "wsl"]:
52
+ self.mp_context = mp.get_context("fork")
53
+ elif self.platform == "windows":
54
+ mp.set_start_method("spawn", force=True)
55
+ self.mp_context = mp.get_context("spawn")
56
+ else:
57
+ self.mp_context = None
58
+
59
+ def _determine_optimal_settings(self) -> Tuple[str, int]:
60
+ logical_cores = psutil.cpu_count(logical=True) or 1
61
+ available_mem = psutil.virtual_memory().available / 1024**3 # GB
62
+
63
+ mem_limit = max(1, int(available_mem / self.mem_per_process))
64
+ return ("process", min(logical_cores, mem_limit))
65
+
66
+ def _get_default_chunk_size(self) -> int:
67
+ return max(10, 100 // (psutil.cpu_count() or 1))
68
+
69
+ def _start_resource_monitor(self):
70
+ def monitor():
71
+ threshold = self.mem_per_process * 1024**3
72
+ while self.running:
73
+ try:
74
+ if psutil.virtual_memory().available < threshold:
75
+ self._scale_down_workers()
76
+ time.sleep(1)
77
+ except Exception as e:
78
+ logging.error(f"Resource monitor error: {e}")
79
+
80
+ threading.Thread(target=monitor, daemon=True).start()
81
+
82
+ def _scale_down_workers(self):
83
+ if self.max_workers > 1:
84
+ new_count = self.max_workers - 1
85
+ logging.warning(f"Scaling down workers from {self.max_workers} to {new_count}")
86
+ self.max_workers = new_count
87
+ self._restart_executor()
88
+
89
+ def _restart_executor(self):
90
+ if self._executor:
91
+ self._executor.shutdown(wait=False)
92
+ self._executor = None
93
+
94
+ def _get_executor(self):
95
+ if not self._executor:
96
+ Executor = ThreadPoolExecutor if self.mode == "thread" else ProcessPoolExecutor
97
+ self._executor = Executor(max_workers=self.max_workers, mp_context=self.mp_context if self.mode == "process" else None)
98
+ return self._executor
99
+
100
+ def run(self, func: Callable, params: List[Tuple], chunk_size: Optional[int] = None) -> List[Any]:
101
+ 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")
112
+
113
+ def _execute_batch(self, func: Callable, params: List[Tuple], chunk_size: int) -> List[Any]:
114
+ if not params:
115
+ return []
116
+
117
+ if len(params) > chunk_size * 2:
118
+ return self._chunked_execution(func, params, chunk_size)
119
+
120
+ results = [None] * len(params)
121
+ with self._get_executor() as executor:
122
+ futures = {executor.submit(func, *args): idx for idx, args in enumerate(params)}
123
+ for future in as_completed(futures):
124
+ idx = futures[future]
125
+ try:
126
+ results[idx] = future.result(timeout=self.timeout_per_task)
127
+ except Exception as e:
128
+ results[idx] = self._handle_error(e, func, params[idx])
129
+ return results
130
+
131
+ def _chunked_execution(self, func: Callable, params: List[Tuple], chunk_size: int) -> List[Any]:
132
+ results = []
133
+ with self._get_executor() as executor:
134
+ futures = []
135
+ for i in range(0, len(params), chunk_size):
136
+ chunk = params[i : i + chunk_size]
137
+ futures.append(executor.submit(self._process_chunk, func, chunk))
138
+
139
+ for future in as_completed(futures):
140
+ try:
141
+ results.extend(future.result(timeout=self.timeout_per_task))
142
+ except Exception as e:
143
+ logging.error(f"Chunk failed: {e}")
144
+ results.extend([None] * chunk_size)
145
+ return results
146
+
147
+ @staticmethod
148
+ def _process_chunk(func: Callable, chunk: List[Tuple]) -> List[Any]:
149
+ return [func(*args) for args in chunk]
150
+
151
+ def _update_settings(self, duration: float, task_count: int):
152
+ self.task_history.append((duration, task_count))
153
+ self.chunk_size = max(5, min(100, self.chunk_size + (1 if duration < 5 else -1)))
154
+
155
+ def _handle_error(self, error: Exception, func: Callable, args: Tuple) -> Any:
156
+ if isinstance(error, TimeoutError):
157
+ logging.warning(f"Timeout processing {func.__name__}{args}")
158
+ elif isinstance(error, MemoryError):
159
+ logging.warning("Memory error detected")
160
+ self._scale_down_workers()
161
+ else:
162
+ logging.error(f"Error processing {func.__name__}{args}: {str(error)}")
163
+ return None
164
+
165
+ def _handle_failure(self):
166
+ if self.max_workers > 2:
167
+ self.max_workers = max(1, self.max_workers // 2)
168
+ self._restart_executor()
169
+
170
+ def shutdown(self):
171
+ self.running = False
172
+ if self._executor:
173
+ try:
174
+ self._executor.shutdown(wait=False)
175
+ except Exception as e:
176
+ logging.error(f"Shutdown error: {e}")
177
+ finally:
178
+ self._executor = None
179
+
180
+ def __enter__(self):
181
+ return self
182
+
183
+ def __exit__(self, *exc_info):
184
+ self.shutdown()
185
+
186
+ def get_stats(self) -> Dict[str, Any]:
187
+ stats = {
188
+ "platform": self.platform,
189
+ "mode": self.mode,
190
+ "workers": self.max_workers,
191
+ "chunk_size": self.chunk_size,
192
+ "total_tasks": sum(count for _, count in self.task_history),
193
+ }
194
+ if self.task_history:
195
+ total_time = sum(time for time, _ in self.task_history)
196
+ stats["avg_task_throughput"] = stats["total_tasks"] / total_time if total_time else 0
197
+ return stats
198
+
199
+
200
+ def _test_func(a, b):
201
+ time.sleep(0.01)
202
+ return a + b
203
+
204
+
205
+ if __name__ == "__main__":
206
+ params = [(i, i * 2) for i in range(1000)]
207
+
208
+ with ParallelExecutor() as executor:
209
+ results = executor.run(_test_func, params)
210
+
211
+ # print("Results:", results)
212
+
213
+ print(f"Processed {len(results)} tasks")
214
+ print("Execution stats:", executor.get_stats())
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf-8
3
+ """
4
+ Author: Liu Kun && 16031215@qq.com
5
+ Date: 2025-04-08 16:18:49
6
+ LastEditors: Liu Kun && 16031215@qq.com
7
+ LastEditTime: 2025-04-08 16:18:50
8
+ FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\_script\\parallel_test.py
9
+ Description:
10
+ EditPlatform: vscode
11
+ ComputerInfo: XPS 15 9510
12
+ SystemInfo: Windows 11
13
+ Python Version: 3.12
14
+ """