wrfrun 0.1.7__py3-none-any.whl → 0.1.9__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.
Files changed (56) hide show
  1. wrfrun/cli.py +128 -0
  2. wrfrun/core/__init__.py +33 -0
  3. wrfrun/core/base.py +246 -75
  4. wrfrun/core/config.py +286 -236
  5. wrfrun/core/error.py +47 -17
  6. wrfrun/core/replay.py +65 -32
  7. wrfrun/core/server.py +139 -79
  8. wrfrun/data.py +10 -5
  9. wrfrun/extension/__init__.py +28 -0
  10. wrfrun/extension/goos_sst/__init__.py +67 -0
  11. wrfrun/extension/goos_sst/core.py +111 -0
  12. wrfrun/extension/goos_sst/res/Vtable.ERA_GOOS_SST +7 -0
  13. wrfrun/extension/goos_sst/res/__init__.py +26 -0
  14. wrfrun/extension/goos_sst/utils.py +97 -0
  15. wrfrun/extension/littler/__init__.py +57 -1
  16. wrfrun/extension/littler/{utils.py → core.py} +326 -40
  17. wrfrun/extension/utils.py +22 -21
  18. wrfrun/model/__init__.py +24 -1
  19. wrfrun/model/plot.py +253 -35
  20. wrfrun/model/utils.py +17 -8
  21. wrfrun/model/wrf/__init__.py +41 -0
  22. wrfrun/model/wrf/core.py +218 -102
  23. wrfrun/model/wrf/exec_wrap.py +49 -35
  24. wrfrun/model/wrf/namelist.py +82 -11
  25. wrfrun/model/wrf/scheme.py +85 -1
  26. wrfrun/model/wrf/{_metgrid.py → utils.py} +36 -2
  27. wrfrun/model/wrf/vtable.py +2 -1
  28. wrfrun/plot/wps.py +66 -58
  29. wrfrun/res/__init__.py +8 -5
  30. wrfrun/res/config/config.template.toml +50 -0
  31. wrfrun/res/{config.toml.template → config/wrf.template.toml} +10 -47
  32. wrfrun/res/run.template.sh +10 -0
  33. wrfrun/res/scheduler/lsf.template +5 -0
  34. wrfrun/res/{job_scheduler → scheduler}/pbs.template +1 -1
  35. wrfrun/res/{job_scheduler → scheduler}/slurm.template +2 -1
  36. wrfrun/run.py +19 -23
  37. wrfrun/scheduler/__init__.py +35 -0
  38. wrfrun/scheduler/env.py +44 -0
  39. wrfrun/scheduler/lsf.py +47 -0
  40. wrfrun/scheduler/pbs.py +48 -0
  41. wrfrun/scheduler/script.py +70 -0
  42. wrfrun/scheduler/slurm.py +48 -0
  43. wrfrun/scheduler/utils.py +14 -0
  44. wrfrun/utils.py +8 -3
  45. wrfrun/workspace/__init__.py +38 -0
  46. wrfrun/workspace/core.py +92 -0
  47. wrfrun/workspace/wrf.py +121 -0
  48. {wrfrun-0.1.7.dist-info → wrfrun-0.1.9.dist-info}/METADATA +4 -3
  49. wrfrun-0.1.9.dist-info/RECORD +62 -0
  50. wrfrun-0.1.9.dist-info/entry_points.txt +3 -0
  51. wrfrun/model/wrf/_ndown.py +0 -39
  52. wrfrun/pbs.py +0 -86
  53. wrfrun/res/run.sh.template +0 -16
  54. wrfrun/workspace.py +0 -88
  55. wrfrun-0.1.7.dist-info/RECORD +0 -46
  56. {wrfrun-0.1.7.dist-info → wrfrun-0.1.9.dist-info}/WHEEL +0 -0
wrfrun/core/error.py CHANGED
@@ -1,80 +1,110 @@
1
+ """
2
+ wrfrun.core.error
3
+ #################
4
+
5
+ This module defines all exceptions used in ``wrfrun``.
6
+
7
+ .. autosummary::
8
+ :toctree: generated/
9
+
10
+ WRFRunBasicError
11
+ ConfigError
12
+ WRFRunContextError
13
+ CommandError
14
+ OutputFileError
15
+ ResourceURIError
16
+ InputFileError
17
+ NamelistError
18
+ NamelistIDError
19
+ ExecRegisterError
20
+ GetExecClassError
21
+ ModelNameError
22
+ """
23
+
24
+
1
25
  class WRFRunBasicError(Exception):
