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 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, 7, 0):
33
+ if pyversion < (3, 11, 0):
40
34
  raise Exception(
41
- "PyroLab requires Python 3.7+ (version "
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
- __name__ = "PyroLab"
42
+ __title__ = "PyroLab"
49
43
  __author__ = "CamachoLab"
50
44
  __copyright__ = "Copyright 2020, The PyroLab Project"
51
- __version__ = "0.4.0"
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
- # Filter warnings
62
- import warnings
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
- # 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()
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
- cfg = PyroLabConfiguration.from_file(USER_CONFIG_FILE)
53
- nscfg = next(iter(cfg.nameservers.values()))
54
- nscfg.update_pyro_config()
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
+ )