python-dmon 0.2.0__py3-none-any.whl → 0.2.2__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.
dmon/cli.py CHANGED
@@ -5,7 +5,7 @@ import sys
5
5
  from colorama import just_fix_windows_console
6
6
 
7
7
  from .config import check_name_in_config, get_task_config
8
- from .control import list_processes, restart, start, stop, status
8
+ from .control import execute, list_processes, restart, start, stop, status
9
9
  from .constants import (
10
10
  DEFAULT_META_DIR,
11
11
  DEFAULT_RUN_NAME,
@@ -16,14 +16,28 @@ from .constants import (
16
16
  from .types import DmonTaskConfig
17
17
 
18
18
 
19
+ def get_version():
20
+ # if python 3.8 or later, use importlib.metadata
21
+ import importlib.metadata
22
+
23
+ return importlib.metadata.version("python-dmon")
24
+
25
+
19
26
  def main():
20
27
  just_fix_windows_console()
21
28
 
22
29
  parser = argparse.ArgumentParser(
23
30
  prog="dmon",
24
- description="Minimal cross-platform daemon manager",
31
+ description=f"dmon v{get_version()} - Lightweight cross-platform daemon manager",
25
32
  # formatter_class=argparse.ArgumentDefaultsHelpFormatter,
26
33
  )
34
+ parser.add_argument(
35
+ "-v",
36
+ "--version",
37
+ action="version",
38
+ version=get_version(),
39
+ )
40
+
27
41
  subparsers = parser.add_subparsers(dest="command")
28
42
 
29
43
  # start subcommand
@@ -34,7 +48,7 @@ def main():
34
48
  # formatter_class=argparse.ArgumentDefaultsHelpFormatter,
35
49
  )
36
50
  sp_start.add_argument(
37
- "name",
51
+ "task",
38
52
  help="Configured task name (default: the only task if there's just one)",
39
53
  nargs="?",
40
54
  )
@@ -55,7 +69,7 @@ def main():
55
69
  # formatter_class=argparse.ArgumentDefaultsHelpFormatter,
56
70
  )
57
71
  sp_stop.add_argument(
58
- "name",
72
+ "task",
59
73
  help="Configured task name (default: the only task if there's just one)",
60
74
  nargs="?",
61
75
  )
@@ -69,7 +83,7 @@ def main():
69
83
  # formatter_class=argparse.ArgumentDefaultsHelpFormatter,
70
84
  )
71
85
  sp_restart.add_argument(
72
- "name",
86
+ "task",
73
87
  help="Configured task name (default: the only task if there's just one)",
74
88
  nargs="?",
75
89
  )
@@ -90,7 +104,7 @@ def main():
90
104
  # formatter_class=argparse.ArgumentDefaultsHelpFormatter,
91
105
  )
92
106
  sp_status.add_argument(
93
- "name",
107
+ "task",
94
108
  help="Configured task name (default: the only task if there's just one)",
95
109
  nargs="?",
96
110
  )
@@ -162,41 +176,65 @@ def main():
162
176
  help="Command (with args) to run",
163
177
  )
164
178
 
179
+ sp_exec = subparsers.add_parser(
180
+ "exec",
181
+ help="Execute a configured task in the foreground",
182
+ description="Execute a configured task in the foreground",
183
+ )
184
+ sp_exec.add_argument(
185
+ "task",
186
+ help="Configured task name (default: the only task if there's just one)",
187
+ nargs="?",
188
+ )
189
+
190
+ # add custom config file option
191
+ for sp in [sp_start, sp_stop, sp_restart, sp_status, sp_exec]:
192
+ sp.add_argument(
193
+ "--config",
194
+ help="Path to config file (YAML or TOML) (default: search from current directory upwards)",
195
+ )
196
+
165
197
  args = parser.parse_args()
166
198
 
167
199
  if args.command in ["start", "restart"]:
168
200
  sp = sp_start if args.command == "start" else sp_restart
169
201
  try:
170
- name, task_cfg = get_task_config(args.name)
202
+ task, task_cfg = get_task_config(args.task, args.config)
171
203
  except Exception as e:
172
204
  sp.error(str(e))
173
205
 
174
206
  task_cfg.meta_path = (
175
- args.meta_file or task_cfg.meta_path or META_PATH_TEMPLATE.format(name=name)
207
+ args.meta_file or task_cfg.meta_path or META_PATH_TEMPLATE.format(task=task)
176
208
  )
177
209
  task_cfg.log_path = (
178
- args.log_file or task_cfg.log_path or LOG_PATH_TEMPLATE.format(name=name)
210
+ args.log_file or task_cfg.log_path or LOG_PATH_TEMPLATE.format(task=task)
179
211
  )
180
212
  task_cfg.rotate_log_path = (
181
- task_cfg.rotate_log_path or ROTATE_LOG_PATH_TEMPLATE.format(name=name)
213
+ task_cfg.rotate_log_path or ROTATE_LOG_PATH_TEMPLATE.format(task=task)
182
214
  )
183
215
  if args.command == "start":
184
216
  sys.exit(start(task_cfg))
185
217
  else:
186
218
  sys.exit(restart(task_cfg))
219
+ elif args.command == "exec":
220
+ try:
221
+ task, task_cfg = get_task_config(args.task, args.config)
222
+ except Exception as e:
223
+ sp_exec.error(str(e))
224
+ sys.exit(execute(task_cfg))
187
225
  elif args.command in ["stop", "status"]:
188
226
  sp = sp_stop if args.command == "stop" else sp_status
189
227
  if args.meta_file:
190
228
  meta_path = args.meta_file
191
229
  else:
192
- if args.name:
193
- name = args.name
230
+ if args.task:
231
+ task = args.task
194
232
  else:
195
233
  try:
196
- name, _ = get_task_config(args.name)
234
+ task, _ = get_task_config(args.task, args.config)
197
235
  except Exception as e:
198
236
  sp.error(str(e))
199
- meta_path = META_PATH_TEMPLATE.format(name=name)
237
+ meta_path = META_PATH_TEMPLATE.format(task=task)
200
238
  if args.command == "stop":
201
239
  sys.exit(stop(meta_path))
202
240
  else:
@@ -209,18 +247,18 @@ def main():
209
247
  sp_run.error("Please provide a non-empty name for the task.")
210
248
  elif check_name_in_config(args.name):
211
249
  sp_run.error(
212
- f"Name '{args.name}' already exists in config. Please choose another name."
250
+ f"Task '{args.name}' already exists in config. Please choose another name."
213
251
  )
214
252
 
215
253
  task_cfg = DmonTaskConfig(
216
- name=args.name,
254
+ task=args.name,
217
255
  cmd=shlex.join(args.command_list) if args.shell else args.command_list,
218
256
  cwd=args.cwd,
219
- meta_path=args.meta_file or META_PATH_TEMPLATE.format(name=args.name),
220
- log_path=args.log_file or LOG_PATH_TEMPLATE.format(name=args.name),
257
+ meta_path=args.meta_file or META_PATH_TEMPLATE.format(task=args.name),
258
+ log_path=args.log_file or LOG_PATH_TEMPLATE.format(task=args.name),
221
259
  log_rotate=args.log_rotate,
222
260
  rotate_log_path=args.rotate_log_path
223
- or ROTATE_LOG_PATH_TEMPLATE.format(name=args.name),
261
+ or ROTATE_LOG_PATH_TEMPLATE.format(task=args.name),
224
262
  )
225
263
  sys.exit(start(task_cfg))
226
264
  else:
dmon/config.py CHANGED
@@ -10,56 +10,45 @@ else:
10
10
  from .types import CmdType, DmonTaskConfig
11
11
 
12
12
 
13
- def find_pyproject_toml(start: Optional[Path] = None):
13
+ def load_config(cfg_path: Optional[str] = None):
14
14
  """
15
- Search for pyproject.toml from the current directory (or specified directory) upwards.
16
- Return a Path object if found.
15
+ Load configuration from the given path, or search it from the current working directory upwards.
17
16
  """
18
- start = start or Path.cwd()
19
- current = start.resolve()
20
17
 