2
26
  """
3
- Basic error type.
27
+ Basic exception class of ``wrfrun``. New exception **MUST** inherit this class.
4
28
  """
5
29
  pass
6
30
 
7
31
 
8
32
  class ConfigError(WRFRunBasicError):
9
33
  """
10
- Config cannot be used error.
34
+ Exception indicates the config of ``wrfrun`` or NWP model can't be used.
11
35
  """
12
36
  pass
13
37
 
14
38
 
15
39
  class WRFRunContextError(WRFRunBasicError):
16
40
  """
17
- Need to enter wrfrun context.
41
+ Exception indicates ``wrfrun`` is running out of the ``wrfrun`` context.
18
42
  """
19
43
  pass
20
44
 
21
45
 
22
46
  class CommandError(WRFRunBasicError):
23
47
  """
24
- The command is wrong.
48
+ Exception indicates the command of ``Executable`` can't be executed successfully.
25
49
  """
26
50
  pass
27
51
 
28
52
 
29
- class LoadConfigError(WRFRunBasicError):
53
+ class OutputFileError(WRFRunBasicError):
30
54
  """
31
- Failed to load config.
55
+ Exception indicates ``wrfrun`` can't find any output files with the given rules.
32
56
  """
33
57
  pass
34
58
 
35
59
 
36
- class OutputFileError(WRFRunBasicError):
60
+ class ResourceURIError(WRFRunBasicError):
37
61
  """
38
- No output found.
62
+ Exception indicates ``wrfrun`` can't parse the URI.
39
63
  """
40
64
  pass
41
65
 
42
66
 
43
- class ResourceURIError(WRFRunBasicError):
67
+ class InputFileError(WRFRunBasicError):
44
68
  """
45
- Error about resource namespace.
69
+ Exception indicates ``wrfrun`` can't find specified input files.
46
70
  """
47
71
  pass
48
72
 
49
73
 
50
- class InputFileError(WRFRunBasicError):
74
+ class NamelistError(WRFRunBasicError):
51
75
  """
52
- Input file error.
76
+ Exception indicates ``wrfrun`` can't find the namelist user want to use.
53
77
  """
54
78
  pass
55
79
 
56
80
 
57
- class NamelistError(WRFRunBasicError):
81
+ class NamelistIDError(WRFRunBasicError):
58
82
  """
59
- Error about namelist.
83
+ Exception indicates ``wrfrun`` can't register the specified namelist id.
60
84
  """
61
85
  pass
62
86
 
63
87
 
64
88
  class ExecRegisterError(WRFRunBasicError):
89
+ """
90
+ Exception indicates ``wrfrun`` can't register the specified ``Executable``.
91
+ """
65
92
  pass
66
93
 
67
94
 
68
95
  class GetExecClassError(WRFRunBasicError):
96
+ """
97
+ Exception indicates ``wrfrun`` can't find the specified ``Executable``.
98
+ """
69
99
  pass
70
100
 
71
101
 
72
102
  class ModelNameError(WRFRunBasicError):
73
103
  """
74
- Name of the model isn't found in the config file.
104
+ Exception indicates ``wrfrun`` can't find config of the specified NWP model in the config file.
75
105
  """
76
106
  pass
77
107
 
78
108
 
79
- __all__ = ["WRFRunBasicError", "ConfigError", "WRFRunContextError", "CommandError", "LoadConfigError", "OutputFileError", "ResourceURIError", "InputFileError",
80
- "NamelistError", "ExecRegisterError", "GetExecClassError", "ModelNameError"]
109
+ __all__ = ["WRFRunBasicError", "ConfigError", "WRFRunContextError", "CommandError", "OutputFileError", "ResourceURIError", "InputFileError",
110
+ "NamelistError", "ExecRegisterError", "GetExecClassError", "ModelNameError", "NamelistIDError"]
wrfrun/core/replay.py CHANGED
@@ -1,6 +1,29 @@
1
+ """
2
+ wrfrun.core.replay
3
+ ##################
4
+
5
+ This module provides methods to read config from ``.replay`` file and reproduce the simulation.
6
+
7
+ .. autosummary::
8
+ :toctree: generated/
9
+
10
+ WRFRunExecutableRegisterCenter
11
+ replay_config_generator
12
+
13
+ WRFRUNExecDB
14
+ ************
15
+
16
+ In order to load ``Executable`` correctly based on the stored ``name`` in ``.replay`` files,
17
+ ``wrfrun`` uses ``WRFRUNExecDB``, which is the instance of :class:`WRFRunExecutableRegisterCenter`,
18
+ to records all ``Executable`` classes and corresponding ``name``.
19
+ When ``wrfrun`` replays the simulation, it gets the right ``Executable`` from ``WRFRUNExecDB`` and executes it.
20
+ """
21
+
22
+ from collections.abc import Generator
1
23
  from json import loads
