Skip to content

Commit 675c3c7

Browse files
author
cav71
authored
scriptlets support (#39)
* change return signature * - cli better help formatter - fix docs - luxos-run: fix typo in tear down flag * new luxminer module * iminer: relax --range flag allowing file path without using @ * iminer: call teardown if present on completion of luxos-run * iminer: support log downloading * iminer: adds is_ramping function, get_profile and Log parser * iminer: update requirements * iminer: version bump * iminer: add new db flag * removed comment * cleanup
1 parent 71f32d4 commit 675c3c7

File tree

7 files changed

+89
-27
lines changed

7 files changed

+89
-27
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
55

66
[project]
77
name = "luxos"
8-
version = "0.2.2"
8+
version = "0.2.3"
99
description = "The all encompassing LuxOS python library."
1010
readme = "README.md"
1111
license = { text = "MIT" } # TODO I don't think this is a MIT??

src/luxos/asyncops.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ async def rexec(
341341
timeout: float | None = None,
342342
retry: int | None = None,
343343
retry_delay: float | None = None,
344-
) -> dict[str, Any] | None:
344+
) -> dict[str, Any]:
345345
"""
346346
Send a command to a host.
347347
@@ -442,6 +442,7 @@ async def rexec(
442442
await logoff(host, port, sid)
443443
if isinstance(failure, Exception):
444444
raise failure
445+
return {}
445446

446447

447448
@contextlib.asynccontextmanager

src/luxos/cli/flags.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ def type_range(txt: str) -> Sequence[tuple[str, int | None]]:
107107
path = None
108108
if txt.startswith("@") and not (path := Path(txt[1:])).exists():
109109
raise argparse.ArgumentTypeError(f"file not found {path}")
110+
if Path(txt).exists():
111+
path = Path(txt)
110112

111113
if path:
112114
with contextlib.suppress(RuntimeError, DataParsingError):
@@ -160,6 +162,48 @@ def validate(self, txt) -> None | datetime.time:
160162
raise argparse.ArgumentTypeError(f"failed conversion into HH:MM for '{txt}'")
161163

162164

165+
class type_database(ArgumentTypeBase):
166+
"""
167+
Validate a type as a database string (sqlalchemy)
168+
169+
Raises:
170+
argparse.ArgumentTypeError: on an invalid input.
171+
172+
Returns:
173+
datetime.time or None
174+
175+
Example:
176+
file.py::
177+
178+
parser.add_argument("-x", type=type_database)
179+
options = parser.parse_args()
180+
...
181+
182+
assert options.x == sqlalchemy.Engine
183+
184+
185+
shell::
186+
187+
file.py -x sqlite:///foobar.db
188+
file.py -x postgresql+psycopg2://<user>:<password>@<host>/<db>
189+
"""
190+
191+
def validate(self, txt) -> Any:
192+
if not txt:
193+
return None
194+
195+
from sqlalchemy import create_engine
196+
from sqlalchemy.engine.url import make_url
197+
from sqlalchemy.exc import ArgumentError
198+
199+
with contextlib.suppress(ArgumentError):
200+
url = make_url(txt)
201+
return create_engine(url)
202+
raise argparse.ArgumentTypeError(
203+
f"wrong sqlalchemy url format '{txt}' (eg. sqlite:///filename.db)"
204+
)
205+
206+
163207
def add_arguments_rexec(parser: LuxosParserBase):
164208
"""adds the rexec timing for timeout/retries/delays
165209

src/luxos/cli/v1.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ def main(parser: argparse.ArgumentParser):
101101
from pathlib import Path
102102
from typing import Any, Callable
103103

104-
from .. import text
105104
from . import flags
106105
from .shared import ArgumentTypeBase, LuxosParserBase
107106

@@ -189,7 +188,9 @@ def parse_args(self, args=None, namespace=None):
189188
@classmethod
190189
def get_parser(cls, modules: list[types.ModuleType], **kwargs):
191190
class Formatter(
192-
argparse.ArgumentDefaultsHelpFormatter, argparse.RawTextHelpFormatter
191+
argparse.RawTextHelpFormatter,
192+
argparse.RawDescriptionHelpFormatter,
193+
argparse.ArgumentDefaultsHelpFormatter,
193194
):
194195
pass
195196