21
- for parent in [current, *current.parents]:
22
- candidate = parent / "pyproject.toml"
23
- if candidate.is_file():
24
- return candidate
25
-
26
- # raise FileNotFoundError(f"No pyproject.toml found from {start} upwards.")
27
-
28
-
29
- def find_dmon_yaml(start: Optional[Path] = None):
30
- """
31
- Search for dmon.yaml / dmon.yml from the current directory (or specified directory) upwards.
32
- Return a Path object if found.
33
- """
34
- start = start or Path.cwd()
35
- current = start.resolve()
36
-
37
- for parent in [current, *current.parents]:
38
- for filename in ["dmon.yaml", "dmon.yml"]:
39
- candidate = parent / filename
40
- if candidate.is_file():
41
- return candidate
42
-
43
- # raise FileNotFoundError(f"No dmon.yaml or dmon.yml found from {start} upwards.")
44
-
45
-
46
- def load_config():
47
- path = find_dmon_yaml()
48
- if path:
49
- import yaml
50
-
51
- with path.open("r", encoding="utf-8") as f:
52
- cfg = yaml.safe_load(f)
18
+ if cfg_path:
19
+ # Load configuration from the given path
20
+ path = Path(cfg_path).resolve()
21
+ if not path.exists():
22
+ raise FileNotFoundError(f"Config file '{path}' does not exist.")
53
23
  else:
54
- path = find_pyproject_toml()
24
+ # No path provided, search for config files
25
+ # starting from the current working directory upwards
26
+ current = Path.cwd().resolve()
27
+ path = None
28
+ for parent in [current, *current.parents]:
29
+ for filename in ["dmon.yaml", "dmon.yml", "pyproject.toml"]:
30
+ path = parent / filename
31
+ if path.is_file():
32
+ break
33
+ else:
34
+ continue # Only break out of the inner loop if a file was found
35
+ break # Break out of the outer loop if a file was found
55
36
  if not path:
56
37
  raise FileNotFoundError(
57
38
  "No dmon.yaml or pyproject.toml found in current or any parent directory."
58
39
  )
59
40
 
41
+ if path.suffix in [".yaml", ".yml"]:
42
+ import yaml
43
+
44
+ with path.open("r", encoding="utf-8") as f:
45
+ cfg = yaml.safe_load(f)
46
+ elif path.suffix == ".toml":
60
47
  with path.open("rb") as f:
61
48
  cfg = tomllib.load(f)
62
49
  cfg = cfg.get("tool", {}).get("dmon", {})
50
+ else:
51
+ raise ValueError("Config file must be YAML (.yaml/.yml) or TOML (.toml)")
63
52
  return cfg, path
64
53
 
65
54
 
@@ -78,7 +67,7 @@ def validate_cmd_type(cmd, name: str) -> CmdType:
78
67
 
79
68
 
80
69
  def validate_task(task, name: str) -> DmonTaskConfig:
81
- ret = DmonTaskConfig(name=name)
70
+ ret = DmonTaskConfig(task=name)
82
71
  if isinstance(task, str) or isinstance(task, list):
83
72
  ret.cmd = validate_cmd_type(task, name)
84
73
  elif isinstance(task, dict):
@@ -151,14 +140,15 @@ def validate_task(task, name: str) -> DmonTaskConfig:
151
140
  return ret
152
141
 
153
142
 
154
- def get_task_config(name: Optional[str] = None):
143
+ def get_task_config(name: Optional[str], cfg_path: Optional[str]):
155
144
  """
156
- Get the validated task configuration for the given task name from [tool.dmon.tasks] or dmon.yaml.
157
-
145
+ Get the validated task configuration for the given task name.
158
146
  If name is None or empty, and there is only one task, return that task; otherwise, raise ValueError.
159
147
  If the task is not found, or required fields are missing, raise TypeError or ValueError.
148
+
149
+ The config is loaded from the given path, or searched for dmon.yaml or pyproject.toml.
160
150
  """
161
- cfg, path = load_config()
151
+ cfg, path = load_config(cfg_path)
162
152
  tasks = cfg.get("tasks", {})
163
153
 
