loone-data-prep 1.3.0__py3-none-any.whl → 1.3.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.
@@ -1,116 +1,68 @@
1
1
  import sys
2
2
  from datetime import datetime
3
- from glob import glob
4
3
  from retry import retry
5
- import os
6
4
  import pandas as pd
7
- from rpy2.robjects import r
8
- from rpy2.rinterface_lib.embedded import RRuntimeError
5
+ from loone_data_prep.utils import df_replace_missing_with_nan, get_dbhydro_api
9
6
 
10
7
 
11
8
  DATE_NOW = datetime.now().strftime("%Y-%m-%d")
12
9
 
13
10
 
14
- @retry(RRuntimeError, tries=5, delay=15, max_delay=60, backoff=2)
11
+ @retry(Exception, tries=5, delay=15, max_delay=60, backoff=2)
15
12
  def get(
16
13
  workspace: str,
17
14
  dbkey: str,
18
15
  date_min: str = "1990-01-01",
19
- date_max: str = DATE_NOW
16
+ date_max: str = DATE_NOW,
17
+ station: str | None = None
20
18
  ) -> None:
21
- r_str = f"""
22
- download_flow_data <- function(workspace, dbkey, date_min, date_max)
23
- {{
24
- # Load the required libraries
25
- library(dbhydroR)
26
- library(dplyr)
27
-
28
- # Retrieve data for the dbkey
29
- data <- get_hydro(dbkey = "{dbkey}", date_min = "{date_min}", date_max = "{date_max}", raw = TRUE)
30
-
31
- # Check if data is empty or contains only the "date" column
32
- if (ncol(data) <= 1) {{
33
- cat("No data found for dbkey", "{dbkey}", "Skipping to the next dbkey.\n")
34
- }}
35
-
36
- # Give data.frame correct column names so it can be cleaned using the clean_hydro function
37
- colnames(data) <- c("station", "dbkey", "date", "data.value", "qualifer", "revision.date")
38
-
39
- # Check if the data.frame has any rows
40
- if (nrow(data) == 0)
41
- {{
42
- # No data given back, It's possible that the dbkey has reached its end date.
43
- print(paste("Empty data.frame returned for dbkey", "{dbkey}", "It's possible that the dbkey has reached its end date. Skipping to the next dbkey."))
44
- return(list(success = FALSE, dbkey = "{dbkey}"))
45
- }}
46
-
47
- # Add a type and units column to data so it can be cleaned using the clean_hydro function
48
- data$type <- "FLOW"
49
- data$units <- "cfs"
50
-
51
- # Get the station
52
- station <- data$station[1]
53
-
54
- # Clean the data.frame
55
- data <- clean_hydro(data)
56
-
57
- # Multiply all columns except "date" column by 0.0283168466 * 86400 to convert Flow rate from cfs to m³/day
58
- data[, -1] <- data[, -1] * (0.0283168466 * 86400)
59
-
60
- # Drop the " _FLOW_cfs" column
61
- data <- data %>% select(-` _FLOW_cfs`)
62
-
63
- # Sort the data by date
64
- data <- data[order(data$date), ]
65
-
66
- # Get the filename for the output CSV file
67
- filename <- paste0(station, "_FLOW", "_{dbkey}_cmd.csv")
68
-
69
- # Save data to a CSV file
70
- write.csv(data, file = paste0("{workspace}/", filename))
71
-
72
- # Print a message indicating the file has been saved
73
- cat("CSV file", filename, "has been saved.\n")
74
-
75
- # Add a delay between requests
76
- Sys.sleep(1) # Wait for 1 second before the next iteration
77
-
78
- # Return the station and dbkey to the python code
79
- list(success = TRUE, station = station, dbkey = "{dbkey}")
80
- }}
19
+ """Fetches daily flow data from DBHYDRO and saves it to a CSV file.
20
+
21
+ Args:
22
+ workspace (str): Path to the workspace directory where data will be saved.
23
+ dbkey (str): The DBHYDRO database key for the station.
24
+ date_min (str): Minimum date for data retrieval in 'YYYY-MM-DD' format.
25
+ date_max (str): Maximum date for data retrieval in 'YYYY-MM-DD' format.
26
+ station (str | None): The station name. If None, the station name will be fetched from DBHYDRO.
81
27
  """
82
-
83
- r(r_str)
28
+ # Get a DbHydroApi instance
29
+ api = get_dbhydro_api()
84
30
 
85
- # Call the R function to download the flow data
86
- result = r.download_flow_data(workspace, dbkey, date_min, date_max)
31
+ # Get the daily data from DbHydro
32
+ response = api.get_daily_data([dbkey], 'id', date_min, date_max, 'NGVD29', False)
87
33
 
88
34
  # Check for failure
89
- success = result.rx2("success")[0]
90
-
91
- if not success:
35
+ if not response.has_data():
92
36
  return
93
37
 
94
- # Get the station name for _reformat_flow_file()
95
- station = result.rx2("station")[0]
38
+ # Get the station name for _reformat_flow_df()
39
+ if station is None:
40
+ station = response.get_site_codes()[0]
41
+
42
+ # Get the data as a dataframe
43
+ df = response.to_dataframe(True)
96
44
 
97
- # Reformat the flow data file to the expected layout
98
- _reformat_flow_file(workspace, station, dbkey)
45
+ # Replace flagged 0 values and -99999.0 with NaN
46
+ df = df_replace_missing_with_nan(df)
47
+
48
+ # Convert flow from cfs to cmd
49
+ df['value'] = df['value'] * (0.0283168466 * 86400)
50
+
51
+ # Prepare the dataframe to be reformatted into the expected layout
52
+ df.reset_index(inplace=True)
53
+ df.rename(columns={'datetime': 'date', 'value': f'{station}_FLOW_cmd'}, inplace=True)
54
+
55
+ # Reformat the flow df to the expected layout
56
+ df = _reformat_flow_df(df, station)
99
57
 
100
58
  # Check if the station name contains a space
101
- if " " in station:
59
+ if ' ' in station:
102
60
  # Replace space with underscore in the station name
103
61
  station_previous = station
104
- station = station.replace(" ", "_")
105
-
106
- # Rename the file
107
- os.rename(f"{workspace}/{station_previous}_FLOW_{dbkey}_cmd.csv", f"{workspace}/{station}_FLOW_{dbkey}_cmd.csv")
62
+ station = station.replace(' ', '_')
108
63
 
109
- # column values are converted to cmd in R. This snippet makes sure column names are updated accordingly.
110
- file = glob(f'{workspace}/*FLOW*{dbkey}_cmd.csv')[0]
111
- df = pd.read_csv(file, index_col=False)
112
- df.columns = df.columns.astype(str).str.replace("_cfs", "_cmd")
113
- df.to_csv(file, index=False)
64
+ # Write the data to a CSV file
65
+ df.to_csv(f'{workspace}/{station}_FLOW_{dbkey}_cmd.csv', index=True)
114
66
 
115
67
 
116
68
  def _reformat_flow_file(workspace:str, station: str, dbkey: str):
@@ -130,8 +82,27 @@ def _reformat_flow_file(workspace:str, station: str, dbkey: str):
130
82
  # Read in the data
131
83
  df = pd.read_csv(f"{workspace}/{station}_FLOW_{dbkey}_cmd.csv")
132
84
 
85
+ # Reformat the data
86
+ df = _reformat_flow_df(df, station)
87
+
88
+ # Write the updated data back to the file
89
+ df.to_csv(f"{workspace}/{station}_FLOW_{dbkey}_cmd.csv")
90
+
91
+
92
+ def _reformat_flow_df(df: pd.DataFrame, station: str) -> pd.DataFrame:
93
+ '''
94
+ Reformat the flow data file to the expected layout.
95
+ Converts the format of the dates in the file to 'YYYY-MM-DD' then sorts the data by date.
96
+
97
+ Args:
98
+ df (pd.DataFrame): The dataframe containing the flow data.
99
+ station (str): The station name.
100
+
101
+ Returns:
102
+ pd.DataFrame: The reformatted dataframe.
103
+ '''
133
104
  # Grab only the columns we need
134
- df = df[['date', f'{station}_FLOW_cfs']]
105
+ df = df[['date', f'{station}_FLOW_cmd']].copy()
135
106
 
136
107
  # Convert date column to datetime
137
108
  df['date'] = pd.to_datetime(df['date'], format='%d-%b-%Y')
@@ -143,10 +114,10 @@ def _reformat_flow_file(workspace:str, station: str, dbkey: str):
143
114
  df.reset_index(drop=True, inplace=True)
144
115
 
145
116
  # Drop rows that are missing values for both the date and value columns
146
- df = df.drop(df[(df['date'].isna()) & (df[f'{station}_FLOW_cfs'].isna())].index)
117
+ df = df.drop(df[(df['date'].isna()) & (df[f'{station}_FLOW_cmd'].isna())].index)
147
118
 
148
- # Write the updated data back to the file
149
- df.to_csv(f"{workspace}/{station}_FLOW_{dbkey}_cmd.csv")
119
+ # Return the updated dataframe
120
+ return df
150
121
 
151
122
 
152
123
  if __name__ == "__main__":
loone_data_prep/utils.py CHANGED
@@ -5,17 +5,14 @@ import math
5
5
  from glob import glob
6
6
  from calendar import monthrange
7
7
  import traceback
8
+ from typing import Literal, Tuple
8
9
  import numpy as np
9
10
  import pandas as pd
10
11
  from retry import retry
11
12
  from scipy.optimize import fsolve
12
13
  from scipy import interpolate
13
- from rpy2.robjects import r
14
- from rpy2.robjects.vectors import (
15
- StrVector as rpy2StrVector,
16
- DataFrame as rpy2DataFrame,
17
- )
18
- from rpy2.rinterface_lib.embedded import RRuntimeError
14
+ from dbhydro_py import DbHydroApi
15
+ from loone_data_prep.dbhydro_insights import get_dbhydro_station_metadata, get_dbhydro_continuous_timeseries_metadata, get_dbhydro_water_quality_metadata
19
16
 
20
17
 
21
18
  DEFAULT_STATION_IDS = ["L001", "L005", "L006", "LZ40"]
@@ -224,7 +221,7 @@ DEFAULT_EXPFUNC_NITROGEN_CONSTANTS = {
224
221
  "S135_P": {"a": 3.09890183766129, "b": 0.657896838486496},
225
222
  }
226
223
 
227
- @retry(RRuntimeError, tries=5, delay=15, max_delay=60, backoff=2)
224
+ @retry(Exception, tries=5, delay=15, max_delay=60, backoff=2)
228
225
  def get_dbkeys(
229
226
  station_ids: list,
230
227
  category: str,
@@ -232,9 +229,8 @@ def get_dbkeys(
232
229
  stat: str,
233
230
  recorder: str,
234
231
  freq: str = "DA",
235
- detail_level: str = "dbkey",
236
232
  *args: str,
237
- ) -> rpy2StrVector | rpy2DataFrame:
233
+ ) -> list[str]:
238
234
  """Get dbkeys. See DBHydroR documentation for more information:
