python-dmon 0.2.1__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/config.py CHANGED
@@ -10,79 +10,45 @@ else:
10
10
  from .types import CmdType, DmonTaskConfig
11
11
 
12
12
 
13
- def find_pyproject_toml(start: Optional[Path] = None):
14
- """
15
- Search for pyproject.toml from the current directory (or specified directory) upwards.
16
- Return a Path object if found.
17
- """
18
- start = start or Path.cwd()
19
- current = start.resolve()
20
-
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
13
  def load_config(cfg_path: Optional[str] = None):
47
14
  """
48
- Load configuration from the given path, or search for dmon.yaml or pyproject.toml.
15
+ Load configuration from the given path, or search it from the current working directory upwards.
49
16
  """
50
17
 
51
- # Load configuration from the given path
52
18
  if cfg_path:
19
+ # Load configuration from the given path
53
20
  path = Path(cfg_path).resolve()
54
21
  if not path.exists():
55
22
  raise FileNotFoundError(f"Config file '{path}' does not exist.")
56
- if path.suffix in [".yaml", ".yml"]:
57
- import yaml
58
-
59
- with path.open("r", encoding="utf-8") as f:
60
- cfg = yaml.safe_load(f)
61
- elif path.suffix == ".toml":
62
- with path.open("rb") as f:
63
- cfg = tomllib.load(f)
64
- cfg = cfg.get("tool", {}).get("dmon", {})
65
- else:
66
- raise ValueError("Config file must be YAML (.yaml/.yml) or TOML (.toml)")
67
- return cfg, path
68
-
69
- # No path provided, search for dmon.yaml first, then pyproject.toml
70
- path = find_dmon_yaml()
71
- if path:
72
- import yaml
73
-
74
- with path.open("r", encoding="utf-8") as f:
75
- cfg = yaml.safe_load(f)
76
23
  else:
77
- 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
78
36
  if not path:
79
37
  raise FileNotFoundError(
80
38
  "No dmon.yaml or pyproject.toml found in current or any parent directory."
81
39
  )
82
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":
83
47
  with path.open("rb") as f:
84
48
  cfg = tomllib.load(f)
85
49
  cfg = cfg.get("tool", {}).get("dmon", {})
50
+ else:
51
+ raise ValueError("Config file must be YAML (.yaml/.yml) or TOML (.toml)")
86
52
  return cfg, path
87
53
 
88
54
 
dmon/control.py CHANGED
@@ -525,14 +525,14 @@ def print_process_table(metas: List[DmonMeta], full_width: bool = False):
525
525
 
526
526
  def list_processes(dir: PathType, full_width: bool):
527
527
  target_dmon_dir = Path(dir).resolve()
528
- metas = []
528
+ metas: List[DmonMeta] = []
529
529
  if target_dmon_dir.exists() and target_dmon_dir.is_dir():
530
530
  for meta_file in target_dmon_dir.glob(f"*{META_SUFFIX}"):
531
531
  meta = DmonMeta.load(meta_file)
532
532
  if meta is not None:
533
533
  metas.append(meta)
534
534
  # sort by name (case-insensitive)
535
- metas.sort(key=lambda m: m.name.lower())
535
+ metas.sort(key=lambda m: m.task.lower())
536
536
  n_task = len(metas)
537
537
  processes = print_process_table(metas, full_width)
538
538
  n_proc = len(processes)
@@ -557,6 +557,9 @@ def execute(cfg: DmonTaskConfig):
557
557
  env = {**os.environ, **cfg.env}
558
558
 
559
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]
560
563
 
561
564
  def signal_handler(signum: int, frame) -> None:
562
565
  if ON_WINDOWS and signum == signal.SIGINT:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: python-dmon
3
- Version: 0.2.1
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
@@ -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
 
@@ -1,14 +1,14 @@
1
1
  dmon/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  dmon/__main__.py,sha256=wu5N2wk8mvBgyvr2ghmQf4prezAe0_i-p123VVreyYc,62
3
3
  dmon/cli.py,sha256=Dqu43_hcsx8iCJXAL1YXqBpp1jFt0Y7qBEr9mYzkAn8,8764
4
- dmon/config.py,sha256=a7YM4U_iMB1wQ03NpWGAjbX-N-8Rdt9n6XTzKzeGZq4,7566
4
+ dmon/config.py,sha256=Q8TvcYDwUTlL_8bcjxVsA-lLE_AlcSmxYpbnMaCkFrw,6650
5
5
  dmon/constants.py,sha256=uJPI66IJvT6vhamqEDeqHjZQS12RxCPe3DD5yCU0UR8,407
6
- dmon/control.py,sha256=u1sz2MtxHHDqsaFyc7QaG7HFPLnAkser_8riTYxFK1A,17276
6
+ dmon/control.py,sha256=KpGNPPsuAsDNX3FTJxnGNnt9f4Q18omvG42zO1CwTy4,17494
7
7
  dmon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  dmon/runner.py,sha256=iyotNIrbO6I_u02EcqwO7R7N3BEBP6b_8eL8yb3zeJQ,6082
9
9
  dmon/types.py,sha256=tTVoJzWk-Z8XiZCUt0VE8Z7-URpXMoS54HfVYXhW-0Q,1795
10
10
  dmon/utils.py,sha256=zVQC9YNhMQu4hrAp9k2ds2egnbWZZv7S5w8i2oVIPJg,808
11
- python_dmon-0.2.1.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
12
- python_dmon-0.2.1.dist-info/entry_points.txt,sha256=rad5ugz4K_IyNXS_IrFPu0tS-UXlBCSJRhJv-OWd_yk,45
13
- python_dmon-0.2.1.dist-info/METADATA,sha256=gIMrWHILmOEnjtU-gZAoYdz8JAOxFYibkos4DYs_ba0,4496
14
- python_dmon-0.2.1.dist-info/RECORD,,
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,,