164
154
  if not isinstance(tasks, dict):
dmon/constants.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from pathlib import Path
2
+ import sys
2
3
 
3
4
 
4
5
  DEFAULT_META_DIR = Path(".dmon")
@@ -6,9 +7,11 @@ DEFAULT_LOG_DIR = Path("logs")
6
7
 
7
8
  META_SUFFIX = ".meta.json"
8
9
 
9
- META_PATH_TEMPLATE = str(DEFAULT_META_DIR / ("{name}" + META_SUFFIX))
10
- LOG_PATH_TEMPLATE = str(DEFAULT_LOG_DIR / "{name}.log")
11
- ROTATE_LOG_PATH_TEMPLATE = str(DEFAULT_LOG_DIR / "{name}.rotate.log")
10
+ META_PATH_TEMPLATE = str(DEFAULT_META_DIR / ("{task}" + META_SUFFIX))
11
+ LOG_PATH_TEMPLATE = str(DEFAULT_LOG_DIR / "{task}.log")
12
+ ROTATE_LOG_PATH_TEMPLATE = str(DEFAULT_LOG_DIR / "{task}.rotate.log")
12
13
 
13
14
 
14
15
  DEFAULT_RUN_NAME = "default_run"
16
+
17
+ ON_WINDOWS = sys.platform.startswith("win")
dmon/control.py CHANGED
@@ -2,6 +2,7 @@ import os
2
2
  from pathlib import Path
3
3
  import shlex
4
4
  import shutil
5
+ import signal
5
6
  import sys
6
7
  import subprocess
7
8
  import time
@@ -10,7 +11,7 @@ from typing import List
10
11
  import psutil
11
12
  from termcolor import colored
12
13
 
13
- from .constants import DEFAULT_META_DIR, META_SUFFIX
14
+ from .constants import DEFAULT_META_DIR, META_SUFFIX, ON_WINDOWS
14
15
  from .types import DmonTaskConfig, DmonMeta, PathType
15
16
  from .utils import len_ansi, pad_ansi
16
17
 
@@ -66,7 +67,7 @@ def start(cfg: DmonTaskConfig):
66
67
 
67
68
  # Platform-specific parameters to run the process in background detached from parent
68
69
  kwargs = {}
69
- if sys.platform.startswith("win"):
70
+ if ON_WINDOWS:
70
71
  # DETACHED_PROCESS = 0x00000008
71
72
  CREATE_NO_WINDOW = 0x08000000
72
73
  kwargs["creationflags"] = CREATE_NO_WINDOW
@@ -78,7 +79,7 @@ def start(cfg: DmonTaskConfig):
78
79
  kwargs["start_new_session"] = True
79
80
 