@@ -217,9 +218,7 @@ def setup(
217218
description, _, epilog = (
218219
(function.__doc__ or module.__doc__ or "").strip().partition("\n")
219220
)
220-
# markdown
221-
# epilog = text.md(f"# {description}\n{epilog}")
222-
epilog = text.md(f"{description}\n{epilog}")
221+
epilog = f"{description}\n{'-'*len(description)}\n{epilog}"
223222
description = ""
224223

225224
kwargs = {}

src/luxos/scripts/luxos_run.py

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
11
"""Script to issue commands to miner(s)
22
3-
This tool is designed to issue a simple command (with parameters) to a set of miners
4-
defined on the command line (--range/--range_start/--range-end) or in a file (yaml/csv).
5-
6-
It loads a `script` file and execute across all the defined miners.
3+
This tool is designed to run commands to a set of miners defined on the command
4+
line using the --range flag (either an ip address or a file prefixed with @.
75
86
Eg.
9-
```shell
107
11-
# my-script.py
12-
from luxos import asyncops
13-
async def main(host: str, port: int):
14-
res = await asyncops.rexec(host, port, "version")
15-
return asyncops.validate(host, port, res, "VERSION")[0]
8+
# my-script.py
9+
from luxos import asyncops
10+
async def main(host: str, port: int):
11+
res = await asyncops.rexec(host, port, "version")
12+
return asyncops.validate(res, "VERSION", 1, 1)
1613
17-
# in the cli
18-
$> luxos-run --range 127.0.0.1 my-script.py --json
19-
```
14+
# in the cli
15+
$> luxos-run --range 127.0.0.1 --json my-script.py
2016
17+
NOTE:
18+
1. you can use multiple --range flags
19+
2. you can pass to --range flag a file (csv or yaml) using --range @file.csv
20+
3. ranges can specify a ip-start:ip-end
2121
"""
2222

2323
from __future__ import annotations
2424

2525
import argparse
2626
import asyncio
27+
import inspect
2728
import json
2829
import logging
2930
import pickle
31+
import sys
3032
from pathlib import Path
3133

3234
from luxos import misc, text, utils
@@ -48,14 +50,14 @@ def add_arguments(parser: cli.LuxosParserBase) -> None:
4850
default="main",
4951
)
5052
parser.add_argument(
51-
"-t", "--tear-donw", dest="teardown", help="script tear down function"
53+
"-t", "--teardown", dest="teardown", help="script tear down function"
5254
)
5355
parser.add_argument("-n", "--batch", type=int, help="limit parallel executions")
5456
parser.add_argument(
5557
"--list", action="store_true", help="just display the machine to run script"
5658
)
5759

58-
group = parser.add_mutually_exclusive_group(required=True)
60+
group = parser.add_mutually_exclusive_group()
5961
group.add_argument("--json", action="store_true", help="json output")
6062
group.add_argument("--pickle", type=Path, help="pickle output")
6163

@@ -73,6 +75,11 @@ async def main(args: argparse.Namespace):
7375
for address in args.addresses:
7476
print(address)
7577
return
78+
79+
# prepend the script dir to pypath
80+
log.debug("inserting %s in PYTHONPATH", args.script.parent)
81+
sys.path.insert(0, str(args.script.parent))
82+
7683
module = misc.loadmod(args.script)
7784

7885
entrypoint = getattr(module, args.entrypoint, None)
@@ -81,10 +88,14 @@ async def main(args: argparse.Namespace):
8188
return
8289

8390
teardown = None
84-
if args.teardown:
91+
if args.teardown == "":
92+
pass
93+
elif args.teardown:
8594
if not hasattr(module, args.teardown):
8695
args.error(f"no tear down function {args.teardown} in {args.script}")
8796
teardown = getattr(module, args.teardown, None)
97+
elif hasattr(module, "teardown"):
98+
teardown = getattr(module, "teardown")
8899

89100
result = {}
90101

@@ -111,14 +122,18 @@ def callback(result):
111122
else:
112123
result[data.address] = data.data
113124

125+
if teardown:
126+
if "result" in inspect.signature(teardown).parameters:
127+
newresult = teardown(result)
128+
else:
129+
newresult = teardown()
130+
result = newresult or result
131+
114132
if args.json:
115133
print(json.dumps(result, indent=2))
116134
if args.pickle:
117135
args.pickle.write_bytes(pickle.dumps(result))
118136

119-
if teardown:
120-
teardown()
121-
122137

123138
def run():
124139
asyncio.run(main())

src/luxos/syncops.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def rexec(
216216
timeout: float | None = None,
217217
retry: int | None = None,
218218
retry_delay: float | None = None,
219-
) -> dict[str, Any] | None:
219+
) -> dict[str, Any]:
220220
parameters = parameters_to_list(parameters)
221221

222222
timeout = TIMEOUT if timeout is None else timeout
@@ -287,6 +287,7 @@ def rexec(
287287
logoff(host, port, sid)
288288
if isinstance(failure, Exception):
289289
raise failure
290+
return {}
290291

291292

292293
# !!! LEGACY CODE BELOW !!!

tests/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
asyncpg
22
build
33
httpx
4+
humanize
45
lxml
56
mypy
67
myst-parser
@@ -20,6 +21,7 @@ sphinx-book-theme
2021
sphinx-markdown-parser
2122
sphinx-design
2223
tqdm
24+
types-python-dateutil
2325
types-pyyaml
2426
types-tqdm
2527

0 commit comments

Comments
 (0)