2
24
  from os.path import exists
3
25
  from shutil import unpack_archive
26
+ from typing import Any
4
27
 
5
28
  from .base import ExecutableBase, ExecutableConfig
6
29
  from .config import WRFRUNConfig
@@ -12,7 +35,8 @@ WRFRUN_REPLAY_URI = ":WRFRUN_REPLAY:"
12
35
 
13
36
  class WRFRunExecutableRegisterCenter:
14
37
  """
15
- Record all executable class.
38
+ This class provides the method to records ``Executable``'s class with a unique ``name``.
39
+ Later you can get the class with the ``name``.
16
40
  """
17
41
  _instance = None
18
42
  _initialized = False
@@ -31,67 +55,76 @@ class WRFRunExecutableRegisterCenter:
31
55
 
32
56
  return cls._instance
33
57
 
34
- def register_exec(self, unique_iq: str, cls: type):
58
+ def register_exec(self, name: str, cls: type):
35
59
  """
36
- Register an executable class.
60
+ Register an ``Executable``'s class with a unique ``name``.
61
+
62
+ If the ``name`` has been used, :class:`ExecRegisterError` will be raised.
37
63
 
38
- :param unique_iq: Unique ID.
39
- :type unique_iq: str
40
- :param cls: Class.
64
+ :param name: ``Executable``'s unique name.
65
+ :type name: str
66
+ :param cls: ``Executable``'s class.
41
67
  :type cls: type
42
- :return:
43
- :rtype:
44
68
  """
45
- if unique_iq in self._exec_db:
46
- logger.error(f"'{unique_iq}' has been registered.")
47
- raise ExecRegisterError(f"'{unique_iq}' has been registered.")
69
+ if name in self._exec_db:
70
+ logger.error(f"'{name}' has been registered.")
71
+ raise ExecRegisterError(f"'{name}' has been registered.")
48
72
 
49
- self._exec_db[unique_iq] = cls
73
+ self._exec_db[name] = cls
50
74
 
51
- def is_registered(self, unique_id: str) -> bool:
75
+ def is_registered(self, name: str) -> bool:
52
76
  """
53
- Check if an executable class has been registered.
77
+ Check if an ``Executable``'s class has been registered.
54
78
 
55
- :param unique_id: Unique ID.
56
- :type unique_id: str
79
+ :param name: ``Executable``'s unique name.
80
+ :type name: str
57
81
  :return: True or False.
58
82
  :rtype: bool
59
83
  """
60
- if unique_id in self._exec_db:
84
+ if name in self._exec_db:
61
85
  return True
62
86
  else:
63
87
  return False
64
88
 
65
- def get_cls(self, unique_id: str) -> type:
89
+ def get_cls(self, name: str) -> type:
66
90
  """
67
- Get a registered executable class.
91
+ Get an ``Executable``'s class with the ``name``.
92
+
93
+ If the ``name`` can't be found, :class:`GetExecClassError` will be raised.
68
94
 
69
- :param unique_id: Unique ID.
70
- :type unique_id: str
71
- :return: Executable class.
95
+ :param name: ``Executable``'s unique name.
96
+ :type name: str
97
+ :return: ``Executable``'s class.
72
98
  :rtype: type
73
99
  """
74
- if unique_id not in self._exec_db:
75
- logger.error(f"Executable class '{unique_id}' not found.")
76
- raise GetExecClassError(f"Executable class '{unique_id}' not found.")
100
+ if name not in self._exec_db:
101
+ logger.error(f"Executable class '{name}' not found.")
102
+ raise GetExecClassError(f"Executable class '{name}' not found.")
77
103
 
78
- return self._exec_db[unique_id]
104
+ return self._exec_db[name]
79
105
 
