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.
Files changed (177) hide show
  1. pydiet/__init__.py +12 -0
  2. pydiet/api_client/__init__.py +6 -0
  3. pydiet/api_client/client.py +57 -0
  4. pydiet/cmd/__init__.py +9 -0
  5. pydiet/cmd/start.py +107 -0
  6. pydiet/data/data_config.toml +242 -0
  7. pydiet/data/description.txt +1 -0
  8. pydiet/data/instruments/description.txt +1 -0
  9. pydiet/data/instruments/megacam/default +0 -0
  10. pydiet/data/instruments/megacam/description.txt +1 -0
  11. pydiet/data/instruments/megacam/detector/description.txt +1 -0
  12. pydiet/data/instruments/megacam/detector/qe/MegaCam_QE.average.fits +0 -0
  13. pydiet/data/instruments/megacam/detector/qe/description.txt +2 -0
  14. pydiet/data/instruments/megacam/filters/CaHK.MP9303.fits +0 -0
  15. pydiet/data/instruments/megacam/filters/Ha.MP9603.fits +0 -0
  16. pydiet/data/instruments/megacam/filters/HaOFF.MP9604.fits +0 -0
  17. pydiet/data/instruments/megacam/filters/M4112.MP9403.fits +0 -0
  18. pydiet/data/instruments/megacam/filters/M4376.MP9404.fits +0 -0
  19. pydiet/data/instruments/megacam/filters/OIII.MP9501.fits +0 -0
  20. pydiet/data/instruments/megacam/filters/OIIIOFF.MP9502.fits +0 -0
  21. pydiet/data/instruments/megacam/filters/description.txt +1 -0
  22. pydiet/data/instruments/megacam/filters/g.MP9402.fits +0 -0
  23. pydiet/data/instruments/megacam/filters/gri.MP9605.fits +0 -0
  24. pydiet/data/instruments/megacam/filters/i.MP9703.fits +0 -0
  25. pydiet/data/instruments/megacam/filters/r.MP9602.fits +0 -0
  26. pydiet/data/instruments/megacam/filters/u.MP9302.fits +0 -0
  27. pydiet/data/instruments/megacam/filters/z.MP9901.fits +0 -0
  28. pydiet/data/instruments/megacam/optics/description.txt +2 -0
  29. pydiet/data/instruments/megacam/optics/transmission/MegaPrime_transmission.fits +0 -0
  30. pydiet/data/instruments/megacam/optics/transmission/description.txt +2 -0
  31. pydiet/data/instruments/wircam/description.txt +1 -0
  32. pydiet/data/instruments/wircam/detector/description.txt +1 -0
  33. pydiet/data/instruments/wircam/detector/qe/WIRCam_QE.average.fits +0 -0
  34. pydiet/data/instruments/wircam/detector/qe/description.txt +2 -0
  35. pydiet/data/instruments/wircam/filters/BrG.WC8305.fits +0 -0
  36. pydiet/data/instruments/wircam/filters/CH4Off.WC8204.fits +0 -0
  37. pydiet/data/instruments/wircam/filters/CH4On.WC8203.fits +0 -0
  38. pydiet/data/instruments/wircam/filters/CO.WC8306.fits +0 -0
  39. pydiet/data/instruments/wircam/filters/H.WC8201.fits +0 -0
  40. pydiet/data/instruments/wircam/filters/H.WC8202.fits +0 -0
  41. pydiet/data/instruments/wircam/filters/H2.WC8304.fits +0 -0
  42. pydiet/data/instruments/wircam/filters/J.WC8101.fits +0 -0
  43. pydiet/data/instruments/wircam/filters/J.WC8103.fits +0 -0
  44. pydiet/data/instruments/wircam/filters/Kcont.WC8303.fits +0 -0
  45. pydiet/data/instruments/wircam/filters/Ks.WC8301.fits +0 -0
  46. pydiet/data/instruments/wircam/filters/Ks.WC8302.fits +0 -0
  47. pydiet/data/instruments/wircam/filters/LowOH1.WC8104.fits +0 -0
  48. pydiet/data/instruments/wircam/filters/LowOH2.WC8102.fits +0 -0
  49. pydiet/data/instruments/wircam/filters/W.WC8105.fits +0 -0
  50. pydiet/data/instruments/wircam/filters/Y.WC8002.fits +0 -0
  51. pydiet/data/instruments/wircam/filters/description.txt +1 -0
  52. pydiet/data/instruments/wircam/optics/description.txt +2 -0
  53. pydiet/data/instruments/wircam/optics/transmission/WIRCam_transmission.fits +0 -0
  54. pydiet/data/instruments/wircam/optics/transmission/description.txt +2 -0
  55. pydiet/data/sites/description.txt +1 -0
  56. pydiet/data/sites/mko/default +0 -0
  57. pydiet/data/sites/mko/description.txt +2 -0
  58. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.0.fits +0 -0
  59. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.1.fits +0 -0
  60. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.2.fits +0 -0
  61. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.3.fits +0 -0
  62. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.4.fits +0 -0
  63. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.5.fits +0 -0
  64. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.6.fits +0 -0
  65. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.7.fits +0 -0
  66. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.8.fits +0 -0
  67. pydiet/data/sites/mko/emission/MKO_emission.bright.AM1.9.fits +0 -0
  68. pydiet/data/sites/mko/emission/MKO_emission.bright.AM2.0.fits +0 -0
  69. pydiet/data/sites/mko/emission/MKO_emission.bright.AM2.5.fits +0 -0
  70. pydiet/data/sites/mko/emission/MKO_emission.bright.AM3.0.fits +0 -0
  71. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.0.fits +0 -0
  72. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.1.fits +0 -0
  73. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.2.fits +0 -0
  74. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.3.fits +0 -0
  75. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.4.fits +0 -0
  76. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.5.fits +0 -0
  77. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.6.fits +0 -0
  78. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.7.fits +0 -0
  79. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.8.fits +0 -0
  80. pydiet/data/sites/mko/emission/MKO_emission.dark.AM1.9.fits +0 -0
  81. pydiet/data/sites/mko/emission/MKO_emission.dark.AM2.0.fits +0 -0
  82. pydiet/data/sites/mko/emission/MKO_emission.dark.AM2.5.fits +0 -0
  83. pydiet/data/sites/mko/emission/MKO_emission.dark.AM3.0.fits +0 -0
  84. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.0.fits +0 -0
  85. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.1.fits +0 -0
  86. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.2.fits +0 -0
  87. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.3.fits +0 -0
  88. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.4.fits +0 -0
  89. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.5.fits +0 -0
  90. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.6.fits +0 -0
  91. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.7.fits +0 -0
  92. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.8.fits +0 -0
  93. pydiet/data/sites/mko/emission/MKO_emission.grey.AM1.9.fits +0 -0
  94. pydiet/data/sites/mko/emission/MKO_emission.grey.AM2.0.fits +0 -0
  95. pydiet/data/sites/mko/emission/MKO_emission.grey.AM2.5.fits +0 -0
  96. pydiet/data/sites/mko/emission/MKO_emission.grey.AM3.0.fits +0 -0
  97. pydiet/data/sites/mko/emission/description.txt +5 -0
  98. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.0.fits +0 -0
  99. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.1.fits +0 -0
  100. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.2.fits +0 -0
  101. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.3.fits +0 -0
  102. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.4.fits +0 -0
  103. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.5.fits +0 -0
  104. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.6.fits +0 -0
  105. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.7.fits +0 -0
  106. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.8.fits +0 -0
  107. pydiet/data/sites/mko/transmission/MKO_transmission.AM1.9.fits +0 -0
  108. pydiet/data/sites/mko/transmission/MKO_transmission.AM2.0.fits +0 -0
  109. pydiet/data/sites/mko/transmission/MKO_transmission.AM2.5.fits +0 -0
  110. pydiet/data/sites/mko/transmission/MKO_transmission.AM3.0.fits +0 -0
  111. pydiet/data/sites/mko/transmission/MKO_transmission.AM3.5.fits +0 -0
  112. pydiet/data/sites/mko/transmission/MKO_transmission.AM4.0.fits +0 -0
  113. pydiet/data/sites/mko/transmission/MKO_transmission.AM4.5.fits +0 -0
  114. pydiet/data/sites/mko/transmission/MKO_transmission.AM5.0.fits +0 -0
  115. pydiet/data/sites/mko/transmission/description.txt +5 -0
  116. pydiet/data/telescopes/cfht/default +0 -0
  117. pydiet/data/telescopes/cfht/description.txt +1 -0
  118. pydiet/data/telescopes/cfht/emission/description.txt +2 -0
  119. pydiet/data/telescopes/cfht/transmission/CFHT_M1_transmission.fits +0 -0
  120. pydiet/data/telescopes/cfht/transmission/description.txt +1 -0
  121. pydiet/data/telescopes/description.txt +1 -0
  122. pydiet/package.py +55 -0
  123. pydiet/py.typed +0 -0
  124. pydiet/server/__init__.py +9 -0
  125. pydiet/server/app.py +369 -0
  126. pydiet/server/config/__init__.py +51 -0
  127. pydiet/server/config/config.py +330 -0
  128. pydiet/server/config/fields.py +49 -0
  129. pydiet/server/config/settings.py +166 -0
  130. pydiet/server/data.py +31 -0
  131. pydiet/server/datafiles.py +367 -0
  132. pydiet/server/image.py +342 -0
  133. pydiet/server/models/__init__.py +34 -0
  134. pydiet/server/models/dataconfig.py +195 -0
  135. pydiet/server/models/default.py +9 -0
  136. pydiet/server/models/exceptions.py +9 -0
  137. pydiet/server/models/instrument.py +314 -0
  138. pydiet/server/models/query.py +172 -0
  139. pydiet/server/models/response.py +97 -0
  140. pydiet/server/models/types.py +35 -0
  141. pydiet/server/photsys.py +71 -0
  142. pydiet/server/response.py +237 -0
  143. pydiet/server/types/__init__.py +8 -0
  144. pydiet/server/types/quantity.py +532 -0
  145. pydiet/server/types/string.py +318 -0
  146. pydiet/templates/common/base.html +80 -0
  147. pydiet/templates/common/plot_filter.html +17 -0
  148. pydiet/templates/common/privacy.html +132 -0
  149. pydiet/templates/common/settings.html +23 -0
  150. pydiet/templates/common/terms.html +101 -0
  151. pydiet/templates/megacam/etc_form.html +319 -0
  152. pydiet/templates/megacam/etc_results.html +190 -0
  153. pydiet/templates/wircam/etc_form.html +319 -0
  154. pydiet/templates/wircam/etc_results.html +190 -0
  155. pydiet/web_client/css/style.css +221 -0
  156. pydiet/web_client/dist/pydiet.js +31 -0
  157. pydiet/web_client/images/logo.svg +6 -0
  158. pydiet/web_client/images/megacam/background.jpg +0 -0
  159. pydiet/web_client/images/megacam/logo.png +0 -0
  160. pydiet/web_client/images/wircam/background.jpg +0 -0
  161. pydiet/web_client/images/wircam/logo.png +0 -0
  162. pydiet/web_client/js/dom.js +51 -0
  163. pydiet/web_client/js/etc.js +63 -0
  164. pydiet/web_client/js/fetch.js +49 -0
  165. pydiet/web_client/js/instrument.js +62 -0
  166. pydiet/web_client/js/main.js +15 -0
  167. pydiet/web_client/js/plot.js +88 -0
  168. pydiet/web_client/js/settings.js +57 -0
  169. pydiet/web_client/js/theme.js +43 -0
  170. pydiet/web_client/js/url.js +12 -0
  171. pydiet/web_client/jsdoc.json +20 -0
  172. pydiet/web_client/package.json +83 -0
  173. pydiet-0.9.3.dist-info/METADATA +118 -0
  174. pydiet-0.9.3.dist-info/RECORD +177 -0
  175. pydiet-0.9.3.dist-info/WHEEL +4 -0
  176. pydiet-0.9.3.dist-info/entry_points.txt +5 -0
  177. 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
+