80
81
  meta = DmonMeta(
81
- name=cfg.name,
82
+ task=cfg.task,
82
83
  meta_path=str(meta_path),
83
84
  log_path=str(log_path),
84
85
  log_rotate=cfg.log_rotate,
@@ -213,7 +214,7 @@ def stop(
213
214
  meta_path.unlink(missing_ok=True)
214
215
  return 1
215
216
 
216
- if sys.platform.startswith("win"):
217
+ if ON_WINDOWS:
217
218
  ret = terminate_win(proc, timeout)
218
219
  else:
219
220
  ret = terminate_posix(proc, timeout)
@@ -416,7 +417,7 @@ def print_status(meta: DmonMeta):
416
417
 
417
418
  # key-value pairs with aligned keys
418
419
  rows = [
419
- ("TASK", colored(meta.name, "cyan", attrs=["bold"])),
420
+ ("TASK", colored(meta.task, "cyan", attrs=["bold"])),
420
421
  ("PID", colored(str(meta.pid), "cyan", attrs=["bold"])),
421
422
  ("STATUS", status),
422
423
  ("CMD", meta.cmd),
@@ -477,7 +478,7 @@ def print_process_table(metas: List[DmonMeta], full_width: bool = False):
477
478
  ppid = "N/A"
478
479
  rows.append(
479
480
  (
480
- colored(meta.name, "cyan", attrs=["bold"]),
481
+ colored(meta.task, "cyan", attrs=["bold"]),
481
482
  colored(meta.pid, "cyan", attrs=["bold"]),
482
483
  ppid,
483
484
  status,
@@ -524,14 +525,14 @@ def print_process_table(metas: List[DmonMeta], full_width: bool = False):
524
525
 
525
526
  def list_processes(dir: PathType, full_width: bool):
526
527
  target_dmon_dir = Path(dir).resolve()
527
- metas = []
528
+ metas: List[DmonMeta] = []
528
529
  if target_dmon_dir.exists() and target_dmon_dir.is_dir():
529
530
  for meta_file in target_dmon_dir.glob(f"*{META_SUFFIX}"):
530
531
  meta = DmonMeta.load(meta_file)
531
532
  if meta is not None:
532
533
  metas.append(meta)
533
534
  # sort by name (case-insensitive)
534
- metas.sort(key=lambda m: m.name.lower())
535
+ metas.sort(key=lambda m: m.task.lower())
535
536
  n_task = len(metas)
536
537
  processes = print_process_table(metas, full_width)
537
538
  n_proc = len(processes)
@@ -540,3 +541,44 @@ def list_processes(dir: PathType, full_width: bool):
540
541
  file=sys.stderr,
541
542
  )
542
543
  return 0
544
+
545
+
546
+ def execute(cfg: DmonTaskConfig):
547
+ """
548
+ Execute the command in the foreground.
549
+ This is used for the 'dmon exec' command.
550
+ """
551
+ cwd = Path(cfg.cwd).resolve()
552
+
553
+ env = None # default behavior of Popen
554
+ if cfg.override_env:
555
+ env = cfg.env
556
+ elif cfg.env:
557
+ env = {**os.environ, **cfg.env}
558
+
559
+ shell = isinstance(cfg.cmd, str)
560
+ if ON_WINDOWS and isinstance(cfg.cmd, list) and len(cfg.cmd) > 0:
561
+ # On Windows, use full path for the executable when shell=False
562
+ cfg.cmd[0] = shutil.which(cfg.cmd[0]) or cfg.cmd[0]
563
+
564
+ def signal_handler(signum: int, frame) -> None:
565
+ if ON_WINDOWS and signum == signal.SIGINT:
566
+ signum = signal.SIGTERM
567
+ proc.send_signal(signum)
568
+
569
+ prev_handle_int = signal.signal(signal.SIGINT, signal_handler)
570
+ prev_handle_term = signal.signal(signal.SIGTERM, signal_handler)
571
+
572
+ proc = subprocess.Popen(
573
+ cfg.cmd,
574
+ cwd=cwd,
575
+ env=env,
576
+ shell=shell,
577
+ bufsize=0, # unbuffered
578
+ close_fds=False,
579
+ )
580
+ ret = proc.wait()
581
+
582
+ signal.signal(signal.SIGTERM, prev_handle_term)
583
+ signal.signal(signal.SIGINT, prev_handle_int)
584
+ return ret
dmon/runner.py CHANGED
@@ -116,21 +116,13 @@ def main(
116
116
  )
117
117
 
118
118
  shell = isinstance(cmd, str)
119
- # Start the child process with stdout/stderr redirected to the log
120
- # env will be inherited from parent process
121
- # cwd will be inherited from parent process
122
- proc = subprocess.Popen(
123
- cmd,
124
- stdout=subprocess.PIPE,
125
- stderr=subprocess.STDOUT,
126
- shell=shell,
127
- text=False, # binary mode
128
- bufsize=0, # unbuffered
129
- )
130
119
 
131
120
  # register signal handler to terminate the child process
132
- def signal_handler(signum, frame):
121
+ def signal_handler(signum: int, frame):
133
122
  logger.info(f"Received signal {signum}, forwarding to child process...")
123
+ if sys.platform.startswith("win") and signum == signal.SIGINT:
124
+ signum = signal.SIGTERM
125
+ logger.info(f"On Windows, convert SIGINT to SIGTERM ({signum})")
134
126
  # proc.terminate()
135
127
  proc.send_signal(signum)
136
128
  logger.info("Waiting for child process to exit...")
@@ -142,6 +134,18 @@ def main(
142
134
  signal.signal(signal.SIGINT, signal_handler)
143
135
  signal.signal(signal.SIGTERM, signal_handler)
144
136
 
137
+ # Start the child process with stdout/stderr redirected to the log
138
+ # env will be inherited from parent process
139
+ # cwd will be inherited from parent process
140
+ proc = subprocess.Popen(
141
+ cmd,
142
+ stdout=subprocess.PIPE,
143
+ stderr=subprocess.STDOUT,
144
+ shell=shell,
145
+ text=False, # binary mode
146
+ bufsize=0, # unbuffered
147
+ )
148
+
145
149
  logger.info(f"Started process {proc.pid} with command: {cmd} (shell={shell})")
146
150
 
147
151
  make_file_dir(log_path)
dmon/types.py CHANGED
@@ -17,7 +17,7 @@ CmdType = Union[str, List[str]]
17
17
 
18
18
  @dataclass
19
19
  class DmonTaskConfig:
20
- name: str = ""
20
+ task: str = ""
21
21
  """Name of the task"""
22
22
  cmd: CmdType = ""
23
23
  """Command to run, either a string (for shell) or a list of strings (for exec)"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: python-dmon
3
- Version: 0.2.0
3
+ Version: 0.2.2
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
@@ -12,7 +12,7 @@ Requires-Dist: termcolor>=2.4.0
12
12
  Requires-Dist: tomli>=2.2.1 ; python_full_version < '3.11'
13
13
  Requires-Python: >=3.8
14
14
  Project-URL: Bug Tracker, https://github.com/atomiechen/python-dmon/issues
15
- Project-URL: Changelog, https://github.com/atomiechen/python-dmon/blob/master/CHANGELOG.md
15
+ Project-URL: Changelog, https://github.com/atomiechen/python-dmon/blob/main/CHANGELOG.md
16
16
  Project-URL: Homepage, https://github.com/atomiechen/python-dmon
17
17
  Description-Content-Type: text/markdown
18
18
 
@@ -86,6 +86,9 @@ dmon stop app
86
86
 
87
87
  # Check task status
88
88
  dmon status app
89
+
90
+ # Execute a task in the foreground (useful for debugging)
91
+ dmon exec app
89
92
  ```
90
93
 
91
94
  If only one task is defined in the config file, you can omit the task name:
@@ -94,6 +97,26 @@ If only one task is defined in the config file, you can omit the task name:
94
97
  dmon start
95
98
  dmon stop
96
99
  dmon status
100
+ dmon exec
101
+ ```
102
+
103
+ You can use `--config` to specify a custom config file:
104
+
105
+ ```sh
106
+ dmon start --config /path/to/dmon.yaml app # YAML
107
+ dmon start --config /path/to/pyproject.toml app # or TOML
108
+ ```
109
+
110
+ And yes, you can use `dmon` to run in a nested manner:
111
+
112
+ ```yaml
113
+ tasks:
114
+ app: ["python", "-u", "server.py"]
115
+ nested: pwd && dmon exec app # nest `dmon exec`
116
+ subdir_task1:
117
+ cwd: /path/to/dir
118
+ cmd: ["dmon", "exec", "app"] # run task defined in another folder
119
+ subdir_task2: dmon exec app --config /path/to/dir/dmon.yaml # like above
97
120
  ```
98
121
 
99
122
 
@@ -136,17 +159,17 @@ tasks:
136
159
  env: # (default: inherit from parent process)
137
160
  PYTHONUNBUFFERED: "1"
138
161
  override_env: false # override parent env and only use env defined here
139
- log_path: "logs/<name>.log" # path to log file
162
+ log_path: "logs/<task>.log" # path to log file
140
163
  log_rotate: false # enable log rotation
141
164
  log_max_size: 5 # max log file size before rotation in MB
142
- rotate_log_path: "logs/<name>.rotate.log" # path to rotation log
165
+ rotate_log_path: "logs/<task>.rotate.log" # path to rotation log
143
166
  rotate_log_max_size: 5 # max rotation log file size in MB
144
- meta_path: ".dmon/<name>.meta.json" # path to meta file
167
+ meta_path: ".dmon/<task>.meta.json" # path to meta file
145
168
  ```
146
169
 
147
170
  ## Under the Hood
148
171
 
149
- Each task is associated with a meta file (e.g. `.dmon/<name>.meta.json`) stored in the current working directory.
172
+ Each task is associated with a meta file (e.g. `.dmon/<task>.meta.json`) stored in the current working directory.
150
173
  The file contains details such as the command, PID, log path, and more.
151
174
  **Do not** modify or delete these files manually.
152
175
 
@@ -0,0 +1,14 @@
1
+ dmon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ dmon/__main__.py,sha256=wu5N2wk8mvBgyvr2ghmQf4prezAe0_i-p123VVreyYc,62
3
+ dmon/cli.py,sha256=Dqu43_hcsx8iCJXAL1YXqBpp1jFt0Y7qBEr9mYzkAn8,8764
4
+ dmon/config.py,sha256=Q8TvcYDwUTlL_8bcjxVsA-lLE_AlcSmxYpbnMaCkFrw,6650
5
+ dmon/constants.py,sha256=uJPI66IJvT6vhamqEDeqHjZQS12RxCPe3DD5yCU0UR8,407
6
+ dmon/control.py,sha256=KpGNPPsuAsDNX3FTJxnGNnt9f4Q18omvG42zO1CwTy4,17494
7
+ dmon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ dmon/runner.py,sha256=iyotNIrbO6I_u02EcqwO7R7N3BEBP6b_8eL8yb3zeJQ,6082
9
+ dmon/types.py,sha256=tTVoJzWk-Z8XiZCUt0VE8Z7-URpXMoS54HfVYXhW-0Q,1795
10
+ dmon/utils.py,sha256=zVQC9YNhMQu4hrAp9k2ds2egnbWZZv7S5w8i2oVIPJg,808
11
+ python_dmon-0.2.2.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
12
+ python_dmon-0.2.2.dist-info/entry_points.txt,sha256=rad5ugz4K_IyNXS_IrFPu0tS-UXlBCSJRhJv-OWd_yk,45
13
+ python_dmon-0.2.2.dist-info/METADATA,sha256=tncJt5ouNhwjFYaGHEKEQgxas34QDLW6IRY1Qd2bO6g,5106
14
+ python_dmon-0.2.2.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- dmon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- dmon/__main__.py,sha256=wu5N2wk8mvBgyvr2ghmQf4prezAe0_i-p123VVreyYc,62
3
- dmon/cli.py,sha256=IVJ5jCHkaLGTQjMmWCuJbhnt4MWEOaNWQajC-mm3LUQ,7599
4
- dmon/config.py,sha256=COzwN0Ms8q32pV6dqsZU59RFE2GQmqyAmfygDotc4CA,6610
5
- dmon/constants.py,sha256=y5S532iTeklaR28BFbvpdOHFj1udbEJ-reD1mTCcC6E,351
6
- dmon/control.py,sha256=0_NrrOA0sUM_lFHNyKGAmQsBbJhXwXfj8kxOp2S8Ni8,16311
7
- dmon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- dmon/runner.py,sha256=zEW7TnHMfIw6g3UTjOTUcLFKueXpqSmNDP3ZOptXQKs,5892
9
- dmon/types.py,sha256=ZtTAK-cfs2MgUFRwDH61zeIU2B4dHZDbx1644tYI1h4,1795
10
- dmon/utils.py,sha256=zVQC9YNhMQu4hrAp9k2ds2egnbWZZv7S5w8i2oVIPJg,808
11
- python_dmon-0.2.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
12
- python_dmon-0.2.0.dist-info/entry_points.txt,sha256=rad5ugz4K_IyNXS_IrFPu0tS-UXlBCSJRhJv-OWd_yk,45
13
- python_dmon-0.2.0.dist-info/METADATA,sha256=s1AoObf2NB_kK_Era10wpm0X6OwH-J0UVeAS8Dg6pcs,4498
14
- python_dmon-0.2.0.dist-info/RECORD,,