Skip to content

Commit e74c003

Browse files
authored
Merge pull request #58 from UMSATS/54-adding-bulk-insert-support-to-telemetrycli
54 adding bulk insert support to telemetrycli
2 parents 89cfd61 + be8cd95 commit e74c003

6 files changed

Lines changed: 189 additions & 14 deletions

File tree

tools/telemetryInputCli/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.csv
2+
3+
!examples/data_temp.csv
4+
!examples/data_lumin.csv

tools/telemetryInputCli/README.MD

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,43 @@
1616
- cmd
1717
- influxdb_client
1818
- dotenv
19+
- yaml
1920

2021
## functionality
2122

2223
- wells data input
24+
25+
## environment
26+
27+
### .env file
28+
29+
The script requires a file called '.env' with the following format:
30+
31+
```env
32+
URL = <your URL>
33+
DB_BUCKET = <your database bucket>
34+
DB_ORG = <your database org tag>
35+
DB_USER = <your database user name>
36+
DB_PASSWORD = <your database password>
37+
DB_TOKEN = <your database token>
38+
...
39+
```
40+
41+
### config.yaml
42+
43+
The script's bulk insert function requires a `config.yaml` to externally store the csv format of each table with the following format (to be extended):
44+
45+
```yaml
46+
tables: # for read the data frame file
47+
- name: temp # need to match the field name in .env and influx table
48+
columns:
49+
- timestamp # UNIX timestamp
50+
- well_num
51+
- temperature
52+
53+
- name: lumin
54+
columns:
55+
- timestamp # UNIX timestamp
56+
- well_num
57+
- luminosity
58+
```
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
tables: # for read the data frame file
2+
- name: temp # need to match the field name in .env and influx table
3+
columns:
4+
- timestamp
5+
- well_num
6+
- temperature
7+
8+
- name: lumin
9+
columns:
10+
- timestamp
11+
- well_num
12+
- luminosity
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
timestamp,well_num,luminosity
2+
1756749600,1,600
3+
1756749600,2,350
4+
1756749600,3,750
5+
1756749600,4,500
6+
1756836000,1,800
7+
1756836000,2,450
8+
1756836000,3,300
9+
1756836000,4,650
10+
1756922400,1,200
11+
1756922400,2,400
12+
1756922400,3,700
13+
1756922400,4,250
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
timestamp,well_num,temperature
2+
1756749600,1,3
3+
1756749600,2,4
4+
1756749600,3,1
5+
1756749600,4,-3
6+
1756836000,1,2
7+
1756836000,2,2
8+
1756836000,3,4
9+
1756836000,4,-2
10+
1756922400,1,-1
11+
1756922400,2,0
12+
1756922400,3,3
13+
1756922400,4,0

tools/telemetryInputCli/telemetryInputCli.py

Lines changed: 111 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,37 @@
11
import cmd
2+
import pandas as pd
3+
import os
4+
import yaml
5+
from typing import List, Dict
26
from datetime import datetime, timezone, timedelta
37
from influxdb_client import InfluxDBClient, Point, WritePrecision
48
from influxdb_client.client.write_api import SYNCHRONOUS
59
from dotenv import load_dotenv, dotenv_values
610

11+
CONFIG_FILE = 'config.yaml'
12+
713
# Load environment variables
814
load_dotenv()
9-
config = dotenv_values(".env")
15+
env = dotenv_values(".env")
16+
17+
def load_table_configs() -> Dict[str, List[str]]:
18+
raw = yaml.safe_load(open(CONFIG_FILE, 'r'))
19+
table_configs: Dict[str, List[str]] = {}
20+
for entry in raw["tables"]:
21+
name = entry["name"]
22+
columns = entry["columns"]
23+
if not isinstance(columns, list):
24+
print(f"Invalid columns for table {name}. Expected a list, got {type(columns)}")
25+
continue
26+
table_configs[name] = (columns)
27+
return table_configs
28+
29+
# convert unix timestamp to datetime
30+
def convert_unix_to_datetime(unix_timestamp: int):
31+
if unix_timestamp < 0:
32+
print(f"Invalid unix timestamp: {unix_timestamp}")
33+
return None
34+
return datetime.fromtimestamp(unix_timestamp, tz=timezone.utc)
1035