80
106
 
81
107
  WRFRUNExecDB = WRFRunExecutableRegisterCenter()
82
108
 
83
109
 
84
- def replay_config_generator(replay_config_file: str):
110
+ def replay_config_generator(replay_config_file: str) -> Generator[tuple[str, ExecutableBase], Any, None]:
85
111
  """
86
- Replay the simulations.
112
+ This method can read the ``.replay`` file and returns a generator which yields ``Executable`` and their names.
113
+ If this method doesn't find ``config.json`` in the ``.replay`` file, ``FileNotFoundError`` will be raised.
114
+
115
+ The ``Executable`` you get from the generator has been initialized so you can execute it directly.
116
+
117
+ >>> replay_file = "./example.replay"
118
+ >>> for name, _exec in replay_config_generator(replay_file):
119
+ >>> _exec.replay()
87
120
 
88
- :param replay_config_file: Replay file path.
121
+ :param replay_config_file: Path of the ``.replay`` file.
89
122
  :type replay_config_file: str
90
- :return:
91
- :rtype:
123
+ :return: A generator that yields: ``(name, Executable)``
124
+ :rtype: Generator
92
125
  """
93
126
  logger.info(f"Loading replay resources from: {replay_config_file}")
94
- work_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_REPLAY_WORK_PATH)
127
+ work_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_WORKSPACE_REPLAY)
95
128
 
96
129
  unpack_archive(replay_config_file, work_path, "zip")
97
130
 
wrfrun/core/server.py CHANGED
@@ -1,9 +1,25 @@
1
+ """
2
+ wrfrun.core.server
3
+ ##################
4
+
5
+ Currently, ``wrfrun`` provides the method to reads log file of WRF and calculates simulation progress.
6
+ In order to report the progress to user, ``wrfrun`` provides :class:`WRFRunServer` to set up a socket server.
7
+
8
+ .. autosummary::
9
+ :toctree: generated/
10
+
11
+ WRFRunServer
12
+ WRFRunServerHandler
13
+ stop_server
14
+ """
15
+
1
16
  import socket
2
17
  import socketserver
3
- import subprocess
18
+ from collections.abc import Callable
4
19
  from datetime import datetime
20
+ from json import dumps
5
21
  from time import time
6
- from typing import Tuple, Optional
22
+ from typing import Tuple
7
23
 
8
24
  from .config import WRFRUNConfig
9
25
  from ..utils import logger
@@ -12,48 +28,53 @@ WRFRUN_SERVER_INSTANCE = None
12
28
  WRFRUN_SERVER_THREAD = None
13
29
 
14
30
 
15
- def get_wrf_simulated_seconds(start_datetime: datetime, log_file_path: Optional[str] = None) -> int:
16
- """
17
- Get how many seconds wrf has integrated.
18
-
19
- :param start_datetime: WRF start datetime.
20
- :type start_datetime: datetime
21
- :param log_file_path: Absolute path of the log file to be parsed.
22
- :type log_file_path: str
23
- :return: Seconds.
24
- :rtype: int
31
+ class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
25
32
  """
26
- # use linux cmd to get the latest line of wrf log files
27
- if log_file_path is None:
28
- log_file_path = WRFRUNConfig.parse_resource_uri(f"{WRFRUNConfig.WRF_WORK_PATH}/rsl.out.0000")
29
- res = subprocess.run(["tail", "-n", "1", log_file_path], capture_output=True)
30
- log_text = res.stdout.decode()
33
+ A socket server to report time usage.
31
34
 
32
- if not (log_text.startswith("d01") or log_text.startswith("d02")):
33
- return -1
35
+ If you want to use the socket server, you need to give four arguments: ``start_date``,
36
+ ``wrf_simulate_seconds``, ``socket_address_port`` and ``WRFRunServerHandler``.
34
37
 
35
- time_string = log_text.split()[1]
38
+ >>> start_date = datetime(2021, 3, 25, 8)
39
+ >>> simulate_seconds = 60 * 60 * 72
40
+ >>> socket_address_port = ("0.0.0.0", 54321)
41
+ >>> _wrfrun_server = WRFRunServer(start_date, simulate_seconds, socket_address_port, WRFRunServerHandler)
36
42
 
