PyDIET 0.9.3__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.
- pydiet/__init__.py +12 -0
- pydiet/api_client/__init__.py +6 -0
- pydiet/api_client/client.py +57 -0
- pydiet/cmd/__init__.py +9 -0
- pydiet/cmd/start.py +107 -0
- pydiet/data/data_config.toml +242 -0
- pydiet/data/description.txt +1 -0
- pydiet/data/instruments/description.txt +1 -0
- pydiet/data/instruments/megacam/default +0 -0
- pydiet/data/instruments/megacam/description.txt +1 -0
- pydiet/data/instruments/megacam/detector/description.txt +1 -0
- pydiet/data/instruments/megacam/detector/qe/MegaCam_QE.average.fits +0 -0
- pydiet/data/instruments/megacam/detector/qe/description.txt +2 -0
- pydiet/data/instruments/megacam/filters/CaHK.MP9303.fits +0 -0
- pydiet/data/instruments/megacam/filters/Ha.MP9603.fits +0 -0
- pydiet/data/instruments/megacam/filters/HaOFF.MP9604.fits +0 -0
- pydiet/data/instruments/megacam/filters/M4112.MP9403.fits +0 -0
- pydiet/data/instruments/megacam/filters/M4376.MP9404.fits +0 -0
- pydiet/data/instruments/megacam/filters/OIII.MP9501.fits +0 -0
- pydiet/data/instruments/megacam/filters/OIIIOFF.MP9502.fits +0 -0
- pydiet/data/instruments/megacam/filters/description.txt +1 -0
- pydiet/data/instruments/megacam/filters/g.MP9402.fits +0 -0
- pydiet/data/instruments/megacam/filters/gri.MP9605.fits +0 -0
- pydiet/data/instruments/megacam/filters/i.MP9703.fits +0 -0
- pydiet/data/instruments/megacam/filters/r.MP9602.fits +0 -0
- pydiet/data/instruments/megacam/filters/u.MP9302.fits +0 -0
- pydiet/data/instruments/megacam/filters/z.MP9901.fits +0 -0
- pydiet/data/instruments/megacam/optics/description.txt +2 -0
- pydiet/data/instruments/megacam/optics/transmission/MegaPrime_transmission.fits +0 -0
- pydiet/data/instruments/megacam/optics/transmission/description.txt +2 -0
- pydiet/data/instruments/wircam/description.txt +1 -0
- pydiet/data/instruments/wircam/detector/description.txt +1 -0
- pydiet/data/instruments/wircam/detector/qe/WIRCam_QE.average.fits +0 -0
- pydiet/data/instruments/wircam/detector/qe/description.txt +2 -0
- pydiet/data/instruments/wircam/filters/BrG.WC8305.fits +0 -0
- pydiet/data/instruments/wircam/filters/CH4Off.WC8204.fits +0 -0
- pydiet/data/instruments/wircam/filters/CH4On.WC8203.fits +0 -0
- pydiet/data/instruments/wircam/filters/CO.WC8306.fits +0 -0
- pydiet/data/instruments/wircam/filters/H.WC8201.fits +0 -0
- pydiet/data/instruments/wircam/filters/H.WC8202.fits +0 -0
- pydiet/data/instruments/wircam/filters/H2.WC8304.fits +0 -0
- pydiet/data/instruments/wircam/filters/J.WC8101.fits +0 -0
- pydiet/data/instruments/wircam/filters/J.WC8103.fits +0 -0
- pydiet/data/instruments/wircam/filters/Kcont.WC8303.fits +0 -0
- pydiet/data/instruments/wircam/filters/Ks.WC8301.fits +0 -0
- pydiet/data/instruments/wircam/filters/Ks.WC8302.fits +0 -0
- pydiet/data/instruments/wircam/filters/LowOH1.WC8104.fits +0 -0
- pydiet/data/instruments/wircam/filters/LowOH2.WC8102.fits +0 -0
- pydiet/data/instruments/wircam/filters/W.WC8105.fits +0 -0
- pydiet/data/instruments/wircam/filters/Y.WC8002.fits +0 -0
- pydiet/data/instruments/wircam/filters/description.txt +1 -0
- pydiet/data/instruments/wircam/optics/description.txt +2 -0
- pydiet/data/instruments/wircam/optics/transmission/WIRCam_transmission.fits +0 -0
- pydiet/data/instruments/wircam/optics/transmission/description.txt +2 -0
- pydiet/data/sites/description.txt +1 -0
- pydiet/data/sites/mko/default +0 -0
- pydiet/data/sites/mko/description.txt +2 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.0.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.1.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.2.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.3.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.4.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.5.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.6.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.7.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.8.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.9.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM2.0.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM2.5.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.bright.AM3.0.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.0.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.1.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.2.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.3.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.4.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.5.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.6.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.7.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.8.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.9.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM2.0.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM2.5.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.dark.AM3.0.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.0.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.1.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.2.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.3.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.4.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.5.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.6.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.7.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.8.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.9.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM2.0.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM2.5.fits +0 -0
- pydiet/data/sites/mko/emission/MKO_emission.grey.AM3.0.fits +0 -0
- pydiet/data/sites/mko/emission/description.txt +5 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM1.0.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM1.1.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM1.2.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM1.3.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM1.4.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM1.5.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM1.6.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM1.7.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM1.8.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM1.9.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM2.0.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM2.5.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM3.0.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM3.5.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM4.0.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM4.5.fits +0 -0
- pydiet/data/sites/mko/transmission/MKO_transmission.AM5.0.fits +0 -0
- pydiet/data/sites/mko/transmission/description.txt +5 -0
- pydiet/data/telescopes/cfht/default +0 -0
- pydiet/data/telescopes/cfht/description.txt +1 -0
- pydiet/data/telescopes/cfht/emission/description.txt +2 -0
- pydiet/data/telescopes/cfht/transmission/CFHT_M1_transmission.fits +0 -0
- pydiet/data/telescopes/cfht/transmission/description.txt +1 -0
- pydiet/data/telescopes/description.txt +1 -0
- pydiet/package.py +55 -0
- pydiet/py.typed +0 -0
- pydiet/server/__init__.py +9 -0
- pydiet/server/app.py +369 -0
- pydiet/server/config/__init__.py +51 -0
- pydiet/server/config/config.py +330 -0
- pydiet/server/config/fields.py +49 -0
- pydiet/server/config/settings.py +166 -0
- pydiet/server/data.py +31 -0
- pydiet/server/datafiles.py +367 -0
- pydiet/server/image.py +342 -0
- pydiet/server/models/__init__.py +34 -0
- pydiet/server/models/dataconfig.py +195 -0
- pydiet/server/models/default.py +9 -0
- pydiet/server/models/exceptions.py +9 -0
- pydiet/server/models/instrument.py +314 -0
- pydiet/server/models/query.py +172 -0
- pydiet/server/models/response.py +97 -0
- pydiet/server/models/types.py +35 -0
- pydiet/server/photsys.py +71 -0
- pydiet/server/response.py +237 -0
- pydiet/server/types/__init__.py +8 -0
- pydiet/server/types/quantity.py +532 -0
- pydiet/server/types/string.py +318 -0
- pydiet/templates/common/base.html +80 -0
- pydiet/templates/common/plot_filter.html +17 -0
- pydiet/templates/common/privacy.html +132 -0
- pydiet/templates/common/settings.html +23 -0
- pydiet/templates/common/terms.html +101 -0
- pydiet/templates/megacam/etc_form.html +319 -0
- pydiet/templates/megacam/etc_results.html +190 -0
- pydiet/templates/wircam/etc_form.html +319 -0
- pydiet/templates/wircam/etc_results.html +190 -0
- pydiet/web_client/css/style.css +221 -0
- pydiet/web_client/dist/pydiet.js +31 -0
- pydiet/web_client/images/logo.svg +6 -0
- pydiet/web_client/images/megacam/background.jpg +0 -0
- pydiet/web_client/images/megacam/logo.png +0 -0
- pydiet/web_client/images/wircam/background.jpg +0 -0
- pydiet/web_client/images/wircam/logo.png +0 -0
- pydiet/web_client/js/dom.js +51 -0
- pydiet/web_client/js/etc.js +63 -0
- pydiet/web_client/js/fetch.js +49 -0
- pydiet/web_client/js/instrument.js +62 -0
- pydiet/web_client/js/main.js +15 -0
- pydiet/web_client/js/plot.js +88 -0
- pydiet/web_client/js/settings.js +57 -0
- pydiet/web_client/js/theme.js +43 -0
- pydiet/web_client/js/url.js +12 -0
- pydiet/web_client/jsdoc.json +20 -0
- pydiet/web_client/package.json +83 -0
- pydiet-0.9.3.dist-info/METADATA +118 -0
- pydiet-0.9.3.dist-info/RECORD +177 -0
- pydiet-0.9.3.dist-info/WHEEL +4 -0
- pydiet-0.9.3.dist-info/entry_points.txt +5 -0
- pydiet-0.9.3.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configure application.
|
|
3
|
+
"""
|
|
4
|
+
# Copyright CEA/CFHT/CNRS/UParisSaclay
|
|
5
|
+
# Licensed under the MIT licence
|
|
6
|
+
|
|
7
|
+
from argparse import ArgumentParser, SUPPRESS
|
|
8
|
+
from configparser import ConfigParser
|
|
9
|
+
from os import environ, makedirs, path
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from pprint import pprint
|
|
12
|
+
from sys import exit, modules
|
|
13
|
+
from time import localtime, strftime
|
|
14
|
+
from typing import Tuple
|
|
15
|
+
|
|
16
|
+
from astropy import units as u #type: ignore[import-untyped]
|
|
17
|
+
from pydantic import ValidationError
|
|
18
|
+
|
|
19
|
+
from ... import package
|
|
20
|
+
from ..types import str_to_quantity_array
|
|
21
|
+
from .settings import AppSettings
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Config(object):
|
|
25
|
+
"""
|
|
26
|
+
Manage application settings in groups.
|
|
27
|
+
|
|
28
|
+
Settings are stored as Pydantic fields.
|
|
29
|
+
"""
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
settings: AppSettings,
|
|
33
|
+
args: bool=True,
|
|
34
|
+
config_file: str=package.config_file):
|
|
35
|
+
|
|
36
|
+
self.settings = settings
|
|
37
|
+
self.groups = tuple(self.settings.dict().keys())
|
|
38
|
+
self.image_filename = None
|
|
39
|
+
self.config_filename = config_file
|
|
40
|
+
|
|
41
|
+
# Skip argument parsing if Sphinx or pytest are involved
|
|
42
|
+
if "PYTEST_CURRENT_TEST" in environ or \
|
|
43
|
+
"COVERAGE_RUN" in environ or \
|
|
44
|
+
"COVERAGE_PROCESS_START" in environ or \
|
|
45
|
+
environ.get("IN_SPHINX_BUILD") == "1":
|
|
46
|
+
args = False
|
|
47
|
+
if args:
|
|
48
|
+
args_dict = self.parse_args()
|
|
49
|
+
if args_dict['version']:
|
|
50
|
+
print(f"{package.title} {package.version}")
|
|
51
|
+
exit(0)
|
|
52
|
+
# Save configuration file if requested
|
|
53
|
+
if args_dict['save_config']:
|
|
54
|
+
# Create config dir if it does not exist
|
|
55
|
+
makedirs(path.dirname(self.config_filename), exist_ok=True)
|
|
56
|
+
self.save_config(self.config_filename)
|
|
57
|
+
exit(0)
|
|
58
|
+
self.config_filename = args_dict['config']
|
|
59
|
+
|
|
60
|
+
# Parse configuration file
|
|
61
|
+
if Path(self.config_filename).exists():
|
|
62
|
+
config_dict = self.parse_config(self.config_filename)
|
|
63
|
+
# Update settings from the config file
|
|
64
|
+
self.update_from_dict(config_dict)
|
|
65
|
+
|
|
66
|
+
# Update settings from the command line (overriding config file values)
|
|
67
|
+
if args:
|
|
68
|
+
self.update_from_dict(args_dict)
|
|
69
|
+
if args_dict['show_config']:
|
|
70
|
+
pprint(self.flat_dict())
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def grouped_dict(self) -> dict:
|
|
74
|
+
"""
|
|
75
|
+
Return a dictionary of all settings, organized in groups.
|
|
76
|
+
|
|
77
|
+
Returns
|
|
78
|
+
-------
|
|
79
|
+
gdict: dict
|
|
80
|
+
Dictionary of settings.
|
|
81
|
+
"""
|
|
82
|
+
return self.settings.dict()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def flat_dict(self) -> dict:
|
|
86
|
+
"""
|
|
87
|
+
Return a flattened dictionary of all settings.
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
fdict: dict
|
|
92
|
+
Dictionary of settings.
|
|
93
|
+
"""
|
|
94
|
+
fdict = {}
|
|
95
|
+
for group in self.groups:
|
|
96
|
+
settings = getattr(self.settings, group).dict()
|
|
97
|
+
for setting in settings:
|
|
98
|
+
fdict[setting] = settings[setting]
|
|
99
|
+
return fdict
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def schema(self) -> dict:
|
|
103
|
+
"""
|
|
104
|
+
Return a schema of the settings as a dictionary.
|
|
105
|
+
|
|
106
|
+
Returns
|
|
107
|
+
-------
|
|
108
|
+
schema: dict
|
|
109
|
+
Schema of the settings, as a dictionary.
|
|
110
|
+
"""
|
|
111
|
+
return self.settings.schema()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def schema_json(self, indent=2) -> str:
|
|
115
|
+
"""
|
|
116
|
+
Return a schema of the settings as a JSON string.
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
indent: int
|
|
121
|
+
Number of indentation spaces.
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
schema: str
|
|
126
|
+
JSON schema of the settings.
|
|
127
|
+
"""
|
|
128
|
+
return self.settings.schema_json(indent=indent)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def parse_args(self) -> dict:
|
|
132
|
+
"""
|
|
133
|
+
Return a dictionary of all settings, with values updated from the
|
|
134
|
+
command line.
|
|
135
|
+
|
|
136
|
+
Extra settings are ignored.
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
gdict: dict
|
|
141
|
+
Dictionary of all settings, organized in groups.
|
|
142
|
+
"""
|
|
143
|
+
parser = ArgumentParser(
|
|
144
|
+
description=f"{package.title} v{package.version} : {package.summary}"
|
|
145
|
+
)
|
|
146
|
+
# Add options not relevant to configuration itself
|
|
147
|
+
parser.add_argument(
|
|
148
|
+
"-V", "--version",
|
|
149
|
+
default=False,
|
|
150
|
+
help="Return the version of the package and exit",
|
|
151
|
+
action='store_true'
|
|
152
|
+
)
|
|
153
|
+
parser.add_argument(
|
|
154
|
+
"-c", "--config",
|
|
155
|
+
type=str, default=package.config_file,
|
|
156
|
+
help=f"Configuration filename (default={package.config_file})",
|
|
157
|
+
metavar="FILE"
|
|
158
|
+
)
|
|
159
|
+
parser.add_argument(
|
|
160
|
+
"-s", "--save_config",
|
|
161
|
+
default=False,
|
|
162
|
+
help=f"Save a default {package.title} configuration file and exit",
|
|
163
|
+
action='store_true'
|
|
164
|
+
)
|
|
165
|
+
parser.add_argument(
|
|
166
|
+
"-S", "--show_config",
|
|
167
|
+
default=False,
|
|
168
|
+
help=f"Print the actual {package.title} configuration settings",
|
|
169
|
+
action='store_true'
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
for group in self.groups:
|
|
173
|
+
args_group = parser.add_argument_group(group.title())
|
|
174
|
+
groupsettings = getattr(self.settings, group)
|
|
175
|
+
settings = groupsettings.schema()['properties']
|
|
176
|
+
defaults = groupsettings.dict()
|
|
177
|
+
for setting in settings:
|
|
178
|
+
props = settings[setting]
|
|
179
|
+
arg = ["-" + props['short'], "--" + setting] \
|
|
180
|
+
if props.get('short') else ["--" + setting]
|
|
181
|
+
default = defaults[setting]
|
|
182
|
+
# Booleans don't have units
|
|
183
|
+
help = props.get('description', "")
|
|
184
|
+
if props.get('type', 'unit')=='boolean':
|
|
185
|
+
args_group.add_argument(
|
|
186
|
+
*arg,
|
|
187
|
+
default=SUPPRESS,
|
|
188
|
+
help=props.get('description', ""),
|
|
189
|
+
action='store_true'
|
|
190
|
+
)
|
|
191
|
+
elif props.get('type', 'unit')=='array':
|
|
192
|
+
deftype = type(default[0])
|
|
193
|
+
args_group.add_argument(
|
|
194
|
+
*arg,
|
|
195
|
+
default=SUPPRESS,
|
|
196
|
+
type=(lambda s: tuple([int(val) for val in s.split(',')]))
|
|
197
|
+
if deftype==int
|
|
198
|
+
else (lambda s: tuple([float(val) for val in s.split(',')])),
|
|
199
|
+
help=f"{help} (default={default})"
|
|
200
|
+
)
|
|
201
|
+
elif isinstance(default, u.Quantity):
|
|
202
|
+
args_group.add_argument(
|
|
203
|
+
*arg,
|
|
204
|
+
default=SUPPRESS,
|
|
205
|
+
type=u.Quantity if default.isscalar else str_to_quantity_array,
|
|
206
|
+
help=f"{help} (default={default})"
|
|
207
|
+
)
|
|
208
|
+
else:
|
|
209
|
+
args_group.add_argument(
|
|
210
|
+
*arg,
|
|
211
|
+
default=SUPPRESS,
|
|
212
|
+
type=type(default),
|
|
213
|
+
help=f"{help} (default={default})"
|
|
214
|
+
)
|
|
215
|
+
# Generate dictionary of args grouped by section
|
|
216
|
+
fdict = vars(parser.parse_args())
|
|
217
|
+
gdict = {}
|
|
218
|
+
# Command-line specific arguments
|
|
219
|
+
gdict['version'] = fdict['version']
|
|
220
|
+
gdict['config'] = fdict['config']
|
|
221
|
+
gdict['save_config'] = fdict['save_config']
|
|
222
|
+
gdict['show_config'] = fdict['show_config']
|
|
223
|
+
for group in self.groups:
|
|
224
|
+
gdict[group] = {}
|
|
225
|
+
gdictg = gdict[group]
|
|
226
|
+
settings = getattr(self.settings, group).dict()
|
|
227
|
+
for setting in settings:
|
|
228
|
+
if setting in fdict:
|
|
229
|
+
gdictg[setting] = fdict[setting]
|
|
230
|
+
return gdict
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def parse_config(self, filename: str) -> dict:
|
|
234
|
+
"""
|
|
235
|
+
Return a dictionary of all settings, with values updated from a
|
|
236
|
+
configuration file in INI format.
|
|
237
|
+
|
|
238
|
+
Extra settings are ignored.
|
|
239
|
+
|
|
240
|
+
Parameters
|
|
241
|
+
----------
|
|
242
|
+
filename: str | ~pathlib.Path
|
|
243
|
+
Configuration filename.
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
gdict: dict
|
|
248
|
+
Dictionary of all settings, organized in groups.
|
|
249
|
+
"""
|
|
250
|
+
config = ConfigParser(converters={})
|
|
251
|
+
config.read(filename)
|
|
252
|
+
gdict: dict = {}
|
|
253
|
+
for group in self.groups:
|
|
254
|
+
gdict[group] = {}
|
|
255
|
+
gdictg = gdict[group]
|
|
256
|
+
settings = getattr(self.settings, group).dict()
|
|
257
|
+
for setting in settings:
|
|
258
|
+
if (value := config.get(group, setting, fallback=None)) is not None:
|
|
259
|
+
default = settings[setting]
|
|
260
|
+
stype = type(default)
|
|
261
|
+
gdictg[setting] = tuple(
|
|
262
|
+
type(settings[setting][i])(val.strip()) \
|
|
263
|
+
for i, val in enumerate(value[1:-1].split(','))
|
|
264
|
+
) if stype == tuple \
|
|
265
|
+
else value.lower() in ("yes", "true", "t", "1") if stype == bool \
|
|
266
|
+
else str_to_quantity_array(value) if \
|
|
267
|
+
isinstance(default, u.Quantity) and not default.isscalar \
|
|
268
|
+
else stype(value)
|
|
269
|
+
return gdict
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def save_config(self, filename) -> None:
|
|
273
|
+
"""
|
|
274
|
+
Save all settings as a configuration file in INI format.
|
|
275
|
+
|
|
276
|
+
Extra settings are ignored.
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
filename: str | ~pathlib.Path
|
|
281
|
+
Configuration filename.
|
|
282
|
+
"""
|
|
283
|
+
config = ConfigParser()
|
|
284
|
+
for group in self.groups:
|
|
285
|
+
config[group] = {}
|
|
286
|
+
settings = getattr(self.settings, group).dict()
|
|
287
|
+
for setting in settings:
|
|
288
|
+
props = f"{settings[setting]}"
|
|
289
|
+
config[group][setting] = props
|
|
290
|
+
|
|
291
|
+
# Ask confirmation if file already exists
|
|
292
|
+
if Path(filename).exists():
|
|
293
|
+
user_input = input(
|
|
294
|
+
f"This will overwrite {filename}! Continue? [y/N]"
|
|
295
|
+
)
|
|
296
|
+
if user_input.lower() not in ('y', 'yes'):
|
|
297
|
+
return
|
|
298
|
+
|
|
299
|
+
with open(filename, 'w') as config_file:
|
|
300
|
+
config_file.write(f"; Default {package.title} configuration file\n")
|
|
301
|
+
nowstr = strftime("%a, %d %b %Y %H:%M:%S %z", localtime())
|
|
302
|
+
config_file.write(f"; {nowstr}\n")
|
|
303
|
+
config.write(config_file)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def update_from_dict(self, settings_dict) -> None:
|
|
307
|
+
"""
|
|
308
|
+
Update internal settings based on a dictionary (in groups)
|
|
309
|
+
|
|
310
|
+
Parameters
|
|
311
|
+
----------
|
|
312
|
+
settings_dict: dict
|
|
313
|
+
Input dictionary.
|
|
314
|
+
"""
|
|
315
|
+
for group in self.groups:
|
|
316
|
+
groupsettings = getattr(self.settings, group)
|
|
317
|
+
groupsettings_dict = groupsettings.dict()
|
|
318
|
+
settings = settings_dict[group]
|
|
319
|
+
for setting in settings:
|
|
320
|
+
vars(groupsettings).update({setting: settings_dict[group][setting]})
|
|
321
|
+
try:
|
|
322
|
+
groupsettings.model_validate(groupsettings.dict())
|
|
323
|
+
except ValidationError as valid_exception:
|
|
324
|
+
print(valid_exception)
|
|
325
|
+
exit(1)
|
|
326
|
+
except Exception as other_exception:
|
|
327
|
+
print(other_exception)
|
|
328
|
+
exit(1)
|
|
329
|
+
|
|
330
|
+
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Group and define Pydantic-compatible fields.
|
|
3
|
+
"""
|
|
4
|
+
# Copyright UParisSaclay/CEA/CFHT/CNRS
|
|
5
|
+
# Licensed under the MIT licence
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic import Field
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def SField(short: str | None = None, **kwargs) -> Any:
|
|
16
|
+
"""
|
|
17
|
+
Return Pydantic field with augmented JSON schema including a command-line
|
|
18
|
+
"shortcut" keyword.
|
|
19
|
+
|
|
20
|
+
Examples
|
|
21
|
+
--------
|
|
22
|
+
>>> from pydantic_settings import BaseSettings
|
|
23
|
+
|
|
24
|
+
>>> class Settings(BaseSettings):
|
|
25
|
+
... parameter: float = SField(
|
|
26
|
+
... short='p',
|
|
27
|
+
... description="an arbitrary parameter",
|
|
28
|
+
... default=10.,
|
|
29
|
+
... )
|
|
30
|
+
|
|
31
|
+
>>> s = Settings(parameter=3.)
|
|
32
|
+
|
|
33
|
+
>>> s.model_json_schema()
|
|
34
|
+
{'additionalProperties': False, 'properties': {'parameter': {'default': 10.0,
|
|
35
|
+
'description': 'an arbitrary parameter', 'short': 'p', 'title': 'Parameter',
|
|
36
|
+
'type': 'number'}}, 'title': 'Settings', 'type': 'object'}
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
short: str, optional
|
|
41
|
+
Shortcut for keyword
|
|
42
|
+
**kwargs:
|
|
43
|
+
Additional Field arguments.
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
Pydantic Field with augmented JSON schema.
|
|
47
|
+
"""
|
|
48
|
+
return Field(**kwargs, json_schema_extra={'short': short})
|
|
49
|
+
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration settings for the application.
|
|
3
|
+
"""
|
|
4
|
+
# Copyright CEA/CFHT/CNRS/UParisSaclay
|
|
5
|
+
# Licensed under the MIT licence
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from os import cpu_count, path
|
|
10
|
+
from typing import Any, Literal, Tuple
|
|
11
|
+
|
|
12
|
+
from astropy import units as u #type: ignore[import-untyped]
|
|
13
|
+
import numpy as np
|
|
14
|
+
from pydantic import Field
|
|
15
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
16
|
+
|
|
17
|
+
from ... import package
|
|
18
|
+
from ..types import AnnotatedQuantity
|
|
19
|
+
from .fields import SField
|
|
20
|
+
|
|
21
|
+
# Enable imperial units such as inches
|
|
22
|
+
u.imperial.enable()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class HostSettings(BaseSettings):
|
|
26
|
+
host: str = SField(
|
|
27
|
+
short='H',
|
|
28
|
+
default="localhost",
|
|
29
|
+
description="Host name or IP address"
|
|
30
|
+
)
|
|
31
|
+
port: int = SField(
|
|
32
|
+
short='p',
|
|
33
|
+
default=8010,
|
|
34
|
+
ge=1,
|
|
35
|
+
le=65535,
|
|
36
|
+
description="Port"
|
|
37
|
+
)
|
|
38
|
+
root_path: str = SField(
|
|
39
|
+
short='R',
|
|
40
|
+
default="",
|
|
41
|
+
description="ASGI root_path"
|
|
42
|
+
)
|
|
43
|
+
access_log: bool = SField(
|
|
44
|
+
short='a',
|
|
45
|
+
default=False,
|
|
46
|
+
description="Display access log"
|
|
47
|
+
)
|
|
48
|
+
reload: bool = SField(
|
|
49
|
+
short='r',
|
|
50
|
+
default=False,
|
|
51
|
+
description="Enable auto-reload (turns off multiple workers)"
|
|
52
|
+
)
|
|
53
|
+
workers: int = SField(
|
|
54
|
+
short='w',
|
|
55
|
+
default=4 if package.isonlinux else 1,
|
|
56
|
+
ge=1,
|
|
57
|
+
description="Number of workers"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
model_config = SettingsConfigDict(
|
|
61
|
+
env_prefix = f"{package.name}_",
|
|
62
|
+
extra = 'ignore',
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ServerSettings(BaseSettings):
|
|
67
|
+
api_path : str = SField(
|
|
68
|
+
default="/api",
|
|
69
|
+
description="Endpoint URL for the webservice API"
|
|
70
|
+
)
|
|
71
|
+
banner_template: str = SField(
|
|
72
|
+
default="common/banner.html",
|
|
73
|
+
description="Name of the HTML template file for the service banner"
|
|
74
|
+
)
|
|
75
|
+
base_template: str = SField(
|
|
76
|
+
default="common/base.html",
|
|
77
|
+
description="Name of the HTML template file for the web client"
|
|
78
|
+
)
|
|
79
|
+
browser: bool = SField(
|
|
80
|
+
short='b',
|
|
81
|
+
default=False,
|
|
82
|
+
description="Start browser when launching the server"
|
|
83
|
+
)
|
|
84
|
+
client_dir: str = SField(
|
|
85
|
+
default=path.join(package.src_dir, "web_client"),
|
|
86
|
+
description="Directory containing the web client code, style and media"
|
|
87
|
+
)
|
|
88
|
+
data_config: str = SField(
|
|
89
|
+
default=path.join(package.root_dir, "data", "data_config.toml"),
|
|
90
|
+
description="Data configuration filename"
|
|
91
|
+
)
|
|
92
|
+
data_dir: str = SField(
|
|
93
|
+
default=path.join(package.root_dir, "data"),
|
|
94
|
+
description="Data root directory"
|
|
95
|
+
)
|
|
96
|
+
doc_dir: str = SField(
|
|
97
|
+
default=path.join(package.root_dir, "doc/html"),
|
|
98
|
+
description="HTML documentation root directory (after build)"
|
|
99
|
+
)
|
|
100
|
+
doc_path: str = SField(
|
|
101
|
+
default="/manual",
|
|
102
|
+
description="Endpoint URL for the root of the HTML documentation"
|
|
103
|
+
)
|
|
104
|
+
extra_dir: str = SField(
|
|
105
|
+
default=".",
|
|
106
|
+
description="Extra data root directory"
|
|
107
|
+
)
|
|
108
|
+
template_dir: str = SField(
|
|
109
|
+
default=path.join(package.src_dir, "templates"),
|
|
110
|
+
description="Directory containing templates"
|
|
111
|
+
)
|
|
112
|
+
userdoc_url: str = SField(
|
|
113
|
+
default = doc_path.default + "/index.html", #type: ignore
|
|
114
|
+
description="Endpoint URL for the user's HTML documentation"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
model_config = SettingsConfigDict(
|
|
118
|
+
env_prefix = f"{package.name}_",
|
|
119
|
+
extra = 'ignore',
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
ncpu = cpu_count()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class EngineSettings(BaseSettings):
|
|
129
|
+
thread_count: int = SField(
|
|
130
|
+
short='t',
|
|
131
|
+
default = ncpu // 2 if ncpu is not None else 4,
|
|
132
|
+
ge=0,
|
|
133
|
+
le=1024,
|
|
134
|
+
description="Number of engine threads"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
model_config = SettingsConfigDict(
|
|
138
|
+
env_prefix = f"{package.name}_",
|
|
139
|
+
extra = 'ignore',
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class MiscSettings(BaseSettings):
|
|
145
|
+
"""
|
|
146
|
+
Miscellaneous settings.
|
|
147
|
+
"""
|
|
148
|
+
verbose: bool = SField(
|
|
149
|
+
short='v',
|
|
150
|
+
default=True,
|
|
151
|
+
description="Verbose output"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
model_config = SettingsConfigDict(
|
|
155
|
+
env_prefix = f"{package.name}_",
|
|
156
|
+
extra = 'ignore',
|
|
157
|
+
arbitrary_types_allowed = True
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class AppSettings(BaseSettings):
|
|
162
|
+
host: BaseSettings = HostSettings()
|
|
163
|
+
server: BaseSettings = ServerSettings()
|
|
164
|
+
engine: BaseSettings = EngineSettings()
|
|
165
|
+
misc: BaseSettings = MiscSettings()
|
|
166
|
+
|
pydiet/server/data.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Gather data and prepare assets.
|
|
3
|
+
"""
|
|
4
|
+
# Copyright CFHT/CNRS/CEA/UParisSaclay
|
|
5
|
+
# Licensed under the MIT licence
|
|
6
|
+
from astropy import units as u #type: ignore[import-untyped]
|
|
7
|
+
from synphot import ConstFlux1D, SourceSpectrum #type: ignore[import-untyped]
|
|
8
|
+
|
|
9
|
+
from .datafiles import (
|
|
10
|
+
get_data_config,
|
|
11
|
+
get_default,
|
|
12
|
+
get_instruments,
|
|
13
|
+
get_webapi_instruments,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
data_config = get_data_config()
|
|
17
|
+
|
|
18
|
+
instruments = get_instruments(data_config)
|
|
19
|
+
|
|
20
|
+
default_instrument = get_default(instruments)
|
|
21
|
+
winstruments = get_webapi_instruments(instruments)
|
|
22
|
+
|
|
23
|
+
filters = {k:v for key,val in instruments.items() for k,v in val.filters.transmissions.items()}
|
|
24
|
+
default_filter = get_default(default_instrument.filters.transmissions)
|
|
25
|
+
|
|
26
|
+
# Load reference spectra
|
|
27
|
+
ab_spectrum = SourceSpectrum(ConstFlux1D, amplitude = 0.*u.ABmag)
|
|
28
|
+
st_spectrum = SourceSpectrum(ConstFlux1D, amplitude = 0.*u.STmag)
|
|
29
|
+
vega_spectrum = SourceSpectrum.from_vega()
|
|
30
|
+
|
|
31
|
+
|