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,5 @@
1
+ ESO SkyCalc sky emission models computed with default settings at various airmasses except:
2
+ Dark: Moon set at -90 deg elevation.
3
+ Grey: 30% Moon component (66.4 deg phase) at 45 deg elevation and 45 deg distance.
4
+ Bright: 60% Moon component (101.5 deg phase) at 45 deg elevation and 45 deg distance.
5
+
@@ -0,0 +1,5 @@
1
+ Atmospheric transmission at Mauna Kea Observatories at airmass 1.0:
2
+ * Computed by E.Bertin (2025) using MODTRAN6, using the default "tropical" atmosphere and the "rural" aerosol model settings.
3
+ * CO2 concentration set to 430ppm.
4
+ * Pointing upward from an altitude of 4204m.
5
+
File without changes
@@ -0,0 +1 @@
1
+ Thermal SEDs of the Canada-France-Hawaii 3.6m telescope.
@@ -0,0 +1,2 @@
1
+ Emission spectrum for the CFHT telescope.
2
+
@@ -0,0 +1 @@
1
+ Transmission of the CFHT 3.58m primary mirror (bare aluminium, freshly coated) as a function of wavelength.
@@ -0,0 +1 @@
1
+ Telescope data.
pydiet/package.py ADDED
@@ -0,0 +1,55 @@
1
+ """
2
+ Package-wide definitions.
3
+ """
4
+ # Copyright CEA/CFHT/CNRS/UParisSaclay
5
+ # Licensed under the MIT license
6
+
7
+ from re import match
8
+ from os import path
9
+ from pathlib import Path
10
+ from importlib.metadata import metadata
11
+ from importlib.resources import files
12
+ import sys
13
+
14
+ import platformdirs
15
+
16
+ # Package information
17
+ # Get the current package name, quick and (very) dirty
18
+ name = __name__.split(".")[-2]
19
+ meta = metadata(name)
20
+ title = meta['Name']
21
+ version = meta['Version']
22
+ summary = meta['Summary']
23
+ description = meta['Description']
24
+ url = meta['Project-URL'].rsplit(", ")[-1]
25
+
26
+ # Contact
27
+ authors = meta['Author-email'].split(", ")
28
+ if authors and authors[0]:
29
+ contact = {'name': "", 'affiliation': "", 'email': ""} if (m:=match(
30
+ r'\"?\s*(?P<name>[A-Za-zÀ-ÖØ-öø-ÿ\s]*?)\s*(?:\(\s*'
31
+ r'(?P<affiliation>[^)]+?)\s*\))?\"?\s*<\s*(?P<email>[^>]+?)\s*>?\s*$',
32
+ authors[0]
33
+ )) is None else m.groupdict()
34
+
35
+ # License
36
+ #license = meta['License']
37
+ license_name = "MIT"
38
+ license_url = "https://spdx.org/licenses/MIT.html"
39
+
40
+ # Package source directory
41
+ src_dir = path.dirname(path.abspath(__file__))
42
+
43
+ # Package root directory
44
+ root_dir = Path(str(files(name)))
45
+
46
+ # Default configuration file
47
+ config_file = path.join(platformdirs.user_config_dir(name), f"{name}.conf")
48
+
49
+ # Default cache dir
50
+ cache_dir = platformdirs.user_cache_dir(name)
51
+
52
+ # Platform
53
+ platform = sys.platform
54
+ isonlinux = sys.platform.startswith('lin')
55
+
pydiet/py.typed ADDED
File without changes
@@ -0,0 +1,9 @@
1
+ """
2
+ PyDIET
3
+ ======
4
+
5
+ [CFHT](https://www.cfht.hawaii.edu)'s new Direct Imaging Exposure Time calculator.
6
+ """
7
+ # Copyright CFHT/CNRS
8
+ # Licensed under the MIT license
9
+
pydiet/server/app.py ADDED
@@ -0,0 +1,369 @@
1
+ """
2
+ Application module
3
+ """
4
+ # Copyright CFHT/CNRS/CEA/UParisSaclay
5
+ # Licensed under the MIT licence
6
+ from io import BytesIO
7
+ from logging import getLogger
8
+ from os.path import abspath, exists, join
9
+ from typing import Annotated, get_args, Literal, Optional, Tuple
10
+ from urllib.parse import urlencode
11
+
12
+ from fastapi import (
13
+ Body,
14
+ Depends,
15
+ FastAPI,
16
+ File,
17
+ Form,
18
+ HTTPException,
19
+ Path,
20
+ Query,
21
+ Request,
22
+ UploadFile,
23
+ responses,
24
+ status
25
+ )
26
+ from fastapi.encoders import jsonable_encoder
27
+ from fastapi.exceptions import RequestValidationError
28
+ from fastapi.middleware.cors import CORSMiddleware
29
+ from fastapi.responses import JSONResponse, HTMLResponse, StreamingResponse
30
+ from fastapi.staticfiles import StaticFiles
31
+ from fastapi.templating import Jinja2Templates
32
+
33
+ from pydantic import BaseModel, ValidationError
34
+ from pydantic_core import InitErrorDetails, PydanticCustomError
35
+
36
+ from .. import package
37
+ from .config import config_filename, settings
38
+ from .response import get_response
39
+
40
+ from .models import (
41
+ ETCQueryModel,
42
+ ETCResponseModel,
43
+ ETCValidationError
44
+ )
45
+
46
+ from .data import winstruments
47
+
48
+
49
+ def create_app() -> FastAPI:
50
+ """
51
+ Create FASTAPI application
52
+ """
53
+
54
+ banner_template = settings["banner_template"]
55
+ base_template = settings["base_template"]
56
+ template_dir = abspath(settings["template_dir"])
57
+ client_dir = abspath(settings["client_dir"])
58
+ data_dir = abspath(settings["data_dir"])
59
+ extra_dir = abspath(settings["extra_dir"])
60
+ doc_dir = settings["doc_dir"]
61
+ doc_path = settings["doc_path"]
62
+ userdoc_url = settings["userdoc_url"]
63
+ api_path = settings["api_path"]
64
+
65
+ logger = getLogger("uvicorn.error")
66
+
67
+ # Provide an endpoint for the user's manual (if it exists)
68
+ if config_filename:
69
+ logger.info(f"Configuration read from {config_filename}.")
70
+ else:
71
+ logger.warning(
72
+ f"Configuration file not found: {config_filename}!"
73
+ )
74
+
75
+ app = FastAPI(
76
+ title=package.title,
77
+ description=package.description,
78
+ version=package.version,
79
+ contact = {
80
+ "name": f"{package.contact['name']} ({package.contact['affiliation']})",
81
+ "url": package.url,
82
+ "email": package.contact['email']
83
+ },
84
+ license_info={
85
+ "name": package.license_name,
86
+ "url": package.license_url
87
+ }
88
+ )
89
+
90
+ """
91
+ origins = [
92
+ "http://halau.cfht.hawaii.edu",
93
+ "https://halau.cfht.hawaii.edu",
94
+ "http://halau",
95
+ "https://halau"
96
+ ]
97
+
98
+ app.add_middleware(
99
+ CORSMiddleware,
100
+ allow_origins=origins,
101
+ allow_credentials=True,
102
+ allow_methods=["*"],
103
+ allow_headers=["*"],
104
+ )
105
+ """
106
+
107
+ # Provide a direct endpoint for static files (such as js and css)
108
+ app.mount(
109
+ "/client",
110
+ StaticFiles(directory=client_dir),
111
+ name="client"
112
+ )
113
+
114
+ # Provide a direct endpoint for extra static data files (such as json files)
115
+ app.mount(
116
+ "/extra",
117
+ StaticFiles(directory=extra_dir),
118
+ name="extra"
119
+ )
120
+
121
+ # Provide an endpoint for the user's manual (if it exists)
122
+ if exists(doc_dir):
123
+ logger.info(f"Default documentation found at {doc_dir}.")
124
+ app.mount(
125
+ doc_path,
126
+ StaticFiles(directory=doc_dir),
127
+ name="manual"
128
+ )
129
+ else:
130
+ logger.warning(f"Default documentation not found in {doc_dir}!")
131
+ logger.warning("Has the HTML documentation been compiled ?")
132
+ logger.warning("De-activating documentation URL in built-in web client.")
133
+ userdoc_url = ""
134
+
135
+ # Instantiate templates
136
+ templates = Jinja2Templates(
137
+ directory=join(package.src_dir, template_dir)
138
+ )
139
+
140
+
141
+ @app.exception_handler(ETCValidationError)
142
+ async def handle_validation_exception(request: Request, exc: ETCValidationError):
143
+ """
144
+ Propagate value errors from custom validators.
145
+
146
+ Returns
147
+ -------
148
+ response: byte stream
149
+ `JSON response <https://fastapi.tiangolo.com/advanced/custom-response/#jsonresponse>`_
150
+ containing the error diagnostic.
151
+ """
152
+ dico = exc.args[0]
153
+ raise RequestValidationError(
154
+ errors=(
155
+ ValidationError.from_exception_data(
156
+ "ValueError",
157
+ [
158
+ InitErrorDetails(
159
+ type=dico["type"],
160
+ loc=dico["loc"],
161
+ input=dico["input"],
162
+ ctx={"expected": dico["expected"]}
163
+ )
164
+ ]
165
+ )
166
+ ).errors()
167
+ )
168
+
169
+
170
+ @app.get(api_path + "/health", tags=["Web API"])
171
+ async def get_health():
172
+ return {"ok": True}
173
+
174
+
175
+ @app.get(api_path + "/instruments", tags=["Web API"])
176
+ async def get_api_instruments():
177
+ """
178
+ Endpoint for instrument list.
179
+
180
+ Returns
181
+ -------
182
+ response: byte stream
183
+ `JSON response <https://fastapi.tiangolo.com/advanced/custom-response/#jsonresponse>`_
184
+ with the list of supported instruments
185
+ """
186
+ return JSONResponse(
187
+ content=jsonable_encoder(winstruments)
188
+ )
189
+
190
+
191
+ # PyDIET web API endpoint with GET query string
192
+ @app.get(api_path + "/{instrument}", tags=["Web API"])
193
+ async def get_api_query(
194
+ request: Request,
195
+ instrument: str = Path(
196
+ title="Instrument ID",
197
+ description="Instrument ID"
198
+ ),
199
+ query: ETCQueryModel = Depends()):
200
+ """
201
+ GET Endpoint for exposure type calculator JSON output.
202
+
203
+ Returns
204
+ -------
205
+ response: byte stream
206
+ `JSON response <https://fastapi.tiangolo.com/advanced/custom-response/#jsonresponse>`_
207
+ containing the computed ETC data.
208
+ """
209
+ return get_response(query).model_dump(exclude_none=True)
210
+
211
+
212
+ # PyDIET web API endpoint with POST query (for uploading filter curves)
213
+ @app.post(api_path + "/{instrument}", tags=["Web API"])
214
+ async def post_api_query(
215
+ request: Request,
216
+ instrument: str = Path(
217
+ title="Instrument ID",
218
+ description="Instrument ID"
219
+ ),
220
+ filter_upload: UploadFile | None = File(None)):
221
+ """
222
+ POST Endpoint for exposure type calculator JSON output, with optional
223
+ filter curve upload.
224
+
225
+ Returns
226
+ -------
227
+ response: byte stream
228
+ `JSON response <https://fastapi.tiangolo.com/advanced/custom-response/#jsonresponse>`_
229
+ containing the computed ETC data.
230
+ """
231
+ form = await request.form()
232
+ # Remove the filter upload field
233
+ data = dict(form)
234
+ data.pop("filter_upload", None)
235
+ query = ETCQueryModel.model_validate(data)
236
+ return get_response(
237
+ query,
238
+ filter=None if filter_upload is None else filter_upload.file
239
+ ).model_dump(exclude_none=True)
240
+
241
+
242
+ # PyDIET UI component endpoint with GET query string
243
+ @app.get("/ui/{instrument}/{component}/query", tags=["UI"], response_class=HTMLResponse)
244
+ async def get_ui_component_query(
245
+ request: Request,
246
+ instrument: str = Path(
247
+ title="Instrument ID",
248
+ description="Instrument ID"
249
+ ),
250
+ component: str = Path(
251
+ title="Component name",
252
+ description="Name of the UI component"
253
+ ),
254
+ query: ETCQueryModel = Depends()):
255
+ """
256
+ Endpoint for UI component with ETC query string.
257
+ Use "common" as instrument for components shared by all instruments.
258
+
259
+ Returns
260
+ -------
261
+ response: byte stream
262
+ `HTML response <https://fastapi.tiangolo.com/advanced/custom-response/#htmlresponse>`_
263
+ with UI component.
264
+ """
265
+ return templates.TemplateResponse(
266
+ request = request,
267
+ name = join(instrument, component + ".html"),
268
+ context = {
269
+ "root_path": request.scope.get("root_path"),
270
+ "package": package,
271
+ "instrument": instrument,
272
+ "r": get_response(query, ui=True)
273
+ }
274
+ )
275
+
276
+ # PyDIET UI component endpoint with POST query (for uploading filter curves)
277
+ @app.post("/ui/{instrument}/{component}/query", tags=["UI"], response_class=HTMLResponse)
278
+ async def post_ui_component_query(
279
+ request: Request,
280
+ instrument: str = Path(
281
+ title="Instrument ID",
282
+ description="Instrument ID"
283
+ ),
284
+ component: str = Path(
285
+ title="Component name",
286
+ description="Name of the UI component"
287
+ ),
288
+ filter_upload: UploadFile | None = File(None)):
289
+ """
290
+ Endpoint for UI component with ETC query string.
291
+ Use "common" as instrument for components shared by all instruments.
292
+
293
+ Returns
294
+ -------
295
+ response: byte stream
296
+ `HTML response <https://fastapi.tiangolo.com/advanced/custom-response/#htmlresponse>`_
297
+ with UI component.
298
+ """
299
+ form = await request.form()
300
+ # Remove the filter upload field
301
+ data = dict(form)
302
+ data.pop("filter_upload", None)
303
+ query = ETCQueryModel.model_validate(data)
304
+ return templates.TemplateResponse(
305
+ request = request,
306
+ name = join(instrument, component + ".html"),
307
+ context = {
308
+ "root_path": request.scope.get("root_path"),
309
+ "package": package,
310
+ "instrument": instrument,
311
+ "r": get_response(
312
+ query,
313
+ filter=None if filter_upload is None else filter_upload.file,
314
+ ui=True
315
+ )
316
+ }
317
+ )
318
+
319
+
320
+ # PyDIET UI component endpoint without a query string
321
+ @app.get("/ui/{instrument}/{component}", tags=["UI"], response_class=HTMLResponse)
322
+ async def get_ui_component(
323
+ request: Request,
324
+ instrument: str = Path(
325
+ title="Instrument ID",
326
+ description="Instrument ID"
327
+ ),
328
+ component: str = Path(
329
+ title="Component name",
330
+ description="Name of the UI component"
331
+ )):
332
+ """
333
+ Endpoint for UI component without an ETC query string.
334
+ Use "common" as instrument for components shared by all instruments.
335
+
336
+ Returns
337
+ -------
338
+ response: byte stream
339
+ `HTML response <https://fastapi.tiangolo.com/advanced/custom-response/#htmlresponse>`_
340
+ with UI component.
341
+ """
342
+ return templates.TemplateResponse(
343
+ request = request,
344
+ name = join(instrument, component + ".html"),
345
+ context = {
346
+ "root_path": request.scope.get("root_path"),
347
+ "package": package,
348
+ "instrument": instrument
349
+ }
350
+ )
351
+
352
+
353
+ # Default PyDIET client endpoint
354
+ @app.get("/", tags=["UI"], response_class=HTMLResponse)
355
+ async def get_ui(request: Request):
356
+ """
357
+ Main web user interface.
358
+ """
359
+ return templates.TemplateResponse(
360
+ request = request,
361
+ name = base_template,
362
+ context = {
363
+ "root_path": request.scope.get("root_path"),
364
+ "doc_url": userdoc_url,
365
+ "package": package
366
+ }
367
+ )
368
+
369
+ return app
@@ -0,0 +1,51 @@
1
+ """
2
+ Configure application.
3
+ """
4
+ # Copyright CEA/CFHT/CNRS/UParisSaclay
5
+ # Licensed under the MIT licence
6
+
7
+ from sys import exit, modules
8
+ from typing import Any
9
+
10
+ from .config import Config
11
+ from .settings import AppSettings
12
+
13
+ # Initialize global dictionary
14
+ # Set up settings by instantiating a configuration object
15
+ config = Config(AppSettings())
16
+ config_filename = None
17
+ settings = config.flat_dict()
18
+
19
+
20
+ def override(key: str, value: Any) -> Any:
21
+ """
22
+ Convenience function that returns the input value unless it is None,
23
+ in which case the settings value from key is returned.
24
+
25
+ Examples
26
+ --------
27
+ >>> from . import override, settings
28
+ >>> settings['port'] = 8009
29
+ >>> print(f"{override('port', 8080)}")
30
+ 8080
31
+
32
+ >>> print(f"{override('port', None)}")
33
+ 8009
34
+
35
+ Parameters
36
+ ----------
37
+ key: str
38
+ Key to settings value.
39
+ value: Any
40
+ Input value.
41
+
42
+ Returns
43
+ -------
44
+ value: Any
45
+ Input value or settings value.
46
+ """
47
+ return settings[key] if value is None else value
48
+
49
+
50
+ config_filename = config.config_filename
51
+