pypromice 1.3.6__py3-none-any.whl → 1.4.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.
Potentially problematic release.
This version of pypromice might be problematic. Click here for more details.
- pypromice/postprocess/bufr_to_csv.py +15 -3
- pypromice/postprocess/bufr_utilities.py +91 -18
- pypromice/postprocess/create_bufr_files.py +178 -0
- pypromice/postprocess/get_bufr.py +248 -397
- pypromice/postprocess/make_metadata_csv.py +214 -0
- pypromice/postprocess/real_time_utilities.py +41 -11
- pypromice/process/L0toL1.py +12 -5
- pypromice/process/L1toL2.py +69 -14
- pypromice/process/L2toL3.py +1034 -186
- pypromice/process/aws.py +139 -808
- pypromice/process/get_l2.py +90 -0
- pypromice/process/get_l2tol3.py +111 -0
- pypromice/process/join_l2.py +112 -0
- pypromice/process/join_l3.py +551 -120
- pypromice/process/load.py +161 -0
- pypromice/process/resample.py +147 -0
- pypromice/process/utilities.py +68 -0
- pypromice/process/write.py +503 -0
- pypromice/qc/github_data_issues.py +10 -16
- pypromice/qc/persistence.py +52 -30
- pypromice/resources/__init__.py +28 -0
- pypromice/{process/metadata.csv → resources/file_attributes.csv} +0 -2
- pypromice/resources/variable_aliases_GC-Net.csv +78 -0
- pypromice/resources/variables.csv +106 -0
- pypromice/station_configuration.py +118 -0
- pypromice/tx/get_l0tx.py +7 -4
- pypromice/tx/payload_formats.csv +1 -0
- pypromice/tx/tx.py +27 -6
- pypromice/utilities/__init__.py +0 -0
- pypromice/utilities/git.py +62 -0
- {pypromice-1.3.6.dist-info → pypromice-1.4.1.dist-info}/METADATA +4 -4
- pypromice-1.4.1.dist-info/RECORD +53 -0
- {pypromice-1.3.6.dist-info → pypromice-1.4.1.dist-info}/WHEEL +1 -1
- pypromice-1.4.1.dist-info/entry_points.txt +13 -0
- pypromice/postprocess/station_configurations.toml +0 -762
- pypromice/process/get_l3.py +0 -46
- pypromice/process/variables.csv +0 -92
- pypromice/qc/persistence_test.py +0 -150
- pypromice/test/test_config1.toml +0 -69
- pypromice/test/test_config2.toml +0 -54
- pypromice/test/test_email +0 -75
- pypromice/test/test_payload_formats.csv +0 -4
- pypromice/test/test_payload_types.csv +0 -7
- pypromice/test/test_percentile.py +0 -229
- pypromice/test/test_raw1.txt +0 -4468
- pypromice/test/test_raw_DataTable2.txt +0 -11167
- pypromice/test/test_raw_SlimTableMem1.txt +0 -1155
- pypromice/test/test_raw_transmitted1.txt +0 -15411
- pypromice/test/test_raw_transmitted2.txt +0 -28
- pypromice-1.3.6.dist-info/RECORD +0 -53
- pypromice-1.3.6.dist-info/entry_points.txt +0 -8
- {pypromice-1.3.6.dist-info → pypromice-1.4.1.dist-info}/LICENSE.txt +0 -0
- {pypromice-1.3.6.dist-info → pypromice-1.4.1.dist-info}/top_level.txt +0 -0
pypromice/qc/persistence.py
CHANGED
|
@@ -9,20 +9,40 @@ __all__ = [
|
|
|
9
9
|
"persistence_qc",
|
|
10
10
|
"find_persistent_regions",
|
|
11
11
|
"count_consecutive_persistent_values",
|
|
12
|
-
"
|
|
12
|
+
"get_duration_consecutive_true",
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
17
|
# period is given in hours, 2 persistent 10 min values will be flagged if period < 0.333
|
|
18
18
|
DEFAULT_VARIABLE_THRESHOLDS = {
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"
|
|
19
|
+
"t_i": {"max_diff": 0.0001, "period": 2},
|
|
20
|
+
"t_u": {"max_diff": 0.0001, "period": 2},
|
|
21
|
+
"t_l": {"max_diff": 0.0001, "period": 2},
|
|
22
|
+
"p_i": {"max_diff": 0.0001, "period": 2},
|
|
23
|
+
# "p_u": {"max_diff": 0.0001, "period": 2},
|
|
24
|
+
# "p_l": {"max_diff": 0.0001, "period": 2},
|
|
25
|
+
"gps_lat_lon": {
|
|
26
|
+
"max_diff": 0.000001,
|
|
27
|
+
"period": 6,
|
|
28
|
+
}, # gets special handling to remove simultaneously constant gps_lat and gps_lon
|
|
29
|
+
"gps_alt": {"max_diff": 0.0001, "period": 6},
|
|
30
|
+
"t_rad": {"max_diff": 0.0001, "period": 2},
|
|
31
|
+
"rh_i": {
|
|
32
|
+
"max_diff": 0.0001,
|
|
33
|
+
"period": 2,
|
|
34
|
+
}, # gets special handling to allow constant 100%
|
|
35
|
+
"rh_u": {
|
|
36
|
+
"max_diff": 0.0001,
|
|
37
|
+
"period": 2,
|
|
38
|
+
}, # gets special handling to allow constant 100%
|
|
39
|
+
"rh_l": {
|
|
40
|
+
"max_diff": 0.0001,
|
|
41
|
+
"period": 2,
|
|
42
|
+
}, # gets special handling to allow constant 100%
|
|
43
|
+
"wspd_i": {"max_diff": 0.0001, "period": 6},
|
|
44
|
+
"wspd_u": {"max_diff": 0.0001, "period": 6},
|
|
45
|
+
"wspd_l": {"max_diff": 0.0001, "period": 6},
|
|
26
46
|
}
|
|
27
47
|
|
|
28
48
|
|
|
@@ -61,11 +81,12 @@ def persistence_qc(
|
|
|
61
81
|
|
|
62
82
|
if variable_thresholds is None:
|
|
63
83
|
variable_thresholds = DEFAULT_VARIABLE_THRESHOLDS
|
|
64
|
-
|
|
65
|
-
|
|
84
|
+
logger.debug(f"Running persistence_qc using {variable_thresholds}")
|
|
85
|
+
else:
|
|
86
|
+
logger.info(f"Running persistence_qc using custom thresholds:\n {variable_thresholds}")
|
|
66
87
|
|
|
67
88
|
for k in variable_thresholds.keys():
|
|
68
|
-
if k in [
|
|
89
|
+
if k in ["t", "p", "rh", "wspd", "wdir", "z_boom"]:
|
|
69
90
|
var_all = [
|
|
70
91
|
k + "_u",
|
|
71
92
|
k + "_l",
|
|
@@ -79,29 +100,28 @@ def persistence_qc(
|
|
|
79
100
|
for v in var_all:
|
|
80
101
|
if v in df:
|
|
81
102
|
mask = find_persistent_regions(df[v], period, max_diff)
|
|
82
|
-
if
|
|
83
|
-
mask = mask & (df[v]<99)
|
|
103
|
+
if "rh" in v:
|
|
104
|
+
mask = mask & (df[v] < 99)
|
|
84
105
|
n_masked = mask.sum()
|
|
85
106
|
n_samples = len(mask)
|
|
86
|
-
logger.
|
|
107
|
+
logger.debug(
|
|
87
108
|
f"Applying persistent QC in {v}. Filtering {n_masked}/{n_samples} samples"
|
|
88
109
|
)
|
|
89
110
|
# setting outliers to NaN
|
|
90
111
|
df.loc[mask, v] = np.nan
|
|
91
|
-
elif v ==
|
|
92
|
-
mask = (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
112
|
+
elif v == "gps_lat_lon":
|
|
113
|
+
mask = find_persistent_regions(
|
|
114
|
+
df["gps_lon"], period, max_diff
|
|
115
|
+
) & find_persistent_regions(df["gps_lat"], period, max_diff)
|
|
96
116
|
|
|
97
117
|
n_masked = mask.sum()
|
|
98
118
|
n_samples = len(mask)
|
|
99
|
-
logger.
|
|
119
|
+
logger.debug(
|
|
100
120
|
f"Applying persistent QC in {v}. Filtering {n_masked}/{n_samples} samples"
|
|
101
121
|
)
|
|
102
122
|
# setting outliers to NaN
|
|
103
|
-
df.loc[mask,
|
|
104
|
-
df.loc[mask,
|
|
123
|
+
df.loc[mask, "gps_lon"] = np.nan
|
|
124
|
+
df.loc[mask, "gps_lat"] = np.nan
|
|
105
125
|
|
|
106
126
|
# Back to xarray, and re-assign the original attrs
|
|
107
127
|
ds_out = df.to_xarray()
|
|
@@ -133,19 +153,21 @@ def count_consecutive_persistent_values(
|
|
|
133
153
|
) -> pd.Series:
|
|
134
154
|
diff = data.ffill().diff().abs() # forward filling all NaNs!
|
|
135
155
|
mask: pd.Series = diff < max_diff
|
|
136
|
-
return
|
|
156
|
+
return get_duration_consecutive_true(mask)
|
|
137
157
|
|
|
138
158
|
|
|
139
|
-
def
|
|
159
|
+
def get_duration_consecutive_true(
|
|
140
160
|
series: pd.Series,
|
|
141
161
|
) -> pd.Series:
|
|
142
162
|
"""
|
|
143
|
-
From a boolean series, calculates the duration, in hours, of the periods with
|
|
163
|
+
From a boolean series, calculates the duration, in hours, of the periods with concecutive true values.
|
|
164
|
+
|
|
165
|
+
The first value will be set to NaN, as it is not possible to calculate the duration of a single value.
|
|
144
166
|
|
|
145
167
|
Examples
|
|
146
168
|
--------
|
|
147
|
-
>>>
|
|
148
|
-
pd.Series([
|
|
169
|
+
>>> get_duration_consecutive_true(pd.Series([False, True, False, False, True, True, True, False, True]))
|
|
170
|
+
pd.Series([np.nan, 1, 0, 0, 1, 2, 3, 0, 1])
|
|
149
171
|
|
|
150
172
|
Parameters
|
|
151
173
|
----------
|
|
@@ -158,9 +180,9 @@ def duration_consecutive_true(
|
|
|
158
180
|
Integer pandas Series or DataFrame with values representing the number of connective true values.
|
|
159
181
|
|
|
160
182
|
"""
|
|
161
|
-
# assert series.dtype == bool
|
|
162
|
-
cumsum = ((series.index - series.index[0]).total_seconds()/3600).to_series(index=series.index)
|
|
163
183
|
is_first = series.astype("int").diff() == 1
|
|
164
|
-
|
|
184
|
+
delta_time = (series.index.to_series().diff().dt.total_seconds() / 3600)
|
|
185
|
+
cumsum = delta_time.cumsum()
|
|
186
|
+
offset = (is_first * (cumsum - delta_time)).replace(0, np.nan).ffill().fillna(0)
|
|
165
187
|
|
|
166
188
|
return (cumsum - offset) * series
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Dict, Union
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
DEFAULT_METADATA_PATH = (Path(__file__).parent / "file_attributes.csv").absolute()
|
|
8
|
+
DEFAULT_VARIABLES_PATH = (Path(__file__).parent / "variables.csv").absolute()
|
|
9
|
+
DEFAULT_VARIABLES_ALIASES_GCNET_PATH = (Path(__file__).parent / "variable_aliases_GC-Net.csv").absolute()
|
|
10
|
+
|
|
11
|
+
def load_metadata(path: Union[None, str, Path] = None) -> Dict[str, str]:
|
|
12
|
+
"""
|
|
13
|
+
Load metadata table from csv file
|
|
14
|
+
"""
|
|
15
|
+
if path is None:
|
|
16
|
+
path = DEFAULT_METADATA_PATH
|
|
17
|
+
with open(path, "r") as f:
|
|
18
|
+
csv_reader = csv.reader(f)
|
|
19
|
+
return {row[0]: row[1] for row in csv_reader}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def load_variables(path: Union[None, str, Path] = None) -> pd.DataFrame:
|
|
23
|
+
"""
|
|
24
|
+
Load variables table from csv file
|
|
25
|
+
"""
|
|
26
|
+
if path is None:
|
|
27
|
+
path = DEFAULT_VARIABLES_PATH
|
|
28
|
+
return pd.read_csv(path, index_col=0, comment="#")
|
|
@@ -40,11 +40,9 @@ metadata_link,
|
|
|
40
40
|
naming_authority,dk.geus.promice
|
|
41
41
|
platform,
|
|
42
42
|
platform_vocabulary,GCMD:GCMD Keywords
|
|
43
|
-
processing_level,Level 3
|
|
44
43
|
product_status,beta
|
|
45
44
|
product_version,4
|
|
46
45
|
program,PROMICE
|
|
47
|
-
project,PROMICE
|
|
48
46
|
publisher_email,info@promice.dk
|
|
49
47
|
publisher_institution,GEUS
|
|
50
48
|
publisher_name,GEUS
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
GEUS_name,old_name
|
|
2
|
+
time,timestamp
|
|
3
|
+
p_u,P
|
|
4
|
+
p_l,
|
|
5
|
+
t_u,TA2
|
|
6
|
+
t_l,TA1
|
|
7
|
+
rh_u,RH2
|
|
8
|
+
rh_u_cor,RH2_cor
|
|
9
|
+
qh_u,Q2
|
|
10
|
+
rh_l,RH1
|
|
11
|
+
rh_l_cor,RH1_cor
|
|
12
|
+
qh_l,Q1
|
|
13
|
+
wspd_u,VW2
|
|
14
|
+
wspd_l,VW1
|
|
15
|
+
wdir_u,DW2
|
|
16
|
+
wdir_l,DW1
|
|
17
|
+
dsr,
|
|
18
|
+
dsr_cor,ISWR
|
|
19
|
+
usr,
|
|
20
|
+
usr_cor,OSWR
|
|
21
|
+
albedo,Alb
|
|
22
|
+
dlr,
|
|
23
|
+
ulr,
|
|
24
|
+
cc,
|
|
25
|
+
t_surf,
|
|
26
|
+
dlhf_u,LHF
|
|
27
|
+
dlhf_l,
|
|
28
|
+
dshf_u,SHF
|
|
29
|
+
dshf_l,
|
|
30
|
+
z_boom_u,HW2
|
|
31
|
+
z_boom_l,HW1
|
|
32
|
+
precip_u,
|
|
33
|
+
precip_u_cor,
|
|
34
|
+
precip_l,
|
|
35
|
+
precip_l_cor,
|
|
36
|
+
t_i_1,TS1
|
|
37
|
+
t_i_2,TS2
|
|
38
|
+
t_i_3,TS3
|
|
39
|
+
t_i_4,TS4
|
|
40
|
+
t_i_5,TS5
|
|
41
|
+
t_i_6,TS6
|
|
42
|
+
t_i_7,TS7
|
|
43
|
+
t_i_8,TS8
|
|
44
|
+
t_i_9,TS9
|
|
45
|
+
t_i_10,TS10
|
|
46
|
+
t_i_11,
|
|
47
|
+
tilt_x,
|
|
48
|
+
tilt_y,
|
|
49
|
+
rot,
|
|
50
|
+
lat,latitude
|
|
51
|
+
lon,longitude
|
|
52
|
+
alt,elevation
|
|
53
|
+
gps_time,
|
|
54
|
+
gps_geounit,
|
|
55
|
+
gps_hdop,
|
|
56
|
+
batt_v,V
|
|
57
|
+
fan_dc_u,
|
|
58
|
+
fan_dc_l,
|
|
59
|
+
t_rad,
|
|
60
|
+
msg_lat,
|
|
61
|
+
msg_lon,
|
|
62
|
+
z_surf_1,HS1
|
|
63
|
+
z_surf_2,HS2
|
|
64
|
+
z_surf_1_adj_flag,HS1_adj_flag
|
|
65
|
+
z_surf_2_adj_flag,HS2_adj_flag
|
|
66
|
+
z_surf_combined,HS_combined
|
|
67
|
+
d_t_i_1,DTS1
|
|
68
|
+
d_t_i_2,DTS2
|
|
69
|
+
d_t_i_3,DTS3
|
|
70
|
+
d_t_i_4,DTS4
|
|
71
|
+
d_t_i_5,DTS5
|
|
72
|
+
d_t_i_6,DTS6
|
|
73
|
+
d_t_i_7,DTS7
|
|
74
|
+
d_t_i_8,DTS8
|
|
75
|
+
d_t_i_9,DTS9
|
|
76
|
+
d_t_i_10,DTS10
|
|
77
|
+
d_t_i_11,
|
|
78
|
+
t_i_10m,TS_10m
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
field,standard_name,long_name,units,coverage_content_type,coordinates,instantaneous_hourly,where_to_find,lo,hi,OOL,station_type,L0,L2,L3,max_decimals
|
|
2
|
+
time,time,Time,yyyy-mm-dd HH:MM:SS,physicalMeasurement,time,,,,,,all,1,1,1,
|
|
3
|
+
rec,record,Record,-,referenceInformation,time,,L0 or L2,,,,all,1,1,0,0
|
|
4
|
+
p_u,air_pressure,Air pressure (upper boom),hPa,physicalMeasurement,time,FALSE,,650,1100,z_pt z_pt_cor dshf_u dlhf_u qh_u,all,1,1,1,4
|
|
5
|
+
p_l,air_pressure,Air pressure (lower boom),hPa,physicalMeasurement,time,FALSE,,650,1100,dshf_l dlhf_l qh_l,two-boom,1,1,1,4
|
|
6
|
+
t_u,air_temperature,Air temperature (upper boom),degrees_C,physicalMeasurement,time,FALSE,,-80,40,rh_u_cor cc dsr_cor usr_cor z_boom z_stake dshf_u dlhf_u qh_u,all,1,1,1,4
|
|
7
|
+
t_l,air_temperature,Air temperature (lower boom),degrees_C,physicalMeasurement,time,FALSE,,-80,40,rh_l_cor z_boom_l dshf_l dlhf_l qh_l,two-boom,1,1,1,4
|
|
8
|
+
rh_u,relative_humidity,Relative humidity (upper boom),%,physicalMeasurement,time,FALSE,,0,100,rh_u_cor,all,1,1,1,4
|
|
9
|
+
rh_u_cor,relative_humidity_corrected,Relative humidity (upper boom) - corrected,%,modelResult,time,FALSE,L2 or later,0,150,dshf_u dlhf_u qh_u,all,0,1,1,4
|
|
10
|
+
qh_u,specific_humidity,Specific humidity (upper boom),kg/kg,modelResult,time,FALSE,L2 or later,0,100,,all,0,1,1,4
|
|
11
|
+
rh_l,relative_humidity,Relative humidity (lower boom),%,physicalMeasurement,time,FALSE,,0,100,rh_l_cor,two-boom,1,1,1,4
|
|
12
|
+
rh_l_cor,relative_humidity_corrected,Relative humidity (lower boom) - corrected,%,modelResult,time,FALSE,L2 or later,0,150,dshf_l dlhf_l qh_l,two-boom,0,1,1,4
|
|
13
|
+
qh_l,specific_humidity,Specific humidity (lower boom),kg/kg,modelResult,time,FALSE,L2 or later,0,100,,two-boom,0,1,1,4
|
|
14
|
+
wspd_u,wind_speed,Wind speed (upper boom),m s-1,physicalMeasurement,time,FALSE,,0,100,"wdir_u wspd_x_u wspd_y_u dshf_u dlhf_u qh_u, precip_u",all,1,1,1,4
|
|
15
|
+
wspd_l,wind_speed,Wind speed (lower boom),m s-1,physicalMeasurement,time,FALSE,,0,100,"wdir_l wspd_x_l wspd_y_l dshf_l dlhf_l qh_l , precip_l",two-boom,1,1,1,4
|
|
16
|
+
wdir_u,wind_from_direction,Wind from direction (upper boom),degrees,physicalMeasurement,time,FALSE,,1,360,wspd_x_u wspd_y_u,all,1,1,1,4
|
|
17
|
+
wdir_std_u,wind_from_direction_standard_deviation,Wind from direction (standard deviation),degrees,qualityInformation,time,FALSE,L0 or L2,,,,one-boom,1,1,0,4
|
|
18
|
+
wdir_l,wind_from_direction,Wind from direction (lower boom),degrees,physicalMeasurement,time,FALSE,,1,360,wspd_x_l wspd_y_l,two-boom,1,1,1,4
|
|
19
|
+
wspd_x_u,wind_speed_from_x_direction,Wind speed from x direction (upper boom),m s-1,modelResult,time,FALSE,L0 or L2,-100,100,wdir_u wspd_u,all,0,1,1,4
|
|
20
|
+
wspd_y_u,wind_speed_from_y_direction,Wind speed from y direction (upper boom),m s-1,modelResult,time,FALSE,L0 or L2,-100,100,wdir_u wspd_u,all,0,1,1,4
|
|
21
|
+
wspd_x_l,wind_speed_from_x_direction,Wind speed from x direction (lower boom),m s-1,modelResult,time,FALSE,L0 or L2,-100,100,wdir_l wspd_l,two-boom,0,1,1,4
|
|
22
|
+
wspd_y_l,wind_speed_from_y_direction,Wind speed from y direction (lower boom),m s-1,modelResult,time,FALSE,L0 or L2,-100,100,wdir_l wspd_l,two-boom,0,1,1,4
|
|
23
|
+
dsr,surface_downwelling_shortwave_flux,Downwelling shortwave radiation,W m-2,physicalMeasurement,time,FALSE,,-10,1500,albedo dsr_cor usr_cor,all,1,1,1,4
|
|
24
|
+
dsr_cor,surface_downwelling_shortwave_flux_corrected,Downwelling shortwave radiation - corrected,W m-2,modelResult,time,FALSE,L2 or later,,,,all,0,1,1,4
|
|
25
|
+
usr,surface_upwelling_shortwave_flux,Upwelling shortwave radiation,W m-2,physicalMeasurement,time,FALSE,,-10,1000,albedo dsr_cor usr_cor,all,1,1,1,4
|
|
26
|
+
usr_cor,surface_upwelling_shortwave_flux_corrected,Upwelling shortwave radiation - corrected,W m-2,modelResult,time,FALSE,L2 or later,0,1000,,all,0,1,1,4
|
|
27
|
+
albedo,surface_albedo,Albedo,-,modelResult,time,FALSE,L2 or later,,,,all,0,1,1,4
|
|
28
|
+
dlr,surface_downwelling_longwave_flux,Downwelling longwave radiation,W m-2,physicalMeasurement,time,FALSE,,50,500,albedo dsr_cor usr_cor cc t_surf,all,1,1,1,4
|
|
29
|
+
ulr,surface_upwelling_longwave_flux,Upwelling longwave radiation,W m-2,physicalMeasurement,time,FALSE,,50,500,t_surf,all,1,1,1,4
|
|
30
|
+
cc,cloud_area_fraction,Cloud cover,%,modelResult,time,FALSE,L2 or later,,,,all,0,1,1,4
|
|
31
|
+
t_surf,surface_temperature,Surface temperature,C,modelResult,time,FALSE,L2 or later,-80,40,dshf_u dlhf_u qh_u,all,0,1,1,4
|
|
32
|
+
dlhf_u,surface_downward_latent_heat_flux,Latent heat flux (upper boom),W m-2,modelResult,time,FALSE,L3 or later,,,,all,0,0,1,4
|
|
33
|
+
dlhf_l,surface_downward_latent_heat_flux,Latent heat flux (lower boom),W m-2,modelResult,time,FALSE,L3 or later,,,,two-boom,0,0,1,4
|
|
34
|
+
dshf_u,surface_downward_sensible_heat_flux,Sensible heat flux (upper boom),W m-2,modelResult,time,FALSE,L3 or later,,,,all,0,0,1,4
|
|
35
|
+
dshf_l,surface_downward_sensible_heat_flux,Sensible heat flux (lower boom),W m-2,modelResult,time,FALSE,L3 or later,,,,two-boom,0,0,1,4
|
|
36
|
+
z_boom_u,distance_to_surface_from_boom,Upper boom height,m,physicalMeasurement,time,TRUE,,0.3,10,dshf_u dlhf_u qh_u,all,1,1,1,4
|
|
37
|
+
z_boom_q_u,distance_to_surface_from_boom_quality,Upper boom height (quality),-,qualityInformation,time,TRUE,L0 or L2,,,,all,1,1,0,4
|
|
38
|
+
z_boom_l,distance_to_surface_from_boom,Lower boom height,m,physicalMeasurement,time,TRUE,,0.3,5,dshf_l dlhf_l qh_l,two-boom,1,1,1,4
|
|
39
|
+
z_boom_q_l,distance_to_surface_from_boom_quality,Lower boom height (quality),-,qualityInformation,time,TRUE,L0 or L2,,,,two-boom,1,1,0,4
|
|
40
|
+
z_stake,distance_to_surface_from_stake_assembly,Stake height,m,physicalMeasurement,time,TRUE,,0.3,8,,one-boom,1,1,1,4
|
|
41
|
+
z_stake_q,distance_to_surface_from_stake_assembly_quality,Stake height (quality),-,qualityInformation,time,TRUE,L0 or L2,,,,one-boom,1,1,0,4
|
|
42
|
+
z_pt,depth_of_pressure_transducer_in_ice,Depth of pressure transducer in ice,m,physicalMeasurement,time,FALSE,,0,30,z_pt_cor,one-boom,1,1,1,4
|
|
43
|
+
z_pt_cor,depth_of_pressure_transducer_in_ice_corrected,Depth of pressure transducer in ice - corrected,m,modelResult,time,FALSE,L2 or later,0,30,,one-boom,0,1,1,4
|
|
44
|
+
z_surf_combined,height_of_surface_combined,"Surface height combined from multiple sensors, relative to ice surface height at installation",m,modelResult,time,FALSE,L3,,,,all,0,0,1,4
|
|
45
|
+
z_ice_surf,height_of_ice_surface,"Ice surface height, relative to ice surface height at installation and calculated from pt_cor and z_stake",m,modelResult,time,FALSE,L3,,,,one-boom,0,0,1,4
|
|
46
|
+
snow_height,height_of_snow,"Snow surface height, relative to ice surface",m,modelResult,time,FALSE,L3,0,,,one-boom,0,0,1,4
|
|
47
|
+
precip_u,precipitation,Precipitation (upper boom) (cumulative solid & liquid),mm,physicalMeasurement,time,TRUE,,0,,precip_u_cor precip_u_rate,all,1,1,1,4
|
|
48
|
+
precip_u_cor,precipitation_corrected,Precipitation (upper boom) (cumulative solid & liquid) – corrected,mm,modelResult,time,TRUE,L2 or later,0,,,all,0,1,1,4
|
|
49
|
+
precip_u_rate,precipitation_rate,Precipitation rate (upper boom) (cumulative solid & liquid) – corrected,mm,modelResult,time,TRUE,L2 or later,0,,,all,0,1,1,4
|
|
50
|
+
precip_l,precipitation,Precipitation (lower boom) (cumulative solid & liquid),mm,physicalMeasurement,time,TRUE,,0,,precip_l_cor precip_l_rate,two-boom,1,1,1,4
|
|
51
|
+
precip_l_cor,precipitation_corrected,Precipitation (lower boom) (cumulative solid & liquid) – corrected,mm,modelResult,time,TRUE,L2 or later,0,,,two-boom,0,1,1,4
|
|
52
|
+
precip_l_rate,precipitation_rate,Precipitation rate (lower boom) (cumulative solid & liquid) – corrected,mm,modelResult,time,TRUE,L2 or later,0,,,two-boom,0,1,1,4
|
|
53
|
+
t_i_1,ice_temperature_at_t1,Ice temperature at sensor 1,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
|
|
54
|
+
t_i_2,ice_temperature_at_t2,Ice temperature at sensor 2,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
|
|
55
|
+
t_i_3,ice_temperature_at_t3,Ice temperature at sensor 3,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
|
|
56
|
+
t_i_4,ice_temperature_at_t4,Ice temperature at sensor 4,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
|
|
57
|
+
t_i_5,ice_temperature_at_t5,Ice temperature at sensor 5,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
|
|
58
|
+
t_i_6,ice_temperature_at_t6,Ice temperature at sensor 6,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
|
|
59
|
+
t_i_7,ice_temperature_at_t7,Ice temperature at sensor 7,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
|
|
60
|
+
t_i_8,ice_temperature_at_t8,Ice temperature at sensor 8,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,all,1,1,1,4
|
|
61
|
+
t_i_9,ice_temperature_at_t9,Ice temperature at sensor 9,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,two-boom,1,1,1,4
|
|
62
|
+
t_i_10,ice_temperature_at_t10,Ice temperature at sensor 10,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,two-boom,1,1,1,4
|
|
63
|
+
t_i_11,ice_temperature_at_t11,Ice temperature at sensor 11,degrees_C,physicalMeasurement,time,FALSE,,-80,1,,two-boom,1,1,1,4
|
|
64
|
+
d_t_i_1,depth_of_thermistor_1,Depth of thermistor 1,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
|
|
65
|
+
d_t_i_2,depth_of_thermistor_2,Depth of thermistor 2,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
|
|
66
|
+
d_t_i_3,depth_of_thermistor_3,Depth of thermistor 3,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
|
|
67
|
+
d_t_i_4,depth_of_thermistor_4,Depth of thermistor 4,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
|
|
68
|
+
d_t_i_5,depth_of_thermistor_5,Depth of thermistor 5,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
|
|
69
|
+
d_t_i_6,depth_of_thermistor_6,Depth of thermistor 6,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
|
|
70
|
+
d_t_i_7,depth_of_thermistor_7,Depth of thermistor 7,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
|
|
71
|
+
d_t_i_8,depth_of_thermistor_8,Depth of thermistor 8,m,modelResult,time,FALSE,L3,-10,100,,all,0,0,1,4
|
|
72
|
+
d_t_i_9,depth_of_thermistor_9,Depth of thermistor 9,m,modelResult,time,FALSE,L3,-10,100,,two-boom,0,0,1,4
|
|
73
|
+
d_t_i_10,depth_of_thermistor_10,Depth of thermistor 10,m,modelResult,time,FALSE,L3,-10,100,,two-boom,0,0,1,4
|
|
74
|
+
d_t_i_11,depth_of_thermistor_11,Depth of thermistor 11,m,modelResult,time,FALSE,L3,-10,100,,two-boom,0,0,1,4
|
|
75
|
+
t_i_10m,10m_subsurface_temperature,10 m subsurface temperature,degrees_C,modelResult,time,FALSE,L3,-70,0,,all,0,0,1,4
|
|
76
|
+
tilt_x,platform_view_angle_x,Tilt to east,degrees,physicalMeasurement,time,FALSE,,-30,30,dsr_cor usr_cor albedo,all,1,1,1,4
|
|
77
|
+
tilt_y,platform_view_angle_y,Tilt to north,degrees,physicalMeasurement,time,FALSE,,-30,30,dsr_cor usr_cor albedo,all,1,1,1,4
|
|
78
|
+
rot,platform_azimuth_angle,Station rotation from true North,degrees,physicalMeasurement,time,FALSE,,0,360,,all,1,1,1,2
|
|
79
|
+
gps_lat,gps_latitude,Latitude,degrees_north,physicalMeasurement,time,TRUE,,50,83,,all,1,1,1,6
|
|
80
|
+
gps_lon,gps_longitude,Longitude,degrees_east,physicalMeasurement,time,TRUE,,5,70,,all,1,1,1,6
|
|
81
|
+
gps_alt,gps_altitude,Altitude above mean sea level (orthometric height),m,physicalMeasurement,time,TRUE,,0,3000,,all,1,1,1,2
|
|
82
|
+
gps_time,gps_time,GPS time,s,physicalMeasurement,time,TRUE,L0 or L2,0,240000,,all,1,1,0,
|
|
83
|
+
gps_geoid,gps_geoid_separation,Height of EGM96 geoid over WGS84 ellipsoid,m,physicalMeasurement,time,TRUE,L0 or L2,,,,one-boom,1,1,0,
|
|
84
|
+
gps_geounit,gps_geounit,GeoUnit,-,qualityInformation,time,TRUE,L0 or L2,,,,all,1,1,0,
|
|
85
|
+
gps_hdop,gps_hdop,GPS horizontal dillution of precision (HDOP),m,qualityInformation,time,TRUE,L0 or L2,,,,all,1,1,0,2
|
|
86
|
+
gps_numsat,gps_numsat,GPS number of satellites,-,qualityInformation,time,TRUE,L0 or L2,,,,,1,1,0,0
|
|
87
|
+
gps_q,gps_q,Quality,-,qualityInformation,time,TRUE,L0 or L2,,,,,1,1,0,
|
|
88
|
+
lat,latitude_postprocessed,smoothed and interpolated latitude of station,degrees_north,modelResult,time,TRUE,L3,,,,all,0,0,1,6
|
|
89
|
+
lon,longitude_postprocessed,smoothed and interpolated longitude of station,degrees_east,modelResult,time,TRUE,L3,,,,all,0,0,1,6
|
|
90
|
+
alt,altitude_postprocessed,smoothed and interpolated altitude of station above mean sea level (orthometric height),m,modelResult,time,TRUE,L3,,,,all,0,0,1,2
|
|
91
|
+
batt_v,battery_voltage,Battery voltage,V,physicalMeasurement,time,TRUE,,0,30,,all,1,1,1,2
|
|
92
|
+
batt_v_ini,,,-,physicalMeasurement,time,TRUE,L0 or L2,0,30,,,1,1,0,2
|
|
93
|
+
batt_v_ss,battery_voltage_at_sample_start,Battery voltage (sample start),V,physicalMeasurement,time,TRUE,L0 or L2,0,30,,,1,1,0,2
|
|
94
|
+
fan_dc_u,fan_current,Fan current (upper boom),mA,physicalMeasurement,time,TRUE,L0 or L2,0,200,,all,1,1,0,2
|
|
95
|
+
fan_dc_l,fan_current,Fan current (lower boom),mA,physicalMeasurement,time,TRUE,,0,200,,two-boom,1,1,0,2
|
|
96
|
+
freq_vw,frequency_of_precipitation_wire_vibration,Frequency of vibrating wire in precipitation gauge,Hz,physicalMeasurement,time,TRUE,L0 or L2,0,10000,precip_u,,1,1,0,
|
|
97
|
+
t_log,temperature_of_logger,Logger temperature,degrees_C,physicalMeasurement,time,TRUE,,-80,40,,one-boom,1,1,0,4
|
|
98
|
+
t_rad,temperature_of_radiation_sensor,Radiation sensor temperature,degrees_C,physicalMeasurement,time,FALSE,,-80,40,t_surf dlr ulr,all,1,1,1,4
|
|
99
|
+
p_i,air_pressure,Air pressure (instantaneous) minus 1000,hPa,physicalMeasurement,time,TRUE,,-350,100,,all,1,1,1,4
|
|
100
|
+
t_i,air_temperature,Air temperature (instantaneous),degrees_C,physicalMeasurement,time,TRUE,,-80,40,,all,1,1,1,4
|
|
101
|
+
rh_i,relative_humidity,Relative humidity (instantaneous),%,physicalMeasurement,time,TRUE,,0,150,rh_i_cor,all,1,1,1,4
|
|
102
|
+
rh_i_cor,relative_humidity_corrected,Relative humidity (instantaneous) – corrected,%,modelResult,time,TRUE,L2 or later,0,100,,all,0,1,1,4
|
|
103
|
+
wspd_i,wind_speed,Wind speed (instantaneous),m s-1,physicalMeasurement,time,TRUE,,0,100,wdir_i wspd_x_i wspd_y_i,all,1,1,1,4
|
|
104
|
+
wdir_i,wind_from_direction,Wind from direction (instantaneous),degrees,physicalMeasurement,time,TRUE,,1,360,wspd_x_i wspd_y_i,all,1,1,1,4
|
|
105
|
+
wspd_x_i,wind_speed_from_x_direction,Wind speed from x direction (instantaneous),m s-1,modelResult,time,TRUE,L2 or later,-100,100,wdir_i wspd_i,all,0,1,1,4
|
|
106
|
+
wspd_y_i,wind_speed_from_y_direction,Wind speed from y direction (instantaneous),m s-1,modelResult,time,TRUE,L2 or later,-100,100,wdir_i wspd_i,all,0,1,1,4
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional, Dict, Mapping, Sequence
|
|
4
|
+
|
|
5
|
+
import attrs
|
|
6
|
+
import toml
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@attrs.define
|
|
10
|
+
class StationConfiguration:
|
|
11
|
+
"""
|
|
12
|
+
Helper class for storing station specific configurations with respect to
|
|
13
|
+
|
|
14
|
+
* Installation specific distance measurements such as height differences between instruments
|
|
15
|
+
* Reference strings such as stid, station_site and wmo_id
|
|
16
|
+
* BUFR export specific parameters
|
|
17
|
+
|
|
18
|
+
# TODO: The station related meta data should be fetched from a station specific configuration files in the future or
|
|
19
|
+
# from header data in data source.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
stid: str
|
|
23
|
+
station_site: str = None
|
|
24
|
+
project: Optional[str] = None
|
|
25
|
+
station_type: Optional[str] = None
|
|
26
|
+
wmo_id: Optional[str] = None
|
|
27
|
+
barometer_from_gps: Optional[float] = None
|
|
28
|
+
anemometer_from_sonic_ranger: Optional[float] = None
|
|
29
|
+
temperature_from_sonic_ranger: Optional[float] = None
|
|
30
|
+
height_of_gps_from_station_ground: Optional[float] = None
|
|
31
|
+
sonic_ranger_from_gps: Optional[float] = None
|
|
32
|
+
static_height_of_gps_from_mean_sea_level: Optional[float] = None
|
|
33
|
+
station_relocation: Sequence[str] = attrs.field(factory=list)
|
|
34
|
+
location_type: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
# The station data will be exported to BUFR if True. Otherwise, it will only export latest position
|
|
37
|
+
export_bufr: bool = False
|
|
38
|
+
comment: Optional[str] = None
|
|
39
|
+
|
|
40
|
+
# skip specific variables for stations
|
|
41
|
+
# If a variable has known bad data, use this collection to skip the variable
|
|
42
|
+
# Note that if a station is not reporting both air temp and pressure it will be skipped,
|
|
43
|
+
# as currently implemented in csv2bufr.min_data_check().
|
|
44
|
+
# ['p_i'], # EXAMPLE
|
|
45
|
+
skipped_variables: Sequence[str] = attrs.field(factory=list)
|
|
46
|
+
|
|
47
|
+
positions_update_timestamp_only: bool = False
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def load_toml(cls, path, skip_unexpected_fields=False):
|
|
51
|
+
config_fields = {field.name for field in attrs.fields(cls)}
|
|
52
|
+
input_dict = toml.load(path)
|
|
53
|
+
unexpected_fields = set(input_dict.keys()) - config_fields
|
|
54
|
+
if unexpected_fields:
|
|
55
|
+
if skip_unexpected_fields:
|
|
56
|
+
logging.info(
|
|
57
|
+
f"Skipping unexpected fields in toml file {path}: "
|
|
58
|
+
+ ", ".join(unexpected_fields)
|
|
59
|
+
)
|
|
60
|
+
for field in unexpected_fields:
|
|
61
|
+
input_dict.pop(field)
|
|
62
|
+
else:
|
|
63
|
+
raise ValueError(f"Unexpected fields: {unexpected_fields}")
|
|
64
|
+
|
|
65
|
+
return cls(**input_dict)
|
|
66
|
+
|
|
67
|
+
def dump_toml(self, path: Path):
|
|
68
|
+
with path.open("w") as fp:
|
|
69
|
+
toml.dump(self.as_dict(), fp)
|
|
70
|
+
|
|
71
|
+
def as_dict(self) -> Dict:
|
|
72
|
+
return attrs.asdict(self)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def load_station_configuration_mapping(
|
|
76
|
+
configurations_root_dir: Path,
|
|
77
|
+
**kwargs,
|
|
78
|
+
) -> Mapping[str, StationConfiguration]:
|
|
79
|
+
"""
|
|
80
|
+
Load station configurations from toml files in configurations_root_dir
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
configurations_root_dir
|
|
85
|
+
Root directory containing toml files
|
|
86
|
+
kwargs
|
|
87
|
+
Additional arguments to pass to StationConfiguration.load_toml
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
Mapping from stid to StationConfiguration
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
return {
|
|
95
|
+
config_file.stem: StationConfiguration.load_toml(config_file, **kwargs)
|
|
96
|
+
for config_file in configurations_root_dir.glob("*.toml")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def write_station_configuration_mapping(
|
|
101
|
+
station_configurations: Mapping[str, StationConfiguration],
|
|
102
|
+
configurations_root_dir: Path,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Write station configurations to toml files in configurations_root_dir
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
station_configurations
|
|
110
|
+
Mapping from stid to StationConfiguration
|
|
111
|
+
configurations_root_dir
|
|
112
|
+
Output directory
|
|
113
|
+
|
|
114
|
+
"""
|
|
115
|
+
configurations_root_dir.mkdir(parents=True, exist_ok=True)
|
|
116
|
+
for stid, station_configuration in station_configurations.items():
|
|
117
|
+
with (configurations_root_dir / f"{stid}.toml").open("w") as fp:
|
|
118
|
+
toml.dump(station_configuration.as_dict(), fp)
|
pypromice/tx/get_l0tx.py
CHANGED
|
@@ -6,7 +6,7 @@ import os, imaplib, email, toml, re, unittest
|
|
|
6
6
|
from glob import glob
|
|
7
7
|
from datetime import datetime, timedelta
|
|
8
8
|
|
|
9
|
-
from pypromice.tx import getMail, L0tx, sortLines
|
|
9
|
+
from pypromice.tx import getMail, L0tx, sortLines, isModified
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def parse_arguments_l0tx():
|
|
@@ -134,9 +134,12 @@ def get_l0tx():
|
|
|
134
134
|
#----------------------------------
|
|
135
135
|
|
|
136
136
|
if out_dir is not None:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
for
|
|
137
|
+
|
|
138
|
+
# Find modified files (within the last hour)
|
|
139
|
+
mfiles = [mfile for mfile in glob(out_dir+'/'+args.name+'*.txt') if isModified(mfile, 1)]
|
|
140
|
+
|
|
141
|
+
# Sort L0tx files and add tails
|
|
142
|
+
for f in mfiles:
|
|
140
143
|
|
|
141
144
|
# Sort lines in L0tx file and remove duplicates
|
|
142
145
|
in_dirn, in_fn = os.path.split(f)
|
pypromice/tx/payload_formats.csv
CHANGED
|
@@ -66,6 +66,7 @@ type,expected_values,format,description,flags,notes
|
|
|
66
66
|
90,47,tfffffffffffffffffffffffffffffffffgneffffffffff,THE GC-NET VERSION!,don’t use,GC-NET stations
|
|
67
67
|
90,48, tfffffffffffffffffffffffffffffffffgnefffffffffff,THE GC-NET VERSION!,don’t use,GC-NET stations
|
|
68
68
|
90,48,tfffffffffffffffffffffffffffffffffgnefffffffffff,THE GC-NET VERSION!,,GC-NET stations
|
|
69
|
+
95,46,tfffffffffffffffffffffffffffffffgnefffffffffff,THE GC-NET VERSION!,,GC-NET stations
|
|
69
70
|
220,29,tfffffffffffffffffffffffnefff,ZAMG Freya 2015 summer message,,ZAMG Freya aws
|
|
70
71
|
221,0,,,,ZAMG Freya aws
|
|
71
72
|
222,29,tfffffffffffffffffffffffnefff,ZAMG Freya 2015 winter message,,ZAMG Freya aws
|
pypromice/tx/tx.py
CHANGED
|
@@ -886,13 +886,13 @@ def sortLines(in_file, out_file, replace_unsorted=True): #
|
|
|
886
886
|
lines = in_f.readlines()
|
|
887
887
|
|
|
888
888
|
# Remove duplicate lines and sort
|
|
889
|
-
unique_lines = findDuplicates(lines)
|
|
889
|
+
unique_lines = findDuplicates(lines.copy())
|
|
890
890
|
unique_lines.sort()
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
891
|
+
if lines != unique_lines:
|
|
892
|
+
# Write sorted file
|
|
893
|
+
with open(out_file, 'w') as out_f:
|
|
894
|
+
# out_f.write(headers)
|
|
895
|
+
out_f.writelines(unique_lines)
|
|
896
896
|
|
|
897
897
|
# Replace input file with new sorted file
|
|
898
898
|
if replace_unsorted:
|
|
@@ -930,6 +930,27 @@ def addTail(in_file, out_dir, aws_name, header_names='', lines_limit=100):
|
|
|
930
930
|
out_f.writelines(tail)
|
|
931
931
|
print(f'Tails file written to {out_pn}')
|
|
932
932
|
|
|
933
|
+
def isModified(filename, time_threshold=1):
|
|
934
|
+
'''Return flag denoting if file is modified within a certain timeframe
|
|
935
|
+
|
|
936
|
+
Parameters
|
|
937
|
+
----------
|
|
938
|
+
filename : str
|
|
939
|
+
File path
|
|
940
|
+
time_threshold : int
|
|
941
|
+
Time threshold (provided in hours)
|
|
942
|
+
|
|
943
|
+
Returns
|
|
944
|
+
-------
|
|
945
|
+
bool
|
|
946
|
+
Flag denoting if modified (True) or not (False)
|
|
947
|
+
'''
|
|
948
|
+
delta = time.time() - os.path.getmtime(filename)
|
|
949
|
+
delta = delta / (60*60)
|
|
950
|
+
if delta < time_threshold:
|
|
951
|
+
return True
|
|
952
|
+
return False
|
|
953
|
+
|
|
933
954
|
def _loadTestMsg():
|
|
934
955
|
'''Load test .msg email file'''
|
|
935
956
|
with pkg_resources.resource_stream('pypromice', 'test/test_email') as stream:
|
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_commit_hash_and_check_dirty(file_path: str | Path) -> str:
|
|
11
|
+
if isinstance(file_path, str):
|
|
12
|
+
file_path = Path(file_path)
|
|
13
|
+
if file_path.is_dir():
|
|
14
|
+
repo_path = file_path
|
|
15
|
+
else:
|
|
16
|
+
repo_path = file_path.parent
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
# Ensure the file path is relative to the repository
|
|
20
|
+
|
|
21
|
+
# Get the latest commit hash for the file
|
|
22
|
+
commit_hash = (
|
|
23
|
+
subprocess.check_output(
|
|
24
|
+
[
|
|
25
|
+
"git",
|
|
26
|
+
"-C",
|
|
27
|
+
repo_path,
|
|
28
|
+
"log",
|
|
29
|
+
"-n",
|
|
30
|
+
"1",
|
|
31
|
+
"--pretty=format:%H",
|
|
32
|
+
],
|
|
33
|
+
stderr=subprocess.STDOUT,
|
|
34
|
+
)
|
|
35
|
+
.strip()
|
|
36
|
+
.decode("utf-8")
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Check if the file is dirty (has uncommitted changes)
|
|
40
|
+
diff_output = (
|
|
41
|
+
subprocess.check_output(
|
|
42
|
+
["git", "-C", repo_path, "diff"],
|
|
43
|
+
stderr=subprocess.STDOUT,
|
|
44
|
+
)
|
|
45
|
+
.strip()
|
|
46
|
+
.decode("utf-8")
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# If diff_output is not empty, the file has uncommitted changes
|
|
50
|
+
is_dirty = len(diff_output) > 0
|
|
51
|
+
|
|
52
|
+
if is_dirty:
|
|
53
|
+
logger.warning(f"Warning: The file {file_path} is dirty compared to the last commit. {commit_hash}")
|
|
54
|
+
return f'{commit_hash} (dirty)'
|
|
55
|
+
if commit_hash == "":
|
|
56
|
+
logger.warning(f"Warning: The file {file_path} is not under version control.")
|
|
57
|
+
return 'unknown'
|
|
58
|
+
|
|
59
|
+
return commit_hash
|
|
60
|
+
except subprocess.CalledProcessError as e:
|
|
61
|
+
logger.warning(f"Error: {e.output.decode('utf-8')}")
|
|
62
|
+
return 'unknown'
|