37
- seconds = -1
38
- try:
39
- current_datetime = datetime.strptime(time_string, "%Y-%m-%d_%H:%M:%S")
40
- # remove timezone info so we can calculate.
41
- date_delta = current_datetime - start_datetime.replace(tzinfo=None)
42
- seconds = date_delta.days * 24 * 60 * 60 + date_delta.seconds
43
+ Usually you don't need to create socket server manually, because :class:`WRFRun` will handle this.
43
44
 
44
- except ValueError:
45
- seconds = -1
45
+ .. py:attribute:: start_timestamp
46
+ :type: datetime
46
47
 
47
- finally:
48
- return seconds
48
+ The time the socket server start.
49
49
 
50
+ .. py:attribute:: start_date
51
+ :type: datetime
50
52
 
51
- class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
52
- """
53
- A socket server to report time usage.
53
+ The simulation's start date.
54
+
55
+ .. py:attribute:: wrf_simulate_seconds
56
+ :type: int
57
+
58
+ The total seconds the simulation will integrate.
59
+
60
+ .. py:attribute:: wrf_log_path
61
+ :type: str
62
+
63
+ Path of the log file the server will read.
54
64
  """
55
65
 
56
- def __init__(self, start_date: datetime, wrf_simulate_seconds: int, *args, **kwargs) -> None:
66
+ def __init__(self, start_date: datetime, total_simulate_seconds: int, *args, **kwargs) -> None:
67
+ """
68
+
69
+ :param start_date: The simulation's start date.
70
+ :type start_date: datetime
71
+ :param total_simulate_seconds: The total seconds the simulation will integrate.
72
+ :type total_simulate_seconds: int
73
+ :param args: Other positional arguments passed to parent class.
74
+ :type args:
75
+ :param kwargs: Other keyword arguments passed to parent class.
76
+ :type kwargs:
77
+ """
57
78
  super().__init__(*args, **kwargs)
58
79
 
59
80
  # record the time the server starts
@@ -63,16 +84,11 @@ class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
63
84
  self.start_date = start_date
64
85
 
65
86
  # record how many seconds the wrf will integral
66
- self.wrf_simulate_seconds = wrf_simulate_seconds
67
-
68
- # we need to parse the log file to track the simulation progress.
69
- self.wrf_log_path = WRFRUNConfig.parse_resource_uri(f"{WRFRUNConfig.WRF_WORK_PATH}/rsl.out.0000")
70
- logger.debug("WRFRun Server will try to track simulation progress with following log files:")
71
- logger.debug(f"WRF: {self.wrf_log_path}")
87
+ self.total_simulate_seconds = total_simulate_seconds
72
88
 
73
89
  def server_bind(self):
74
90
  """
75
- Bind address and port.
91
+ Bind and listen on the address and port.
76
92
  """
77
93
  # reuse address and port to prevent the error `Address already in use`
78
94
  self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -88,32 +104,84 @@ class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
88
104
  """
89
105
  return self.start_timestamp
90
106
 
91
- def get_wrf_simulate_settings(self) -> Tuple[datetime, int]:
107
+ def get_model_simulate_settings(self) -> Tuple[datetime, int]:
92
108
  """
93
109
  Get the start date of the case the NWP simulates and the total seconds of the simulation.
94
110
 
95
111
  :return: (start date, simulation seconds)
96
112
  :rtype: tuple
97
113
  """
98
- return self.start_date, self.wrf_simulate_seconds
114
+ return self.start_date, self.total_simulate_seconds
99
115
 
100
116
 
101
117
  class WRFRunServerHandler(socketserver.StreamRequestHandler):
102
118
  """
103
- Socket server handler.
119
+ :class:`WRFRunServer` handler.
120
+
121
+ This handler can report time usage and simulation progress of the running model, simulation settings, and stop the server:
104
122
 
