python-dmon 0.2.3__tar.gz → 0.3.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: python-dmon
3
- Version: 0.2.3
3
+ Version: 0.3.0
4
4
  Summary: A lightweight, cross-platform daemon manager that runs any command as a background process.
5
5
  Keywords: python-dmon,dmon,daemon,background,detach,process management
6
6
  Author: Atomie CHEN
@@ -21,6 +21,7 @@ Description-Content-Type: text/markdown
21
21
 
22
22
  [![GitHub](https://img.shields.io/badge/github-python--dmon-blue?logo=github)](https://github.com/atomiechen/python-dmon)
23
23
  [![PyPI](https://img.shields.io/pypi/v/python--dmon?logo=pypi&logoColor=white)](https://pypi.org/project/python-dmon/)
24
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/atomiechen/python-dmon)
24
25
 
25
26
 
26
27
  A lightweight, cross-platform daemon manager that runs any command — called a *task* — as a background process.
@@ -38,6 +39,8 @@ It is a Python-based and more powerful successor to the [handy-backend shell scr
38
39
  - 🧩 **Flexible tasks:** Tasks can be configured in `pyproject.toml` or `dmon.yaml`; or run ad-hoc commands directly.
39
40
  - 🪵 **Logging & log rotation:** Automatically manage log files to prevent uncontrolled growth.
40
41
 
42
+ ![dmon-demo-gif](https://github.com/user-attachments/assets/9bae2f46-5ef4-4784-aced-18d573204efc)
43
+
41
44
 
42
45
  ## Installation
43
46
 
@@ -84,14 +87,16 @@ Create a `dmon.yaml` file:
84
87
 
85
88
  ```yaml
86
89
  tasks:
87
- app: ["python", "-u", "server.py"]
90
+ app: ["python", "-u", "server.py"] # option 1: exec form
91
+ # app: "python -u server.py" # option 2: shell string
88
92
  ```
89
93
 
90
94
  Or add to your `pyproject.toml`:
91
95
 
92
96
  ```toml
93
97
  [tool.dmon.tasks]
94
- app = ["python", "-u", "server.py"]
98
+ app = ["python", "-u", "server.py"] # option 1: exec form
99
+ # app = "python -u server.py" # option 2: shell string
95
100
  ```
96
101
 
97
102
  Commands can be a single string (run in shell), or list of strings (exec form).
@@ -109,6 +114,9 @@ dmon start app
109
114
  # Stop a running task
110
115
  dmon stop app
111
116
 
117
+ # Restart a task
118
+ dmon restart app
119
+
112
120
  # Check task status
113
121
  dmon status app
114
122
 
@@ -116,11 +124,23 @@ dmon status app
116
124
  dmon exec app
117
125
  ```
118
126
 
127
+ You can specify multiple tasks at once, e.g.: `dmon start app1 app2 app3`, except for `dmon exec` which only accepts one task.
128
+
129
+ Or use `--all` to operate on all tasks:
130
+
131
+ ```sh
132
+ # all configured tasks
133
+ dmon start/stop/restart --all
134
+ # all running tasks
135
+ dmon stop/status --all
136
+ ```
137
+
119
138
  If you have defined `default_task`, or only one task is defined in the config file, you can omit the task name:
120
139
 
121
140
  ```sh
122
141
  dmon start
123
142
  dmon stop
143
+ dmon restart
124
144
  dmon status
125
145
  dmon exec
126
146
  ```
@@ -3,6 +3,7 @@
3
3
 
4
4
  [![GitHub](https://img.shields.io/badge/github-python--dmon-blue?logo=github)](https://github.com/atomiechen/python-dmon)
5
5
  [![PyPI](https://img.shields.io/pypi/v/python--dmon?logo=pypi&logoColor=white)](https://pypi.org/project/python-dmon/)
6
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/atomiechen/python-dmon)
6
7
 
7
8
 
8
9
  A lightweight, cross-platform daemon manager that runs any command — called a *task* — as a background process.
@@ -20,6 +21,8 @@ It is a Python-based and more powerful successor to the [handy-backend shell scr
20
21
  - 🧩 **Flexible tasks:** Tasks can be configured in `pyproject.toml` or `dmon.yaml`; or run ad-hoc commands directly.
21
22
  - 🪵 **Logging & log rotation:** Automatically manage log files to prevent uncontrolled growth.
22
23
 
24
+ ![dmon-demo-gif](https://github.com/user-attachments/assets/9bae2f46-5ef4-4784-aced-18d573204efc)
25
+
23
26
 
24
27
  ## Installation
25
28
 
@@ -66,14 +69,16 @@ Create a `dmon.yaml` file:
66
69
 
67
70
  ```yaml
68
71
  tasks:
69
- app: ["python", "-u", "server.py"]
72
+ app: ["python", "-u", "server.py"] # option 1: exec form
73
+ # app: "python -u server.py" # option 2: shell string
70
74
  ```
71
75
 
72
76
  Or add to your `pyproject.toml`:
73
77
 
74
78
  ```toml
75
79
  [tool.dmon.tasks]
76
- app = ["python", "-u", "server.py"]
80
+ app = ["python", "-u", "server.py"] # option 1: exec form
81
+ # app = "python -u server.py" # option 2: shell string
77
82
  ```
78
83
 
79
84
  Commands can be a single string (run in shell), or list of strings (exec form).
@@ -91,6 +96,9 @@ dmon start app
91
96
  # Stop a running task
92
97
  dmon stop app
93
98
 
99
+ # Restart a task
100
+ dmon restart app
101
+
94
102
  # Check task status
95
103
  dmon status app
96
104
 
@@ -98,11 +106,23 @@ dmon status app
98
106
  dmon exec app
99
107
  ```
100
108
 
109
+ You can specify multiple tasks at once, e.g.: `dmon start app1 app2 app3`, except for `dmon exec` which only accepts one task.
110
+
111
+ Or use `--all` to operate on all tasks:
112
+
113
+ ```sh
114
+ # all configured tasks
115
+ dmon start/stop/restart --all
116
+ # all running tasks
117
+ dmon stop/status --all
118
+ ```
119
+
101
120
  If you have defined `default_task`, or only one task is defined in the config file, you can omit the task name:
102
121
 
103
122
  ```sh
104
123
  dmon start
105
124
  dmon stop
125
+ dmon restart
106
126
  dmon status
107
127
  dmon exec
108
128
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-dmon"
3
- version = "0.2.3"
3
+ version = "0.3.0"
4
4
  description = "A lightweight, cross-platform daemon manager that runs any command as a background process."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,11 +1,20 @@
1
1
  import argparse
2
+ from pathlib import Path
2
3
  import shlex
3
4
  import sys
4
5
 
5
6
  from colorama import just_fix_windows_console
6
7
 
7
8
  from .config import check_name_in_config, get_task_config
8
- from .control import execute, list_processes, restart, start, stop, status
9
+ from .control import (
10
+ execute,
11
+ get_meta_paths,
12
+ list_processes,
13
+ restart,
14
+ start,
15
+ stop,
16
+ status,
17
+ )
9
18
  from .constants import (
10
19
  DEFAULT_META_DIR,
11
20
  DEFAULT_RUN_NAME,
@@ -50,7 +59,7 @@ def main():
50
59
  sp_start.add_argument(
51
60
  "task",
52
61
  help="Configured task name (default: the only task if there's just one)",
53
- nargs="?",
62
+ nargs="*",
54
63
  )
55
64
  sp_start.add_argument(
56
65
  "--meta-file",
@@ -60,20 +69,26 @@ def main():
60
69
  "--log-file",
61
70
  help=f"Path to log file (default: task configured or {LOG_PATH_TEMPLATE})",
62
71
  )
72
+ sp_start.add_argument("--all", action="store_true", help="Start all processes")
63
73
 
64
74
  # stop subcommand
65
75
  sp_stop = subparsers.add_parser(
66
76
  "stop",
67
- help="Stop a background process",
68
- description="Stop a background process given name or meta file",
77
+ help="Stop background process(es)",
78
+ description="Stop background process(es) given name or meta file",
69
79
  # formatter_class=argparse.ArgumentDefaultsHelpFormatter,
70
80
  )
71
81
  sp_stop.add_argument(
72
82
  "task",
73
83
  help="Configured task name (default: the only task if there's just one)",
74
- nargs="?",
84
+ nargs="*",
75
85
  )
76
86
  sp_stop.add_argument("--meta-file", help="Path to meta file")
87
+ sp_stop.add_argument(
88
+ "--all",
89
+ action="store_true",
90
+ help=f"Stop all processes in meta dir ({DEFAULT_META_DIR})",
91
+ )
77
92
 
78
93
  # restart subcommand
79
94
  sp_restart = subparsers.add_parser(
@@ -85,7 +100,7 @@ def main():
85
100
  sp_restart.add_argument(
86
101
  "task",
87
102
  help="Configured task name (default: the only task if there's just one)",
88
- nargs="?",
103
+ nargs="*",
89
104
  )
90
105
  sp_restart.add_argument(
91
106
  "--meta-file",
@@ -95,23 +110,30 @@ def main():
95
110
  "--log-file",
96
111
  help=f"Path to log file (default: task configured or {LOG_PATH_TEMPLATE})",
97
112
  )
113
+ sp_restart.add_argument("--all", action="store_true", help="Restart all processes")
98
114
 
99
115
  # status subcommand
100
116
  sp_status = subparsers.add_parser(
101
117
  "status",
102
- help="Check process status",
103
- description="Check status of a background process given name or meta file",
118
+ help="Check status of background process(es)",
119
+ description="Check status of background process(es) given name or meta file",
104
120
  # formatter_class=argparse.ArgumentDefaultsHelpFormatter,
105
121
  )
106
122
  sp_status.add_argument(
107
123
  "task",
108
124
  help="Configured task name (default: the only task if there's just one)",
109
- nargs="?",
125
+ nargs="*",
110
126
  )
111
127
  sp_status.add_argument(
112
128
  "--meta-file",
113
129
  help=f"Path to meta file (default: {META_PATH_TEMPLATE})",
114
130
  )
131
+ sp_status.add_argument(
132
+ "-a",
133
+ "--all",
134
+ action="store_true",
135
+ help=f"Check status of all processes in meta dir ({DEFAULT_META_DIR})",
136
+ )
115
137
 
116
138
  # list subcommand
117
139
  sp_list = subparsers.add_parser(
@@ -199,46 +221,71 @@ def main():
199
221
  if args.command in ["start", "restart"]:
200
222
  sp = sp_start if args.command == "start" else sp_restart
201
223
  try:
202
- task, task_cfg = get_task_config(args.task, args.config)
224
+ tasks, task_cfgs = get_task_config(args.task, args.config, args.all)
203
225
  except Exception as e:
204
226
  sp.error(str(e))
205
227
 
206
- task_cfg.meta_path = (
207
- args.meta_file or task_cfg.meta_path or META_PATH_TEMPLATE.format(task=task)
208
- )
209
- task_cfg.log_path = (
210
- args.log_file or task_cfg.log_path or LOG_PATH_TEMPLATE.format(task=task)
211
- )
212
- task_cfg.rotate_log_path = (
213
- task_cfg.rotate_log_path or ROTATE_LOG_PATH_TEMPLATE.format(task=task)
214
- )
228
+ # check if meta_file or log_file path is provided;
229
+ # if so, only one task should be specified
230
+ if args.meta_file or args.log_file:
231
+ if len(tasks) == 1:
232
+ task_cfgs[0].meta_path = args.meta_file or task_cfgs[0].meta_path
233
+ task_cfgs[0].log_path = args.log_file or task_cfgs[0].log_path
234
+ else:
235
+ sp.error(
236
+ f"'--meta-file' and '--log-file' can only be specified when {args.command}ing a single task"
237
+ )
238
+ # fill in default values if not provided
239
+ for task, task_cfg in zip(tasks, task_cfgs):
240
+ task_cfg.meta_path = task_cfg.meta_path or META_PATH_TEMPLATE.format(
241
+ task=task
242
+ )
243
+ task_cfg.log_path = task_cfg.log_path or LOG_PATH_TEMPLATE.format(task=task)
244
+ task_cfg.rotate_log_path = (
245
+ task_cfg.rotate_log_path or ROTATE_LOG_PATH_TEMPLATE.format(task=task)
246
+ )
215
247
  if args.command == "start":
216
- sys.exit(start(task_cfg))
248
+ sys.exit(start(task_cfgs))
217
249
  else:
218
- sys.exit(restart(task_cfg))
250
+ sys.exit(restart(task_cfgs))
219
251
  elif args.command == "exec":
220
252
  try:
221
- task, task_cfg = get_task_config(args.task, args.config)
253
+ _, task_cfgs = get_task_config(args.task, args.config)
222
254
  except Exception as e:
223
255
  sp_exec.error(str(e))
224
- sys.exit(execute(task_cfg))
256
+ sys.exit(execute(task_cfgs[0]))
225
257
  elif args.command in ["stop", "status"]:
226
258
  sp = sp_stop if args.command == "stop" else sp_status
259
+ meta_paths = []
260
+
261
+ # Collect meta paths from --all
262
+ if args.all:
263
+ meta_paths.extend(get_meta_paths(DEFAULT_META_DIR))
264
+
265
+ # Collect meta paths from --meta-file
227
266
  if args.meta_file:
228
- meta_path = args.meta_file
229
- else:
230
- if args.task:
231
- task = args.task
232
- else:
233
- try:
234
- task, _ = get_task_config(args.task, args.config)
235
- except Exception as e:
236
- sp.error(str(e))
237
- meta_path = META_PATH_TEMPLATE.format(task=task)
267
+ meta_paths.append(args.meta_file)
268
+
269
+ # Collect meta paths from task names
270
+ if len(args.task) > 0:
271
+ tasks = args.task
272
+ meta_paths.extend([META_PATH_TEMPLATE.format(task=task) for task in tasks])
273
+
274
+ # If no meta paths collected, use default task
275
+ if len(meta_paths) == 0:
276
+ try:
277
+ tasks, _ = get_task_config(args.task, args.config)
278
+ except Exception as e:
279
+ sp.error(str(e))
280
+ meta_paths.extend([META_PATH_TEMPLATE.format(task=task) for task in tasks])
281
+
282
+ # Remove duplicates
283
+ unique_meta_paths = sorted(set(Path(p).resolve() for p in meta_paths))
284
+
238
285
  if args.command == "stop":
239
- sys.exit(stop(meta_path))
286
+ sys.exit(stop(unique_meta_paths))
240
287
  else:
241
- sys.exit(status(meta_path))
288
+ sys.exit(status(unique_meta_paths))
242
289
  elif args.command == "list":
243
290
  dir = args.dir or DEFAULT_META_DIR
244
291
  sys.exit(list_processes(dir, args.full))
@@ -260,7 +307,7 @@ def main():
260
307
  rotate_log_path=args.rotate_log_path
261
308
  or ROTATE_LOG_PATH_TEMPLATE.format(task=args.name),
262
309
  )
263
- sys.exit(start(task_cfg))
310
+ sys.exit(start([task_cfg]))
264
311
  else:
265
312
  parser.print_help()
266
313
  sys.exit(1)
@@ -1,6 +1,6 @@
1
1
  import sys
2
2
  from pathlib import Path
3
- from typing import Dict, Optional, cast
3
+ from typing import Dict, List, Optional, Sequence, Tuple, Union, cast
4
4
 
5
5
  if sys.version_info >= (3, 11):
6
6
  import tomllib
@@ -121,9 +121,12 @@ def validate_task(task, name: str) -> DmonTaskConfig:
121
121
  ret.log_rotate = task["log_rotate"]
122
122
 
123
123
  if "log_max_size" in task:
124
- if not isinstance(task["log_max_size"], int) or task["log_max_size"] <= 0:
124
+ if (
125
+ not isinstance(task["log_max_size"], (int, float))
126
+ or task["log_max_size"] <= 0
127
+ ):
125
128
  raise TypeError(
126
- f"Task '{name}' 'log_max_size' field must be a positive integer"
129
+ f"Task '{name}' 'log_max_size' field must be a positive number"
127
130
  )
128
131
  ret.log_max_size = task["log_max_size"]
129
132
 
@@ -136,11 +139,11 @@ def validate_task(task, name: str) -> DmonTaskConfig:
136
139
 
137
140
  if "rotate_log_max_size" in task:
138
141
  if (
139
- not isinstance(task["rotate_log_max_size"], int)
142
+ not isinstance(task["rotate_log_max_size"], (int, float))
140
143
  or task["rotate_log_max_size"] <= 0
141
144
  ):
142
145
  raise TypeError(
143
- f"Task '{name}' 'rotate_log_max_size' field must be a positive integer"
146
+ f"Task '{name}' 'rotate_log_max_size' field must be a positive number"
144
147
  )
145
148
  ret.rotate_log_max_size = task["rotate_log_max_size"]
146
149
 
@@ -155,11 +158,14 @@ def validate_task(task, name: str) -> DmonTaskConfig:
155
158
  return ret
156
159
 
157
160
 
158
- def get_task_config(name: Optional[str], cfg_path: Optional[str]):
161
+ def get_task_config(
162
+ names: Union[Sequence[str], str, None], cfg_path: Optional[str], all: bool = False
163
+ ) -> Tuple[Sequence[str], List[DmonTaskConfig]]:
159
164
  """
160
- Get the validated task configuration for the given task name.
161
- If name is None or empty, and there is only one task, return that task; otherwise, raise ValueError.
162
- If the task is not found, or required fields are missing, raise TypeError or ValueError.
165
+ Get the validated task configurations for the given task names.
166
+ If 'all' is True, return all tasks.
167
+ If no name specified, and there is only one task, return that task; otherwise, raise ValueError.
168
+ If any task is not found, or required fields are missing, raise TypeError or ValueError.
163
169
 
164
170
  The config is loaded from the given path, or searched for dmon.yaml or pyproject.toml.
165
171
  """
@@ -169,23 +175,35 @@ def get_task_config(name: Optional[str], cfg_path: Optional[str]):
169
175
  if not isinstance(tasks, dict):
170
176
  raise TypeError("'tasks' must be a table")
171
177
 
172
- name = name or cfg.get("default_task", None)
173
-
174
- if not name:
175
- if len(tasks) == 0:
176
- raise ValueError(f"No task found in {path}")
177
- elif len(tasks) == 1:
178
- name = next(iter(tasks))
178
+ if all:
179
+ names = list(tasks.keys())
180
+ elif isinstance(names, str):
181
+ names = [names]
182
+ elif names is None or len(names) == 0:
183
+ default_task_name = cfg.get("default_task", None)
184
+ if default_task_name:
185
+ if not isinstance(default_task_name, str):
186
+ raise TypeError("'default_task' must be a string")
187
+ names = [default_task_name]
179
188
  else:
180
- raise ValueError(f"Multiple tasks found in {path}; please specify one.")
181
- else:
189
+ if len(tasks) == 0:
190
+ raise ValueError(f"No task found in {path}")
191
+ elif len(tasks) == 1:
192
+ name = next(iter(tasks))
193
+ assert isinstance(name, str)
194
+ names = [name]
195
+ else:
196
+ raise ValueError(f"Multiple tasks found in {path}; please specify one.")
197
+
198
+ ret_tasks = []
199
+ for name in names:
182
200
  name = name.lower()
183
201
  if name not in tasks:
184
202
  raise ValueError(f"Task '{name}' not found in {path}")
185
203
 
186
- assert isinstance(name, str)
187
- task = validate_task(tasks[name], name)
188
- return name, task
204
+ task = validate_task(tasks[name], name)
205
+ ret_tasks.append(task)
206
+ return names, ret_tasks
189
207
 
190
208
 
191
209
  def check_name_in_config(name: str) -> bool:
@@ -6,7 +6,7 @@ import signal
6
6
  import sys
7
7
  import subprocess
8
8
  import time
9
- from typing import List
9
+ from typing import List, Sequence
10
10
 
11
11
  import psutil
12
12
  from termcolor import colored
@@ -33,7 +33,17 @@ def ensure_log_dir(log_path: Path):
33
33
  log_path.parent.mkdir(parents=True, exist_ok=True)
34
34
 
35
35
 
36
- def start(cfg: DmonTaskConfig):
36
+ def start(cfgs: Sequence[DmonTaskConfig]):
37
+ ret = 0
38
+ for idx, cfg in enumerate(cfgs):
39
+ # non-zero if any start() fails
40
+ ret |= start_single(cfg)
41
+ if idx < len(cfgs) - 1:
42
+ print("---", file=sys.stderr) # print a blank line between tasks
43
+ return ret
44
+
45
+
46
+ def start_single(cfg: DmonTaskConfig):
37
47
  meta_path = Path(cfg.meta_path).resolve()
38
48
  log_path = Path(cfg.log_path).resolve()
39
49
  cwd = Path(cfg.cwd).resolve()
@@ -166,7 +176,17 @@ def start(cfg: DmonTaskConfig):
166
176
  return 0
167
177
 
168
178
 
169
- def stop(
179
+ def stop(meta_paths: Sequence[PathType], timeout=5.0):
180
+ ret = 0
181
+ for idx, meta_path in enumerate(meta_paths):
182
+ # non-zero if any stop() fails
183
+ ret |= stop_single(meta_path=meta_path, timeout=timeout)
184
+ if idx < len(meta_paths) - 1:
185
+ print(file=sys.stderr) # print a blank line between tasks
186
+ return ret
187
+
188
+
189
+ def stop_single(
170
190
  meta_path: PathType,
171
191
  timeout=5.0,
172
192
  ):
@@ -345,33 +365,41 @@ def terminate_win(proc: psutil.Process, timeout):
345
365
 
346
366
 
347
367
  def restart(
348
- cfg: DmonTaskConfig,
368
+ cfgs: Sequence[DmonTaskConfig],
349
369
  timeout=5.0,
350
370
  ):
351
- stop(cfg.meta_path, timeout)
371
+ meta_paths = [cfg.meta_path for cfg in cfgs]
372
+ stop(meta_paths, timeout=timeout)
352
373
  print("--- Restarting ---", file=sys.stderr)
353
- return start(cfg)
374
+ return start(cfgs)
354
375
 
355
376
 
356
- def status(meta_path: PathType):
357
- meta_path = Path(meta_path).resolve()
358
- meta = DmonMeta.load(meta_path)
359
- if meta is None:
360
- print(
361
- colored(
362
- "Status failed: meta file not found (maybe not started)\n",
363
- color="red",
364
- attrs=["bold"],
377
+ def status(meta_paths: Sequence[PathType]):
378
+ ret = 0
379
+ metas = []
380
+ for idx, meta_path in enumerate(meta_paths):
381
+ meta_path = Path(meta_path).resolve()
382
+ meta = DmonMeta.load(meta_path)
383
+ if meta is None:
384
+ print(
385
+ colored(
386
+ "Status failed: meta file not found (maybe not started)\n",
387
+ color="red",
388
+ attrs=["bold"],
389
+ )
390
+ + str(meta_path),
391
+ file=sys.stderr,
365
392
  )
366
- + str(meta_path),
367
- file=sys.stderr,
368
- )
369
- return 1
370
-
371
- print_status(meta)
372
- print("\nProcess Tree:", file=sys.stderr)
373
- print_process_table([meta])
374
- return 0
393
+ ret |= 1
394
+ else:
395
+ print_status(meta)
396
+ metas.append(meta)
397
+ if idx < len(meta_paths) - 1:
398
+ print("---", file=sys.stderr)
399
+ if metas:
400
+ print("\nProcess Tree:", file=sys.stderr)
401
+ print_process_table(metas)
402
+ return ret
375
403
 
376
404
 
377
405
  def check_same_process(proc: psutil.Process, create_time: float) -> bool:
@@ -523,14 +551,23 @@ def print_process_table(metas: List[DmonMeta], full_width: bool = False):
523
551
  return processes
524
552
 
525
553
 
526
- def list_processes(dir: PathType, full_width: bool):
554
+ def get_meta_paths(dir: PathType) -> List[Path]:
527
555
  target_dmon_dir = Path(dir).resolve()
528
- metas: List[DmonMeta] = []
556
+ meta_paths: List[Path] = []
529
557
  if target_dmon_dir.exists() and target_dmon_dir.is_dir():
530
558
  for meta_file in target_dmon_dir.glob(f"*{META_SUFFIX}"):
531
- meta = DmonMeta.load(meta_file)
532
- if meta is not None:
533
- metas.append(meta)
559
+ meta_paths.append(meta_file)
560
+ return meta_paths
561
+
562
+
563
+ def list_processes(dir: PathType, full_width: bool):
564
+ target_dmon_dir = Path(dir).resolve()
565
+ meta_paths = get_meta_paths(target_dmon_dir)
566
+ metas = []
567
+ for meta_path in meta_paths:
568
+ meta = DmonMeta.load(meta_path)
569
+ if meta is not None:
570
+ metas.append(meta)
534
571
  # sort by name (case-insensitive)
535
572
  metas.sort(key=lambda m: m.task.lower())
536
573
  n_task = len(metas)
@@ -167,7 +167,7 @@ if __name__ == "__main__":
167
167
  parser.add_argument(
168
168
  "--max-log-size",
169
169
  help="Max log file size (MB); 0 for no rotation",
170
- type=int,
170
+ type=float,
171
171
  default=5,
172
172
  )
173
173
  parser.add_argument(
@@ -178,15 +178,15 @@ if __name__ == "__main__":
178
178
  parser.add_argument(
179
179
  "--max-rotate-log-size",
180
180
  help="Max file size (MB) for this rotation log; 0 for no rotation",
181
- type=int,
181
+ type=float,
182
182
  default=5,
183
183
  )
184
184
  args = parser.parse_args()
185
185
  main(
186
186
  " ".join(args.command) if args.shell else args.command,
187
187
  args.log_path,
188
- args.max_log_size * 1024 * 1024,
188
+ int(args.max_log_size * 1024 * 1024),
189
189
  args.rotate_log_path,
190
- args.max_rotate_log_size * 1024 * 1024,
190
+ int(args.max_rotate_log_size * 1024 * 1024),
191
191
  )
192
192
  logger.info("Process finished.")
@@ -31,11 +31,11 @@ class DmonTaskConfig:
31
31
  """Path to log file"""
32
32
  log_rotate: bool = False
33
33
  """Whether to rotate log file"""
34
- log_max_size: int = 5
34
+ log_max_size: float = 5
35
35
  """Size in MB to rotate log file"""
36
36
  rotate_log_path: str = ""
37
37
  """Path to rotation log file"""
38
- rotate_log_max_size: int = 5
38
+ rotate_log_max_size: float = 5
39
39
  """Size in MB to rotation log file"""
40
40
  meta_path: str = ""
41
41
  """Path to meta file"""