disdrodb 0.0.21__py3-none-any.whl → 0.1.1__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.
- disdrodb/__init__.py +132 -15
- disdrodb/_config.py +4 -2
- disdrodb/_version.py +9 -4
- disdrodb/api/checks.py +264 -237
- disdrodb/api/configs.py +4 -8
- disdrodb/api/create_directories.py +235 -290
- disdrodb/api/info.py +217 -26
- disdrodb/api/io.py +306 -270
- disdrodb/api/path.py +597 -173
- disdrodb/api/search.py +486 -0
- disdrodb/{metadata/scripts → cli}/disdrodb_check_metadata_archive.py +12 -7
- disdrodb/{utils/pandas.py → cli/disdrodb_data_archive_directory.py} +9 -18
- disdrodb/cli/disdrodb_download_archive.py +86 -0
- disdrodb/cli/disdrodb_download_metadata_archive.py +53 -0
- disdrodb/cli/disdrodb_download_station.py +84 -0
- disdrodb/{api/scripts → cli}/disdrodb_initialize_station.py +22 -10
- disdrodb/cli/disdrodb_metadata_archive_directory.py +32 -0
- disdrodb/{data_transfer/scripts/disdrodb_download_station.py → cli/disdrodb_open_data_archive.py} +22 -22
- disdrodb/cli/disdrodb_open_logs_directory.py +69 -0
- disdrodb/{data_transfer/scripts/disdrodb_upload_station.py → cli/disdrodb_open_metadata_archive.py} +22 -24
- disdrodb/cli/disdrodb_open_metadata_directory.py +71 -0
- disdrodb/cli/disdrodb_open_product_directory.py +74 -0
- disdrodb/cli/disdrodb_open_readers_directory.py +32 -0
- disdrodb/{l0/scripts → cli}/disdrodb_run_l0.py +38 -31
- disdrodb/{l0/scripts → cli}/disdrodb_run_l0_station.py +32 -30
- disdrodb/{l0/scripts → cli}/disdrodb_run_l0a.py +30 -21
- disdrodb/{l0/scripts → cli}/disdrodb_run_l0a_station.py +24 -33
- disdrodb/{l0/scripts → cli}/disdrodb_run_l0b.py +30 -21
- disdrodb/{l0/scripts → cli}/disdrodb_run_l0b_station.py +25 -34
- disdrodb/cli/disdrodb_run_l0c.py +130 -0
- disdrodb/cli/disdrodb_run_l0c_station.py +129 -0
- disdrodb/cli/disdrodb_run_l1.py +122 -0
- disdrodb/cli/disdrodb_run_l1_station.py +121 -0
- disdrodb/cli/disdrodb_run_l2e.py +122 -0
- disdrodb/cli/disdrodb_run_l2e_station.py +122 -0
- disdrodb/cli/disdrodb_run_l2m.py +122 -0
- disdrodb/cli/disdrodb_run_l2m_station.py +122 -0
- disdrodb/cli/disdrodb_upload_archive.py +105 -0
- disdrodb/cli/disdrodb_upload_station.py +98 -0
- disdrodb/configs.py +90 -25
- disdrodb/data_transfer/__init__.py +22 -0
- disdrodb/data_transfer/download_data.py +87 -90
- disdrodb/data_transfer/upload_data.py +64 -37
- disdrodb/data_transfer/zenodo.py +15 -18
- disdrodb/docs.py +1 -1
- disdrodb/issue/__init__.py +17 -4
- disdrodb/issue/checks.py +10 -23
- disdrodb/issue/reader.py +9 -12
- disdrodb/issue/writer.py +14 -17
- disdrodb/l0/__init__.py +17 -26
- disdrodb/l0/check_configs.py +35 -23
- disdrodb/l0/check_standards.py +46 -51
- disdrodb/l0/configs/{Thies_LPM → LPM}/bins_diameter.yml +44 -44
- disdrodb/l0/configs/{Thies_LPM → LPM}/bins_velocity.yml +40 -40
- disdrodb/l0/configs/LPM/l0a_encodings.yml +80 -0
- disdrodb/l0/configs/{Thies_LPM → LPM}/l0b_cf_attrs.yml +84 -65
- disdrodb/l0/configs/{Thies_LPM → LPM}/l0b_encodings.yml +50 -9
- disdrodb/l0/configs/{Thies_LPM → LPM}/raw_data_format.yml +285 -245
- disdrodb/l0/configs/{OTT_Parsivel → PARSIVEL}/bins_diameter.yml +66 -66
- disdrodb/l0/configs/{OTT_Parsivel → PARSIVEL}/bins_velocity.yml +64 -64
- disdrodb/l0/configs/PARSIVEL/l0a_encodings.yml +32 -0
- disdrodb/l0/configs/{OTT_Parsivel → PARSIVEL}/l0b_cf_attrs.yml +23 -21
- disdrodb/l0/configs/{OTT_Parsivel → PARSIVEL}/l0b_encodings.yml +17 -17
- disdrodb/l0/configs/{OTT_Parsivel → PARSIVEL}/raw_data_format.yml +77 -77
- disdrodb/l0/configs/{OTT_Parsivel2 → PARSIVEL2}/bins_diameter.yml +64 -64
- disdrodb/l0/configs/{OTT_Parsivel2 → PARSIVEL2}/bins_velocity.yml +64 -64
- disdrodb/l0/configs/PARSIVEL2/l0a_encodings.yml +39 -0
- disdrodb/l0/configs/{OTT_Parsivel2 → PARSIVEL2}/l0b_cf_attrs.yml +28 -26
- disdrodb/l0/configs/{OTT_Parsivel2 → PARSIVEL2}/l0b_encodings.yml +20 -20
- disdrodb/l0/configs/{OTT_Parsivel2 → PARSIVEL2}/raw_data_format.yml +107 -107
- disdrodb/l0/configs/PWS100/bins_diameter.yml +173 -0
- disdrodb/l0/configs/PWS100/bins_velocity.yml +173 -0
- disdrodb/l0/configs/PWS100/l0a_encodings.yml +19 -0
- disdrodb/l0/configs/PWS100/l0b_cf_attrs.yml +76 -0
- disdrodb/l0/configs/PWS100/l0b_encodings.yml +176 -0
- disdrodb/l0/configs/PWS100/raw_data_format.yml +182 -0
- disdrodb/l0/configs/{RD_80 → RD80}/bins_diameter.yml +40 -40
- disdrodb/l0/configs/RD80/l0a_encodings.yml +16 -0
- disdrodb/l0/configs/{RD_80 → RD80}/l0b_cf_attrs.yml +3 -3
- disdrodb/l0/configs/RD80/l0b_encodings.yml +135 -0
- disdrodb/l0/configs/{RD_80 → RD80}/raw_data_format.yml +46 -50
- disdrodb/l0/l0_reader.py +216 -340
- disdrodb/l0/l0a_processing.py +237 -208
- disdrodb/l0/l0b_nc_processing.py +227 -80
- disdrodb/l0/l0b_processing.py +96 -174
- disdrodb/l0/l0c_processing.py +627 -0
- disdrodb/l0/readers/{ARM → LPM/ARM}/ARM_LPM.py +36 -58
- disdrodb/l0/readers/LPM/AUSTRALIA/MELBOURNE_2007_LPM.py +236 -0
- disdrodb/l0/readers/LPM/BRAZIL/CHUVA_LPM.py +185 -0
- disdrodb/l0/readers/LPM/BRAZIL/GOAMAZON_LPM.py +185 -0
- disdrodb/l0/readers/LPM/ITALY/GID_LPM.py +195 -0
- disdrodb/l0/readers/LPM/ITALY/GID_LPM_W.py +210 -0
- disdrodb/l0/readers/{BRAZIL/GOAMAZON_LPM.py → LPM/KIT/CHWALA.py} +97 -76
- disdrodb/l0/readers/LPM/SLOVENIA/ARSO.py +197 -0
- disdrodb/l0/readers/LPM/SLOVENIA/CRNI_VRH.py +197 -0
- disdrodb/l0/readers/{UK → LPM/UK}/DIVEN.py +14 -35
- disdrodb/l0/readers/PARSIVEL/AUSTRALIA/MELBOURNE_2007_PARSIVEL.py +157 -0
- disdrodb/l0/readers/PARSIVEL/CHINA/CHONGQING.py +113 -0
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/ARCTIC_2021.py +40 -57
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/COMMON_2011.py +37 -54
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/DAVOS_2009_2011.py +34 -51
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/EPFL_2009.py +34 -51
- disdrodb/l0/readers/{EPFL/PARADISO_2014.py → PARSIVEL/EPFL/EPFL_ROOF_2008.py} +38 -50
- disdrodb/l0/readers/PARSIVEL/EPFL/EPFL_ROOF_2010.py +105 -0
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/EPFL_ROOF_2011.py +34 -51
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/EPFL_ROOF_2012.py +33 -51
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/GENEPI_2007.py +25 -44
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/GRAND_ST_BERNARD_2007.py +25 -44
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/GRAND_ST_BERNARD_2007_2.py +25 -44
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/HPICONET_2010.py +34 -51
- disdrodb/l0/readers/{EPFL/EPFL_ROOF_2010.py → PARSIVEL/EPFL/HYMEX_LTE_SOP2.py} +37 -50
- disdrodb/l0/readers/PARSIVEL/EPFL/HYMEX_LTE_SOP3.py +111 -0
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/HYMEX_LTE_SOP4.py +36 -54
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/LOCARNO_2018.py +34 -52
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/LOCARNO_2019.py +38 -56
- disdrodb/l0/readers/PARSIVEL/EPFL/PARADISO_2014.py +105 -0
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/PARSIVEL_2007.py +27 -45
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/PLATO_2019.py +24 -44
- disdrodb/l0/readers/PARSIVEL/EPFL/RACLETS_2019.py +140 -0
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/RACLETS_2019_WJF.py +41 -59
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/RIETHOLZBACH_2011.py +34 -51
- disdrodb/l0/readers/PARSIVEL/EPFL/SAMOYLOV_2017.py +117 -0
- disdrodb/l0/readers/PARSIVEL/EPFL/SAMOYLOV_2019.py +137 -0
- disdrodb/l0/readers/{EPFL → PARSIVEL/EPFL}/UNIL_2022.py +42 -55
- disdrodb/l0/readers/PARSIVEL/GPM/IFLOODS.py +104 -0
- disdrodb/l0/readers/{GPM → PARSIVEL/GPM}/LPVEX.py +29 -48
- disdrodb/l0/readers/PARSIVEL/GPM/MC3E.py +184 -0
- disdrodb/l0/readers/PARSIVEL/KIT/BURKINA_FASO.py +133 -0
- disdrodb/l0/readers/PARSIVEL/NCAR/CCOPE_2015.py +113 -0
- disdrodb/l0/readers/{NCAR/VORTEX_SE_2016_P1.py → PARSIVEL/NCAR/OWLES_MIPS.py} +46 -72
- disdrodb/l0/readers/PARSIVEL/NCAR/PECAN_MOBILE.py +125 -0
- disdrodb/l0/readers/{NCAR/OWLES_MIPS.py → PARSIVEL/NCAR/PLOWS_MIPS.py} +45 -64
- disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2009.py +114 -0
- disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010.py +176 -0
- disdrodb/l0/readers/PARSIVEL/NCAR/VORTEX2_2010_UF.py +183 -0
- disdrodb/l0/readers/PARSIVEL/SLOVENIA/UL_FGG.py +121 -0
- disdrodb/l0/readers/{ARM/ARM_LD.py → PARSIVEL2/ARM/ARM_PARSIVEL2.py} +27 -50
- disdrodb/l0/readers/PARSIVEL2/BRAZIL/CHUVA_PARSIVEL2.py +163 -0
- disdrodb/l0/readers/PARSIVEL2/BRAZIL/GOAMAZON_PARSIVEL2.py +163 -0
- disdrodb/l0/readers/{DENMARK → PARSIVEL2/DENMARK}/EROSION_nc.py +14 -35
- disdrodb/l0/readers/PARSIVEL2/FRANCE/ENPC_PARSIVEL2.py +189 -0
- disdrodb/l0/readers/PARSIVEL2/FRANCE/SIRTA_PARSIVEL2.py +119 -0
- disdrodb/l0/readers/PARSIVEL2/GPM/GCPEX.py +104 -0
- disdrodb/l0/readers/PARSIVEL2/GPM/NSSTC.py +176 -0
- disdrodb/l0/readers/PARSIVEL2/ITALY/GID_PARSIVEL2.py +32 -0
- disdrodb/l0/readers/PARSIVEL2/MEXICO/OH_IIUNAM_nc.py +56 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/PECAN_FP3.py +120 -0
- disdrodb/l0/readers/{NCAR → PARSIVEL2/NCAR}/PECAN_MIPS.py +45 -64
- disdrodb/l0/readers/PARSIVEL2/NCAR/RELAMPAGO_PARSIVEL2.py +181 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_PJ.py +160 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/SNOWIE_SB.py +160 -0
- disdrodb/l0/readers/{NCAR/PLOWS_MIPS.py → PARSIVEL2/NCAR/VORTEX_SE_2016_P1.py} +49 -66
- disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_P2.py +118 -0
- disdrodb/l0/readers/PARSIVEL2/NCAR/VORTEX_SE_2016_PIPS.py +152 -0
- disdrodb/l0/readers/PARSIVEL2/NETHERLANDS/DELFT.py +166 -0
- disdrodb/l0/readers/PWS100/FRANCE/ENPC_PWS100.py +150 -0
- disdrodb/l0/readers/{NCAR/RELAMPAGO_RD80.py → RD80/BRAZIL/CHUVA_RD80.py} +36 -60
- disdrodb/l0/readers/{BRAZIL → RD80/BRAZIL}/GOAMAZON_RD80.py +36 -55
- disdrodb/l0/readers/{NCAR → RD80/NCAR}/CINDY_2011_RD80.py +35 -54
- disdrodb/l0/readers/{BRAZIL/CHUVA_RD80.py → RD80/NCAR/RELAMPAGO_RD80.py} +40 -54
- disdrodb/l0/readers/RD80/NOAA/PSL_RD80.py +274 -0
- disdrodb/l0/readers/template_reader_raw_netcdf_data.py +62 -0
- disdrodb/l0/readers/{reader_template.py → template_reader_raw_text_data.py} +20 -44
- disdrodb/l0/routines.py +885 -581
- disdrodb/l0/standards.py +77 -238
- disdrodb/l0/template_tools.py +105 -110
- disdrodb/l1/__init__.py +17 -0
- disdrodb/l1/beard_model.py +716 -0
- disdrodb/l1/encoding_attrs.py +635 -0
- disdrodb/l1/fall_velocity.py +260 -0
- disdrodb/l1/filters.py +192 -0
- disdrodb/l1/processing.py +202 -0
- disdrodb/l1/resampling.py +236 -0
- disdrodb/l1/routines.py +358 -0
- disdrodb/l1_env/__init__.py +17 -0
- disdrodb/l1_env/routines.py +38 -0
- disdrodb/l2/__init__.py +17 -0
- disdrodb/l2/empirical_dsd.py +1833 -0
- disdrodb/l2/event.py +388 -0
- disdrodb/l2/processing.py +528 -0
- disdrodb/l2/processing_options.py +213 -0
- disdrodb/l2/routines.py +868 -0
- disdrodb/metadata/__init__.py +9 -2
- disdrodb/metadata/checks.py +180 -124
- disdrodb/metadata/download.py +81 -0
- disdrodb/metadata/geolocation.py +146 -0
- disdrodb/metadata/info.py +20 -13
- disdrodb/metadata/manipulation.py +3 -3
- disdrodb/metadata/reader.py +59 -8
- disdrodb/metadata/search.py +77 -144
- disdrodb/metadata/standards.py +83 -80
- disdrodb/metadata/writer.py +10 -16
- disdrodb/psd/__init__.py +38 -0
- disdrodb/psd/fitting.py +2146 -0
- disdrodb/psd/models.py +774 -0
- disdrodb/routines.py +1412 -0
- disdrodb/scattering/__init__.py +28 -0
- disdrodb/scattering/axis_ratio.py +344 -0
- disdrodb/scattering/routines.py +456 -0
- disdrodb/utils/__init__.py +17 -0
- disdrodb/utils/attrs.py +208 -0
- disdrodb/utils/cli.py +269 -0
- disdrodb/utils/compression.py +60 -42
- disdrodb/utils/dask.py +62 -0
- disdrodb/utils/dataframe.py +342 -0
- disdrodb/utils/decorators.py +110 -0
- disdrodb/utils/directories.py +107 -46
- disdrodb/utils/encoding.py +127 -0
- disdrodb/utils/list.py +29 -0
- disdrodb/utils/logger.py +168 -46
- disdrodb/utils/time.py +657 -0
- disdrodb/utils/warnings.py +30 -0
- disdrodb/utils/writer.py +57 -0
- disdrodb/utils/xarray.py +138 -47
- disdrodb/utils/yaml.py +0 -1
- disdrodb/viz/__init__.py +17 -0
- disdrodb/viz/plots.py +17 -0
- disdrodb-0.1.1.dist-info/METADATA +294 -0
- disdrodb-0.1.1.dist-info/RECORD +232 -0
- {disdrodb-0.0.21.dist-info → disdrodb-0.1.1.dist-info}/WHEEL +1 -1
- disdrodb-0.1.1.dist-info/entry_points.txt +30 -0
- disdrodb/data_transfer/scripts/disdrodb_download_archive.py +0 -53
- disdrodb/data_transfer/scripts/disdrodb_upload_archive.py +0 -57
- disdrodb/l0/configs/OTT_Parsivel/l0a_encodings.yml +0 -32
- disdrodb/l0/configs/OTT_Parsivel2/l0a_encodings.yml +0 -39
- disdrodb/l0/configs/RD_80/l0a_encodings.yml +0 -16
- disdrodb/l0/configs/RD_80/l0b_encodings.yml +0 -135
- disdrodb/l0/configs/Thies_LPM/l0a_encodings.yml +0 -80
- disdrodb/l0/io.py +0 -257
- disdrodb/l0/l0_processing.py +0 -1091
- disdrodb/l0/readers/AUSTRALIA/MELBOURNE_2007_OTT.py +0 -178
- disdrodb/l0/readers/AUSTRALIA/MELBOURNE_2007_THIES.py +0 -247
- disdrodb/l0/readers/BRAZIL/CHUVA_LPM.py +0 -204
- disdrodb/l0/readers/BRAZIL/CHUVA_OTT.py +0 -183
- disdrodb/l0/readers/BRAZIL/GOAMAZON_OTT.py +0 -183
- disdrodb/l0/readers/CHINA/CHONGQING.py +0 -131
- disdrodb/l0/readers/EPFL/EPFL_ROOF_2008.py +0 -128
- disdrodb/l0/readers/EPFL/HYMEX_LTE_SOP2.py +0 -127
- disdrodb/l0/readers/EPFL/HYMEX_LTE_SOP3.py +0 -129
- disdrodb/l0/readers/EPFL/RACLETS_2019.py +0 -158
- disdrodb/l0/readers/EPFL/SAMOYLOV_2017.py +0 -136
- disdrodb/l0/readers/EPFL/SAMOYLOV_2019.py +0 -158
- disdrodb/l0/readers/FRANCE/SIRTA_OTT2.py +0 -138
- disdrodb/l0/readers/GPM/GCPEX.py +0 -123
- disdrodb/l0/readers/GPM/IFLOODS.py +0 -123
- disdrodb/l0/readers/GPM/MC3E.py +0 -123
- disdrodb/l0/readers/GPM/NSSTC.py +0 -164
- disdrodb/l0/readers/ITALY/GID.py +0 -199
- disdrodb/l0/readers/MEXICO/OH_IIUNAM_nc.py +0 -92
- disdrodb/l0/readers/NCAR/CCOPE_2015.py +0 -133
- disdrodb/l0/readers/NCAR/PECAN_FP3.py +0 -137
- disdrodb/l0/readers/NCAR/PECAN_MOBILE.py +0 -144
- disdrodb/l0/readers/NCAR/RELAMPAGO_OTT.py +0 -195
- disdrodb/l0/readers/NCAR/SNOWIE_PJ.py +0 -172
- disdrodb/l0/readers/NCAR/SNOWIE_SB.py +0 -179
- disdrodb/l0/readers/NCAR/VORTEX2_2009.py +0 -133
- disdrodb/l0/readers/NCAR/VORTEX2_2010.py +0 -188
- disdrodb/l0/readers/NCAR/VORTEX2_2010_UF.py +0 -191
- disdrodb/l0/readers/NCAR/VORTEX_SE_2016_P2.py +0 -135
- disdrodb/l0/readers/NCAR/VORTEX_SE_2016_PIPS.py +0 -170
- disdrodb/l0/readers/NETHERLANDS/DELFT.py +0 -187
- disdrodb/l0/readers/SPAIN/SBEGUERIA.py +0 -179
- disdrodb/l0/scripts/disdrodb_run_l0b_concat.py +0 -93
- disdrodb/l0/scripts/disdrodb_run_l0b_concat_station.py +0 -85
- disdrodb/utils/netcdf.py +0 -452
- disdrodb/utils/scripts.py +0 -102
- disdrodb-0.0.21.dist-info/AUTHORS.md +0 -18
- disdrodb-0.0.21.dist-info/METADATA +0 -186
- disdrodb-0.0.21.dist-info/RECORD +0 -168
- disdrodb-0.0.21.dist-info/entry_points.txt +0 -15
- /disdrodb/l0/configs/{RD_80 → RD80}/bins_velocity.yml +0 -0
- /disdrodb/l0/manuals/{Thies_LPM.pdf → LPM.pdf} +0 -0
- /disdrodb/l0/manuals/{ODM_470.pdf → ODM470.pdf} +0 -0
- /disdrodb/l0/manuals/{OTT_Parsivel.pdf → PARSIVEL.pdf} +0 -0
- /disdrodb/l0/manuals/{OTT_Parsivel2.pdf → PARSIVEL2.pdf} +0 -0
- /disdrodb/l0/manuals/{PWS_100.pdf → PWS100.pdf} +0 -0
- /disdrodb/l0/manuals/{RD_80.pdf → RD80.pdf} +0 -0
- {disdrodb-0.0.21.dist-info → disdrodb-0.1.1.dist-info/licenses}/LICENSE +0 -0
- {disdrodb-0.0.21.dist-info → disdrodb-0.1.1.dist-info}/top_level.txt +0 -0
disdrodb/utils/directories.py
CHANGED
|
@@ -23,34 +23,107 @@ import logging
|
|
|
23
23
|
import os
|
|
24
24
|
import pathlib
|
|
25
25
|
import shutil
|
|
26
|
+
from typing import Union
|
|
27
|
+
|
|
28
|
+
from disdrodb.utils.list import flatten_list
|
|
29
|
+
from disdrodb.utils.logger import log_info
|
|
26
30
|
|
|
27
31
|
logger = logging.getLogger(__name__)
|
|
28
32
|
|
|
29
33
|
|
|
30
34
|
def ensure_string_path(path, msg, accepth_pathlib=False):
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
else:
|
|
34
|
-
valid_types = str
|
|
35
|
+
"""Ensure that the path is a string."""
|
|
36
|
+
valid_types = (str, pathlib.PurePath) if accepth_pathlib else str
|
|
35
37
|
if not isinstance(path, valid_types):
|
|
36
38
|
raise TypeError(msg)
|
|
37
39
|
return str(path)
|
|
38
40
|
|
|
39
41
|
|
|
42
|
+
def contains_netcdf_or_parquet_files(dir_path: str) -> bool:
|
|
43
|
+
"""Check (recursively) if a directory has any Parquet or netCDF file.
|
|
44
|
+
|
|
45
|
+
os.walk under the hood uses os.scandir
|
|
46
|
+
os.walk file generator + any() avoid use of while loop
|
|
47
|
+
|
|
48
|
+
The function returns True as soon as one file is found (short-circuit)^; False otherwise.
|
|
49
|
+
"""
|
|
50
|
+
suffixes = (".nc", ".parquet")
|
|
51
|
+
return any(fname.endswith(suffixes) for _, _, files in os.walk(dir_path) for fname in files)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def contains_files(dir_path: str) -> bool:
|
|
55
|
+
"""Check (recursively) if a directory contains any file.
|
|
56
|
+
|
|
57
|
+
os.walk under the hood uses os.scandir
|
|
58
|
+
os.walk file generator + any() avoid use of while loop
|
|
59
|
+
|
|
60
|
+
The function returns True as soon as one file is found (short-circuit); False otherwise.
|
|
61
|
+
"""
|
|
62
|
+
return any(fname for _, _, files in os.walk(dir_path) for fname in files)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def check_glob_pattern(pattern: str) -> None:
|
|
66
|
+
"""Check if glob pattern is a string and is a valid pattern.
|
|
67
|
+
|
|
68
|
+
Parameters
|
|
69
|
+
----------
|
|
70
|
+
pattern : str
|
|
71
|
+
String to be checked.
|
|
72
|
+
"""
|
|
73
|
+
if not isinstance(pattern, str):
|
|
74
|
+
raise TypeError("Expect pattern as a string.")
|
|
75
|
+
if pattern[0] == "/":
|
|
76
|
+
raise ValueError("glob_pattern should not start with /")
|
|
77
|
+
if "//" in pattern:
|
|
78
|
+
raise ValueError("glob_pattern expects path with single separators: /, not //")
|
|
79
|
+
if "\\" in pattern:
|
|
80
|
+
raise ValueError("glob_pattern expects path separators to be /, not \\")
|
|
81
|
+
return pattern
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def check_glob_patterns(patterns: Union[str, list]) -> list:
|
|
85
|
+
"""Check if glob patterns are valids."""
|
|
86
|
+
if not isinstance(patterns, (str, list)):
|
|
87
|
+
raise ValueError("'glob_patterns' must be a str or list of strings.")
|
|
88
|
+
if isinstance(patterns, str):
|
|
89
|
+
patterns = [patterns]
|
|
90
|
+
patterns = [check_glob_pattern(pattern) for pattern in patterns]
|
|
91
|
+
return patterns
|
|
92
|
+
|
|
93
|
+
|
|
40
94
|
def _recursive_glob(dir_path, glob_pattern):
|
|
41
|
-
# ** search for all
|
|
42
|
-
# glob_pattern = os.path.join(base_dir, "**", "metadata", f"{station_name}.yml")
|
|
43
|
-
# metadata_filepaths = glob.glob(glob_pattern, recursive=True)
|
|
95
|
+
# ** search for in zero or all subdirectories recursively
|
|
44
96
|
|
|
45
97
|
dir_path = pathlib.Path(dir_path)
|
|
46
98
|
return [str(path) for path in dir_path.rglob(glob_pattern)]
|
|
47
99
|
|
|
48
100
|
|
|
49
|
-
def
|
|
101
|
+
def _list_paths(dir_path, glob_pattern, recursive=False):
|
|
102
|
+
"""Return a list of filepaths and directory paths based on a single glob pattern."""
|
|
103
|
+
# If glob pattern has separators, disable recursive option
|
|
104
|
+
if "/" in glob_pattern and "**" not in glob_pattern:
|
|
105
|
+
recursive = False
|
|
106
|
+
# Search paths
|
|
50
107
|
if not recursive:
|
|
51
108
|
return glob.glob(os.path.join(dir_path, glob_pattern))
|
|
52
|
-
|
|
53
|
-
|
|
109
|
+
return _recursive_glob(dir_path, glob_pattern)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def list_paths(dir_path, glob_pattern, recursive=False):
|
|
113
|
+
"""Return a list of filepaths and directory paths.
|
|
114
|
+
|
|
115
|
+
This function accept also a list of glob patterns !
|
|
116
|
+
"""
|
|
117
|
+
# Check validity of glob pattern(s)
|
|
118
|
+
glob_patterns = check_glob_patterns(glob_pattern)
|
|
119
|
+
# Search path for specified glob patterns
|
|
120
|
+
paths = flatten_list(
|
|
121
|
+
[
|
|
122
|
+
_list_paths(dir_path=dir_path, glob_pattern=glob_pattern, recursive=recursive)
|
|
123
|
+
for glob_pattern in glob_patterns
|
|
124
|
+
],
|
|
125
|
+
)
|
|
126
|
+
return paths
|
|
54
127
|
|
|
55
128
|
|
|
56
129
|
def list_files(dir_path, glob_pattern, recursive=False):
|
|
@@ -78,7 +151,7 @@ def count_directories(dir_path, glob_pattern, recursive=False):
|
|
|
78
151
|
|
|
79
152
|
|
|
80
153
|
def check_directory_exists(dir_path):
|
|
81
|
-
"""Check if the directory
|
|
154
|
+
"""Check if the directory exists."""
|
|
82
155
|
if not os.path.exists(dir_path):
|
|
83
156
|
raise ValueError(f"{dir_path} directory does not exist.")
|
|
84
157
|
if not os.path.isdir(dir_path):
|
|
@@ -90,29 +163,24 @@ def create_directory(path: str, exist_ok=True) -> None:
|
|
|
90
163
|
path = ensure_string_path(path, msg="'path' must be a string", accepth_pathlib=True)
|
|
91
164
|
try:
|
|
92
165
|
os.makedirs(path, exist_ok=exist_ok)
|
|
93
|
-
logger.debug(f"Created directory {path}.")
|
|
94
166
|
except Exception as e:
|
|
167
|
+
dir_path = os.path.dirname(path)
|
|
95
168
|
dir_name = os.path.basename(path)
|
|
96
|
-
msg = f"Can not create directory {dir_name} inside
|
|
97
|
-
logger.exception(msg)
|
|
169
|
+
msg = f"Can not create directory {dir_name} inside {dir_path}. Error: {e}"
|
|
98
170
|
raise FileNotFoundError(msg)
|
|
99
171
|
|
|
100
172
|
|
|
101
|
-
def create_required_directory(dir_path, dir_name):
|
|
102
|
-
"""Create directory
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
except Exception as e:
|
|
107
|
-
msg = f"Can not create directory {dir_name} at {new_dir}. Error: {e}"
|
|
108
|
-
logger.exception(msg)
|
|
109
|
-
raise FileNotFoundError(msg)
|
|
173
|
+
def create_required_directory(dir_path, dir_name, exist_ok=True):
|
|
174
|
+
"""Create directory ``dir_name`` inside the ``dir_path`` directory."""
|
|
175
|
+
dir_path = ensure_string_path(dir_path, msg="'path' must be a string", accepth_pathlib=True)
|
|
176
|
+
new_dir_path = os.path.join(dir_path, dir_name)
|
|
177
|
+
create_directory(path=new_dir_path, exist_ok=exist_ok)
|
|
110
178
|
|
|
111
179
|
|
|
112
180
|
def is_empty_directory(path):
|
|
113
181
|
"""Check if a directory path is empty.
|
|
114
182
|
|
|
115
|
-
Return False if path is a file or non-empty directory.
|
|
183
|
+
Return ``False`` if path is a file or non-empty directory.
|
|
116
184
|
If the path does not exist, raise an error.
|
|
117
185
|
"""
|
|
118
186
|
if not os.path.exists(path):
|
|
@@ -121,53 +189,47 @@ def is_empty_directory(path):
|
|
|
121
189
|
return False
|
|
122
190
|
|
|
123
191
|
paths = os.listdir(path)
|
|
124
|
-
|
|
125
|
-
return True
|
|
126
|
-
else:
|
|
127
|
-
return False
|
|
192
|
+
return len(paths) == 0
|
|
128
193
|
|
|
129
194
|
|
|
130
|
-
def _remove_file_or_directories(path):
|
|
131
|
-
"""Return the file/directory or subdirectories tree of
|
|
195
|
+
def _remove_file_or_directories(path, logger=None):
|
|
196
|
+
"""Return the file/directory or subdirectories tree of ``path``.
|
|
132
197
|
|
|
133
198
|
Use this function with caution.
|
|
134
199
|
"""
|
|
135
200
|
# If file
|
|
136
201
|
if os.path.isfile(path):
|
|
137
202
|
os.remove(path)
|
|
138
|
-
logger
|
|
203
|
+
log_info(logger, msg=f"Deleted the file {path}")
|
|
139
204
|
# If empty directory
|
|
140
205
|
elif is_empty_directory(path):
|
|
141
206
|
os.rmdir(path)
|
|
142
|
-
logger
|
|
207
|
+
log_info(logger, msg=f"Deleted the empty directory {path}")
|
|
143
208
|
# If not empty directory
|
|
144
209
|
else:
|
|
145
210
|
shutil.rmtree(path)
|
|
146
|
-
logger
|
|
147
|
-
return None
|
|
211
|
+
log_info(logger, msg=f"Deleted directories within {path}")
|
|
148
212
|
|
|
149
213
|
|
|
150
|
-
def remove_if_exists(path: str, force: bool = False) -> None:
|
|
151
|
-
"""Remove file or directory if exists and force=True
|
|
214
|
+
def remove_if_exists(path: str, force: bool = False, logger=None) -> None:
|
|
215
|
+
"""Remove file or directory if exists and ``force=True``.
|
|
152
216
|
|
|
153
|
-
If force=False
|
|
217
|
+
If ``force=False``, it raises an error.
|
|
154
218
|
"""
|
|
155
219
|
# If the path does not exist, do nothing
|
|
156
220
|
if not os.path.exists(path):
|
|
157
|
-
return
|
|
221
|
+
return
|
|
158
222
|
|
|
159
223
|
# If the path exists and force=False, raise Error
|
|
160
224
|
if not force:
|
|
161
225
|
msg = f"--force is False and a file already exists at: {path}"
|
|
162
|
-
logger.error(msg)
|
|
163
226
|
raise ValueError(msg)
|
|
164
227
|
|
|
165
228
|
# If force=True, remove the file/directory or subdirectories and files !
|
|
166
229
|
try:
|
|
167
|
-
_remove_file_or_directories(path)
|
|
230
|
+
_remove_file_or_directories(path, logger=logger)
|
|
168
231
|
except Exception as e:
|
|
169
232
|
msg = f"Can not delete file(s) at {path}. The error is: {e}"
|
|
170
|
-
logger.error(msg)
|
|
171
233
|
raise ValueError(msg)
|
|
172
234
|
|
|
173
235
|
|
|
@@ -181,18 +243,17 @@ def copy_file(src_filepath, dst_filepath):
|
|
|
181
243
|
logger.info(msg)
|
|
182
244
|
except Exception as e:
|
|
183
245
|
msg = f"Something went wrong when copying {filename} into {dst_dir}.\n The error is: {e}."
|
|
184
|
-
logger.error(msg)
|
|
185
246
|
raise ValueError(msg)
|
|
186
247
|
|
|
187
248
|
|
|
188
249
|
def remove_path_trailing_slash(path: str) -> str:
|
|
189
|
-
"""
|
|
250
|
+
r"""
|
|
190
251
|
Removes a trailing slash or backslash from a file path if it exists.
|
|
191
252
|
|
|
192
253
|
This function ensures that the provided file path is normalized by removing
|
|
193
|
-
any trailing directory separator characters ('/' or '\\').
|
|
194
|
-
maintaining consistency in path strings and for
|
|
195
|
-
that may not expect a trailing slash.
|
|
254
|
+
any trailing directory separator characters (``'/'`` or ``'\\'``).
|
|
255
|
+
This is useful for maintaining consistency in path strings and for
|
|
256
|
+
preparing paths for operations that may not expect a trailing slash.
|
|
196
257
|
|
|
197
258
|
Parameters
|
|
198
259
|
----------
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# -----------------------------------------------------------------------------.
|
|
4
|
+
# Copyright (c) 2021-2023 DISDRODB developers
|
|
5
|
+
#
|
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU General Public License
|
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18
|
+
# -----------------------------------------------------------------------------.
|
|
19
|
+
"""DISDRODB netCDF4 encoding utilities."""
|
|
20
|
+
import xarray as xr
|
|
21
|
+
|
|
22
|
+
EPOCH = "seconds since 1970-01-01 00:00:00"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def set_encodings(ds: xr.Dataset, encoding_dict: dict) -> xr.Dataset:
|
|
26
|
+
"""Apply the encodings to the xarray Dataset.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
ds : xarray.Dataset
|
|
31
|
+
Input xarray dataset.
|
|
32
|
+
encoding_dict : dict
|
|
33
|
+
Dictionary with encoding specifications.
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
xarray.Dataset
|
|
38
|
+
Output xarray dataset.
|
|
39
|
+
"""
|
|
40
|
+
# Subset encoding dictionary
|
|
41
|
+
# - Here below encoding_dict contains only keys (variables) within the dataset
|
|
42
|
+
encoding_dict = {var: encoding_dict[var] for var in ds.data_vars if var in encoding_dict}
|
|
43
|
+
|
|
44
|
+
# Ensure chunksize smaller than the array shape
|
|
45
|
+
encoding_dict = sanitize_encodings_dict(encoding_dict, ds)
|
|
46
|
+
|
|
47
|
+
# Rechunk variables for fast writing !
|
|
48
|
+
# - This pop the chunksize argument from the encoding dict !
|
|
49
|
+
ds = rechunk_dataset(ds, encoding_dict)
|
|
50
|
+
|
|
51
|
+
# Set time encoding
|
|
52
|
+
ds["time"].encoding.update(get_time_encoding())
|
|
53
|
+
|
|
54
|
+
# Set the variable encodings
|
|
55
|
+
for var, encoding in encoding_dict.items():
|
|
56
|
+
ds[var].encoding.update(encoding)
|
|
57
|
+
|
|
58
|
+
# Ensure no deprecated "missing_value" attribute
|
|
59
|
+
# - When source dataset is netcdf (i.e. ARM)
|
|
60
|
+
for var in list(ds.variables):
|
|
61
|
+
_ = ds[var].encoding.pop("missing_value", None)
|
|
62
|
+
|
|
63
|
+
return ds
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def sanitize_encodings_dict(encoding_dict: dict, ds: xr.Dataset) -> dict:
|
|
67
|
+
"""Ensure chunk size to be smaller than the array shape.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
encoding_dict : dict
|
|
72
|
+
Dictionary containing the variable encodings.
|
|
73
|
+
ds : xarray.Dataset
|
|
74
|
+
Input dataset.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
dict
|
|
79
|
+
Encoding dictionary.
|
|
80
|
+
"""
|
|
81
|
+
for var in ds.data_vars:
|
|
82
|
+
if var in encoding_dict:
|
|
83
|
+
shape = ds[var].shape
|
|
84
|
+
chunks = encoding_dict[var].get("chunksizes", None)
|
|
85
|
+
if chunks is not None:
|
|
86
|
+
chunks = [shape[i] if chunks[i] > shape[i] else chunks[i] for i in range(len(chunks))]
|
|
87
|
+
encoding_dict[var]["chunksizes"] = chunks
|
|
88
|
+
return encoding_dict
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def rechunk_dataset(ds: xr.Dataset, encoding_dict: dict) -> xr.Dataset:
|
|
92
|
+
"""Coerce the dataset arrays to have the chunk size specified in the encoding dictionary.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
ds : xarray.Dataset
|
|
97
|
+
Input xarray dataset
|
|
98
|
+
encoding_dict : dict
|
|
99
|
+
Dictionary containing the encoding to write the xarray dataset as a netCDF.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
xarray.Dataset
|
|
104
|
+
Output xarray dataset
|
|
105
|
+
"""
|
|
106
|
+
for var in ds.data_vars:
|
|
107
|
+
if var in encoding_dict:
|
|
108
|
+
chunks = encoding_dict[var].pop("chunksizes", None)
|
|
109
|
+
if chunks is not None:
|
|
110
|
+
dims = list(ds[var].dims)
|
|
111
|
+
chunks_dict = dict(zip(dims, chunks))
|
|
112
|
+
ds[var] = ds[var].chunk(chunks_dict)
|
|
113
|
+
return ds
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_time_encoding() -> dict:
|
|
117
|
+
"""Create time encoding.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
dict
|
|
122
|
+
Time encoding.
|
|
123
|
+
"""
|
|
124
|
+
encoding = {}
|
|
125
|
+
encoding["units"] = EPOCH
|
|
126
|
+
encoding["calendar"] = "proleptic_gregorian"
|
|
127
|
+
return encoding
|
disdrodb/utils/list.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# -----------------------------------------------------------------------------.
|
|
4
|
+
# Copyright (c) 2021-2023 DISDRODB developers
|
|
5
|
+
#
|
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
# (at your option) any later version.
|
|
10
|
+
#
|
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
# GNU General Public License for more details.
|
|
15
|
+
#
|
|
16
|
+
# You should have received a copy of the GNU General Public License
|
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
18
|
+
# -----------------------------------------------------------------------------.
|
|
19
|
+
"""Utilities to work with lists."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def flatten_list(nested_list):
|
|
23
|
+
"""Flatten a nested list into a single-level list."""
|
|
24
|
+
if isinstance(nested_list, list) and len(nested_list) == 0:
|
|
25
|
+
return nested_list
|
|
26
|
+
# If list is already flat, return as is to avoid flattening to chars
|
|
27
|
+
if isinstance(nested_list, list) and not isinstance(nested_list[0], list):
|
|
28
|
+
return nested_list
|
|
29
|
+
return [item for sublist in nested_list for item in sublist] if isinstance(nested_list, list) else [nested_list]
|