123
+ 1. If this handler receives ``"stop"``, it stops the server.
124
+ 2. If this handler receives ``"debug"``, it returns the simulation settings in JSON.
125
+ 3. It returns time usage and simulation progress in JSON when receiving any other messages.
126
+
127
+ **On receiving "stop"**
128
+
129
+ This handler will stop the server, and return a plain message ``Server stop``.
130
+
131
+ **On receiving "debug"**
132
+
133
+ This handler will return simulation settings in a JSON string like:
134
+
135
+ .. code-block:: json
136
+
137
+ {
138
+ "start_date": "2021-03-25 00:00",
139
+ "total_simulate_seconds": 360000,
140
+ }
141
+
142
+ **On receiving any other messages**
143
+
144
+ This handler will return time usage and simulation progress in a JSON string like:
145
+
146
+ .. code-block:: json
147
+
148
+ {
149
+ "usage": 3600,
150
+ "status": "geogrid",
151
+ "progress": 35,
152
+ }
153
+
154
+ where ``usage`` represents the seconds ``wrfrun`` has spent running the NWP model,
155
+ ``status`` represents work status,
156
+ ``progress`` represents simulation progress of the status in percentage.
105
157
  """
106
- def __init__(self, request, client_address, server: WRFRunServer) -> None:
158
+ def __init__(self, request, client_address, server: WRFRunServer, log_parse_func: Callable[[datetime], int] | None) -> None:
159
+ """
160
+ :class:`WRFRunServer` handler.
161
+
162
+ :param request:
163
+ :type request:
164
+ :param client_address:
165
+ :type client_address:
166
+ :param server: :class:`WRFRunServer` instance.
167
+ :type server: WRFRunServer
168
+ :param log_parse_func: Function used to get simulated seconds from model's log file.
169
+ If the function can't parse the simulated seconds, it should return ``-1``.
170
+ :type log_parse_func: Callable[[datetime], int]
171
+ """
107
172
  super().__init__(request, client_address, server)
108
173
 
109
174
  # get server
110
175
  self.server: WRFRunServer = server
176
+ self.log_parse_func = log_parse_func
111
177
 
112
- def calculate_time_usage(self) -> str:
113
- """Calculate time usage from server start (usually the time to run wrfrun)
178
+ def calculate_time_usage(self) -> int:
179
+ """
180
+ Calculate the duration from the server's start time to the present,
181
+ which represents the time ``wrfrun`` has spent running the NWP model.
114
182
 
115
- Returns:
116
- str: Time usage in `"%H:%M:%S"`
183
+ :return: Seconds.
184
+ :rtype: int
117
185
  """
118
186
  # get current timestamp
119
187
  current_timestamp = datetime.fromtimestamp(time())
@@ -122,42 +190,34 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
122
190
  seconds_diff = current_timestamp - self.server.get_start_time()
123
191
  seconds_diff = seconds_diff.seconds
124
192
 
125
- # calculate hours, minutes and seconds
126
- seconds = seconds_diff % 60
127
- minutes = (seconds_diff % 3600) // 60
128
- hours = seconds_diff // 3600
129
-
130
- time_usage = ":".join([
131
- str(hours).rjust(2, '0'),
132
- str(minutes).rjust(2, '0'),
133
- str(seconds).rjust(2, '0')
134
- ])
193
+ return seconds_diff
135
194
 
136
- return time_usage
137
-
138
- def calculate_progress(self) -> str:
195
+ def calculate_progress(self) -> tuple[str, int]:
139
196
  """
140
- Calculate the simulation progress.
197
+ Read the log file and calculate the simulation progress.
141
198
 
142
- :return:
143
- :rtype:
199
+ :return: ``(status, progress)``. ``status`` represents work status,
200
+ ``progress`` represents simulation progress of the status in percentage.
201
+ :rtype: tuple[str, int]
144
202
  """
145
- start_date, simulate_seconds = self.server.get_wrf_simulate_settings()
203
+ start_date, simulate_seconds = self.server.get_model_simulate_settings()
146
204
 
147
- simulated_seconds = get_wrf_simulated_seconds(start_date, self.server.wrf_log_path)
205
+ if self.log_parse_func is None:
206
+ simulated_seconds = -1
207
+ else:
208
+ simulated_seconds = self.log_parse_func(start_date)
148
209
 
149
210
  if simulated_seconds > 0:
150
211
  progress = simulated_seconds * 100 // simulate_seconds
151
-
152
212
  else:
153
- progress = 0
213
+ progress = -1
154
214
 
155
215
  status = WRFRUNConfig.WRFRUN_WORK_STATUS
156
216
 
157
217
  if status == "":
158
218
  status = "*"
159
219
 
160
- return f"{status}: {progress}%"
220
+ return status, progress
161
221
 
162
222
  def handle(self) -> None:
