ctao-calibpipe 0.3.0rc2__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 (105) hide show
  1. calibpipe/__init__.py +5 -0
  2. calibpipe/_dev_version/__init__.py +9 -0
  3. calibpipe/_version.py +34 -0
  4. calibpipe/atmosphere/__init__.py +1 -0
  5. calibpipe/atmosphere/atmosphere_containers.py +109 -0
  6. calibpipe/atmosphere/meteo_data_handlers.py +485 -0
  7. calibpipe/atmosphere/models/README.md +14 -0
  8. calibpipe/atmosphere/models/__init__.py +1 -0
  9. calibpipe/atmosphere/models/macobac.ecsv +23 -0
  10. calibpipe/atmosphere/models/reference_MDPs/__init__.py +1 -0
  11. calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-north_intermediate.ecsv +8 -0
  12. calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-north_summer.ecsv +8 -0
  13. calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-north_winter.ecsv +8 -0
  14. calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-south_summer.ecsv +8 -0
  15. calibpipe/atmosphere/models/reference_MDPs/ref_density_at_15km_ctao-south_winter.ecsv +8 -0
  16. calibpipe/atmosphere/models/reference_atmospheres/__init__.py +1 -0
  17. calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-north_intermediate.ecsv +73 -0
  18. calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-north_summer.ecsv +73 -0
  19. calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-north_winter.ecsv +73 -0
  20. calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-south_summer.ecsv +73 -0
  21. calibpipe/atmosphere/models/reference_atmospheres/reference_atmo_model_v0_ctao-south_winter.ecsv +73 -0
  22. calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/__init__.py +1 -0
  23. calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-north_intermediate.ecsv +857 -0
  24. calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-north_summer.ecsv +857 -0
  25. calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-north_winter.ecsv +857 -0
  26. calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-south_summer.ecsv +857 -0
  27. calibpipe/atmosphere/models/reference_rayleigh_scattering_profiles/reference_rayleigh_extinction_profile_v0_ctao-south_winter.ecsv +857 -0
  28. calibpipe/atmosphere/templates/request_templates/__init__.py +1 -0
  29. calibpipe/atmosphere/templates/request_templates/copernicus.json +11 -0
  30. calibpipe/atmosphere/templates/request_templates/gdas.json +12 -0
  31. calibpipe/core/__init__.py +39 -0
  32. calibpipe/core/common_metadata_containers.py +198 -0
  33. calibpipe/core/exceptions.py +87 -0
  34. calibpipe/database/__init__.py +24 -0
  35. calibpipe/database/adapter/__init__.py +23 -0
  36. calibpipe/database/adapter/adapter.py +80 -0
  37. calibpipe/database/adapter/database_containers/__init__.py +63 -0
  38. calibpipe/database/adapter/database_containers/atmosphere.py +199 -0
  39. calibpipe/database/adapter/database_containers/common_metadata.py +150 -0
  40. calibpipe/database/adapter/database_containers/container_map.py +59 -0
  41. calibpipe/database/adapter/database_containers/observatory.py +61 -0
  42. calibpipe/database/adapter/database_containers/table_version_manager.py +39 -0
  43. calibpipe/database/adapter/database_containers/throughput.py +30 -0
  44. calibpipe/database/adapter/database_containers/version_control.py +17 -0
  45. calibpipe/database/connections/__init__.py +28 -0
  46. calibpipe/database/connections/calibpipe_database.py +60 -0
  47. calibpipe/database/connections/postgres_utils.py +97 -0
  48. calibpipe/database/connections/sql_connection.py +103 -0
  49. calibpipe/database/connections/user_confirmation.py +19 -0
  50. calibpipe/database/interfaces/__init__.py +71 -0
  51. calibpipe/database/interfaces/hashable_row_data.py +54 -0
  52. calibpipe/database/interfaces/queries.py +180 -0
  53. calibpipe/database/interfaces/sql_column_info.py +67 -0
  54. calibpipe/database/interfaces/sql_metadata.py +6 -0
  55. calibpipe/database/interfaces/sql_table_info.py +131 -0
  56. calibpipe/database/interfaces/table_handler.py +333 -0
  57. calibpipe/database/interfaces/types.py +96 -0
  58. calibpipe/telescope/throughput/containers.py +66 -0
  59. calibpipe/tests/conftest.py +274 -0
  60. calibpipe/tests/data/atmosphere/molecular_atmosphere/__init__.py +0 -0
  61. calibpipe/tests/data/atmosphere/molecular_atmosphere/contemporary_MDP.ecsv +34 -0
  62. calibpipe/tests/data/atmosphere/molecular_atmosphere/macobac.csv +852 -0
  63. calibpipe/tests/data/atmosphere/molecular_atmosphere/macobac.ecsv +23 -0
  64. calibpipe/tests/data/atmosphere/molecular_atmosphere/merged_file.ecsv +1082 -0
  65. calibpipe/tests/data/atmosphere/molecular_atmosphere/meteo_data_copernicus.ecsv +1082 -0
  66. calibpipe/tests/data/atmosphere/molecular_atmosphere/meteo_data_gdas.ecsv +66 -0
  67. calibpipe/tests/data/atmosphere/molecular_atmosphere/observatory_configurations.json +71 -0
  68. calibpipe/tests/data/utils/__init__.py +0 -0
  69. calibpipe/tests/data/utils/meteo_data_winter_and_summer.ecsv +12992 -0
  70. calibpipe/tests/test_conftest_data.py +200 -0
  71. calibpipe/tests/unittests/array/test_cross_calibration.py +412 -0
  72. calibpipe/tests/unittests/atmosphere/astral_testing.py +107 -0
  73. calibpipe/tests/unittests/atmosphere/test_meteo_data_handler.py +775 -0
  74. calibpipe/tests/unittests/atmosphere/test_molecular_atmosphere.py +327 -0
  75. calibpipe/tests/unittests/database/test_table_handler.py +163 -0
  76. calibpipe/tests/unittests/database/test_types.py +38 -0
  77. calibpipe/tests/unittests/telescope/camera/test_calculate_camcalib_coefficients.py +456 -0
  78. calibpipe/tests/unittests/telescope/camera/test_produce_camcalib_test_data.py +37 -0
  79. calibpipe/tests/unittests/telescope/throughput/test_muon_throughput_calibrator.py +693 -0
  80. calibpipe/tests/unittests/test_bootstrap_db.py +79 -0
  81. calibpipe/tests/unittests/utils/test_observatory.py +309 -0
  82. calibpipe/tools/atmospheric_base_tool.py +78 -0
  83. calibpipe/tools/atmospheric_model_db_loader.py +181 -0
  84. calibpipe/tools/basic_tool_with_db.py +38 -0
  85. calibpipe/tools/camcalib_test_data.py +374 -0
  86. calibpipe/tools/camera_calibrator.py +462 -0
  87. calibpipe/tools/contemporary_mdp_producer.py +87 -0
  88. calibpipe/tools/init_db.py +37 -0
  89. calibpipe/tools/macobac_calculator.py +82 -0
  90. calibpipe/tools/molecular_atmospheric_model_producer.py +197 -0
  91. calibpipe/tools/muon_throughput_calculator.py +219 -0
  92. calibpipe/tools/observatory_data_db_loader.py +71 -0
  93. calibpipe/tools/reference_atmospheric_model_selector.py +201 -0
  94. calibpipe/tools/telescope_cross_calibration_calculator.py +721 -0
  95. calibpipe/utils/__init__.py +10 -0
  96. calibpipe/utils/observatory.py +486 -0
  97. calibpipe/utils/observatory_containers.py +26 -0
  98. calibpipe/version.py +24 -0
  99. ctao_calibpipe-0.3.0rc2.dist-info/METADATA +92 -0
  100. ctao_calibpipe-0.3.0rc2.dist-info/RECORD +105 -0
  101. ctao_calibpipe-0.3.0rc2.dist-info/WHEEL +5 -0
  102. ctao_calibpipe-0.3.0rc2.dist-info/entry_points.txt +12 -0
  103. ctao_calibpipe-0.3.0rc2.dist-info/licenses/AUTHORS.md +13 -0
  104. ctao_calibpipe-0.3.0rc2.dist-info/licenses/LICENSE +21 -0
  105. ctao_calibpipe-0.3.0rc2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,327 @@
