PyroLab 0.3.2__py3-none-any.whl → 0.4.0__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.
- pyrolab/__init__.py +56 -7
- pyrolab/api.py +6 -6
- pyrolab/cli.py +220 -98
- pyrolab/configure.py +109 -71
- pyrolab/drivers/VISAResourceExtensions.py +65 -34
- pyrolab/drivers/__init__.py +20 -19
- pyrolab/drivers/arduino/arduino.py +28 -13
- pyrolab/drivers/cameras/amcam.py +61 -0
- pyrolab/drivers/cameras/sciTSI.py +39 -32
- pyrolab/drivers/cameras/thorcam.py +119 -68
- pyrolab/drivers/cameras/uc480.py +66 -60
- pyrolab/drivers/fpgas/__init__.py +16 -0
- pyrolab/drivers/fpgas/analogdiscovery.py +310 -0
- pyrolab/drivers/function_generators/__init__.py +16 -0
- pyrolab/drivers/function_generators/smcv100b.py +389 -0
- pyrolab/drivers/lasers/ppcl55x.py +259 -233
- pyrolab/drivers/lasers/sachertec.py +25 -20
- pyrolab/drivers/lasers/tsl550.py +218 -165
- pyrolab/drivers/motion/KMTS25E.py +92 -0
- pyrolab/drivers/motion/kinesis/__init__.py +9 -5
- pyrolab/drivers/motion/kinesis/bpc303.py +86 -42
- pyrolab/drivers/motion/kinesis/kdc101.py +121 -93
- pyrolab/drivers/motion/max31x.py +31 -23
- pyrolab/drivers/motion/prm1z8.py +2 -1
- pyrolab/drivers/motion/z8xx.py +13 -10
- pyrolab/drivers/sample.py +13 -8
- pyrolab/drivers/scopes/rohdeschwarz.py +119 -104
- pyrolab/drivers/smu/keysight.py +94 -83
- pyrolab/manager.py +138 -85
- pyrolab/nameserver.py +28 -16
- pyrolab/pyrolabd.py +37 -19
- pyrolab/server.py +69 -57
- pyrolab/service.py +13 -7
- pyrolab/utils.py +13 -5
- pyrolab-0.4.0.dist-info/METADATA +299 -0
- pyrolab-0.4.0.dist-info/RECORD +48 -0
- {pyrolab-0.3.2.dist-info → pyrolab-0.4.0.dist-info}/WHEEL +1 -1
- {pyrolab-0.3.2.dist-info → pyrolab-0.4.0.dist-info/licenses}/NOTICE.txt +1 -1
- pyrolab-0.3.2.dist-info/AUTHORS.txt +0 -20
- pyrolab-0.3.2.dist-info/METADATA +0 -973
- pyrolab-0.3.2.dist-info/RECORD +0 -43
- {pyrolab-0.3.2.dist-info → pyrolab-0.4.0.dist-info}/entry_points.txt +0 -0
- {pyrolab-0.3.2.dist-info → pyrolab-0.4.0.dist-info/licenses}/LICENSE +0 -0
- {pyrolab-0.3.2.dist-info → pyrolab-0.4.0.dist-info}/top_level.txt +0 -0
pyrolab/__init__.py
CHANGED
|
@@ -27,6 +27,12 @@ import os
|
|
|
27
27
|
import pathlib
|
|
28
28
|
import platform
|
|
29
29
|
import sys
|
|
30
|
+
import atexit
|
|
31
|
+
import fileinput
|
|
32
|
+
import re
|
|
33
|
+
from typing import Callable, Iterable
|
|
34
|
+
from time import strptime
|
|
35
|
+
from pathlib import Path
|
|
30
36
|
|
|
31
37
|
# Check if Python version is supported
|
|
32
38
|
pyversion = sys.version_info
|
|
@@ -42,11 +48,11 @@ if pyversion < (3, 7, 0):
|
|
|
42
48
|
__name__ = "PyroLab"
|
|
43
49
|
__author__ = "CamachoLab"
|
|
44
50
|
__copyright__ = "Copyright 2020, The PyroLab Project"
|
|
45
|
-
__version__ = "0.
|
|
51
|
+
__version__ = "0.4.0"
|
|
46
52
|
__license__ = "GPLv3+"
|
|
47
53
|
__maintainer__ = "Sequoia Ploeg"
|
|
48
54
|
__maintainer_email__ = "sequoia.ploeg@byu.edu"
|
|
49
|
-
__status__ = "Development"
|
|
55
|
+
__status__ = "Development" # "Production"
|
|
50
56
|
__project_url__ = "https://github.com/sequoiap/pyrolab"
|
|
51
57
|
__forum_url__ = "https://github.com/sequoiap/pyrolab/issues"
|
|
52
58
|
__website_url__ = "https://camacholab.byu.edu/"
|
|
@@ -66,6 +72,7 @@ if pyversion < (3, 9, 0):
|
|
|
66
72
|
base_path = pathlib.Path(__file__).resolve().parent
|
|
67
73
|
else:
|
|
68
74
|
from importlib.resources import files
|
|
75
|
+
|
|
69
76
|
base_path = files("pyrolab")
|
|
70
77
|
base_path = base_path / "data" / "local"
|
|
71
78
|
|
|
@@ -78,6 +85,7 @@ NAMESERVER_STORAGE.mkdir(parents=True, exist_ok=True)
|
|
|
78
85
|
|
|
79
86
|
PYROLAB_LOGDIR = PYROLAB_DATA_DIR / "logs"
|
|
80
87
|
PYROLAB_LOGDIR.mkdir(parents=True, exist_ok=True)
|
|
88
|
+
PYROLAB_MASTERLOG = PYROLAB_LOGDIR / "pyrolab.log"
|
|
81
89
|
|
|
82
90
|
LOCKFILE = PYROLAB_DATA_DIR / "pyrolabd.lock"
|
|
83
91
|
USER_CONFIG_FILE = PYROLAB_DATA_DIR / "user_configuration.yaml"
|
|
@@ -98,11 +106,14 @@ def get_loglevel() -> int:
|
|
|
98
106
|
return loglevel
|
|
99
107
|
|
|
100
108
|
|
|
101
|
-
if len(logging.root.handlers) == 0:
|
|
109
|
+
if len(logging.root.handlers) == 0:
|
|
102
110
|
logfile = PYROLAB_LOGDIR / f"pyrolab_{os.getpid()}.log"
|
|
103
111
|
root = logging.getLogger()
|
|
104
|
-
h = logging.handlers.RotatingFileHandler(logfile,
|
|
105
|
-
f = logging.Formatter(
|
|
112
|
+
h = logging.handlers.RotatingFileHandler(logfile, "a", 30000, 10)
|
|
113
|
+
f = logging.Formatter(
|
|
114
|
+
"[%(asctime)s.%(msecs)03d] %(levelname)-8s %(process)-5s %(processName)-16s %(name)-20s %(message)s",
|
|
115
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
116
|
+
)
|
|
106
117
|
h.setFormatter(f)
|
|
107
118
|
root.addHandler(h)
|
|
108
119
|
root.setLevel(get_loglevel())
|
|
@@ -124,10 +135,10 @@ try:
|
|
|
124
135
|
|
|
125
136
|
url = "https://pypi.org/pypi/pyrolab/json"
|
|
126
137
|
with requests.Session() as s:
|
|
127
|
-
s.mount(
|
|
138
|
+
s.mount("https://pypi.org", HTTPAdapter(max_retries=3))
|
|
128
139
|
resp = s.get(url).text
|
|
129
140
|
|
|
130
|
-
v = json.loads(resp)[
|
|
141
|
+
v = json.loads(resp)["info"]["version"]
|
|
131
142
|
curver = parse(__version__)
|
|
132
143
|
latest = parse(v)
|
|
133
144
|
|
|
@@ -139,3 +150,41 @@ try:
|
|
|
139
150
|
log.info(message)
|
|
140
151
|
except:
|
|
141
152
|
pass
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def try_itr(func: Callable, itr: Iterable, *exceptions, **kwargs):
|
|
156
|
+
"""
|
|
157
|
+
Tests a function on an iterable, yields iterable if no exception is raised.
|
|
158
|
+
"""
|
|
159
|
+
for elem in itr:
|
|
160
|
+
try:
|
|
161
|
+
func(elem, **kwargs)
|
|
162
|
+
yield elem
|
|
163
|
+
except exceptions:
|
|
164
|
+
pass
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# @atexit.register
|
|
168
|
+
# def condense_logs():
|
|
169
|
+
# """
|
|
170
|
+
# Cleans up the logfile directory every once in a while.
|
|
171
|
+
|
|
172
|
+
# Runs at exit or when "pyrolab down" is called. Condenses all process log
|
|
173
|
+
# files into a single logfile that is sorted by timestamp.
|
|
174
|
+
# """
|
|
175
|
+
# f_names = list(PYROLAB_LOGDIR.glob("*.*"))
|
|
176
|
+
# lines = list(fileinput.input(f_names))
|
|
177
|
+
# t_fmt = "%Y-%m-%d %H:%M:%S.%f" # format of time stamps
|
|
178
|
+
# t_pat = re.compile(r"\[(.+?)\]") # pattern to extract timestamp
|
|
179
|
+
# lines = list(
|
|
180
|
+
# try_itr(
|
|
181
|
+
# lambda l: strptime(t_pat.search(l).group(1), t_fmt), lines, AttributeError
|
|
182
|
+
# )
|
|
183
|
+
# )
|
|
184
|
+
# with PYROLAB_MASTERLOG.open(mode="w") as f:
|
|
185
|
+
# for l in sorted(lines, key=lambda l: strptime(t_pat.search(l).group(1), t_fmt)):
|
|
186
|
+
# f.write(l)
|
|
187
|
+
# if PYROLAB_MASTERLOG in f_names:
|
|
188
|
+
# f_names.remove(PYROLAB_MASTERLOG)
|
|
189
|
+
# for f in f_names:
|
|
190
|
+
# f.unlink()
|
pyrolab/api.py
CHANGED
|
@@ -14,11 +14,11 @@ from Pyro5.core import locate_ns
|
|
|
14
14
|
from Pyro5.server import behavior, expose, oneway, serve
|
|
15
15
|
from pyrolab import USER_CONFIG_FILE
|
|
16
16
|
from pyrolab.configure import (
|
|
17
|
-
PyroLabConfiguration,
|
|
18
|
-
NameServerConfiguration,
|
|
19
|
-
DaemonConfiguration,
|
|
20
|
-
ServiceConfiguration,
|
|
21
|
-
reset_config,
|
|
17
|
+
PyroLabConfiguration,
|
|
18
|
+
NameServerConfiguration,
|
|
19
|
+
DaemonConfiguration,
|
|
20
|
+
ServiceConfiguration,
|
|
21
|
+
reset_config,
|
|
22
22
|
update_config,
|
|
23
23
|
)
|
|
24
24
|
from pyrolab.server import Daemon, LockableDaemon
|
|
@@ -47,7 +47,7 @@ __all__ = [
|
|
|
47
47
|
]
|
|
48
48
|
|
|
49
49
|
# If a user config file exists, load the first listed nameserver by default,
|
|
50
|
-
# so that locate_ns "just works."
|
|
50
|
+
# so that locate_ns "just works."
|
|
51
51
|
if USER_CONFIG_FILE.exists():
|
|
52
52
|
cfg = PyroLabConfiguration.from_file(USER_CONFIG_FILE)
|
|
53
53
|
nscfg = next(iter(cfg.nameservers.values()))
|
pyrolab/cli.py
CHANGED
|
@@ -20,10 +20,16 @@ import re
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from typing import Callable, Iterable, Optional
|
|
22
22
|
from time import sleep, strptime
|
|
23
|
+
from tabulate import tabulate
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
try:
|
|
26
|
+
import importlib.resources as pkg_resources
|
|
27
|
+
except ImportError:
|
|
28
|
+
import pkg_resources
|
|
25
29
|
import typer
|
|
30
|
+
from Pyro5.errors import CommunicationError
|
|
26
31
|
|
|
32
|
+
import pyrolab
|
|
27
33
|
from pyrolab import LOCKFILE, PYROLAB_LOGDIR, RUNTIME_CONFIG, USER_CONFIG_FILE
|
|
28
34
|
from pyrolab.api import Proxy
|
|
29
35
|
from pyrolab.configure import (
|
|
@@ -38,16 +44,23 @@ from pyrolab.pyrolabd import InstanceInfo, PyroLabDaemon
|
|
|
38
44
|
def get_daemon(abort=True, suppress_reload_message=False) -> PyroLabDaemon:
|
|
39
45
|
if LOCKFILE.exists():
|
|
40
46
|
ii = InstanceInfo.parse_file(LOCKFILE)
|
|
41
|
-
|
|
47
|
+
try:
|
|
48
|
+
DAEMON = Proxy(ii.uri)
|
|
49
|
+
DAEMON._pyroBind()
|
|
50
|
+
except CommunicationError:
|
|
51
|
+
raise ConnectionRefusedError("Could not connect to daemon.")
|
|
42
52
|
if not suppress_reload_message and RUNTIME_CONFIG.exists():
|
|
43
53
|
if RUNTIME_CONFIG.stat().st_mtime < USER_CONFIG_FILE.stat().st_mtime:
|
|
44
54
|
typer.secho(
|
|
45
55
|
"The configuration file has been updated. Run 'pyrolab reload' for changes to take effect.",
|
|
46
|
-
fg=typer.colors.RED
|
|
56
|
+
fg=typer.colors.RED,
|
|
47
57
|
)
|
|
48
58
|
return DAEMON
|
|
49
59
|
elif abort:
|
|
50
|
-
typer.secho(
|
|
60
|
+
typer.secho(
|
|
61
|
+
"PyroLab daemon is not running! Try 'pyrolab up' first.",
|
|
62
|
+
fg=typer.colors.RED,
|
|
63
|
+
)
|
|
51
64
|
raise typer.Abort()
|
|
52
65
|
else:
|
|
53
66
|
return None
|
|
@@ -55,7 +68,7 @@ def get_daemon(abort=True, suppress_reload_message=False) -> PyroLabDaemon:
|
|
|
55
68
|
|
|
56
69
|
###############################################################################
|
|
57
70
|
# pyrolab Main App
|
|
58
|
-
#
|
|
71
|
+
#
|
|
59
72
|
# COMMANDS
|
|
60
73
|
# --------
|
|
61
74
|
# pyrolab up
|
|
@@ -68,29 +81,49 @@ def get_daemon(abort=True, suppress_reload_message=False) -> PyroLabDaemon:
|
|
|
68
81
|
app = typer.Typer(no_args_is_help=True)
|
|
69
82
|
|
|
70
83
|
|
|
71
|
-
def _version_callback(value: bool=True) -> None:
|
|
84
|
+
def _version_callback(value: bool = True) -> None:
|
|
72
85
|
if value:
|
|
73
86
|
from pyrolab import __version__
|
|
87
|
+
|
|
74
88
|
typer.echo(f"PyroLab {__version__}")
|
|
75
89
|
raise typer.Exit()
|
|
76
90
|
|
|
77
|
-
|
|
91
|
+
|
|
92
|
+
def _show_data_dir(value: bool = True) -> None:
|
|
78
93
|
if value:
|
|
79
94
|
from pyrolab import PYROLAB_DATA_DIR
|
|
95
|
+
|
|
80
96
|
typer.echo(f"{PYROLAB_DATA_DIR}")
|
|
81
97
|
raise typer.Exit()
|
|
82
98
|
|
|
99
|
+
|
|
83
100
|
@app.callback()
|
|
84
101
|
def main(
|
|
85
|
-
version: bool = typer.Option(
|
|
86
|
-
|
|
102
|
+
version: bool = typer.Option(
|
|
103
|
+
False,
|
|
104
|
+
"--version",
|
|
105
|
+
"-v",
|
|
106
|
+
help="Show the version and exit.",
|
|
107
|
+
callback=_version_callback,
|
|
108
|
+
is_eager=True,
|
|
109
|
+
),
|
|
110
|
+
show_data_dir: bool = typer.Option(
|
|
111
|
+
False,
|
|
112
|
+
"--data",
|
|
113
|
+
"-d",
|
|
114
|
+
help="Show the data directories and exit.",
|
|
115
|
+
callback=_show_data_dir,
|
|
116
|
+
is_eager=True,
|
|
117
|
+
),
|
|
87
118
|
):
|
|
88
119
|
return
|
|
89
120
|
|
|
121
|
+
|
|
90
122
|
@app.command()
|
|
91
123
|
def up(
|
|
92
|
-
port: int = typer.Option(
|
|
93
|
-
|
|
124
|
+
port: int = typer.Option(
|
|
125
|
+
None, "--port", "-p", help="Port to use for the PyroLab daemon."
|
|
126
|
+
),
|
|
94
127
|
):
|
|
95
128
|
"""
|
|
96
129
|
Start the background PyroLab daemon.
|
|
@@ -98,13 +131,21 @@ def up(
|
|
|
98
131
|
Only use the `--force` flag if you're sure the daemon is dead, or you may
|
|
99
132
|
orphan the process.
|
|
100
133
|
"""
|
|
101
|
-
|
|
134
|
+
try:
|
|
135
|
+
daemon = get_daemon(abort=False, suppress_reload_message=True)
|
|
136
|
+
force = False
|
|
137
|
+
except ConnectionRefusedError:
|
|
138
|
+
daemon = None
|
|
139
|
+
force = True
|
|
102
140
|
if daemon is None or force:
|
|
103
141
|
if force and LOCKFILE.exists():
|
|
104
142
|
LOCKFILE.unlink()
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
rsrc = pkg_resources.files(pyrolab) / "pyrolabd.py"
|
|
146
|
+
except AttributeError:
|
|
147
|
+
rsrc = Path(pkg_resources.resource_filename("pyrolab", "pyrolabd.py"))
|
|
148
|
+
|
|
108
149
|
if port:
|
|
109
150
|
args = [sys.executable, str(rsrc), str(port)]
|
|
110
151
|
else:
|
|
@@ -116,15 +157,23 @@ def up(
|
|
|
116
157
|
# same directory (certainly true for conda installations).
|
|
117
158
|
executable = Path(sys.exec_prefix) / "pythonw.exe"
|
|
118
159
|
args[0] = str(executable)
|
|
119
|
-
subprocess.Popen(
|
|
160
|
+
subprocess.Popen(
|
|
161
|
+
args,
|
|
162
|
+
close_fds=True,
|
|
163
|
+
start_new_session=True,
|
|
164
|
+
creationflags=DETACHED_PROCESS,
|
|
165
|
+
)
|
|
120
166
|
else:
|
|
121
|
-
subprocess.Popen(
|
|
122
|
-
|
|
167
|
+
subprocess.Popen(
|
|
168
|
+
args, close_fds=True, start_new_session=True
|
|
169
|
+
) # stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
|
170
|
+
|
|
123
171
|
typer.secho("PyroLab daemon launched.", fg=typer.colors.GREEN)
|
|
124
172
|
else:
|
|
125
173
|
typer.secho("PyroLab daemon is already running!", fg=typer.colors.RED)
|
|
126
174
|
raise typer.Abort()
|
|
127
175
|
|
|
176
|
+
|
|
128
177
|
@app.command()
|
|
129
178
|
def down():
|
|
130
179
|
"""
|
|
@@ -136,6 +185,7 @@ def down():
|
|
|
136
185
|
sleep(0.1)
|
|
137
186
|
typer.secho("PyroLab daemon shutdown.", fg=typer.colors.GREEN)
|
|
138
187
|
|
|
188
|
+
|
|
139
189
|
@app.command()
|
|
140
190
|
def reload():
|
|
141
191
|
"""
|
|
@@ -150,6 +200,7 @@ def reload():
|
|
|
150
200
|
else:
|
|
151
201
|
typer.secho("PyroLab daemon reload failed.", fg=typer.colors.RED)
|
|
152
202
|
|
|
203
|
+
|
|
153
204
|
@app.command()
|
|
154
205
|
def ps():
|
|
155
206
|
"""
|
|
@@ -158,28 +209,38 @@ def ps():
|
|
|
158
209
|
daemon = get_daemon()
|
|
159
210
|
typer.echo(daemon.ps())
|
|
160
211
|
|
|
212
|
+
|
|
161
213
|
###############################################################################
|
|
162
214
|
# pyrolab config
|
|
163
215
|
###############################################################################
|
|
164
216
|
|
|
165
217
|
config_app = typer.Typer()
|
|
166
|
-
app.add_typer(
|
|
218
|
+
app.add_typer(
|
|
219
|
+
config_app,
|
|
220
|
+
name="config",
|
|
221
|
+
help="Configure PyroLab nameservers, daemons, and services.",
|
|
222
|
+
)
|
|
223
|
+
|
|
167
224
|
|
|
168
225
|
@config_app.command("update")
|
|
169
226
|
def config_update(filename: str):
|
|
170
227
|
"""Update the configuration file"""
|
|
171
228
|
update_config(filename)
|
|
172
|
-
|
|
229
|
+
|
|
230
|
+
|
|
173
231
|
@config_app.command("reset")
|
|
174
232
|
def config_reset():
|
|
175
233
|
"""Reset the configuration file"""
|
|
176
|
-
delete = typer.confirm(
|
|
234
|
+
delete = typer.confirm(
|
|
235
|
+
"Are you sure you want to reset the configuration? This cannot be undone."
|
|
236
|
+
)
|
|
177
237
|
if not delete:
|
|
178
238
|
typer.secho("No changes made.", fg=typer.colors.GREEN)
|
|
179
239
|
raise typer.Exit()
|
|
180
240
|
reset_config()
|
|
181
241
|
typer.secho("Configuration reset.", fg=typer.colors.RED)
|
|
182
242
|
|
|
243
|
+
|
|
183
244
|
@config_app.command("export")
|
|
184
245
|
def config_export(filename: str):
|
|
185
246
|
"""Export the configuration file"""
|
|
@@ -189,12 +250,16 @@ def config_export(filename: str):
|
|
|
189
250
|
typer.secho("No configuration file found.", fg=typer.colors.RED)
|
|
190
251
|
raise typer.Abort()
|
|
191
252
|
|
|
253
|
+
|
|
192
254
|
###############################################################################
|
|
193
255
|
# pyrolab start
|
|
194
256
|
###############################################################################
|
|
195
257
|
|
|
196
258
|
start_app = typer.Typer()
|
|
197
|
-
app.add_typer(
|
|
259
|
+
app.add_typer(
|
|
260
|
+
start_app, name="start", help="Start a nameserver or daemon (and its services)."
|
|
261
|
+
)
|
|
262
|
+
|
|
198
263
|
|
|
199
264
|
@start_app.command("nameserver")
|
|
200
265
|
def start_nameserver(name: str):
|
|
@@ -204,6 +269,7 @@ def start_nameserver(name: str):
|
|
|
204
269
|
daemon = get_daemon()
|
|
205
270
|
daemon.start_nameserver(name)
|
|
206
271
|
|
|
272
|
+
|
|
207
273
|
@start_app.command("daemon")
|
|
208
274
|
def start_daemon(name: str):
|
|
209
275
|
"""
|
|
@@ -212,116 +278,110 @@ def start_daemon(name: str):
|
|
|
212
278
|
daemon = get_daemon()
|
|
213
279
|
daemon.start_daemon(name)
|
|
214
280
|
|
|
281
|
+
|
|
215
282
|
###############################################################################
|
|
216
283
|
# pyrolab stop
|
|
217
284
|
###############################################################################
|
|
218
285
|
|
|
219
286
|
stop_app = typer.Typer()
|
|
220
|
-
app.add_typer(
|
|
287
|
+
app.add_typer(
|
|
288
|
+
stop_app, name="stop", help="Stop a nameserver or daemon (and its services)."
|
|
289
|
+
)
|
|
290
|
+
|
|
221
291
|
|
|
222
292
|
@stop_app.command("nameserver")
|
|
223
|
-
def stop_nameserver(
|
|
293
|
+
def stop_nameserver(
|
|
294
|
+
name: Optional[str] = typer.Argument(None, help="Name of the service to stop"),
|
|
295
|
+
):
|
|
224
296
|
"""
|
|
225
297
|
Stop a nameserver.
|
|
226
298
|
"""
|
|
227
299
|
daemon = get_daemon()
|
|
228
300
|
daemon.stop_nameserver(name)
|
|
229
301
|
|
|
302
|
+
|
|
230
303
|
@stop_app.command("daemon")
|
|
231
|
-
def stop_daemon(
|
|
304
|
+
def stop_daemon(
|
|
305
|
+
name: Optional[str] = typer.Argument(None, help="Name of the service to stop"),
|
|
306
|
+
):
|
|
232
307
|
"""
|
|
233
308
|
Stop a daemon.
|
|
234
309
|
"""
|
|
235
310
|
daemon = get_daemon()
|
|
236
311
|
daemon.stop_daemon(name)
|
|
237
312
|
|
|
313
|
+
|
|
238
314
|
###############################################################################
|
|
239
315
|
# pyrolab info
|
|
240
316
|
###############################################################################
|
|
241
317
|
|
|
242
|
-
info_app = typer.Typer()
|
|
243
|
-
app.add_typer(info_app, name="info", help="Print details about a nameserver, daemon, or service.")
|
|
244
318
|
|
|
245
|
-
@
|
|
246
|
-
def
|
|
247
|
-
name: str,
|
|
248
|
-
):
|
|
249
|
-
"""
|
|
250
|
-
Get information on a nameserver.
|
|
251
|
-
"""
|
|
252
|
-
if RUNTIME_CONFIG.exists():
|
|
253
|
-
config = PyroLabConfiguration.from_file(RUNTIME_CONFIG)
|
|
254
|
-
elif USER_CONFIG_FILE.exists():
|
|
255
|
-
config = PyroLabConfiguration.from_file(USER_CONFIG_FILE)
|
|
256
|
-
else:
|
|
257
|
-
typer.secho("No configuration file found.", fg=typer.colors.RED)
|
|
258
|
-
raise typer.Exit()
|
|
259
|
-
|
|
260
|
-
if name in config.nameservers:
|
|
261
|
-
info = textwrap.indent(str(config.nameservers[name].yaml()), ' ')
|
|
262
|
-
typer.echo(f"{name}\n{info}")
|
|
263
|
-
else:
|
|
264
|
-
typer.secho("Nameserver not found.", fg=typer.colors.RED)
|
|
265
|
-
raise typer.Exit()
|
|
266
|
-
|
|
267
|
-
@info_app.command("daemon")
|
|
268
|
-
def info_daemon(
|
|
269
|
-
name: str,
|
|
270
|
-
):
|
|
319
|
+
@app.command()
|
|
320
|
+
def info():
|
|
271
321
|
"""
|
|
272
|
-
|
|
322
|
+
Show details about the current PyroLab configuration.
|
|
273
323
|
"""
|
|
274
324
|
if RUNTIME_CONFIG.exists():
|
|
275
325
|
config = PyroLabConfiguration.from_file(RUNTIME_CONFIG)
|
|
276
326
|
elif USER_CONFIG_FILE.exists():
|
|
277
327
|
config = PyroLabConfiguration.from_file(USER_CONFIG_FILE)
|
|
278
328
|
else:
|
|
279
|
-
typer.secho("No configuration
|
|
329
|
+
typer.secho("No configuration installed.", fg=typer.colors.RED)
|
|
280
330
|
raise typer.Exit()
|
|
281
331
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
332
|
+
ns_data = []
|
|
333
|
+
for name in config.nameservers:
|
|
334
|
+
ns_data.append({"name": name, **config.nameservers[name].dict()})
|
|
335
|
+
for item in ns_data:
|
|
336
|
+
item["ns_autoclean"] = (
|
|
337
|
+
f'{item["ns_autoclean"]} sec' if item["ns_autoclean"] else "Off"
|
|
338
|
+
)
|
|
339
|
+
if ns_data:
|
|
340
|
+
typer.echo("\nNameservers")
|
|
341
|
+
typer.echo(tabulate(ns_data, headers="keys", tablefmt="rounded_grid"))
|
|
342
|
+
|
|
343
|
+
daemon_data = []
|
|
344
|
+
for name in config.daemons:
|
|
345
|
+
daemon_data.append({"name": name, **config.daemons[name].dict()})
|
|
346
|
+
for item in daemon_data:
|
|
347
|
+
item["nameservers"] = "".join([f"- {name}\n" for name in item["nameservers"]])
|
|
348
|
+
if daemon_data:
|
|
349
|
+
typer.echo("\nDaemons")
|
|
350
|
+
typer.echo(tabulate(daemon_data, headers="keys", tablefmt="rounded_grid"))
|
|
351
|
+
|
|
352
|
+
service_data = []
|
|
353
|
+
for name in config.services:
|
|
354
|
+
service_data.append({"name": name, **config.services[name].dict()})
|
|
355
|
+
for item in service_data:
|
|
356
|
+
item["parameters"] = "".join(
|
|
357
|
+
[f"{k}: {v}\n" for k, v in item["parameters"].items()]
|
|
358
|
+
)
|
|
359
|
+
item["nameservers"] = "".join([f"- {name}\n" for name in item["nameservers"]])
|
|
360
|
+
if service_data:
|
|
361
|
+
typer.echo("\nServices")
|
|
362
|
+
typer.echo(tabulate(service_data, headers="keys", tablefmt="rounded_grid"))
|
|
288
363
|
|
|
289
|
-
@info_app.command("service")
|
|
290
|
-
def info_service(
|
|
291
|
-
name: str,
|
|
292
|
-
):
|
|
293
|
-
"""
|
|
294
|
-
Get information on a service.
|
|
295
|
-
"""
|
|
296
|
-
if RUNTIME_CONFIG.exists():
|
|
297
|
-
config = PyroLabConfiguration.from_file(RUNTIME_CONFIG)
|
|
298
|
-
elif USER_CONFIG_FILE.exists():
|
|
299
|
-
config = PyroLabConfiguration.from_file(USER_CONFIG_FILE)
|
|
300
|
-
else:
|
|
301
|
-
typer.secho("No configuration file found.", fg=typer.colors.RED)
|
|
302
|
-
raise typer.Exit()
|
|
303
|
-
|
|
304
|
-
if name in config.services:
|
|
305
|
-
info = textwrap.indent(str(config.services[name].yaml()), ' ')
|
|
306
|
-
typer.echo(f"{name}\n{info}")
|
|
307
|
-
else:
|
|
308
|
-
typer.secho("Service not found.", fg=typer.colors.RED)
|
|
309
|
-
typer.echo(config.services.keys())
|
|
310
|
-
raise typer.Exit()
|
|
311
364
|
|
|
312
365
|
###############################################################################
|
|
313
366
|
# pyrolab logs
|
|
314
367
|
###############################################################################
|
|
315
368
|
|
|
316
369
|
logs_app = typer.Typer()
|
|
317
|
-
app.add_typer(logs_app, name="logs")
|
|
370
|
+
app.add_typer(logs_app, name="logs", help="Compile and export log files.")
|
|
371
|
+
|
|
318
372
|
|
|
319
373
|
@logs_app.command("clean")
|
|
320
374
|
def logs_clean():
|
|
321
375
|
"""
|
|
322
376
|
Deletes all log files.
|
|
323
377
|
"""
|
|
324
|
-
|
|
378
|
+
for f in PYROLAB_LOGDIR.glob("*.*"):
|
|
379
|
+
# Try to delete file if not in use
|
|
380
|
+
try:
|
|
381
|
+
f.unlink()
|
|
382
|
+
except PermissionError:
|
|
383
|
+
pass
|
|
384
|
+
|
|
325
385
|
|
|
326
386
|
def try_itr(func: Callable, itr: Iterable, *exceptions, **kwargs):
|
|
327
387
|
"""
|
|
@@ -334,21 +394,45 @@ def try_itr(func: Callable, itr: Iterable, *exceptions, **kwargs):
|
|
|
334
394
|
except exceptions:
|
|
335
395
|
pass
|
|
336
396
|
|
|
397
|
+
|
|
337
398
|
@logs_app.command("export")
|
|
338
399
|
def logs_export(filename: str):
|
|
339
400
|
"""
|
|
340
401
|
Exports the log file to a file.
|
|
341
402
|
"""
|
|
342
|
-
f_names = PYROLAB_LOGDIR.glob(
|
|
403
|
+
f_names = PYROLAB_LOGDIR.glob("*.*")
|
|
343
404
|
lines = list(fileinput.input(f_names))
|
|
344
|
-
t_fmt = "%Y-%m-%d %H:%M:%S.%f"
|
|
345
|
-
t_pat = re.compile(r
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
405
|
+
t_fmt = "%Y-%m-%d %H:%M:%S.%f" # format of time stamps
|
|
406
|
+
t_pat = re.compile(r"\[(.+?)\]") # pattern to extract timestamp
|
|
407
|
+
|
|
408
|
+
# Group lines into log entries
|
|
409
|
+
log_entries = []
|
|
410
|
+
current_entry = []
|
|
411
|
+
for line in lines:
|
|
412
|
+
# If we've found a new timestamp and we have a current entry, add it to
|
|
413
|
+
# the list and start a new entry.
|
|
414
|
+
if t_pat.match(line) and current_entry:
|
|
415
|
+
log_entries.append("".join(current_entry))
|
|
416
|
+
current_entry = [line]
|
|
417
|
+
# Otherwise, keep extending the current entry.
|
|
418
|
+
else:
|
|
419
|
+
current_entry.append(line)
|
|
420
|
+
# Add the last entry to the list
|
|
421
|
+
if current_entry:
|
|
422
|
+
log_entries.append("".join(current_entry))
|
|
423
|
+
|
|
424
|
+
# Sort log entries by timestamp
|
|
425
|
+
log_entries = sorted(
|
|
426
|
+
log_entries, key=lambda entry: strptime(t_pat.search(entry).group(1), t_fmt)
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
# Write sorted log entries to the output file
|
|
430
|
+
with Path(filename).open(mode="w") as f:
|
|
431
|
+
for entry in log_entries:
|
|
432
|
+
f.write(entry)
|
|
350
433
|
typer.secho(f"Exported logs to {filename}", fg=typer.colors.GREEN)
|
|
351
434
|
|
|
435
|
+
|
|
352
436
|
###############################################################################
|
|
353
437
|
# pyrolab rename
|
|
354
438
|
###############################################################################
|
|
@@ -356,10 +440,11 @@ def logs_export(filename: str):
|
|
|
356
440
|
rename_app = typer.Typer()
|
|
357
441
|
app.add_typer(rename_app, name="rename", help="Rename a nameserver, daemon or service.")
|
|
358
442
|
|
|
443
|
+
|
|
359
444
|
@rename_app.command("nameserver")
|
|
360
445
|
def rename_nameserver(
|
|
361
|
-
old_name: str,
|
|
362
|
-
new_name: str,
|
|
446
|
+
old_name: str,
|
|
447
|
+
new_name: str,
|
|
363
448
|
):
|
|
364
449
|
"""
|
|
365
450
|
Rename a nameserver.
|
|
@@ -376,10 +461,11 @@ def rename_nameserver(
|
|
|
376
461
|
typer.secho("No user configuration file found.", fg=typer.colors.RED)
|
|
377
462
|
raise typer.Exit()
|
|
378
463
|
|
|
464
|
+
|
|
379
465
|
@rename_app.command("daemon")
|
|
380
466
|
def rename_daemon(
|
|
381
|
-
old_name: str,
|
|
382
|
-
new_name: str,
|
|
467
|
+
old_name: str,
|
|
468
|
+
new_name: str,
|
|
383
469
|
):
|
|
384
470
|
"""
|
|
385
471
|
Rename a daemon.
|
|
@@ -396,10 +482,11 @@ def rename_daemon(
|
|
|
396
482
|
typer.secho("No user configuration file found.", fg=typer.colors.RED)
|
|
397
483
|
raise typer.Exit()
|
|
398
484
|
|
|
485
|
+
|
|
399
486
|
@rename_app.command("service")
|
|
400
487
|
def rename_service(
|
|
401
|
-
old_name: str,
|
|
402
|
-
new_name: str,
|
|
488
|
+
old_name: str,
|
|
489
|
+
new_name: str,
|
|
403
490
|
):
|
|
404
491
|
"""
|
|
405
492
|
Rename a service.
|
|
@@ -416,6 +503,41 @@ def rename_service(
|
|
|
416
503
|
typer.secho("No user configuration file found.", fg=typer.colors.RED)
|
|
417
504
|
raise typer.Exit()
|
|
418
505
|
|
|
506
|
+
|
|
507
|
+
###############################################################################
|
|
508
|
+
# pyrolab ns_list
|
|
509
|
+
###############################################################################
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
@app.command("nslist")
|
|
513
|
+
def ns_list(
|
|
514
|
+
host: str = typer.Option(
|
|
515
|
+
"localhost",
|
|
516
|
+
"--host",
|
|
517
|
+
"-h",
|
|
518
|
+
help="Nameserver host to list all registered services for (default localhost).",
|
|
519
|
+
),
|
|
520
|
+
port: int = typer.Option(
|
|
521
|
+
9090, "--port", "-p", help="Port to use for the PyroLab daemon (default 9090)."
|
|
522
|
+
),
|
|
523
|
+
):
|
|
524
|
+
"""
|
|
525
|
+
List all services registered with a nameserver.
|
|
526
|
+
"""
|
|
527
|
+
from pyrolab.api import locate_ns
|
|
528
|
+
|
|
529
|
+
ns = locate_ns(host=host, port=port)
|
|
530
|
+
services = ns.list(return_metadata=True)
|
|
531
|
+
|
|
532
|
+
listing = []
|
|
533
|
+
for k, v in services.items():
|
|
534
|
+
name = k
|
|
535
|
+
uri, description = v
|
|
536
|
+
listing.append([name, uri, ": ".join(description)])
|
|
537
|
+
|
|
538
|
+
typer.echo(tabulate(listing, headers=["NAME", "URI", "DESCRIPTION"]))
|
|
539
|
+
|
|
540
|
+
|
|
419
541
|
###############################################################################
|
|
420
542
|
# pyrolab restart
|
|
421
543
|
###############################################################################
|