239
235
  https://cran.r-project.org/web/packages/dbhydroR/dbhydroR.pdf
240
236
 
@@ -245,27 +241,68 @@ def get_dbkeys(
245
241
  stat (str): Statistic of data to retrieve.
246
242
  recorder (str): Recorder of data to retrieve.
247
243
  freq (str, optional): Frequency of data to retrieve. Defaults to "DA".
248
- detail_level (str, optional): Detail level of data to retrieve. Defaults to "dbkey". Options are "dbkey",
249
- "summary", or "full".
250
244
 
251
245
  Returns:
252
- rpy2StrVector | rpy2DataFrame: dbkeys info at the specified detail level.
246
+ list[str]: dbkeys info for the specified parameters.
253
247
  """
248
+ # Retrieve the metadata for the specified parameters
249
+ metadata = get_dbhydro_continuous_timeseries_metadata(station_ids, [category], [param], [stat], [recorder], [freq])
250
+
251
+ # A set to hold the dbkeys to avoid duplicates
252
+ dbkeys = set()
253
+
254
+ # No data returned from API
255
+ if metadata is None:
256
+ return list(dbkeys)
257
+
258
+ # Get the dbkeys from the metadata
259
+ for result in metadata['results']:
260
+ dbkeys.add(result['timeseriesId'])
261
+
262
+ # Return the dbkeys as a list
263
+ return list(dbkeys)
254
264
 
255
- station_ids_str = '"' + '", "'.join(station_ids) + '"'
256
265
 
257
- dbkeys = r(
258
- f"""
259
- library(dbhydroR)
266
+ def get_stations_latitude_longitude(station_ids: list[str]):
267
+ """Gets the latitudes and longitudes of the given stations.
260
268
 
261
- station_ids <- c({station_ids_str})
262
- dbkeys <- get_dbkey(stationid = station_ids, category = "{category}", param = "{param}", stat = "{stat}", recorder="{recorder}", freq = "{freq}", detail.level = "{detail_level}")
263
- print(dbkeys)
264
- return(dbkeys)
265
- """ # noqa: E501
266
- )
269
+ Args:
270
+ station_ids (list[str]): The ids of the stations to get the
271
+ latitudes/longitudes of. Example: ['L OKEE', 'FISHP']
272
+
273
+ Returns:
274
+ (dict[str, tuple[numpy.float64, numpy.float64]]): A dictionary of
275
+ format dict<station_id:(latitude,longitude)>
267
276
 