1136
# ask for period of time
1237
def askingForPeriod() -> str:
@@ -38,17 +63,30 @@ def insertDataIntoWell(well: int, field: str, data: float, time: datetime, host:
3863
point = Point("well").tag("well", well).field(field, data).time(time, WritePrecision.NS).tag("host", host)
3964
write_api.write(bucket, org, point)
4065

66+
# ask for file path
67+
def askingForFilePath() -> str:
68+
path = input("Please enter the file path: ")
69+
if not path:
70+
print("Invalid file path")
71+
return None
72+
if not os.path.exists(path):
73+
print("File does not exist")
74+
return None
75+
return path
76+
4177
class TelemetryInputCli(cmd.Cmd):
78+
# load up config file
79+
table_configs = load_table_configs()
4280
# Set up the CLI
43-
url = config.get('URL')
44-
bucket = config.get('DB_BUCKET')
45-
org = config.get('DB_ORG')
46-
user = config.get('DB_USER')
47-
password = config.get('DB_PASSWORD')
48-
token = config.get('DB_TOKEN')
49-
payloadTag = config.get('PAYLOAD_TAG')
50-
wellTempField = config.get('WELL_TEMP_FIELD')
51-
wellLuminField = config.get('WELL_LUMIN_FIELD')
81+
url = env.get('URL')
82+
bucket = env.get('DB_BUCKET')
83+
org = env.get('DB_ORG')
84+
user = env.get('DB_USER')
85+
password = env.get('DB_PASSWORD')
86+
token = env.get('DB_TOKEN')
87+
payloadTag = env.get('PAYLOAD_TAG')
88+
wellTempField = env.get('WELL_TEMP_FIELD')
89+
wellLuminField = env.get('WELL_LUMIN_FIELD')
5290
client = InfluxDBClient(url=url, token=token, org=org)
5391
write_api = client.write_api(write_options=SYNCHRONOUS)
5492
prompt = '> '
@@ -69,16 +107,34 @@ def do_insert_temp_well(self, arg):
69107
'Insert temperature data into a specific well with specified time'
70108
self.insert_data_into_well(self.wellTempField)
71109

72-
# insert humidity data into all wells with specified time
110+
# insert luminosity data into all wells with specified time
73111
def do_insert_lumin_wells(self, arg):
74-
'Insert luminance data into all wells with specified time'
112+
'Insert luminosity data into all wells with specified time'
75113
self.insert_data_into_wells(self.wellLuminField)
76114

77-
# insert humidity data into a specific well with specified time
115+
# insert luminosity data into a specific well with specified time
78116
def do_insert_lumin_well(self, arg):
79-
'Insert luminance data into a specific well with specified time'
117+
'Insert luminosity data into a specific well with specified time'
80118
self.insert_data_into_well(self.wellLuminField)
81119

120+
def do_bulk_insert(self, arg):
121+
'Bulk insert data from a CSV file into the specified table'
122+
# print out available option from table_configs
123+
if not self.table_configs:
124+
print("No tables available for bulk insert.")
125+
return
126+
print("Available tables for bulk insert:")
127+
for table in self.table_configs.keys():
128+
print(f"- {table}")
129+
table = input("Please enter the table name: ")
130+
if table not in self.table_configs:
131+
print(f"Table {table} does not exist.")
132+
return
133+
filePath = askingForFilePath()
134+
if not filePath:
135+
return
136+
self.insert_data_from_df(table, filePath)
137+
82138
# helper function to insert data into all wells
83139
def insert_data_into_wells(self, field):
84140
period = askingForPeriod()
@@ -102,6 +158,47 @@ def insert_data_into_well(self, field):
102158
return
103159
insertDataIntoWell(well, field, data, calculatedTime, self.payloadTag, self.write_api, self.bucket, self.org)
104160
print("Data inserted successfully")
161+
162+
# bulk insert data into bucket
163+
def insert_data_from_df(self, table:str, filePath:str):
164+
if not filePath:
165+
return
166+
if filePath.endswith('.csv'):
167+
try:
168+
df = pd.read_csv(filePath, usecols=self.table_configs[table])
169+
except ValueError as e:
170+
print(f"Error reading CSV file: {e}")
171+
return
172+
else:
173+
print("Unsupported file format. Please provide a CSV file.")
174+
return
175+
# print out record
176+
print(f"Loading {len(df)} records from {filePath} for table {table}.")
177+
if df.empty:
178+
print("No data to insert.")
179+
return
180+
# convert unix timestamp to datetime
181+
if 'timestamp' in df.columns:
182+
df['timestamp'] = df['timestamp'].apply(lambda x: convert_unix_to_datetime(x) if pd.notnull(x) else None)
183+
else:
184+
print("No timestamp column found in the data.")
185+
return
186+
# insert data into influxdb
187+
if table == self.wellTempField or table == self.wellLuminField:
188+
for index, row in df.iterrows():
189+
well_num = row.get('well_num')
190+
if pd.notnull(well_num) and 1 <= well_num <= 16:
191+
time = row.get('timestamp')
192+
if time is not None:
193+
data = row.get('temperature') if table == self.wellTempField else row.get('luminosity')
194+
if pd.notnull(data):
195+
insertDataIntoWell(well_num, table, data, time, self.payloadTag, self.write_api, self.bucket, self.org)
196+
else:
197+
print(f"Invalid well number {well_num} at index {index}. Skipping this record.")
198+
else:
199+
print(f"Unsupported table {table}. Only 'temp' and 'lumin' are supported for bulk insert.")
200+
return
201+
print(f"Data from {filePath} inserted into {table} table successfully.")
105202

106203
# helper function to get data from user
107204
def get_data_from_user(self):

0 commit comments

Comments
 (0)