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
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
+
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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.
|
|
Binary file
|
|
@@ -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
|
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
|
+
|