268
- return dbkeys
277
+ If a station's latitude/longitude fails to download then its station_id
278
+ won't be a key in the returned dictionary.
279
+ """
280
+ # Dictionary to hold the latitude/longitude of each station
281
+ station_data = {}
282
+
283
+ # Get the latitude and longitude for each station
284
+ for station_id in station_ids:
285
+ # Retrieve the current station's metadata
286
+ station_metadata = get_dbhydro_station_metadata(station_id)
287
+
288
+ # Check if the metadata was successfully retrieved
289
+ if station_metadata is None:
290
+ print(f'Failed to get latitude/longitude for station {station_id} - No data given back from API')
291
+ continue
292
+
293
+ # Extract the latitude and longitude from the metadata
294
+ try:
295
+ latitude = station_metadata['features'][0]['attributes']['LAT']
296
+ longitude = station_metadata['features'][0]['attributes']['LONG']
297
+ except KeyError:
298
+ print(f'Failed to get latitude/longitude for station {station_id} - Unexpected response structure from API')
299
+ continue
300
+
301
+ # Add the latitude and longitude to the dictionary
302
+ station_data[station_id] = latitude, longitude
303
+
304
+ # Return the dictionary of station latitudes and longitudes
305
+ return station_data
269
306
 
270
307
 
271
308
  def data_interpolations(
@@ -916,9 +953,17 @@ def find_last_date_in_csv(workspace: str, file_name: str) -> str:
916
953
 
917
954
  # Helper Functions
918
955
  def is_valid_date(date_string):
956
+ # Check for date without time part
919
957
  try:
920
958
  datetime.datetime.strptime(date_string, "%Y-%m-%d")
921
959
  return True
960
+ except ValueError:
961
+ pass
962
+
963
+ # Check for date with time part
964
+ try:
965
+ datetime.datetime.strptime(date_string, "%Y-%m-%d %H:%M:%S")
966
+ return True
922
967
  except ValueError:
923
968
  return False
924
969
 
@@ -955,23 +1000,69 @@ def find_last_date_in_csv(workspace: str, file_name: str) -> str:
955
1000
  return None
956
1001
 
957
1002
 
958
- def dbhydro_data_is_latest(date_latest: str):
1003
+ def dbhydro_data_is_latest(date_latest: str, dbkey: str | None = None) -> bool:
959
1004
  """