163
223
  """
@@ -172,25 +232,25 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
172
232
  self.wfile.write(f"Server stop\n".encode())
173
233
 
174
234
  elif msg == "debug":
175
- start_date, simulate_seconds = self.server.get_wrf_simulate_settings()
235
+ start_date, simulate_seconds = self.server.get_model_simulate_settings()
176
236
  start_date = start_date.strftime("%Y-%m-%d %H:%M")
177
237
 
178
- self.wfile.write(f"{start_date}\n{simulate_seconds}\n".encode())
238
+ self.wfile.write(dumps({"start_date": start_date, "total_seconds": simulate_seconds}).encode())
179
239
 
180
240
  else:
181
- progress = self.calculate_progress()
241
+ status, progress = self.calculate_progress()
182
242
  time_usage = self.calculate_time_usage()
183
243
 
184
244
  # send the message
185
- self.wfile.write(f"{progress}\n{time_usage}".encode())
245
+ self.wfile.write(dumps({"usage": time_usage, "status": status, "progress": progress}).encode())
186
246
 
187
247
 
188
248
  def stop_server(socket_ip: str, socket_port: int):
189
- """Try to stop server.
249
+ """
250
+ Stop the socket server.
190
251
 
191
- Args:
192
- socket_ip (str): Server IP.
193
- socket_port (int): Server Port.
252
+ :param socket_ip: Server address.
253
+ :param socket_port: Server port.
194
254
  """
195
255
  try:
196
256
  sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -209,4 +269,4 @@ def stop_server(socket_ip: str, socket_port: int):
209
269
  logger.warning("Fail to stop WRFRunServer, maybe it doesn't start at all.")
210
270
 
211
271
 
212
- __all__ = ["WRFRunServer", "WRFRunServerHandler", "get_wrf_simulated_seconds", "stop_server"]
272
+ __all__ = ["WRFRunServer", "WRFRunServerHandler", "stop_server"]
wrfrun/data.py CHANGED
@@ -6,12 +6,11 @@ from typing import Union, List, Tuple
6
6
  from pandas import date_range
7
7
  # from seafog import goos_sst_find_data
8
8
 
9
- import cdsapi
10
-
11
9
  from .core.config import WRFRUNConfig
12
10
  from .utils import logger
13
11
 
14
- CDS_CLIENT = cdsapi.Client()
12
+ # lazy initialize
13
+ CDS_CLIENT = None
15
14
 
16
15
 
17
16
  class ERA5CONFIG:
@@ -214,6 +213,8 @@ def find_era5_data(date: Union[List[str], List[datetime]], area: Tuple[int, int,
214
213
  Returns: data path
215
214
 
216
215
  """
216
+ global CDS_CLIENT
217
+
217
218
  # check variables and datasets
218
219
  if not _check_variables_and_datasets(variables, dataset):
219
220
  logger.error(
@@ -281,8 +282,12 @@ def find_era5_data(date: Union[List[str], List[datetime]], area: Tuple[int, int,
281
282
  exit(1)
282
283
 
283
284
  # download data
284
- logger.info(
285
- f"Downloading data to {save_path}, it may take several tens of minutes, please wait...")
285
+ logger.info(f"Downloading data to {save_path}, it may take several tens of minutes, please wait...")
286
+
287
+ if CDS_CLIENT is None:
288
+ import cdsapi
289
+ CDS_CLIENT = cdsapi.Client()
290
+
286
291
  CDS_CLIENT.retrieve(dataset, params_dict, save_path)
287
292
 
288
293
  return save_path
@@ -1 +1,29 @@
1
+ """
2
+ wrfrun.extension
3
+ ################
4
+
5
+ Through extensions, developers can add more functionality to ``wrfrun``.
6
+ ``wrfrun`` now has the following extensions:
7
+
8
+ ========================================= ==========================================
9
+ :doc:`goos_sst </api/extension.goos_sst>` Process NEAR-GOOS SST data.
10
+ :doc:`littler </api/extension.littler>` Process LITTLE_R observation data.
11
+ ========================================= ==========================================
12
+
13
+ Other Submodules
14
+ ****************
15
+
16
+ =================================== ==========================================
17
+ :doc:`utils </api/extension.utils>` Utility methods can be used by extensions.
18
+ =================================== ==========================================
19
+
20
+ .. toctree::
21
+ :maxdepth: 1
22
+ :hidden:
23
+
24
+ goos_sst <extension.goos_sst>
25
+ littler <extension.littler>
26
+ utils <extension.utils>
27
+ """
28
+
1
29
  from .utils import *