PyroLab 0.4.0__py3-none-any.whl → 0.5.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 +40 -140
- pyrolab/api.py +15 -3
- pyrolab/cli.py +462 -185
- pyrolab/configure.py +221 -36
- pyrolab/drivers/__init__.py +32 -8
- pyrolab/drivers/arduino/arduino.py +42 -5
- pyrolab/drivers/cameras/amcam.py +9 -9
- pyrolab/drivers/cameras/sciTSI.py +2 -2
- pyrolab/drivers/cameras/thorcam.py +3 -5
- pyrolab/drivers/cameras/uc480.py +2 -3
- pyrolab/drivers/fpgas/analogdiscovery.py +2 -3
- pyrolab/drivers/lasers/ppcl55x.py +57 -43
- pyrolab/drivers/lasers/tsl550.py +14 -11
- pyrolab/drivers/motion/kinesis/bpc303.py +0 -1
- pyrolab/drivers/motion/kinesis/kdc101.py +10 -10
- pyrolab/drivers/motion/max31x.py +0 -1
- pyrolab/drivers/sample.py +3 -3
- pyrolab/drivers/scopes/rohdeschwarz.py +19 -11
- pyrolab/locations.py +129 -0
- pyrolab/logs.py +391 -0
- pyrolab/manager.py +835 -260
- pyrolab/nameserver.py +35 -14
- pyrolab/pyrolabd.py +254 -56
- pyrolab/server.py +41 -5
- pyrolab/service.py +0 -1
- pyrolab/updates.py +108 -0
- pyrolab/utils.py +167 -7
- {pyrolab-0.4.0.dist-info → pyrolab-0.5.0.dist-info}/METADATA +59 -57
- pyrolab-0.5.0.dist-info/RECORD +51 -0
- {pyrolab-0.4.0.dist-info → pyrolab-0.5.0.dist-info}/WHEEL +1 -1
- pyrolab-0.5.0.dist-info/entry_points.txt +2 -0
- pyrolab-0.4.0.dist-info/RECORD +0 -48
- pyrolab-0.4.0.dist-info/entry_points.txt +0 -2
- {pyrolab-0.4.0.dist-info → pyrolab-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {pyrolab-0.4.0.dist-info → pyrolab-0.5.0.dist-info}/licenses/NOTICE.txt +0 -0
- {pyrolab-0.4.0.dist-info → pyrolab-0.5.0.dist-info}/top_level.txt +0 -0
pyrolab/__init__.py
CHANGED
|
@@ -23,32 +23,32 @@ PyroLab
|
|
|
23
23
|
A framework for using remote lab instruments as local resources built on Pyro5.
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
+
import logging
|
|
26
27
|
import os
|
|
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
|
|
36
30
|
|
|
37
31
|
# Check if Python version is supported
|
|
38
32
|
pyversion = sys.version_info
|
|
39
|
-
if pyversion < (3,
|
|
33
|
+
if pyversion < (3, 11, 0):
|
|
40
34
|
raise Exception(
|
|
41
|
-
"PyroLab requires Python 3.
|
|
35
|
+
"PyroLab requires Python 3.11+ (version "
|
|
42
36
|
+ platform.python_version()
|
|
43
37
|
+ " detected)."
|
|
44
38
|
)
|
|
45
39
|
|
|
46
40
|
|
|
47
41
|
# Metadata
|
|
48
|
-
|
|
42
|
+
__title__ = "PyroLab"
|
|
49
43
|
__author__ = "CamachoLab"
|
|
50
44
|
__copyright__ = "Copyright 2020, The PyroLab Project"
|
|
51
|
-
|
|
45
|
+
# pyproject.toml is the single source of the version (see `uv version`).
|
|
46
|
+
import importlib.metadata as _metadata
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
__version__ = _metadata.version("pyrolab")
|
|
50
|
+
except _metadata.PackageNotFoundError: # source tree that isn't installed
|
|
51
|
+
__version__ = "0.0.0+unknown"
|
|
52
52
|
__license__ = "GPLv3+"
|
|
53
53
|
__maintainer__ = "Sequoia Ploeg"
|
|
54
54
|
__maintainer_email__ = "sequoia.ploeg@byu.edu"
|
|
@@ -58,133 +58,33 @@ __forum_url__ = "https://github.com/sequoiap/pyrolab/issues"
|
|
|
58
58
|
__website_url__ = "https://camacholab.byu.edu/"
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
#
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
warnings.filterwarnings("default", category=DeprecationWarning)
|
|
65
|
-
if "PYROLAB_HUSH_DEPRECATION" in os.environ:
|
|
66
|
-
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
# Configuration directories
|
|
70
|
-
# Old api deprecated in 3.11, new api added in 3.9
|
|
71
|
-
if pyversion < (3, 9, 0):
|
|
72
|
-
base_path = pathlib.Path(__file__).resolve().parent
|
|
73
|
-
else:
|
|
74
|
-
from importlib.resources import files
|
|
75
|
-
|
|
76
|
-
base_path = files("pyrolab")
|
|
77
|
-
base_path = base_path / "data" / "local"
|
|
78
|
-
|
|
79
|
-
# Data directories
|
|
80
|
-
PYROLAB_DATA_DIR = pathlib.Path(base_path)
|
|
81
|
-
PYROLAB_DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
82
|
-
|
|
83
|
-
NAMESERVER_STORAGE = PYROLAB_DATA_DIR / "nameserver"
|
|
84
|
-
NAMESERVER_STORAGE.mkdir(parents=True, exist_ok=True)
|
|
85
|
-
|
|
86
|
-
PYROLAB_LOGDIR = PYROLAB_DATA_DIR / "logs"
|
|
87
|
-
PYROLAB_LOGDIR.mkdir(parents=True, exist_ok=True)
|
|
88
|
-
PYROLAB_MASTERLOG = PYROLAB_LOGDIR / "pyrolab.log"
|
|
89
|
-
|
|
90
|
-
LOCKFILE = PYROLAB_DATA_DIR / "pyrolabd.lock"
|
|
91
|
-
USER_CONFIG_FILE = PYROLAB_DATA_DIR / "user_configuration.yaml"
|
|
92
|
-
RUNTIME_CONFIG = PYROLAB_DATA_DIR / "runtime_config.yaml"
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
# Set up logging to file
|
|
96
|
-
import logging
|
|
97
|
-
import logging.handlers
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
def get_loglevel() -> int:
|
|
101
|
-
loglevel = os.getenv("PYROLAB_LOGLEVEL", "INFO")
|
|
102
|
-
try:
|
|
103
|
-
loglevel = getattr(logging, loglevel.upper())
|
|
104
|
-
except AttributeError:
|
|
105
|
-
loglevel = logging.INFO
|
|
106
|
-
return loglevel
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if len(logging.root.handlers) == 0:
|
|
110
|
-
logfile = PYROLAB_LOGDIR / f"pyrolab_{os.getpid()}.log"
|
|
111
|
-
root = logging.getLogger()
|
|
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
|
-
)
|
|
117
|
-
h.setFormatter(f)
|
|
118
|
-
root.addHandler(h)
|
|
119
|
-
root.setLevel(get_loglevel())
|
|
120
|
-
root.debug("PyroLab logging configured")
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
# Include remote traceback in local tracebacks
|
|
124
|
-
import Pyro5.errors
|
|
125
|
-
|
|
126
|
-
sys.excepthook = Pyro5.errors.excepthook
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
# Check for updates to PyroLab
|
|
130
|
-
try:
|
|
131
|
-
import json
|
|
132
|
-
import requests
|
|
133
|
-
from requests.adapters import HTTPAdapter
|
|
134
|
-
from packaging.version import parse
|
|
135
|
-
|
|
136
|
-
url = "https://pypi.org/pypi/pyrolab/json"
|
|
137
|
-
with requests.Session() as s:
|
|
138
|
-
s.mount("https://pypi.org", HTTPAdapter(max_retries=3))
|
|
139
|
-
resp = s.get(url).text
|
|
140
|
-
|
|
141
|
-
v = json.loads(resp)["info"]["version"]
|
|
142
|
-
curver = parse(__version__)
|
|
143
|
-
latest = parse(v)
|
|
144
|
-
|
|
145
|
-
log = logging.getLogger(__name__)
|
|
146
|
-
|
|
147
|
-
if curver < latest:
|
|
148
|
-
message = f"A new version of PyroLab is available (latest is {latest}, but {curver} is installed)."
|
|
149
|
-
warnings.warn(message, stacklevel=2)
|
|
150
|
-
log.info(message)
|
|
151
|
-
except:
|
|
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.
|
|
61
|
+
# Where PyroLab keeps its files: per-user directories (see pyrolab.locations).
|
|
62
|
+
# Importing creates nothing; files and directories are created when written.
|
|
63
|
+
from pathlib import Path
|
|
171
64
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
#
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
#
|
|
188
|
-
|
|
189
|
-
#
|
|
190
|
-
|
|
65
|
+
from pyrolab.locations import LEGACY_DATA_DIR
|
|
66
|
+
from pyrolab.locations import locations as _find_locations
|
|
67
|
+
|
|
68
|
+
_locations = _find_locations()
|
|
69
|
+
|
|
70
|
+
PYROLAB_DATA_DIR = _locations.data_dir
|
|
71
|
+
NAMESERVER_STORAGE = _locations.data_dir / "nameserver"
|
|
72
|
+
PYROLAB_LOGDIR = _locations.log_dir
|
|
73
|
+
# The daemon's single log file; PYROLAB_LOGFILE overrides where it goes.
|
|
74
|
+
PYROLAB_LOGFILE = Path(
|
|
75
|
+
os.environ.get("PYROLAB_LOGFILE") or PYROLAB_LOGDIR / "pyrolab.log"
|
|
76
|
+
)
|
|
77
|
+
USER_CONFIG_FILE = _locations.config_dir / "user_configuration.yaml"
|
|
78
|
+
LOCKFILE = _locations.state_dir / "pyrolabd.lock"
|
|
79
|
+
RUNTIME_CONFIG = _locations.state_dir / "runtime_config.yaml"
|
|
80
|
+
# Why the background daemon exited before it could serve requests, for `up`.
|
|
81
|
+
STARTUP_ERROR_FILE = _locations.state_dir / "pyrolabd_startup_error.txt"
|
|
82
|
+
# Records that files from a pre-0.5 install were copied (see pyrolab.locations).
|
|
83
|
+
LEGACY_MIGRATION_MARKER = _locations.state_dir / "legacy_files_copied.txt"
|
|
84
|
+
UPDATE_CHECK_FILE = _locations.cache_dir / "update_check.json"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# As a library, PyroLab leaves logging to the application: its loggers do
|
|
88
|
+
# nothing until the application (or PyroLab's own CLI and daemon, see
|
|
89
|
+
# pyrolab.logs) configures logging.
|
|
90
|
+
logging.getLogger(__name__).addHandler(logging.NullHandler())
|
pyrolab/api.py
CHANGED
|
@@ -48,7 +48,19 @@ __all__ = [
|
|
|
48
48
|
|
|
49
49
|
# If a user config file exists, load the first listed nameserver by default,
|
|
50
50
|
# so that locate_ns "just works."
|
|
51
|
+
# This is a convenience only: importing the API must never fail because the
|
|
52
|
+
# local configuration is invalid, has no nameservers, or names a "public"
|
|
53
|
+
# host that can't be resolved -- otherwise neither client scripts nor the
|
|
54
|
+
# CLI commands that would fix the configuration could even start.
|
|
51
55
|
if USER_CONFIG_FILE.exists():
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
try:
|
|
57
|
+
cfg = PyroLabConfiguration.from_file(USER_CONFIG_FILE)
|
|
58
|
+
nscfg = next(iter(cfg.nameservers.values()), None)
|
|
59
|
+
if nscfg is not None:
|
|
60
|
+
nscfg.update_pyro_config()
|
|
61
|
+
except Exception as e:
|
|
62
|
+
import logging
|
|
63
|
+
|
|
64
|
+
logging.getLogger(__name__).warning(
|
|
65
|
+
"Not applying nameserver defaults from %s: %s", USER_CONFIG_FILE, e
|
|
66
|
+
)
|