960
1005
  Checks whether the given date is the most recent date possible to get data from dbhydro.
961
1006
  Can be used to check whether dbhydro data is up-to-date.
962
1007
 
963
1008
  Args:
964
1009
  date_latest (str): The date of the most recent data of the dbhydro data you have
1010
+ dbkey (str | None, optional): The dbkey of the data you are checking. Defaults to None.
965
1011
 
966
1012
  Returns:
967
1013
  bool: True if the date_latest is the most recent date possible to get data from dbhydro, False otherwise
968
1014
  """
969
- date_latest_object = datetime.datetime.strptime(
970
- date_latest, "%Y-%m-%d"
971
- ).date()
972
- return date_latest_object == (
973
- datetime.datetime.now().date() - datetime.timedelta(days=1)
974
- )
1015
+ # Convert date_latest to a date object
1016
+ date_latest_object = pd.to_datetime(date_latest).date()
1017
+
1018
+ # No dbkey provided
1019
+ if dbkey is None:
1020
+ # Assume latest data available is yesterday
1021
+ return date_latest_object == (datetime.datetime.now().date() - datetime.timedelta(days=1))
1022
+
1023
+ # Get dbhydro api
1024
+ dbhydro_api = get_dbhydro_api()
1025
+
1026
+ # Retrieve the last date available from dbhydro for the given dbkey
1027
+ data = dbhydro_api.get_daily_data([dbkey], 'id', '1900-01-01', '1900-01-02', 'NGVD29', False)
1028
+ last_date = data.time_series[0].period_of_record.por_last_date
1029
+
1030
+ # Use date part only (exclude time)
1031
+ last_date = last_date.split("T")[0]
1032
+
1033
+ # Convert last_date to a date object
1034
+ last_date_object = datetime.datetime.strptime(last_date, "%Y-%m-%d").date()
1035
+
1036
+ # Compare given date to last date from dbhydro
1037
+ return date_latest_object >= last_date_object
1038
+
1039
+
1040
+ def dbhydro_water_quality_data_is_latest(date_latest: str, station: str, station_type: Literal['SITE', 'STATION'], test_number: int) -> bool:
1041
+ """
1042
+ Checks whether the given date is the most recent date possible to get water quality data from dbhydro.
1043
+ Can be used to check whether dbhydro water quality data is up-to-date.
1044
+
1045
+ Args:
1046
+ date_latest (str): The date of the most recent data of the dbhydro water quality data you have.
1047
+ station (str): The station ID of the water quality data you are checking.
1048
+ test_number (int): The test number of the water quality data you are checking. Test numbers map to parameters such as 'PHOSPHATE, TOTAL AS P'.
1049
+
1050
+ Returns:
1051
+ bool: True if the date_latest is the most recent date possible to get water quality data from dbhydro, False otherwise
1052
+ """
1053
+ # Get the date range from dbhydro water quality data
1054
+ date_start, date_end = get_dbhydro_water_quality_date_range(station, station_type, test_number)
1055
+
1056
+ # No end date available
1057
+ if date_end is None:
1058
+ # Assume data is not up-to-date
1059
+ return False
1060
+
1061
+ # Convert date_latest to a datetime object
1062
+ date_latest_object = pd.to_datetime(date_latest)
1063
+
1064
+ # Compare given date to last date from dbhydro
1065
+ return date_latest_object >= date_end
975
1066
 
976
1067
 
977
1068
  def get_synthetic_data(date_start: str, df: pd.DataFrame):
@@ -1038,6 +1129,128 @@ def get_synthetic_data(date_start: str, df: pd.DataFrame):
1038
1129
  return df
1039
1130
 
1040
1131
 
1132
+ def df_replace_missing_with_nan(df: pd.DataFrame, qualifier_codes: set = {'M', 'N'}, no_data_value: float = -99999.0) -> pd.DataFrame:
1133
+ """
1134
+ Replace values in the 'value' column of the DataFrame with NaN where the 'qualifier' column contains specified qualifier codes.
1135
+
1136
+ This was designed to work with dataframes created from dbhydro_py response data.
1137
+ The dataframe must have 'value' and 'qualifier' columns.
1138
+ Qualifier/Codes can be found here: https://insightsdata.sfwmd.gov/#/reference-tables?lookup=qualityCode
1139
+
1140
+ Args:
1141
+ df (pd.DataFrame): DataFrame that was created from a dbhydro_py response. Must have value and qualifier columns.
1142
+ qualifier_codes (set, optional): Set of qualifier codes indicating missing data. Defaults to {'M', 'N'}.
1143
+ no_data_value (float, optional): Value representing no data. Defaults to -99999.0. Values equal to this will also be replaced with NaN.
1144
+
1145
+ Returns:
1146
+ pd.DataFrame: DataFrame with specified values replaced with NaN.
1147
+ """
1148
+ # Replace 0 values with NaN when their qualifier is in qualifier_codes
1149
+ # 'M' = Missing, 'N' = Not Yet Available
1150
+ # Qualifier/Codes can be found here: https://insightsdata.sfwmd.gov/#/reference-tables?lookup=qualityCode
1151
+ df.loc[df['qualifier'].isin(qualifier_codes), 'value'] = np.nan
1152
+
1153
+ # Also replace no_data_value with NaN
1154
+ df.loc[np.isclose(df['value'], no_data_value), 'value'] = np.nan
1155
+
1156
+ # Return modified dataframe
1157
+ return df
1158
+
1159
+
1160
+ def get_dbhydro_water_quality_date_range(station: str, station_type: Literal['SITE', 'STATION'], test_number: int) -> Tuple[pd.Timestamp | None, pd.Timestamp | None]:
1161
+ """Get the start date and end date for the given station and test number from DBHYDRO water quality data.
1162
+
1163
+ Args:
1164
+ station (str): The station names.
1165
+ station_type (Literal['SITE', 'STATION']): The type of the station.
1166
+ test_number (int): The test number of the data. Test numbers map to parameters such as 'PHOSPHATE, TOTAL AS P'.
1167
+
1168
+ Returns:
1169
+ Tuple[pd.Timestamp | None, pd.Timestamp | None]: A tuple containing the start date and end date in 'MM/DD/YYYY' format.
1170
+ """
1171
+ response = get_dbhydro_water_quality_metadata([(station, station_type)], [test_number])
1172
+
1173
+ # No data given back by api
1174
+ if response is None:
1175
+ return (None, None)
1176
+
1177
+ # Get the date range from the response
1178
+ if 'results' in response:
1179
+ results = response['results']
1180
+ if len(results) > 0:
1181
+ # Find the first non-None start and end dates
1182
+ date_start = None
1183
+ date_end = None
1184
+ for result in results:
1185
+ date_start = result.get('startDate', None)
1186
+ date_end = result.get('endDate', None)
1187
+
1188
+ # Dates found
1189
+ if date_start is not None and date_end is not None:
1190
+ break
1191
+
1192
+ # If no valid dates were found, return early
1193
+ if date_start is None or date_end is None:
1194
+ return (date_start, date_end)
1195
+
1196
+ # Find the earliest start date and latest end date
1197
+ for result in results:
1198
+ date_start_current = result.get('startDate', None)
1199
+ date_end_current = result.get('endDate', None)
1200
+ if date_start_current is not None and pd.to_datetime(date_start_current) < pd.to_datetime(date_start):
1201
+ date_start = date_start_current
1202
+ if date_end_current is not None and pd.to_datetime(date_end_current) > pd.to_datetime(date_end):
1203
+ date_end = date_end_current
1204
+
1205
+ # Convert dates to datetime objects
1206
+ if date_start is not None:
1207
+ date_start = pd.to_datetime(date_start)
1208
+ if date_end is not None:
1209
+ date_end = pd.to_datetime(date_end)
1210
+
1211
+ # Return the earliest start date and latest end date
1212
+ return (date_start, date_end)
1213
+
1214
+ # No results found
1215
+ return (None, None)
1216
+
1217
+
1218
+ def get_dbhydro_api_keys_from_environment() -> dict[str, str]:
1219
+ """Get DBHYDRO API keys from environment variables.
1220
+
1221
+ Returns:
1222
+ Dict[str, str]: A dictionary containing the DBHYDRO API keys where dict keys are 'client_id' and 'client_secret'.
1223
+ """
1224
+ # Get API keys from environment variables
1225
+ api_keys = {
1226
+ "client_id": os.environ.get("DBHYDRO_API_CLIENT_ID", ""),
1227
+ "client_secret": os.environ.get("DBHYDRO_API_CLIENT_SECRET", ""),
1228
+ }
1229
+
1230
+ # Return the API keys
1231
+ return api_keys
1232
+
1233
+
1234
+ def get_dbhydro_api_keys() -> dict[str, str]:
1235
+ """Get DBHYDRO API keys.
1236
+
1237
+ Returns:
1238
+ Dict[str, str]: A dictionary containing the DBHYDRO API keys where dict keys are 'client_id' and 'client_secret'.
1239
+ """
1240
+ return get_dbhydro_api_keys_from_environment()
1241
+
1242
+
1243
+ def get_dbhydro_api() -> DbHydroApi:
1244
+ """Get a configured DbHydroApi instance.
1245
+
1246
+ Returns:
1247
+ DbHydroApi: An instance of the DbHydroApi class.
1248
+ """
1249
+ api_keys = get_dbhydro_api_keys()
1250
+ dbhydro_api = DbHydroApi.with_default_adapter(client_id=api_keys["client_id"], client_secret=api_keys["client_secret"])
1251
+ return dbhydro_api
1252
+
1253
+
1041
1254
  if __name__ == "__main__":
1042
1255
  if sys.argv[1] == "get_dbkeys":
1043
1256
  get_dbkeys(