gammasimtools 0.23.0__py3-none-any.whl → 0.24.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.24.0.dist-info}/METADATA +1 -1
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.24.0.dist-info}/RECORD +59 -58
- simtools/_version.py +2 -2
- simtools/application_control.py +4 -4
- simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -1
- simtools/applications/db_add_file_to_db.py +2 -2
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
- simtools/applications/db_add_value_from_json_to_db.py +2 -2
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +1 -1
- simtools/applications/db_generate_compound_indexes.py +1 -1
- simtools/applications/db_get_array_layouts_from_db.py +2 -2
- simtools/applications/db_get_file_from_db.py +1 -1
- simtools/applications/db_get_parameter_from_db.py +1 -1
- simtools/applications/db_inspect_databases.py +4 -2
- simtools/applications/db_upload_model_repository.py +1 -1
- simtools/applications/derive_ctao_array_layouts.py +1 -1
- simtools/applications/generate_array_config.py +1 -1
- simtools/applications/maintain_simulation_model_add_production.py +11 -21
- simtools/applications/production_generate_grid.py +1 -1
- simtools/applications/submit_array_layouts.py +2 -2
- simtools/applications/validate_camera_fov.py +1 -1
- simtools/applications/validate_cumulative_psf.py +2 -2
- simtools/applications/validate_optics.py +1 -1
- simtools/configuration/commandline_parser.py +7 -9
- simtools/configuration/configurator.py +1 -1
- simtools/corsika/corsika_config.py +2 -4
- simtools/data_model/model_data_writer.py +1 -1
- simtools/data_model/schema.py +36 -34
- simtools/db/db_handler.py +61 -294
- simtools/db/db_model_upload.py +1 -1
- simtools/db/mongo_db.py +535 -0
- simtools/dependencies.py +33 -8
- simtools/layout/array_layout.py +7 -7
- simtools/layout/array_layout_utils.py +3 -3
- simtools/model/array_model.py +36 -67
- simtools/model/calibration_model.py +12 -9
- simtools/model/model_parameter.py +196 -159
- simtools/model/model_repository.py +159 -35
- simtools/model/model_utils.py +3 -3
- simtools/model/site_model.py +59 -27
- simtools/model/telescope_model.py +21 -13
- simtools/ray_tracing/mirror_panel_psf.py +4 -4
- simtools/ray_tracing/psf_parameter_optimisation.py +1 -1
- simtools/reporting/docs_auto_report_generator.py +1 -1
- simtools/reporting/docs_read_parameters.py +3 -2
- simtools/schemas/simulation_models_info.schema.yml +2 -1
- simtools/simtel/simtel_config_writer.py +97 -20
- simtools/simulator.py +2 -1
- simtools/testing/assertions.py +50 -6
- simtools/testing/validate_output.py +4 -8
- simtools/utils/value_conversion.py +10 -5
- simtools/version.py +24 -0
- simtools/visualization/plot_pixels.py +1 -1
- simtools/visualization/plot_psf.py +1 -1
- simtools/visualization/plot_tables.py +1 -1
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.24.0.dist-info}/WHEEL +0 -0
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.24.0.dist-info}/entry_points.txt +0 -0
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.24.0.dist-info}/licenses/LICENSE +0 -0
- {gammasimtools-0.23.0.dist-info → gammasimtools-0.24.0.dist-info}/top_level.txt +0 -0
simtools/db/mongo_db.py
ADDED
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
"""MongoDB database handler for direct database operations."""
|
|
2
|
+
|
|
3
|
+
import io
|
|
4
|
+
import logging
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from threading import Lock
|
|
8
|
+
|
|
9
|
+
import gridfs
|
|
10
|
+
import jsonschema
|
|
11
|
+
from astropy.table import Table
|
|
12
|
+
from bson.objectid import ObjectId
|
|
13
|
+
from pymongo import MongoClient
|
|
14
|
+
|
|
15
|
+
from simtools.io import ascii_handler
|
|
16
|
+
|
|
17
|
+
logging.getLogger("pymongo").setLevel(logging.WARNING)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
jsonschema_db_dict = {
|
|
21
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema#",
|
|
22
|
+
"type": "object",
|
|
23
|
+
"description": "MongoDB configuration",
|
|
24
|
+
"properties": {
|
|
25
|
+
"db_server": {"type": "string", "description": "DB server address"},
|
|
26
|
+
"db_api_port": {
|
|
27
|
+
"type": "integer",
|
|
28
|
+
"minimum": 1,
|
|
29
|
+
"maximum": 65535,
|
|
30
|
+
"default": 27017,
|
|
31
|
+
"description": "Port to use",
|
|
32
|
+
},
|
|
33
|
+
"db_api_user": {"type": "string", "description": "API username"},
|
|
34
|
+
"db_api_pw": {"type": "string", "description": "Password for the API user"},
|
|
35
|
+
"db_api_authentication_database": {
|
|
36
|
+
"type": ["string", "null"],
|
|
37
|
+
"default": "admin",
|
|
38
|
+
"description": "DB with user info (optional)",
|
|
39
|
+
},
|
|
40
|
+
"db_simulation_model": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "Name of simulation model database",
|
|
43
|
+
},
|
|
44
|
+
"db_simulation_model_version": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Version of simulation model database",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
"required": [
|
|
50
|
+
"db_server",
|
|
51
|
+
"db_api_port",
|
|
52
|
+
"db_api_user",
|
|
53
|
+
"db_api_pw",
|
|
54
|
+
"db_simulation_model",
|
|
55
|
+
"db_simulation_model_version",
|
|
56
|
+
],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class MongoDBHandler: # pylint: disable=unsubscriptable-object
|
|
61
|
+
"""
|
|
62
|
+
MongoDBHandler provides low-level interface to MongoDB operations.
|
|
63
|
+
|
|
64
|
+
This class handles direct MongoDB operations including connection management,
|
|
65
|
+
database queries, file operations via GridFS, and index generation.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
db_config: dict
|
|
70
|
+
Dictionary with the MongoDB configuration (see jsonschema_db_dict for details).
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
db_client: MongoClient | None = None
|
|
74
|
+
_lock = Lock()
|
|
75
|
+
|
|
76
|
+
def __init__(self, db_config=None):
|
|
77
|
+
"""Initialize the MongoDBHandler class."""
|
|
78
|
+
self._logger = logging.getLogger(__name__)
|
|
79
|
+
self.db_config = MongoDBHandler.validate_db_config(db_config)
|
|
80
|
+
self.list_of_collections = {}
|
|
81
|
+
|
|
82
|
+
if self.db_config and MongoDBHandler.db_client is None:
|
|
83
|
+
with MongoDBHandler._lock:
|
|
84
|
+
if MongoDBHandler.db_client is None:
|
|
85
|
+
MongoDBHandler.db_client = self._open_db()
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def validate_db_config(db_config):
|
|
89
|
+
"""
|
|
90
|
+
Validate the MongoDB configuration.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
db_config: dict
|
|
95
|
+
Dictionary with the MongoDB configuration.
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
dict or None
|
|
100
|
+
Validated MongoDB configuration or None if no valid config provided.
|
|
101
|
+
|
|
102
|
+
Raises
|
|
103
|
+
------
|
|
104
|
+
ValueError
|
|
105
|
+
If the MongoDB configuration is invalid.
|
|
106
|
+
"""
|
|
107
|
+
if db_config is None or all(value is None for value in db_config.values()):
|
|
108
|
+
return None
|
|
109
|
+
try:
|
|
110
|
+
jsonschema.validate(instance=db_config, schema=jsonschema_db_dict)
|
|
111
|
+
return db_config
|
|
112
|
+
except jsonschema.exceptions.ValidationError as err:
|
|
113
|
+
raise ValueError("Invalid MongoDB configuration") from err
|
|
114
|
+
|
|
115
|
+
def _open_db(self):
|
|
116
|
+
"""
|
|
117
|
+
Open a connection to MongoDB and return the client.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
MongoClient
|
|
122
|
+
A PyMongo DB client
|
|
123
|
+
|
|
124
|
+
Raises
|
|
125
|
+
------
|
|
126
|
+
KeyError
|
|
127
|
+
If the DB configuration is invalid
|
|
128
|
+
"""
|
|
129
|
+
direct_connection = self.db_config["db_server"] in (
|
|
130
|
+
"localhost",
|
|
131
|
+
"simtools-mongodb",
|
|
132
|
+
"mongodb",
|
|
133
|
+
)
|
|
134
|
+
return MongoClient(
|
|
135
|
+
self.db_config["db_server"],
|
|
136
|
+
port=self.db_config["db_api_port"],
|
|
137
|
+
username=self.db_config["db_api_user"],
|
|
138
|
+
password=self.db_config["db_api_pw"],
|
|
139
|
+
authSource=(
|
|
140
|
+
self.db_config.get("db_api_authentication_database")
|
|
141
|
+
if self.db_config.get("db_api_authentication_database")
|
|
142
|
+
else "admin"
|
|
143
|
+
),
|
|
144
|
+
directConnection=direct_connection,
|
|
145
|
+
ssl=not direct_connection,
|
|
146
|
+
tlsallowinvalidhostnames=True,
|
|
147
|
+
tlsallowinvalidcertificates=True,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
@staticmethod
|
|
151
|
+
def get_db_name(db_name=None, db_simulation_model_version=None, model_name=None):
|
|
152
|
+
"""
|
|
153
|
+
Build DB name from configuration.
|
|
154
|
+
|
|
155
|
+
Parameters
|
|
156
|
+
----------
|
|
157
|
+
db_name: str
|
|
158
|
+
Direct database name (if provided, returns this).
|
|
159
|
+
db_simulation_model_version: str
|
|
160
|
+
Version of the simulation model.
|
|
161
|
+
model_name: str
|
|
162
|
+
Name of the simulation model.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
str or None
|
|
167
|
+
Database name.
|
|
168
|
+
"""
|
|
169
|
+
if db_name:
|
|
170
|
+
return db_name
|
|
171
|
+
if db_simulation_model_version and model_name:
|
|
172
|
+
return f"{model_name}-{db_simulation_model_version.replace('.', '-')}"
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
def print_connection_info(self, db_name):
|
|
176
|
+
"""
|
|
177
|
+
Print the connection information.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
db_name: str
|
|
182
|
+
Name of the database.
|
|
183
|
+
"""
|
|
184
|
+
if self.db_config:
|
|
185
|
+
self._logger.info(
|
|
186
|
+
f"Connected to MongoDB at {self.db_config['db_server']}:"
|
|
187
|
+
f"{self.db_config['db_api_port']} "
|
|
188
|
+
f"using database: {db_name}"
|
|
189
|
+
)
|
|
190
|
+
else:
|
|
191
|
+
self._logger.info("No MongoDB configuration provided.")
|
|
192
|
+
|
|
193
|
+
def is_remote_database(self):
|
|
194
|
+
"""
|
|
195
|
+
Check if the database is remote.
|
|
196
|
+
|
|
197
|
+
Check for domain pattern like "cta-simpipe-protodb.zeuthen.desy.de"
|
|
198
|
+
|
|
199
|
+
Returns
|
|
200
|
+
-------
|
|
201
|
+
bool
|
|
202
|
+
True if the database is remote, False otherwise.
|
|
203
|
+
"""
|
|
204
|
+
if self.db_config:
|
|
205
|
+
db_server = self.db_config["db_server"]
|
|
206
|
+
domain_pattern = r"^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$"
|
|
207
|
+
return bool(re.match(domain_pattern, db_server))
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
@staticmethod
|
|
211
|
+
def get_entry_date_from_document(document):
|
|
212
|
+
"""
|
|
213
|
+
Extract entry date from a MongoDB document's ObjectId.
|
|
214
|
+
|
|
215
|
+
Parameters
|
|
216
|
+
----------
|
|
217
|
+
document: dict
|
|
218
|
+
MongoDB document with '_id' field.
|
|
219
|
+
|
|
220
|
+
Returns
|
|
221
|
+
-------
|
|
222
|
+
datetime.datetime
|
|
223
|
+
The generation time of the document's ObjectId.
|
|
224
|
+
"""
|
|
225
|
+
return ObjectId(document["_id"]).generation_time
|
|
226
|
+
|
|
227
|
+
def get_collection(self, collection_name, db_name):
|
|
228
|
+
"""
|
|
229
|
+
Get a collection from the DB.
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
collection_name: str
|
|
234
|
+
Name of the collection.
|
|
235
|
+
db_name: str
|
|
236
|
+
Name of the DB.
|
|
237
|
+
|
|
238
|
+
Returns
|
|
239
|
+
-------
|
|
240
|
+
pymongo.collection.Collection
|
|
241
|
+
The collection from the DB.
|
|
242
|
+
"""
|
|
243
|
+
return MongoDBHandler.db_client[db_name][collection_name]
|
|
244
|
+
|
|
245
|
+
def get_collections(self, db_name, model_collections_only=False):
|
|
246
|
+
"""
|
|
247
|
+
List of collections in the DB.
|
|
248
|
+
|
|
249
|
+
Parameters
|
|
250
|
+
----------
|
|
251
|
+
db_name: str
|
|
252
|
+
Database name.
|
|
253
|
+
model_collections_only: bool
|
|
254
|
+
If True, only return model collections (i.e. exclude fs.files, fs.chunks)
|
|
255
|
+
|
|
256
|
+
Returns
|
|
257
|
+
-------
|
|
258
|
+
list
|
|
259
|
+
List of collection names
|
|
260
|
+
"""
|
|
261
|
+
if db_name not in self.list_of_collections:
|
|
262
|
+
self.list_of_collections[db_name] = MongoDBHandler.db_client[
|
|
263
|
+
db_name
|
|
264
|
+
].list_collection_names()
|
|
265
|
+
collections = self.list_of_collections[db_name]
|
|
266
|
+
if model_collections_only:
|
|
267
|
+
return [collection for collection in collections if not collection.startswith("fs.")]
|
|
268
|
+
return collections
|
|
269
|
+
|
|
270
|
+
def list_database_names(self):
|
|
271
|
+
"""
|
|
272
|
+
Get list of database names.
|
|
273
|
+
|
|
274
|
+
Returns
|
|
275
|
+
-------
|
|
276
|
+
list
|
|
277
|
+
List of database names.
|
|
278
|
+
"""
|
|
279
|
+
return MongoDBHandler.db_client.list_database_names()
|
|
280
|
+
|
|
281
|
+
def generate_compound_indexes_for_databases(
|
|
282
|
+
self, db_name, db_simulation_model, db_simulation_model_version
|
|
283
|
+
):
|
|
284
|
+
"""
|
|
285
|
+
Generate compound indexes for several databases.
|
|
286
|
+
|
|
287
|
+
Parameters
|
|
288
|
+
----------
|
|
289
|
+
db_name: str
|
|
290
|
+
Name of the database.
|
|
291
|
+
db_simulation_model: str
|
|
292
|
+
Name of the simulation model.
|
|
293
|
+
db_simulation_model_version: str
|
|
294
|
+
Version of the simulation model.
|
|
295
|
+
|
|
296
|
+
Raises
|
|
297
|
+
------
|
|
298
|
+
ValueError
|
|
299
|
+
If the requested database is not found.
|
|
300
|
+
"""
|
|
301
|
+
databases = [
|
|
302
|
+
d
|
|
303
|
+
for d in MongoDBHandler.db_client.list_database_names()
|
|
304
|
+
if d not in ("config", "admin", "local")
|
|
305
|
+
]
|
|
306
|
+
requested = self.get_db_name(
|
|
307
|
+
db_name=db_name,
|
|
308
|
+
db_simulation_model_version=db_simulation_model_version,
|
|
309
|
+
model_name=db_simulation_model,
|
|
310
|
+
)
|
|
311
|
+
if requested != "all" and requested not in databases:
|
|
312
|
+
raise ValueError(
|
|
313
|
+
f"Requested database '{requested}' not found. "
|
|
314
|
+
f"Following databases are available: {', '.join(databases)}"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
databases = databases if requested == "all" else [requested]
|
|
318
|
+
for dbs in databases:
|
|
319
|
+
self._logger.info(f"Generating compound indexes for database: {dbs}")
|
|
320
|
+
self.generate_compound_indexes(db_name=dbs)
|
|
321
|
+
|
|
322
|
+
def generate_compound_indexes(self, db_name):
|
|
323
|
+
"""
|
|
324
|
+
Generate compound indexes for the MongoDB collections.
|
|
325
|
+
|
|
326
|
+
Indexes based on the typical query patterns.
|
|
327
|
+
|
|
328
|
+
Parameters
|
|
329
|
+
----------
|
|
330
|
+
db_name: str
|
|
331
|
+
Name of the database.
|
|
332
|
+
"""
|
|
333
|
+
collection_names = [
|
|
334
|
+
"telescopes",
|
|
335
|
+
"sites",
|
|
336
|
+
"configuration_sim_telarray",
|
|
337
|
+
"configuration_corsika",
|
|
338
|
+
"calibration_devices",
|
|
339
|
+
]
|
|
340
|
+
for collection_name in collection_names:
|
|
341
|
+
db_collection = self.get_collection(collection_name, db_name=db_name)
|
|
342
|
+
db_collection.create_index(
|
|
343
|
+
[("instrument", 1), ("site", 1), ("parameter", 1), ("parameter_version", 1)]
|
|
344
|
+
)
|
|
345
|
+
db_collection = self.get_collection("production_tables", db_name=db_name)
|
|
346
|
+
db_collection.create_index([("collection", 1), ("model_version", 1)])
|
|
347
|
+
|
|
348
|
+
def query_db(self, query, collection_name, db_name):
|
|
349
|
+
"""
|
|
350
|
+
Query MongoDB and return results as list.
|
|
351
|
+
|
|
352
|
+
Parameters
|
|
353
|
+
----------
|
|
354
|
+
query: dict
|
|
355
|
+
Query to execute.
|
|
356
|
+
collection_name: str
|
|
357
|
+
Collection name.
|
|
358
|
+
db_name: str
|
|
359
|
+
Database name.
|
|
360
|
+
|
|
361
|
+
Returns
|
|
362
|
+
-------
|
|
363
|
+
list
|
|
364
|
+
List of documents matching the query.
|
|
365
|
+
|
|
366
|
+
Raises
|
|
367
|
+
------
|
|
368
|
+
ValueError
|
|
369
|
+
if query returned no results.
|
|
370
|
+
"""
|
|
371
|
+
collection = self.get_collection(collection_name, db_name=db_name)
|
|
372
|
+
posts = list(collection.find(query))
|
|
373
|
+
if not posts:
|
|
374
|
+
raise ValueError(
|
|
375
|
+
f"The following query for {collection_name} returned zero results: {query} "
|
|
376
|
+
)
|
|
377
|
+
return posts
|
|
378
|
+
|
|
379
|
+
def find_one(self, query, collection_name, db_name):
|
|
380
|
+
"""
|
|
381
|
+
Query MongoDB and return first result.
|
|
382
|
+
|
|
383
|
+
Parameters
|
|
384
|
+
----------
|
|
385
|
+
query: dict
|
|
386
|
+
Query to execute.
|
|
387
|
+
collection_name: str
|
|
388
|
+
Collection name.
|
|
389
|
+
db_name: str
|
|
390
|
+
Database name.
|
|
391
|
+
|
|
392
|
+
Returns
|
|
393
|
+
-------
|
|
394
|
+
dict or None
|
|
395
|
+
First document matching the query or None.
|
|
396
|
+
"""
|
|
397
|
+
collection = self.get_collection(collection_name, db_name=db_name)
|
|
398
|
+
return collection.find_one(query)
|
|
399
|
+
|
|
400
|
+
def insert_one(self, document, collection_name, db_name):
|
|
401
|
+
"""
|
|
402
|
+
Insert a document into a collection.
|
|
403
|
+
|
|
404
|
+
Parameters
|
|
405
|
+
----------
|
|
406
|
+
document: dict
|
|
407
|
+
Document to insert.
|
|
408
|
+
collection_name: str
|
|
409
|
+
Collection name.
|
|
410
|
+
db_name: str
|
|
411
|
+
Database name.
|
|
412
|
+
|
|
413
|
+
Returns
|
|
414
|
+
-------
|
|
415
|
+
InsertOneResult
|
|
416
|
+
Result of the insert operation.
|
|
417
|
+
"""
|
|
418
|
+
collection = self.get_collection(collection_name, db_name=db_name)
|
|
419
|
+
return collection.insert_one(document)
|
|
420
|
+
|
|
421
|
+
def get_file_from_db(self, db_name, file_name):
|
|
422
|
+
"""
|
|
423
|
+
Extract a file from MongoDB and return GridFS file instance.
|
|
424
|
+
|
|
425
|
+
Parameters
|
|
426
|
+
----------
|
|
427
|
+
db_name: str
|
|
428
|
+
The name of the DB with files of tabulated data
|
|
429
|
+
file_name: str
|
|
430
|
+
The name of the file requested
|
|
431
|
+
|
|
432
|
+
Returns
|
|
433
|
+
-------
|
|
434
|
+
GridOut
|
|
435
|
+
A file instance returned by GridFS find_one
|
|
436
|
+
|
|
437
|
+
Raises
|
|
438
|
+
------
|
|
439
|
+
FileNotFoundError
|
|
440
|
+
If the desired file is not found.
|
|
441
|
+
"""
|
|
442
|
+
db = MongoDBHandler.db_client[db_name]
|
|
443
|
+
file_system = gridfs.GridFS(db)
|
|
444
|
+
if file_system.exists({"filename": file_name}):
|
|
445
|
+
return file_system.find_one({"filename": file_name})
|
|
446
|
+
|
|
447
|
+
raise FileNotFoundError(f"The file {file_name} does not exist in the database {db_name}")
|
|
448
|
+
|
|
449
|
+
def write_file_from_db_to_disk(self, db_name, path, file):
|
|
450
|
+
"""
|
|
451
|
+
Extract a file from MongoDB and write it to disk.
|
|
452
|
+
|
|
453
|
+
Parameters
|
|
454
|
+
----------
|
|
455
|
+
db_name: str
|
|
456
|
+
The name of the DB with files of tabulated data
|
|
457
|
+
path: str or Path
|
|
458
|
+
The path to write the file to
|
|
459
|
+
file: GridOut
|
|
460
|
+
A file instance returned by GridFS find_one
|
|
461
|
+
"""
|
|
462
|
+
db = MongoDBHandler.db_client[db_name]
|
|
463
|
+
fs_output = gridfs.GridFSBucket(db)
|
|
464
|
+
with open(Path(path).joinpath(file.filename), "wb") as output_file:
|
|
465
|
+
fs_output.download_to_stream_by_name(file.filename, output_file)
|
|
466
|
+
|
|
467
|
+
def get_ecsv_file_as_astropy_table(self, file_name, db_name):
|
|
468
|
+
"""
|
|
469
|
+
Read contents of an ECSV file from the database and return it as an Astropy Table.
|
|
470
|
+
|
|
471
|
+
Files are not written to disk.
|
|
472
|
+
|
|
473
|
+
Parameters
|
|
474
|
+
----------
|
|
475
|
+
file_name: str
|
|
476
|
+
The name of the ECSV file.
|
|
477
|
+
db_name: str
|
|
478
|
+
The name of the database.
|
|
479
|
+
|
|
480
|
+
Returns
|
|
481
|
+
-------
|
|
482
|
+
astropy.table.Table
|
|
483
|
+
The contents of the ECSV file as an Astropy Table.
|
|
484
|
+
"""
|
|
485
|
+
db = MongoDBHandler.db_client[db_name]
|
|
486
|
+
fs = gridfs.GridFSBucket(db)
|
|
487
|
+
|
|
488
|
+
buf = io.BytesIO()
|
|
489
|
+
try:
|
|
490
|
+
fs.download_to_stream_by_name(file_name, buf)
|
|
491
|
+
except gridfs.errors.NoFile as exc:
|
|
492
|
+
raise FileNotFoundError(f"ECSV file '{file_name}' not found in DB.") from exc
|
|
493
|
+
buf.seek(0)
|
|
494
|
+
return Table.read(buf.getvalue().decode("utf-8"), format="ascii.ecsv")
|
|
495
|
+
|
|
496
|
+
def insert_file_to_db(self, file_name, db_name, **kwargs):
|
|
497
|
+
"""
|
|
498
|
+
Insert a file to the DB.
|
|
499
|
+
|
|
500
|
+
Parameters
|
|
501
|
+
----------
|
|
502
|
+
file_name: str or Path
|
|
503
|
+
The name of the file to insert (full path).
|
|
504
|
+
db_name: str
|
|
505
|
+
The name of the DB
|
|
506
|
+
**kwargs (optional): keyword arguments for file creation.
|
|
507
|
+
The full list of arguments can be found in
|
|
508
|
+
https://www.mongodb.com/docs/manual/core/gridfs/
|
|
509
|
+
|
|
510
|
+
Returns
|
|
511
|
+
-------
|
|
512
|
+
file_id: GridOut._id
|
|
513
|
+
If the file exists, return its GridOut._id, otherwise insert the file and return
|
|
514
|
+
its newly created DB GridOut._id.
|
|
515
|
+
"""
|
|
516
|
+
db = MongoDBHandler.db_client[db_name]
|
|
517
|
+
file_system = gridfs.GridFS(db)
|
|
518
|
+
|
|
519
|
+
kwargs.setdefault("content_type", "ascii/dat")
|
|
520
|
+
kwargs.setdefault("filename", Path(file_name).name)
|
|
521
|
+
|
|
522
|
+
if file_system.exists({"filename": kwargs["filename"]}):
|
|
523
|
+
self._logger.warning(
|
|
524
|
+
f"The file {kwargs['filename']} exists in the DB. Returning its ID"
|
|
525
|
+
)
|
|
526
|
+
# _id is a public attribute in GridFS GridOut objects
|
|
527
|
+
# pylint: disable=protected-access
|
|
528
|
+
return file_system.find_one({"filename": kwargs["filename"]})._id
|
|
529
|
+
|
|
530
|
+
if not ascii_handler.is_utf8_file(file_name):
|
|
531
|
+
raise ValueError(f"File is not UTF-8 encoded: {file_name}")
|
|
532
|
+
|
|
533
|
+
self._logger.debug(f"Writing file to DB: {file_name}")
|
|
534
|
+
with open(file_name, "rb") as data_file:
|
|
535
|
+
return file_system.put(data_file, **kwargs)
|
simtools/dependencies.py
CHANGED
|
@@ -16,8 +16,8 @@ from pathlib import Path
|
|
|
16
16
|
|
|
17
17
|
import yaml
|
|
18
18
|
|
|
19
|
-
from simtools.db.db_handler import DatabaseHandler
|
|
20
19
|
from simtools.io import ascii_handler
|
|
20
|
+
from simtools.version import __version__
|
|
21
21
|
|
|
22
22
|
_logger = logging.getLogger(__name__)
|
|
23
23
|
|
|
@@ -49,6 +49,30 @@ def get_version_string(db_config=None, run_time=None):
|
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
def get_software_version(software):
|
|
53
|
+
"""
|
|
54
|
+
Return the version of the specified software package.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
software : str
|
|
59
|
+
Name of the software package.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
str
|
|
64
|
+
Version of the specified software package.
|
|
65
|
+
"""
|
|
66
|
+
if software.lower() == "simtools":
|
|
67
|
+
return __version__
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
version_call = f"get_{software.lower()}_version"
|
|
71
|
+
return globals()[version_call]()
|
|
72
|
+
except KeyError as exc:
|
|
73
|
+
raise ValueError(f"Unknown software: {software}") from exc
|
|
74
|
+
|
|
75
|
+
|
|
52
76
|
def get_database_version_or_name(db_config, version=True):
|
|
53
77
|
"""
|
|
54
78
|
Get the version or name of the simulation model data base used.
|
|
@@ -66,18 +90,17 @@ def get_database_version_or_name(db_config, version=True):
|
|
|
66
90
|
Version or name of the simulation model data base used.
|
|
67
91
|
|
|
68
92
|
"""
|
|
69
|
-
if
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
return db.mongo_db_config.get(
|
|
73
|
-
"db_simulation_model_version" if version else "db_simulation_model"
|
|
74
|
-
)
|
|
93
|
+
if version:
|
|
94
|
+
return db_config and db_config.get("db_simulation_model_version")
|
|
95
|
+
return db_config and db_config.get("db_simulation_model")
|
|
75
96
|
|
|
76
97
|
|
|
77
|
-
def get_sim_telarray_version(run_time):
|
|
98
|
+
def get_sim_telarray_version(run_time=None):
|
|
78
99
|
"""
|
|
79
100
|
Get the version of the sim_telarray package using 'sim_telarray --version'.
|
|
80
101
|
|
|
102
|
+
Version strings for sim_telarray are of the form "2024.271.0" (year.day_of_year.patch).
|
|
103
|
+
|
|
81
104
|
Parameters
|
|
82
105
|
----------
|
|
83
106
|
run_time : list, optional
|
|
@@ -116,6 +139,8 @@ def get_corsika_version(run_time=None):
|
|
|
116
139
|
"""
|
|
117
140
|
Get the version of the CORSIKA package.
|
|
118
141
|
|
|
142
|
+
Version strings for CORSIKA are of the form "7.7550" (major.minor with 4-digit minor).
|
|
143
|
+
|
|
119
144
|
Parameters
|
|
120
145
|
----------
|
|
121
146
|
run_time : list, optional
|
simtools/layout/array_layout.py
CHANGED
|
@@ -32,8 +32,8 @@ class ArrayLayout:
|
|
|
32
32
|
|
|
33
33
|
Parameters
|
|
34
34
|
----------
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
db_config: dict
|
|
36
|
+
Database configuration.
|
|
37
37
|
site: str
|
|
38
38
|
Site name or location (e.g., North/South or LaPalma/Paranal)
|
|
39
39
|
model_version: str
|
|
@@ -52,7 +52,7 @@ class ArrayLayout:
|
|
|
52
52
|
|
|
53
53
|
def __init__(
|
|
54
54
|
self,
|
|
55
|
-
|
|
55
|
+
db_config,
|
|
56
56
|
site,
|
|
57
57
|
model_version,
|
|
58
58
|
label=None,
|
|
@@ -67,7 +67,7 @@ class ArrayLayout:
|
|
|
67
67
|
self.model_version = model_version
|
|
68
68
|
self.label = label
|
|
69
69
|
self.name = name
|
|
70
|
-
self.
|
|
70
|
+
self.db_config = db_config
|
|
71
71
|
self.site = None if site is None else names.validate_site_name(site)
|
|
72
72
|
self.site_model = None
|
|
73
73
|
self.io_handler = io_handler.IOHandler()
|
|
@@ -95,13 +95,13 @@ class ArrayLayout:
|
|
|
95
95
|
def _initialize_site_parameters_from_db(self):
|
|
96
96
|
"""Initialize site parameters required for transformations using the database."""
|
|
97
97
|
self._logger.debug("Initialize parameters from DB")
|
|
98
|
-
if self.
|
|
98
|
+
if self.db_config is None:
|
|
99
99
|
raise ValueError("No database configuration provided")
|
|
100
100
|
|
|
101
101
|
self.site_model = SiteModel(
|
|
102
102
|
site=self.site,
|
|
103
103
|
model_version=self.model_version,
|
|
104
|
-
|
|
104
|
+
db_config=self.db_config,
|
|
105
105
|
)
|
|
106
106
|
self._corsika_observation_level = self.site_model.get_corsika_site_parameters().get(
|
|
107
107
|
"corsika_observation_level", None
|
|
@@ -419,7 +419,7 @@ class ArrayLayout:
|
|
|
419
419
|
site=self.site,
|
|
420
420
|
telescope_name=telescope_name,
|
|
421
421
|
model_version=self.model_version,
|
|
422
|
-
|
|
422
|
+
db_config=self.db_config,
|
|
423
423
|
label=self.label,
|
|
424
424
|
)
|
|
425
425
|
|
|
@@ -280,7 +280,7 @@ def get_array_layouts_from_db(
|
|
|
280
280
|
if layout_name:
|
|
281
281
|
layout_names = gen.ensure_iterable(layout_name)
|
|
282
282
|
else:
|
|
283
|
-
site_model = SiteModel(site=site, model_version=model_version,
|
|
283
|
+
site_model = SiteModel(site=site, model_version=model_version, db_config=db_config)
|
|
284
284
|
layout_names = site_model.get_list_of_array_layouts()
|
|
285
285
|
|
|
286
286
|
layouts = []
|
|
@@ -373,7 +373,7 @@ def _get_array_layout_dict(
|
|
|
373
373
|
):
|
|
374
374
|
"""Return array layout dictionary for a given telescope list."""
|
|
375
375
|
array_model = ArrayModel(
|
|
376
|
-
|
|
376
|
+
db_config=db_config,
|
|
377
377
|
model_version=model_version,
|
|
378
378
|
site=site,
|
|
379
379
|
array_elements=telescope_list,
|
|
@@ -416,7 +416,7 @@ def get_array_elements_from_db_for_layouts(layouts, site, model_version, db_conf
|
|
|
416
416
|
dict
|
|
417
417
|
Dictionary mapping layout names to telescope IDs.
|
|
418
418
|
"""
|
|
419
|
-
site_model = SiteModel(site=site, model_version=model_version,
|
|
419
|
+
site_model = SiteModel(site=site, model_version=model_version, db_config=db_config)
|
|
420
420
|
layout_names = site_model.get_list_of_array_layouts() if layouts == ["all"] else layouts
|
|
421
421
|
layout_dict = {}
|
|
422
422
|
for layout_name in layout_names:
|