oafuncs 0.0.98.4__tar.gz → 0.0.98.5__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 (50) hide show
  1. {oafuncs-0.0.98.4/oafuncs.egg-info → oafuncs-0.0.98.5}/PKG-INFO +1 -1
  2. oafuncs-0.0.98.4/oafuncs/oa_down/hycom_3hourly_20250416.py → oafuncs-0.0.98.5/oafuncs/oa_down/hycom_3hourly.py +111 -102
  3. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5/oafuncs.egg-info}/PKG-INFO +1 -1
  4. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs.egg-info/SOURCES.txt +0 -2
  5. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/setup.py +1 -1
  6. oafuncs-0.0.98.4/oafuncs/oa_down/hycom_3hourly.py +0 -1216
  7. oafuncs-0.0.98.4/oafuncs/oa_down/hycom_3hourly_20250407.py +0 -1295
  8. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/LICENSE.txt +0 -0
  9. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/MANIFEST.in +0 -0
  10. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/README.md +0 -0
  11. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/__init__.py +0 -0
  12. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/_data/hycom.png +0 -0
  13. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/_data/oafuncs.png +0 -0
  14. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/_script/cprogressbar.py +0 -0
  15. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/_script/email.py +0 -0
  16. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/_script/netcdf_merge.py +0 -0
  17. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/_script/netcdf_modify.py +0 -0
  18. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/_script/netcdf_write.py +0 -0
  19. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/_script/parallel.py +0 -0
  20. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/_script/parallel_test.py +0 -0
  21. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/_script/plot_dataset.py +0 -0
  22. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/_script/replace_file_content.py +0 -0
  23. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_cmap.py +0 -0
  24. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_data.py +0 -0
  25. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_date.py +0 -0
  26. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_down/User_Agent-list.txt +0 -0
  27. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_down/__init__.py +0 -0
  28. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_down/idm.py +0 -0
  29. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_down/literature.py +0 -0
  30. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_down/test_ua.py +0 -0
  31. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_down/user_agent.py +0 -0
  32. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_draw.py +0 -0
  33. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_file.py +0 -0
  34. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_help.py +0 -0
  35. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_model/__init__.py +0 -0
  36. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_model/roms/__init__.py +0 -0
  37. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_model/roms/test.py +0 -0
  38. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_model/wrf/__init__.py +0 -0
  39. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_model/wrf/little_r.py +0 -0
  40. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_nc.py +0 -0
  41. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_python.py +0 -0
  42. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_sign/__init__.py +0 -0
  43. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_sign/meteorological.py +0 -0
  44. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_sign/ocean.py +0 -0
  45. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_sign/scientific.py +0 -0
  46. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs/oa_tool.py +0 -0
  47. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs.egg-info/dependency_links.txt +0 -0
  48. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs.egg-info/requires.txt +0 -0
  49. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/oafuncs.egg-info/top_level.txt +0 -0
  50. {oafuncs-0.0.98.4 → oafuncs-0.0.98.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oafuncs
3
- Version: 0.0.98.4
3
+ Version: 0.0.98.5
4
4
  Summary: Oceanic and Atmospheric Functions
5
5
  Home-page: https://github.com/Industry-Pays/OAFuncs
6
6
  Author: Kun Liu
@@ -2,9 +2,9 @@
2
2
  # coding=utf-8
3
3
  """
4
4
  Author: Liu Kun && 16031215@qq.com
5
- Date: 2025-04-16 11:36:15
5
+ Date: 2025-04-07 10:51:09
6
6
  LastEditors: Liu Kun && 16031215@qq.com
7
- LastEditTime: 2025-04-16 11:36:16
7
+ LastEditTime: 2025-04-07 10:51:09
8
8
  FilePath: \\Python\\My_Funcs\\OAFuncs\\oafuncs\\oa_down\\hycom_3hourly copy.py
9
9
  Description:
10
10
  EditPlatform: vscode
@@ -13,9 +13,9 @@ SystemInfo: Windows 11
13
13
  Python Version: 3.12
14
14
  """
15
15
 
16
-
17
-
16
+ import asyncio
18
17
  import datetime
18
+ import logging
19
19
  import os
20
20
  import random
21
21
  import re
@@ -25,11 +25,11 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
25
25
  from pathlib import Path
26
26
  from threading import Lock
27
27
 
28
+ import httpx
28
29
  import matplotlib.pyplot as plt
29
30
  import netCDF4 as nc
30
31
  import numpy as np
31
32
  import pandas as pd
32
- import requests
33
33
  import xarray as xr
34
34
  from rich import print
35
35
  from rich.progress import Progress
@@ -40,6 +40,9 @@ from oafuncs.oa_file import file_size
40
40
  from oafuncs.oa_nc import check as check_nc
41
41
  from oafuncs.oa_nc import modify as modify_nc
42
42
 
43
+ logging.getLogger("httpx").setLevel(logging.WARNING) # 关闭 httpx 的 INFO 日志,只显示 WARNING 及以上
44
+
45
+
43
46
  warnings.filterwarnings("ignore", category=RuntimeWarning, message="Engine '.*' loading failed:.*")
44
47
 
45
48
  __all__ = ["draw_time_range", "download"]
@@ -416,13 +419,13 @@ def _check_time_in_dataset_and_version(time_input, time_end=None):
416
419
  trange_list.append(f"{time_s}-{time_e}")
417
420
  have_data = True
418
421
 
419
- # 输出结果
420
- if match_time is None:
421
- print(f"[bold red]{time_input_str} is in the following dataset and version:")
422
422
  if have_data:
423
423
  if match_time is None:
424
+ print(f"[bold red]Time {time_input_str} included in:")
425
+ dv_num = 1
424
426
  for d, v, trange in zip(d_list, v_list, trange_list):
425
- print(f"[bold blue]{d} {v} {trange}")
427
+ print(f"{dv_num} -> [bold blue]{d} - {v} : {trange}")
428
+ dv_num += 1
426
429
  if is_single_time:
427
430
  return True
428
431
  else:
@@ -434,7 +437,7 @@ def _check_time_in_dataset_and_version(time_input, time_end=None):
434
437
  print(f"[bold red]{time_start} to {time_end} is in different datasets or versions, so you can't download them together")
435
438
  return False
436
439
  else:
437
- print(f"[bold red]{time_input_str} is not in any dataset and version")
440
+ print(f"[bold red]Time {time_input_str} has no data")
438
441
  return False
439
442
 
440
443
 
@@ -509,7 +512,8 @@ def _direct_choose_dataset_and_version(time_input, time_end=None):
509
512
 
510
513
  if dataset_name_out is not None and version_name_out is not None:
511
514
  if match_time is None:
512
- print(f"[bold purple]dataset: {dataset_name_out}, version: {version_name_out} is chosen")
515
+ # print(f"[bold purple]dataset: {dataset_name_out}, version: {version_name_out} is chosen")
516
+ print(f"[bold purple]Chosen dataset: {dataset_name_out} - {version_name_out}")
513
517
 
514
518
  # 如果没有找到匹配的数据集和版本,会返回 None
515
519
  return dataset_name_out, version_name_out
@@ -664,117 +668,122 @@ def _correct_time(nc_file):
664
668
  modify_nc(nc_file, "time", None, time_difference)
665
669
 
666
670
 
671
+ class _HycomDownloader:
672
+ def __init__(self, tasks, delay_range=(3, 6), timeout_factor=120, max_var_count=5, max_retries=3):
673
+ """
674
+ :param tasks: List of (url, save_path)
675
+ """
676
+ self.tasks = tasks
677
+ self.delay_range = delay_range
678
+ self.timeout_factor = timeout_factor
679
+ self.max_var_count = max_var_count
680
+ self.max_retries = max_retries
681
+ self.count = {"success": 0, "fail": 0}
682
+
683
+ def user_agent(self):
684
+ return get_ua()
685
+
686
+ async def _download_one(self, url, save_path):
687
+ file_name = os.path.basename(save_path)
688
+ headers = {"User-Agent": self.user_agent()}
689
+ var_count = min(max(url.count("var="), 1), self.max_var_count)
690
+ timeout_max = self.timeout_factor * var_count
691
+ timeout = random.randint(timeout_max // 2, timeout_max)
692
+
693
+ retry = 0
694
+ while retry <= self.max_retries:
695
+ try:
696
+ await asyncio.sleep(random.uniform(*self.delay_range))
697
+ start = datetime.datetime.now()
698
+ async with httpx.AsyncClient(
699
+ timeout=httpx.Timeout(timeout),
700
+ limits=httpx.Limits(max_connections=2, max_keepalive_connections=2),
701
+ transport=httpx.AsyncHTTPTransport(retries=2),
702
+ ) as client:
703
+ print(f"[bold #f0f6d0]Requesting {file_name} (Attempt {retry + 1}) ...")
704
+ response = await client.get(url, headers=headers, follow_redirects=True)
705
+ response.raise_for_status()
706
+ if not response.content:
707
+ raise ValueError("Empty response received")
708
+
709
+ print(f"[bold #96cbd7]Downloading {file_name} ...")
710
+ with open(save_path, "wb") as f:
711
+ async for chunk in response.aiter_bytes(32 * 1024):
712
+ f.write(chunk)
713
+
714
+ elapsed = datetime.datetime.now() - start
715
+ print(f"[#3dfc40]File [bold #dfff73]{file_name} [#3dfc40]downloaded, Time: [#39cbdd]{elapsed}")
716
+ self.count["success"] += 1
717
+ count_dict["success"] += 1
718
+ return
719
+
720
+ except Exception as e:
721
+ print(f"[bold red]Failed ({type(e).__name__}): {e}")
722
+ if retry < self.max_retries:
723
+ backoff = 2**retry
724
+ print(f"[yellow]Retrying in {backoff:.1f}s ...")
725
+ await asyncio.sleep(backoff)
726
+ retry += 1
727
+ else:
728
+ print(f"[red]Giving up on {file_name}")
729
+ self.count["fail"] += 1
730
+ count_dict["fail"] += 1
731
+ return
732
+
733
+ async def run(self):
734
+ print(f"📥 Starting download of {len(self.tasks)} files ...")
735
+ for url, save_path in self.tasks:
736
+ await self._download_one(url, save_path)
737
+
738
+ print("\n✅ All tasks completed.")
739
+ print(f"✔️ Success: {self.count['success']} | ❌ Fail: {self.count['fail']}")
740
+
741
+
667
742
  def _download_file(target_url, store_path, file_name, cover=False):
668
- fname = Path(store_path) / file_name
743
+ save_path = Path(store_path) / file_name
669
744
  file_name_split = file_name.split("_")
670
745
  file_name_split = file_name_split[:-1]
671
746
  same_file = "_".join(file_name_split) + "*nc"
672
747
 
673
748
  if match_time is not None:
674
- if check_nc(fname, print_messages=False):
675
- if not _check_ftime(fname, if_print=True):
749
+ if check_nc(save_path, print_messages=False):
750
+ if not _check_ftime(save_path, if_print=True):
676
751
  if match_time:
677
- _correct_time(fname)
752
+ _correct_time(save_path)
678
753
  count_dict["skip"] += 1
679
754
  else:
680
- _clear_existing_file(fname)
755
+ _clear_existing_file(save_path)
681
756
  count_dict["no_data"] += 1
682
757
  else:
683
758
  count_dict["skip"] += 1
684
759
  print(f"[bold green]{file_name} is correct")
685
760
  return
686
761
 
687
- if not cover and os.path.exists(fname):
688
- print(f"[bold #FFA54F]{fname} exists, skipping ...")
762
+ if not cover and os.path.exists(save_path):
763
+ print(f"[bold #FFA54F]{save_path} exists, skipping ...")
689
764
  count_dict["skip"] += 1
690
765
  return
691
766
 
692
767
  if same_file not in fsize_dict.keys():
693
- check_nc(fname, delete_if_invalid=True, print_messages=False)
768
+ check_nc(save_path, delete_if_invalid=True, print_messages=False)
694
769
 
695
- get_mean_size = _get_mean_size_move(same_file, fname)
770
+ get_mean_size = _get_mean_size_move(same_file, save_path)
696
771
 
697
- if _check_existing_file(fname, get_mean_size):
772
+ if _check_existing_file(save_path, get_mean_size):
698
773
  count_dict["skip"] += 1
699
774
  return
700
775
 
701
- _clear_existing_file(fname)
776
+ _clear_existing_file(save_path)
702
777
 
703
778
  if not use_idm:
704
- print(f"[bold #f0f6d0]Requesting {file_name} ...")
705
- s = requests.Session()
706
- download_success = False
707
- request_times = 0
708
-
709
- def calculate_wait_time(time_str, target_url):
710
- time_pattern = r"\d{10}"
711
- times_in_str = re.findall(time_pattern, time_str)
712
- num_times_str = len(times_in_str)
713
-
714
- if num_times_str > 1:
715
- delta_t = datetime.datetime.strptime(times_in_str[1], "%Y%m%d%H") - datetime.datetime.strptime(times_in_str[0], "%Y%m%d%H")
716
- delta_t = delta_t.total_seconds() / 3600
717
- delta_t = delta_t / 3 + 1
718
- else:
719
- delta_t = 1
720
- num_var = int(target_url.count("var="))
721
- if num_var <= 0:
722
- num_var = 1
723
- return int(delta_t * 5 * 60 * num_var)
724
-
725
- max_timeout = calculate_wait_time(file_name, target_url)
726
- print(f"[bold #912dbc]Max timeout: {max_timeout} seconds")
727
-
728
- download_time_s = datetime.datetime.now()
729
- order_list = ["1st", "2nd", "3rd", "4th", "5th", "6th", "7th", "8th", "9th", "10th"]
730
- while not download_success:
731
- if request_times >= 10:
732
- print(f"[bold #ffe5c0]Download failed after {request_times} times\nYou can skip it and try again later")
733
- count_dict["fail"] += 1
734
- break
735
- if request_times > 0:
736
- print(f"[bold #ffe5c0]Retrying the {order_list[request_times - 1]} time...")
737
- try:
738
- referer_center = target_url.split("?")[0].split("ncss/")[-1]
739
- headers = {
740
- "User-Agent": get_ua(), # 后面几项可以不加,依旧能下载
741
- "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
742
- "Accept-Encoding": "gzip, deflate, br, zstd",
743
- "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
744
- "Referer": rf"https://ncss.hycom.org/thredds/ncss/grid/{referer_center}/dataset.html",
745
- }
746
- response = s.get(target_url, headers=headers, stream=True, timeout=random.randint(5, max_timeout))
747
- response.raise_for_status()
748
- with open(fname, "wb") as f:
749
- print(f"[bold #96cbd7]Downloading {file_name} ...")
750
- for chunk in response.iter_content(chunk_size=1024):
751
- if chunk:
752
- f.write(chunk)
753
-
754
- f.close()
755
-
756
- if os.path.exists(fname):
757
- download_success = True
758
- download_time_e = datetime.datetime.now()
759
- download_delta = download_time_e - download_time_s
760
- print(f"[#3dfc40]File [bold #dfff73]{fname} [#3dfc40]has been downloaded successfully, Time: [#39cbdd]{download_delta}")
761
- count_dict["success"] += 1
762
-
763
- except requests.exceptions.HTTPError as errh:
764
- print(f"Http Error: {errh}")
765
- except requests.exceptions.ConnectionError as errc:
766
- print(f"Error Connecting: {errc}")
767
- except requests.exceptions.Timeout as errt:
768
- print(f"Timeout Error: {errt}")
769
- except requests.exceptions.RequestException as err:
770
- print(f"OOps: Something Else: {err}")
771
-
772
- time.sleep(3)
773
- request_times += 1
779
+ python_downloader = _HycomDownloader([(target_url, save_path)])
780
+ asyncio.run(python_downloader.run())
781
+ time.sleep(3 + random.uniform(0, 10))
774
782
  else:
775
783
  idm_downloader(target_url, store_path, file_name, given_idm_engine)
776
- idm_download_list.append(fname)
777
- print(f"[bold #3dfc40]File [bold #dfff73]{fname} [#3dfc40]has been submit to IDM for downloading")
784
+ idm_download_list.append(save_path)
785
+ # print(f"[bold #3dfc40]File [bold #dfff73]{save_path} [#3dfc40]has been submit to IDM for downloading")
786
+ time.sleep(3 + random.uniform(0, 10))
778
787
 
779
788
 
780
789
  def _check_hour_is_valid(ymdh_str):
@@ -1167,20 +1176,20 @@ if __name__ == "__main__":
1167
1176
 
1168
1177
  options = {
1169
1178
  "variables": var_list,
1170
- "start_time": "2025010300",
1171
- "end_time": "2025010309",
1172
- "output_dir": r"I:\Data\HYCOM\3hourly_test",
1179
+ "start_time": "2018010100",
1180
+ "end_time": "2019063000",
1181
+ "output_dir": r"G:\Data\HYCOM\china_sea\hourly_24",
1173
1182
  "lon_min": 105,
1174
- "lon_max": 130,
1175
- "lat_min": 15,
1183
+ "lon_max": 135,
1184
+ "lat_min": 10,
1176
1185
  "lat_max": 45,
1177
1186
  "workers": 1,
1178
1187
  "overwrite": False,
1179
1188
  "depth": None,
1180
1189
  "level": None,
1181
- "validate_time": True,
1182
- "idm_path": r"D:\Programs\Internet Download Manager\IDMan.exe",
1183
- "interval_hours": 3,
1190
+ "validate_time": None,
1191
+ # "idm_path": r"D:\Programs\Internet Download Manager\IDMan.exe",
1192
+ "interval_hours": 24,
1184
1193
  }
1185
1194
 
1186
1195
  if single_var:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oafuncs
3
- Version: 0.0.98.4
3
+ Version: 0.0.98.5
4
4
  Summary: Oceanic and Atmospheric Functions
5
5
  Home-page: https://github.com/Industry-Pays/OAFuncs
6
6
  Author: Kun Liu
@@ -31,8 +31,6 @@ oafuncs/_script/replace_file_content.py
31
31
  oafuncs/oa_down/User_Agent-list.txt
32
32
  oafuncs/oa_down/__init__.py
33
33
  oafuncs/oa_down/hycom_3hourly.py
34
- oafuncs/oa_down/hycom_3hourly_20250407.py
35
- oafuncs/oa_down/hycom_3hourly_20250416.py
36
34
  oafuncs/oa_down/idm.py
37
35
  oafuncs/oa_down/literature.py
38
36
  oafuncs/oa_down/test_ua.py
@@ -18,7 +18,7 @@ URL = "https://github.com/Industry-Pays/OAFuncs"
18
18
  EMAIL = "liukun0312@stu.ouc.edu.cn"
19
19
  AUTHOR = "Kun Liu"
20
20
  REQUIRES_PYTHON = ">=3.9.0" # 2025/03/13
21
- VERSION = "0.0.98.4"
21
+ VERSION = "0.0.98.5"
22
22
 
23
23
  # What packages are required for this module to be executed?
24
24
  REQUIRED = [