1
+ # Set up logging
2
+ from datetime import datetime, timezone
3
+ from pathlib import Path
4
+ from unittest.mock import MagicMock, patch
5
+
6
+ import pytest
7
+ import yaml
8
+ from astropy.table import QTable
9
+ from astropy.time import Time
10
+ from calibpipe.core.exceptions import IntermittentError, MissingInputDataError
11
+ from calibpipe.tools.contemporary_mdp_producer import CreateMolecularDensityProfile
12
+ from calibpipe.tools.macobac_calculator import CalculateMACOBAC
13
+ from calibpipe.tools.molecular_atmospheric_model_producer import (
14
+ CreateMolecularAtmosphericModel,
15
+ )
16
+ from calibpipe.tools.reference_atmospheric_model_selector import (
17
+ SelectMolecularAtmosphericModel,
18
+ )
19
+ from ctapipe.core import run_tool
20
+ from traitlets.config import Config
21
+
22
+
23
+ @pytest.mark.verifies_usecase("UC-120-1.2")
24
+ def test_calculate_macobac(tmp_path):
25
+ config = Config()
26
+ config.CalculateMACOBAC = Config()
27
+ config.CalculateMACOBAC.output_file = str(tmp_path / "macobac.ecsv")
28
+
29
+ with patch(
30
+ "calibpipe.tools.macobac_calculator.CO2DataHandler",
31
+ new_callable=MagicMock,
32
+ ) as mock_class:
33
+ mock_class.return_value.data_path = str(
34
+ Path(__file__).parent.parent.parent
35
+ / "data/atmosphere/molecular_atmosphere/"
36
+ )
37
+ tool = CalculateMACOBAC(config=config)
38
+ run_tool(tool)
39
+
40
+ output_file = Path(config.CalculateMACOBAC.output_file)
41
+ assert output_file.exists(), "Output file was not created."
42
+
43
+ result_table = QTable.read(output_file, format="ascii.ecsv")
44
+ expected_co2_concentration = 419.3
45
+ assert (
46
+ result_table["co2_concentration"][0].value
47
+ == pytest.approx(expected_co2_concentration, abs=0.1)
48
+ ), f"CO2 concentration does not match expected value ({result_table['co2_concentration'][0]} != {expected_co2_concentration})."
49
+ assert (
50
+ result_table["estimation_date"][0]
51
+ == Time(str(datetime.now(timezone.utc).date()), out_subfmt="date").iso
52
+ ), "Estimation date does not match expected value."
53
+
54
+
55
+ @pytest.mark.db()
56
+ @pytest.mark.verifies_usecase("UC-120-1.7")
57
+ @pytest.mark.verifies_usecase("UC-120-1.8")
58
+ def test_create_molecular_atmospheric_model(tmp_path):
59
+ config_path = (
60
+ Path(__file__).parent.parent.parent.parent.parent.parent
61
+ / "docs/source/user_guide/atmosphere/configuration/create_molecular_atmospheric_model.yaml"
62
+ )
63
+ with open(config_path) as file:
64
+ config = Config(yaml.load(file, Loader=yaml.SafeLoader))
65
+
66
+ config.CreateMolecularAtmosphericModel.output_path = str(tmp_path)
67
+ # Mock the necessary components
68
+ with patch(
69
+ # "calibpipe.tools.molecular_atmospheric_model_producer.MeteoDataHandler",
70
+ "calibpipe.tools.atmospheric_base_tool.MeteoDataHandler",
71
+ new_callable=MagicMock,
72
+ ) as mock_meteo_handler_class:
73
+ tool = CreateMolecularAtmosphericModel(config=config)
74
+ # Test missing input data
75
+ with pytest.raises(MissingInputDataError):
76
+ run_tool(
77
+ tool,
78
+ argv=[
79
+ "-c",
80
+ str(
81
+ Path(__file__).parent.parent.parent.parent.parent.parent
82
+ / "docs/source/user_guide/utils/configuration/db_config.yaml"
83
+ ),
84
+ "--macobac12-table-path",
85
+ str(
86
+ Path(__file__).parent.parent.parent
87
+ / "data/atmosphere/molecular_atmosphere/macobac.ecsv"
88
+ ),
89
+ ],
90
+ raises=True,
91
+ )
92
+
93
+ # Now set up the mock data handler
94
+ mock_meteo_handler = MagicMock()
95
+ mock_meteo_handler_class.from_name.return_value = mock_meteo_handler
96
+ mock_meteo_handler.data_path = str(
97
+ Path(__file__).parent.parent.parent
98
+ / "data/atmosphere/molecular_atmosphere/"
99
+ )
100
+ mock_meteo_handler.request_data.return_value = 0
101
+
102
+ run_tool(
103
+ tool,
104
+ argv=[
105
+ "-c",
106
+ str(
107
+ Path(__file__).parent.parent.parent.parent.parent.parent
108
+ / "docs/source/user_guide/utils/configuration/db_config.yaml"
109
+ ),
110
+ "--macobac12-table-path",
111
+ str(
112
+ Path(__file__).parent.parent.parent
113
+ / "data/atmosphere/molecular_atmosphere/macobac.ecsv"
114
+ ),
115
+ ],
116
+ raises=True,
117
+ )
118
+
119
+ # Check if the output files were created
120
+ output_profile = (
121
+ Path(config.CreateMolecularAtmosphericModel.output_path)
122
+ / "contemporary_atmospheric_profile.ascii.ecsv"
123
+ )
124
+ output_extinction = (
125
+ Path(config.CreateMolecularAtmosphericModel.output_path)
126
+ / "contemporary_rayleigh_extinction_profile.ascii.ecsv"
127
+ )
128
+ assert (
129
+ output_profile.exists()
130
+ ), "Contemporary atmospheric profile file was not created."
131
+ assert (
132
+ output_extinction.exists()
133
+ ), "Contemporary Rayleigh extinction profile file was not created."
134
+
135
+ # Read and validate the output files
136
+ profile_table = QTable.read(output_profile, format="ascii.ecsv")
137
+ extinction_table = QTable.read(output_extinction, format="ascii.ecsv")
138
+
139
+ expected_profile_columns = [
140
+ "altitude",
141
+ "atmospheric_density",
142
+ "atmospheric_thickness",
143
+ "refractive_index_m_1",
144
+ "temperature",
145
+ "pressure",
146
+ "partial_water_pressure",
147
+ ]
148
+
149
+ for column in expected_profile_columns:
150
+ assert (
151
+ column in profile_table.colnames
152
+ ), f"{column} column missing in profile table."
153
+
154
+ assert (
155
+ "altitude_max" in extinction_table.colnames
156
+ ), "Altitude_max column missing in extinction table."
157
+ assert (
158
+ "altitude_min" in extinction_table.colnames
159
+ ), "Altitude_min column missing in extinction table."
160
+
161
+
162
+ @pytest.mark.db()
163
+ @pytest.mark.verifies_usecase("UC-120-1.6")
164
+ def test_create_molecular_density_profile(tmp_path):
165
+ config_path = (
166
+ Path(__file__).parent.parent.parent.parent.parent.parent
167
+ / "docs/source/user_guide/atmosphere/configuration/create_molecular_density_profile.yaml"
168
+ )
169
+ with open(config_path) as file:
170
+ config = Config(yaml.load(file, Loader=yaml.SafeLoader))
171
+
172
+ config.CreateMolecularDensityProfile.output_path = str(tmp_path)
173
+ # Mock the necessary components
174
+ with patch(
175
+ "calibpipe.tools.atmospheric_base_tool.MeteoDataHandler",
176
+ new_callable=MagicMock,
177
+ ) as mock_meteo_handler_class:
178
+ tool = CreateMolecularDensityProfile(config=config)
179
+ # Test missing input data
180
+ with pytest.raises(MissingInputDataError):
181
+ run_tool(
182
+ tool,
183
+ argv=[
184
+ "-c",
185
+ str(
186
+ Path(__file__).parent.parent.parent.parent.parent.parent
187
+ / "docs/source/user_guide/utils/configuration/db_config.yaml"
188
+ ),
189
+ ],
190
+ raises=True,
191
+ )
192
+
193
+ # Now set up the mock data handler
194
+ mock_meteo_handler = MagicMock()
195
+ mock_meteo_handler_class.from_name.return_value = mock_meteo_handler
196
+ mock_meteo_handler.data_path = str(
197
+ Path(__file__).parent.parent.parent
198
+ / "data/atmosphere/molecular_atmosphere/"
199
+ )
200
+ mock_meteo_handler.request_data.return_value = 0
201
+
202
+ run_tool(
203
+ tool,
204
+ argv=[
205
+ "-c",
206
+ str(
207
+ Path(__file__).parent.parent.parent.parent.parent.parent
208
+ / "docs/source/user_guide/utils/configuration/db_config.yaml"
209
+ ),
210
+ ],
211
+ raises=True,
212
+ )
213
+
214
+ # Check if the output file was created
215
+ output_file = (
216
+ Path(config.CreateMolecularDensityProfile.output_path)
217
+ / "contemporary_molecular_density_profile.ascii.ecsv"
218
+ )
219
+ assert (
220
+ output_file.exists()
221
+ ), "Contemporary molecular density profile file was not created."
222
+
223
+ # Read and validate the output file
224
+ mdp_table = QTable.read(output_file, format="ascii.ecsv")
225
+
226
+ expected_columns = [
227
+ "altitude",
228
+ "number density",
229
+ ]
230
+
231
+ for column in expected_columns:
232
+ assert (
233
+ column in mdp_table.colnames
234
+ ), f"{column} column missing in molecular density profile table."
235
+
236
+
237
+ @pytest.mark.db()
238
+ @pytest.mark.verifies_usecase("UC-120-1.3")
239
+ def test_select_molecular_atmospheric_model(tmp_path):
240
+ config_path = (
241
+ Path(__file__).parent.parent.parent.parent.parent.parent
242
+ / "docs/source/user_guide/atmosphere/configuration/select_reference_atmospheric_model.yaml"
243
+ )
244
+ with open(config_path) as file:
245
+ config = Config(yaml.load(file, Loader=yaml.SafeLoader))
246
+
247
+ config.SelectMolecularAtmosphericModel.output_path = str(tmp_path)
248
+ # Mock the necessary components
249
+ with patch(
250
+ "calibpipe.tools.atmospheric_base_tool.MeteoDataHandler",
251
+ new_callable=MagicMock,
252
+ ) as mock_meteo_handler_class:
253
+ tool = SelectMolecularAtmosphericModel(config=config)
254
+
255
+ # Mock database and data handler behavior
256
+ mock_meteo_handler = MagicMock()
257
+ mock_meteo_handler_class.from_name.return_value = mock_meteo_handler
258
+ mock_meteo_handler.data_path = str(
259
+ Path(__file__).parent.parent.parent
260
+ / "data/atmosphere/molecular_atmosphere/"
261
+ )
262
+
263
+ # Test missing input data
264
+ mock_meteo_handler.request_data.return_value = 1
265
+
266
+ with pytest.raises(IntermittentError):
267
+ run_tool(
268
+ tool,
269
+ argv=[
270
+ "-c",
271
+ str(
272
+ Path(__file__).parent.parent.parent.parent.parent.parent
273
+ / "docs/source/user_guide/utils/configuration/db_config.yaml"
274
+ ),
275
+ ],
276
+ raises=True,
277
+ )
278
+
279
+ # Check if the output files were created
280
+ output_profile = (
281
+ Path(config.SelectMolecularAtmosphericModel.output_path)
282
+ / "selected_atmospheric_profile.ascii.ecsv"
283
+ )
284
+ output_extinction = (
285
+ Path(config.SelectMolecularAtmosphericModel.output_path)
286
+ / "selected_rayleigh_extinction_profile.ascii.ecsv"
287
+ )
288
+ assert (
289
+ output_profile.exists()
290
+ ), "Selected atmospheric profile file was not created."
291
+ assert (
292
+ output_extinction.exists()
293
+ ), "Selected Rayleigh extinction profile file was not created."
294
+
295
+ # Read and validate the output files
296
+ profile_table = QTable.read(output_profile, format="ascii.ecsv")
297
+ extinction_table = QTable.read(output_extinction, format="ascii.ecsv")
298
+
299
+ expected_profile_columns = [
300
+ "altitude",
301
+ "atmospheric_density",
302
+ "atmospheric_thickness",
303
+ "refractive_index_m_1",
304
+ "temperature",
305
+ "pressure",
306
+ "partial_water_pressure",
307
+ ]
308
+
309
+ for column in expected_profile_columns:
310
+ assert (
311
+ column in profile_table.colnames
312
+ ), f"{column} column missing in profile table."
313
+
314
+ expected_extinction_columns = ["altitude_min", "altitude_max"]
315
+ expected_extinction_columns.extend([f"{i}.0 nm" for i in range(200, 1000)])
316
+
317
+ for column in expected_extinction_columns:
318
+ assert (
319
+ column in extinction_table.colnames
320
+ ), f"{column} column missing in extinction table."
321
+ if "altitude" in column:
322
+ assert (
323
+ extinction_table[column].unit == "km"
324
+ ), f"Column {column} unit does not match expected unit."
325
+ assert (
326
+ extinction_table[column].ndim == 1
327
+ ), f"Column {column} does not have the expected number of dimensions."
@@ -0,0 +1,163 @@
1
+ # Import the necessary modules and classes for testing
2
+ import datetime
3
+ from pathlib import Path
4
+ from unittest.mock import MagicMock, patch
5
+
6
+ import astropy.units as u
7
+ import pytest
8
+ import yaml
9
+ from calibpipe.core.common_metadata_containers import (
10
+ ContactReferenceMetadataContainer,
11
+ ProductReferenceMetadataContainer,
12
+ ReferenceMetadataContainer,
13
+ )
14
+ from calibpipe.database.adapter import Adapter
15
+ from calibpipe.database.connections import CalibPipeDatabase
16
+ from calibpipe.database.interfaces import TableHandler
17
+ from calibpipe.telescope.throughput.containers import OpticalThoughtputContainer
18
+ from calibpipe.utils.observatory import (
19
+ Observatory,
20
+ )
21
+ from traitlets.config import Config
22
+
23
+
24
+ @pytest.fixture()
25
+ def mock_connection():
26
+ """
27
+ Fixture to create a mock database connection.
28
+ """
29
+ connection = MagicMock(spec=CalibPipeDatabase)
30
+ return connection
31
+
32
+
33
+ # Fixture to provide a database connection
34
+ @pytest.fixture()
35
+ def test_config():
36
+ # Setup and connect to the test database
37
+ config_path = Path(__file__).parent.joinpath(
38
+ "../../../../../docs/source/user_guide/utils/configuration/"
39
+ )
40
+ with open(config_path.joinpath("upload_observatory_data_db.yaml")) as yaml_file:
41
+ config_data = yaml.safe_load(yaml_file)
42
+ config_data = config_data["UploadObservatoryData"]
43
+
44
+ with open(config_path.joinpath("db_config.yaml")) as yaml_file:
45
+ config_data |= yaml.safe_load(yaml_file)
46
+ return config_data
47
+
48
+
49
+ @pytest.fixture()
50
+ def test_container(test_config):
51
+ return Observatory(config=Config(test_config["observatories"][0])).containers[0]
52
+
53
+
54
+ # Test cases for TableHandler class and other functions in the module
55
+ class TestTableHandler:
56
+ # Test get_database_table_insertion method
57
+ @pytest.mark.db()
58
+ def test_get_database_table_insertion(self, test_container):
59
+ # Prepare a mock container and call the method
60
+ table, kwargs = TableHandler.get_database_table_insertion(test_container)
61
+
62
+ # Assert that the table and kwargs are not None
63
+ assert table is not None
64
+ assert kwargs is not None
65
+
66
+ # Test read_table_from_database method
67
+ @pytest.mark.db()
68
+ def test_read_table_from_database(self, test_container, test_config):
69
+ TableHandler.prepare_db_tables(
70
+ [
71
+ test_container,
72
+ ],
73
+ test_config["database_configuration"],
74
+ )
75
+ condition = "c.elevation == 3000"
76
+ with CalibPipeDatabase(**test_config["database_configuration"]) as connection:
77
+ qtable = TableHandler.read_table_from_database(
78
+ type(test_container), connection, condition
79
+ )
80
+
81
+ # Assert that qtable is not None and has the expected columns
82
+ assert qtable is not None
83
+ assert "elevation" in qtable.colnames
84
+ assert qtable["elevation"].unit == u.m
85
+
86
+
87
+ def test_upload_data(mock_connection):
88
+ """
89
+ Test the upload_data function to ensure it correctly uploads data and metadata.
90
+ """
91
+ # Use OpticalThoughtputContainer as the data container
92
+ data = OpticalThoughtputContainer(
93
+ mean=0.95,
94
+ median=0.90,
95
+ std=0.02,
96
+ sem=0.001,
97
+ method="muon analysis",
98
+ time_start=datetime.datetime(2025, 1, 1, tzinfo=datetime.timezone.utc),
99
+ time_end=datetime.datetime(2025, 12, 31, tzinfo=datetime.timezone.utc),
100
+ obs_id=12345,
101
+ )
102
+
103
+ # Mock metadata containers
104
+ reference_metadata = ReferenceMetadataContainer(
105
+ version_atmospheric_model="1.0",
106
+ version=1,
107
+ ID_optical_throughput=None, # Will be set during the upload
108
+ )
109
+ product_metadata = ProductReferenceMetadataContainer(
110
+ description="Test product",
111
+ creation_time="2025-04-08T12:00:00Z",
112
+ product_id="12345",
113
+ )
114
+ contact_metadata = ContactReferenceMetadataContainer(
115
+ organization="Test Organization",
116
+ name="Test User",
117
+ email="test@example.com",
118
+ )
119
+
120
+ # Combine metadata into a list
121
+ metadata = [reference_metadata, product_metadata, contact_metadata]
122
+
123
+ # Mock database behavior
124
+ mock_connection.execute.return_value.fetchone.return_value = MagicMock(
125
+ _asdict=lambda: {"ID": 1}
126
+ )
127
+
128
+ # Patch the insert_row_in_database method
129
+ with patch(
130
+ "calibpipe.database.interfaces.table_handler.TableHandler.insert_row_in_database",
131
+ ) as mock_insert:
132
+ # Call the upload_data function
133
+ TableHandler.upload_data(data, metadata, mock_connection)
134
+
135
+ # Assertions to verify correct behavior
136
+ # Verify that the main data was inserted
137
+ data_table, data_kwargs = Adapter.to_postgres(data)
138
+
139
+ calls = mock_insert.call_args_list
140
+
141
+ # Check that a call was made with the expected table and connection
142
+ assert any(
143
+ call_args[0][0] == data_table
144
+ and call_args[0][2] == mock_connection
145
+ and call_args[0][1]["mean"] == 0.95
146
+ for call_args in calls
147
+ ), "Expected call to insert_row_in_database not found."
148
+
149
+ ref_table, ref_kwargs = Adapter.to_postgres(reference_metadata)
150
+
151
+ assert any(
152
+ call_args[0][0] == ref_table
153
+ and call_args[0][2] == mock_connection
154
+ and call_args[0][1]["version_atmospheric_model"] == "1.0"
155
+ for call_args in calls
156
+ ), "Expected call to insert_row_in_database not found."
157
+
158
+ for container in metadata[1:]:
159
+ meta_table, meta_kwargs = Adapter.to_postgres(container)
160
+ assert any(
161
+ call_args[0][0] == meta_table and call_args[0][2] == mock_connection
162
+ for call_args in calls
163
+ ), "Expected call to insert_row_in_database not found."
@@ -0,0 +1,38 @@
1
+ """Test sqlalchemy types."""
2
+
3
+ from calibpipe.database.interfaces.types import (
4
+ BigInteger,
5
+ Boolean,
6
+ ColumnType,
7
+ Date,
8
+ DateTime,
9
+ Double,
10
+ Float,
11
+ Integer,
12
+ NDArray,
13
+ Numeric,
14
+ SmallInteger,
15
+ String,
16
+ Time,
17
+ )
18
+ from sqlalchemy.sql.type_api import TypeEngine
19
+
20
+
21
+ def test_types():
22
+ """Test that types are instance of TypeEngine."""
23
+ for type_ in [
24
+ Boolean,
25
+ SmallInteger,
26
+ Integer,
27
+ BigInteger,
28
+ Float,
29
+ Double,
30
+ Numeric,
31
+ String,
32
+ Date,
33
+ DateTime,
34
+ Time,
35
+ ColumnType,
36
+ NDArray,
37
+ ]:
38
+ assert issubclass(type_, TypeEngine)