iplotDataAccess 1.3.1.post1__tar.gz
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.
- iplotdataaccess-1.3.1.post1/MANIFEST.in +1 -0
- iplotdataaccess-1.3.1.post1/PKG-INFO +79 -0
- iplotdataaccess-1.3.1.post1/README.md +51 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/__init__.py +3 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/_version.py +21 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/appDataAccess.py +26 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/csvAccess.py +191 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/dataAccess.py +195 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/dataCommon.py +130 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/dataHandling/exportData/__init__.py +0 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/dataHandling/exportData/exportData.py +259 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/dataSource.py +187 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/dataSourceConfig.py +0 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/data_sources.cfg +14 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/imasDBMaster.py +193 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/imasUtils.py +269 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/imaspyAccess.py +536 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/nestedDatatype.py +163 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/realTimeStreamer.py +283 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/tests/csv/ITER/MCTB-TEST/data_111.csv +12 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/tests/test_imas.py +750 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/tests/test_uda.py +92 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/tests/test_zstreamer.py +137 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess/udaAccess.py +732 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess.egg-info/PKG-INFO +79 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess.egg-info/SOURCES.txt +32 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess.egg-info/dependency_links.txt +1 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess.egg-info/requires.txt +13 -0
- iplotdataaccess-1.3.1.post1/iplotDataAccess.egg-info/top_level.txt +1 -0
- iplotdataaccess-1.3.1.post1/pyproject.toml +62 -0
- iplotdataaccess-1.3.1.post1/setup.cfg +12 -0
- iplotdataaccess-1.3.1.post1/setup.py +7 -0
- iplotdataaccess-1.3.1.post1/versioneer.py +2296 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include iplotDataAccess/tests/csv/ITER/MCTB-TEST/data_111.csv
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: iplotDataAccess
|
|
3
|
+
Version: 1.3.1.post1
|
|
4
|
+
Summary: Data access for applications using IDSs or CBS
|
|
5
|
+
Author-email: ITER Organization <jscabanilla@minsait.com>
|
|
6
|
+
Maintainer-email: Jhon Steeven Cabanilla Alvarado <jscabanilla@minsait.com>, Pablo Martin Villares <pmartin@minsait.com>, Mario Gomez Ballesteros <mgomezb@minsait.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/iplot-viz/iplotdataaccess
|
|
9
|
+
Project-URL: Repository, https://github.com/iplot-viz/iplotdataaccess.git
|
|
10
|
+
Project-URL: Issues, https://github.com/iplot-viz/iplotdataaccess/issues
|
|
11
|
+
Keywords: IDS,CBS,Interface Data Structures,Control Breakdown Structure
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: cachetools>=4.2.0
|
|
18
|
+
Requires-Dist: iplotLogging>=1.2.0
|
|
19
|
+
Requires-Dist: requests>=2.25.1
|
|
20
|
+
Requires-Dist: sseclient-py>=1.7
|
|
21
|
+
Requires-Dist: pyarrow>=16.1.0
|
|
22
|
+
Requires-Dist: h5py>=3.11.0
|
|
23
|
+
Requires-Dist: pandas>=1.2.0
|
|
24
|
+
Provides-Extra: imaspy
|
|
25
|
+
Requires-Dist: PyYAML>=6.0.1; extra == "imaspy"
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest>=6.0; extra == "test"
|
|
28
|
+
|
|
29
|
+
# Data Access Library
|
|
30
|
+
|
|
31
|
+
Data access for applications represented using Interface Data Structures (IDSs) or Control Breakdown Structure (CBS).
|
|
32
|
+
|
|
33
|
+
## Requirements
|
|
34
|
+
|
|
35
|
+
1. **python <= 3.11**
|
|
36
|
+
2. **Dependencies**: Managed
|
|
37
|
+
via [pyproject.toml](https://github.com/iplot-viz/iplotdataaccess/blob/develop/pyproject.toml).
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
Install the package from PyPi:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install iplotDataAccess
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Installation with optional dependencies
|
|
48
|
+
|
|
49
|
+
You can also install optional dependencies (e.g. imaspy and test):
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install "iplotDataAccess[imaspy, test]"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### List of optional dependencies
|
|
56
|
+
|
|
57
|
+
- ```imaspy```: installs required packages.
|
|
58
|
+
- ```test```: installs required packages to run the tests with pytest.
|
|
59
|
+
|
|
60
|
+
### Usage Example
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
from iplotDataAccess.dataAccess import DataAccess
|
|
64
|
+
|
|
65
|
+
# Create a DataAccess object
|
|
66
|
+
da = DataAccess()
|
|
67
|
+
|
|
68
|
+
# Example: load data from CODAC UDA source
|
|
69
|
+
data_obj = da.get_data("codacuda", varname="IC_ICH1_FAFB_MEAS/phase", tsS="2018-11-21T09:30:00", tsE="2018-11-21T09:45:00", nbp=-1)
|
|
70
|
+
print(data_obj.xdata, data_obj.ydata)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Contributing
|
|
74
|
+
|
|
75
|
+
1. Fork it!
|
|
76
|
+
2. Create your feature branch: ```git checkout -b my-new-feature ```
|
|
77
|
+
3. Commit your changes: ```git commit -am 'Add some feature' ```
|
|
78
|
+
4. Push to the branch:```git push origin my-new-feature ```
|
|
79
|
+
5. Submit a pull request
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Data Access Library
|
|
2
|
+
|
|
3
|
+
Data access for applications represented using Interface Data Structures (IDSs) or Control Breakdown Structure (CBS).
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
1. **python <= 3.11**
|
|
8
|
+
2. **Dependencies**: Managed
|
|
9
|
+
via [pyproject.toml](https://github.com/iplot-viz/iplotdataaccess/blob/develop/pyproject.toml).
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Install the package from PyPi:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install iplotDataAccess
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Installation with optional dependencies
|
|
20
|
+
|
|
21
|
+
You can also install optional dependencies (e.g. imaspy and test):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install "iplotDataAccess[imaspy, test]"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### List of optional dependencies
|
|
28
|
+
|
|
29
|
+
- ```imaspy```: installs required packages.
|
|
30
|
+
- ```test```: installs required packages to run the tests with pytest.
|
|
31
|
+
|
|
32
|
+
### Usage Example
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
from iplotDataAccess.dataAccess import DataAccess
|
|
36
|
+
|
|
37
|
+
# Create a DataAccess object
|
|
38
|
+
da = DataAccess()
|
|
39
|
+
|
|
40
|
+
# Example: load data from CODAC UDA source
|
|
41
|
+
data_obj = da.get_data("codacuda", varname="IC_ICH1_FAFB_MEAS/phase", tsS="2018-11-21T09:30:00", tsE="2018-11-21T09:45:00", nbp=-1)
|
|
42
|
+
print(data_obj.xdata, data_obj.ydata)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Contributing
|
|
46
|
+
|
|
47
|
+
1. Fork it!
|
|
48
|
+
2. Create your feature branch: ```git checkout -b my-new-feature ```
|
|
49
|
+
3. Commit your changes: ```git commit -am 'Add some feature' ```
|
|
50
|
+
4. Push to the branch:```git push origin my-new-feature ```
|
|
51
|
+
5. Submit a pull request
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
# This file was generated by 'versioneer.py' (0.29) from
|
|
3
|
+
# revision-control system data, or from the parent directory name of an
|
|
4
|
+
# unpacked source archive. Distribution tarballs contain a pre-generated copy
|
|
5
|
+
# of this file.
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
version_json = '''
|
|
10
|
+
{
|
|
11
|
+
"date": "2025-12-19T09:53:38+0000",
|
|
12
|
+
"dirty": false,
|
|
13
|
+
"error": null,
|
|
14
|
+
"full-revisionid": "afb231aec5bbd77563b4857880ccc3c9e6ef3a33",
|
|
15
|
+
"version": "1.3.1.post1"
|
|
16
|
+
}
|
|
17
|
+
''' # END VERSION_JSON
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_versions():
|
|
21
|
+
return json.loads(version_json)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from iplotDataAccess.dataAccess import DataAccess
|
|
2
|
+
from iplotLogging import setupLogger
|
|
3
|
+
|
|
4
|
+
logger = setupLogger.get_logger(__name__)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AppDataAccess:
|
|
8
|
+
da: DataAccess = None
|
|
9
|
+
configured: bool = False
|
|
10
|
+
|
|
11
|
+
# ---------------
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def initialize(config_file=None):
|
|
15
|
+
if AppDataAccess.da is None:
|
|
16
|
+
AppDataAccess.da = DataAccess()
|
|
17
|
+
AppDataAccess.configured = AppDataAccess.da.load_config(config_file)
|
|
18
|
+
return AppDataAccess.configured
|
|
19
|
+
|
|
20
|
+
@staticmethod
|
|
21
|
+
def get_data_access():
|
|
22
|
+
return AppDataAccess.da
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def is_configured():
|
|
26
|
+
return AppDataAccess.configured
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from pandas import DataFrame
|
|
5
|
+
|
|
6
|
+
from iplotDataAccess import dataCommon
|
|
7
|
+
from iplotDataAccess.dataSource import DataSource
|
|
8
|
+
import iplotLogging.setupLogger as setupLog
|
|
9
|
+
|
|
10
|
+
logger = setupLog.get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CsvAccess(DataSource):
|
|
14
|
+
source_type = "CSV"
|
|
15
|
+
|
|
16
|
+
def __init__(self, name: str, config: dict):
|
|
17
|
+
super().__init__(name, config)
|
|
18
|
+
|
|
19
|
+
self.folder_path = config.get("path", "") # Store the folder path for accessing CSV files
|
|
20
|
+
self.def_pulse_location = self.folder_path.split('/')[-1]
|
|
21
|
+
|
|
22
|
+
def connect(self) -> bool:
|
|
23
|
+
self.connected = os.path.isdir(self.folder_path)
|
|
24
|
+
return self.connected
|
|
25
|
+
|
|
26
|
+
# Method to get data from a CSV file based on the 'pulse' and 'varname' arguments in kwargs
|
|
27
|
+
def get_data(self, **kwargs):
|
|
28
|
+
|
|
29
|
+
# Create a DataObj instance for storing the data
|
|
30
|
+
data_obj = dataCommon.DataObj()
|
|
31
|
+
|
|
32
|
+
# Transform the 'pulse' argument to a valid file path
|
|
33
|
+
path = self.transform_pulse_to_file_path(kwargs.get("pulse"))
|
|
34
|
+
|
|
35
|
+
# Read the CSV file into a pandas DataFrame
|
|
36
|
+
data = pd.read_csv(path)
|
|
37
|
+
|
|
38
|
+
if kwargs.get("tsS") is not None:
|
|
39
|
+
data = data[data['Time'] >= kwargs.get("tsS")]
|
|
40
|
+
if kwargs.get("tsE") is not None:
|
|
41
|
+
data = data[data['Time'] <= kwargs.get("tsE")]
|
|
42
|
+
|
|
43
|
+
sub_data = data.filter(like=kwargs.get("varname"))
|
|
44
|
+
|
|
45
|
+
# If multiple columns match 'varname', return an error
|
|
46
|
+
if len(sub_data.columns) > 1:
|
|
47
|
+
data_obj.set_err(-1, "Multiple columns with same variable name")
|
|
48
|
+
return data_obj
|
|
49
|
+
|
|
50
|
+
if len(sub_data.columns) == 0:
|
|
51
|
+
data_obj.set_err(-1, f"No columns found matching variable name '{kwargs.get('varname')}'")
|
|
52
|
+
return data_obj
|
|
53
|
+
|
|
54
|
+
# Set the data for the variable in the DataObj
|
|
55
|
+
data_obj.set_data(sub_data.iloc[:, 0].values, 2)
|
|
56
|
+
logger.debug(" found data_obj %s ", data_obj)
|
|
57
|
+
|
|
58
|
+
# Extract the unit from the variable's column name (if present)
|
|
59
|
+
yunit = re.findall(r' \((.*?)\)', sub_data.columns[0])
|
|
60
|
+
if yunit:
|
|
61
|
+
data_obj.yunit = yunit[0]
|
|
62
|
+
|
|
63
|
+
# Set the 'Time' column data in the DataObj and define xunit as "seconds"
|
|
64
|
+
data_obj.set_data(data["Time"].values, 1)
|
|
65
|
+
data_obj.xunit = "s"
|
|
66
|
+
|
|
67
|
+
return data_obj # Return the populated DataObj
|
|
68
|
+
|
|
69
|
+
# Method to get all pulses (files) in the folder matching a pattern as a list
|
|
70
|
+
def get_pulses_df(self, pattern='.*') -> DataFrame:
|
|
71
|
+
all_pulses = []
|
|
72
|
+
pstatus = "completed"
|
|
73
|
+
|
|
74
|
+
# Walk through all files and folders in the directory
|
|
75
|
+
for folder, _, files in os.walk(self.folder_path):
|
|
76
|
+
relative_folder = os.path.relpath(folder, self.folder_path).replace(os.sep, "/")
|
|
77
|
+
# For each file, create a key with the folder and cleaned file name
|
|
78
|
+
for file in files:
|
|
79
|
+
if not file.endswith(".csv"):
|
|
80
|
+
continue
|
|
81
|
+
value = f"{self.def_pulse_location}:{relative_folder}/{file.replace('.csv', '').replace('data_', '')};{pstatus}"
|
|
82
|
+
logger.debug(" found value and file %s %s", value, file)
|
|
83
|
+
if re.match(pattern, value):
|
|
84
|
+
all_pulses.append(value)
|
|
85
|
+
|
|
86
|
+
# Return the pulses
|
|
87
|
+
logger.debug(" pulses %s", all_pulses)
|
|
88
|
+
return pd.DataFrame([value.split(";") for value in all_pulses], columns=["pulseId", "Status"])
|
|
89
|
+
|
|
90
|
+
def get_cbs_list(self, pattern=".*"):
|
|
91
|
+
var_list = self.get_var_list(pattern)
|
|
92
|
+
cbs = set()
|
|
93
|
+
|
|
94
|
+
for v in var_list:
|
|
95
|
+
cbs.add(v + "?V")
|
|
96
|
+
|
|
97
|
+
logger.debug(" cbs list %s", cbs)
|
|
98
|
+
return cbs
|
|
99
|
+
|
|
100
|
+
def get_cbs_dictX(self, pattern='.*') -> dict:
|
|
101
|
+
cbs_list = self.get_cbs_list(pattern)
|
|
102
|
+
cbs_dict = dict()
|
|
103
|
+
for line in cbs_list:
|
|
104
|
+
cur_dict = cbs_dict
|
|
105
|
+
cbs = line.split(":")
|
|
106
|
+
for counter, s in enumerate(cbs):
|
|
107
|
+
data = '-'.join(cbs[0:counter])
|
|
108
|
+
if data not in cur_dict:
|
|
109
|
+
cur_dict = cur_dict.setdefault(data, {})
|
|
110
|
+
cur_dict = cur_dict.setdefault(line.replace('?V'), '')
|
|
111
|
+
logger.debug(" cbs list %s", cbs_dict)
|
|
112
|
+
return cbs_dict
|
|
113
|
+
|
|
114
|
+
def get_cbs_dict(self, pattern='.*') -> dict:
|
|
115
|
+
cbs_list = self.get_cbs_list(pattern)
|
|
116
|
+
cbs_dict = dict()
|
|
117
|
+
for line in cbs_list:
|
|
118
|
+
cur_dict = cbs_dict
|
|
119
|
+
list_line = line.split('-')
|
|
120
|
+
for var in list_line:
|
|
121
|
+
if var.endswith('?V'):
|
|
122
|
+
cur_dict = cur_dict.setdefault('-'.join(list_line).replace('?V', ''), '')
|
|
123
|
+
else:
|
|
124
|
+
cur_dict = cur_dict.setdefault(var, {})
|
|
125
|
+
|
|
126
|
+
return cbs_dict
|
|
127
|
+
|
|
128
|
+
def get_pulse_info(self, pulse, run):
|
|
129
|
+
all_pulses = self.get_pulses_df()
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
def get_var_dict(self, pattern='.*'):
|
|
133
|
+
return self.get_cbs_dict(pattern)
|
|
134
|
+
|
|
135
|
+
def is_connected(self):
|
|
136
|
+
return self.connected is not None
|
|
137
|
+
|
|
138
|
+
def search_pulses_df(self, text) -> DataFrame:
|
|
139
|
+
return self.get_pulses_df(pattern=text)
|
|
140
|
+
|
|
141
|
+
# Method to get a list of all unique variables from CSV files in the folder
|
|
142
|
+
def get_var_list(self, patt='.*'):
|
|
143
|
+
all_variables = set() # Use a set to store unique variables
|
|
144
|
+
|
|
145
|
+
# Walk through all files and folders in the directory
|
|
146
|
+
for folder, _, files in os.walk(self.folder_path):
|
|
147
|
+
for file in files:
|
|
148
|
+
if not file.endswith(".csv"):
|
|
149
|
+
continue
|
|
150
|
+
file_path = os.path.join(folder, file) # Get the full file path
|
|
151
|
+
try:
|
|
152
|
+
# Open each file and read the header line to extract variables
|
|
153
|
+
with open(file_path, 'r') as f:
|
|
154
|
+
x = f.readline().split(",")[1:] # Skip the first column (Time)
|
|
155
|
+
all_variables = all_variables.union([i.split(" ")[0] for i in x])
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
logger.error("Could not open file %s for %s ", file_path, e) # Handle file reading errors
|
|
159
|
+
logger.debug(" all variable %s", all_variables)
|
|
160
|
+
|
|
161
|
+
# Filter variables matching the given pattern
|
|
162
|
+
filtered_vars = []
|
|
163
|
+
for variable in all_variables:
|
|
164
|
+
s = re.match(patt, variable.strip())
|
|
165
|
+
|
|
166
|
+
if s is not None:
|
|
167
|
+
filtered_vars.append(s[0])
|
|
168
|
+
|
|
169
|
+
logger.debug(" variable %s and pattern %s ", filtered_vars, patt)
|
|
170
|
+
|
|
171
|
+
return filtered_vars # Return the filtered list of variables
|
|
172
|
+
|
|
173
|
+
# Method to transform a pulse string into a file path
|
|
174
|
+
# Example: input 'ITER/COMM:111' into
|
|
175
|
+
# '{user_path}\\iplotdataaccess\\iplotDataAccess\\ITER\\COMM\\data_111.csv'
|
|
176
|
+
def transform_pulse_to_file_path(self, pulse):
|
|
177
|
+
# Split the pulse into folder and file
|
|
178
|
+
folders, file = pulse.split(":", 1)
|
|
179
|
+
f1, f2 = file.split("/", 1)
|
|
180
|
+
print(f" variable {pulse} ")
|
|
181
|
+
# Replace slashes with the OS-specific separator
|
|
182
|
+
folders = folders.replace("/", os.sep)
|
|
183
|
+
# Join all parts
|
|
184
|
+
result = f"{self.folder_path}{os.sep}{f1}{os.sep}data_{f2}.csv"
|
|
185
|
+
return result # Return the constructed file path
|
|
186
|
+
|
|
187
|
+
def clear_cache(self):
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
def get_envelope(self):
|
|
191
|
+
pass
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import importlib.util
|
|
4
|
+
import importlib.resources as pkg_resources
|
|
5
|
+
from typing import Dict, List, Union, Type
|
|
6
|
+
|
|
7
|
+
from iplotDataAccess.dataSource import DataSource
|
|
8
|
+
from iplotLogging import setupLogger
|
|
9
|
+
from iplotDataAccess.dataCommon import DataObj, DataEnvelope
|
|
10
|
+
|
|
11
|
+
logger = setupLogger.get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# class to interface with data source - here UDA
|
|
15
|
+
class DataAccess:
|
|
16
|
+
DEFAULT_DATA_SOURCES_CFG_FILE: str = 'mydatasources.cfg'
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.proto: Dict[str, Type[DataSource]] = self.get_supported_data_source()
|
|
20
|
+
self.ds_list: Dict[str, DataSource] = {}
|
|
21
|
+
self.default_ds: Union[DataSource, None] = None
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def get_supported_data_source() -> Dict[str, Type[DataSource]]:
|
|
25
|
+
supported_data_sources = {}
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
file_path = str(pkg_resources.files('iplotDataAccess').joinpath('data_sources.cfg'))
|
|
29
|
+
with open(file_path, 'r') as file:
|
|
30
|
+
|
|
31
|
+
data_sources = json.load(file)
|
|
32
|
+
for key, value in data_sources.items():
|
|
33
|
+
try:
|
|
34
|
+
module = importlib.import_module(value['pymodule'])
|
|
35
|
+
imported_class = getattr(module, value['class'])
|
|
36
|
+
supported_data_sources[imported_class.source_type] = imported_class
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logger.error(f"Error loading DataSource {key} -> {e}")
|
|
39
|
+
|
|
40
|
+
except Exception as e:
|
|
41
|
+
logger.error(f"Error loading DataSources config file ->{e}")
|
|
42
|
+
|
|
43
|
+
return supported_data_sources
|
|
44
|
+
|
|
45
|
+
def get_default_ds_name(self):
|
|
46
|
+
if self.default_ds is None:
|
|
47
|
+
return None
|
|
48
|
+
else:
|
|
49
|
+
return self.default_ds.name
|
|
50
|
+
|
|
51
|
+
def load_config(self, conf_file=None) -> bool:
|
|
52
|
+
conf_files = [conf_file, os.environ.get('IPLOT_SOURCES_CONFIG'), self.DEFAULT_DATA_SOURCES_CFG_FILE]
|
|
53
|
+
# Remove None values
|
|
54
|
+
conf_files = list(filter(None, conf_files))
|
|
55
|
+
for ix, file in enumerate(conf_files):
|
|
56
|
+
try:
|
|
57
|
+
return self.load_config_file(file)
|
|
58
|
+
except (OSError, IOError, FileNotFoundError) as _:
|
|
59
|
+
if ix < len(conf_files) - 1:
|
|
60
|
+
logger.warning(f"Error loading {file} data source file, fallback to {conf_files[ix + 1]}")
|
|
61
|
+
return False
|
|
62
|
+
|
|
63
|
+
def load_config_file(self, dspath: str) -> bool:
|
|
64
|
+
with open(dspath) as f:
|
|
65
|
+
try:
|
|
66
|
+
config = json.load(f)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
logger.warning(f"Wrong json format in {dspath} -> {e}")
|
|
69
|
+
return False
|
|
70
|
+
for ds_name, ds_config in config.items():
|
|
71
|
+
ds_type = ds_config.get("type")
|
|
72
|
+
ds_class = self.proto.get(ds_type)
|
|
73
|
+
if not ds_class:
|
|
74
|
+
logger.warning(f"DataSource '{ds_name}' has an unsupported data source type-> {ds_type}")
|
|
75
|
+
continue
|
|
76
|
+
try:
|
|
77
|
+
data_source = ds_class(ds_name, ds_config)
|
|
78
|
+
if data_source.connect():
|
|
79
|
+
self.ds_list[ds_name] = data_source
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.warning(f"Error importing class {ds_class} with error {e}")
|
|
82
|
+
|
|
83
|
+
if self.ds_list:
|
|
84
|
+
# Check which data source to set by default
|
|
85
|
+
# Set first DataSource that has default=true if no one has it, set the first one
|
|
86
|
+
self.default_ds = next(
|
|
87
|
+
(ds for ds in self.ds_list.values() if ds.default),
|
|
88
|
+
list(self.ds_list.values())[0])
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
def get_data_source(self, data_s_name):
|
|
94
|
+
logger.debug("entering getDataSource %s", data_s_name)
|
|
95
|
+
if data_s_name is None:
|
|
96
|
+
if self.default_ds is not None:
|
|
97
|
+
logger.info("default source used ")
|
|
98
|
+
return self.default_ds
|
|
99
|
+
else:
|
|
100
|
+
logger.error("DataSourceName is None and not default data source name has been defined")
|
|
101
|
+
return None
|
|
102
|
+
if data_s_name not in self.ds_list.keys():
|
|
103
|
+
logger.warning(" Data source %s not found", data_s_name)
|
|
104
|
+
return None
|
|
105
|
+
else:
|
|
106
|
+
ds = self.ds_list[data_s_name]
|
|
107
|
+
if ds is None:
|
|
108
|
+
logger.debug("Invalid data source pointer for ds name %s", data_s_name)
|
|
109
|
+
return ds
|
|
110
|
+
|
|
111
|
+
def get_data(self, data_s_name, **kwargs):
|
|
112
|
+
# we can use the var prefix to get the data source while we introduce
|
|
113
|
+
logger.debug("entering getdata %s", data_s_name)
|
|
114
|
+
if data_s_name is not None and data_s_name in self.ds_list.keys():
|
|
115
|
+
if self.ds_list[data_s_name] is None:
|
|
116
|
+
dobj = DataObj()
|
|
117
|
+
|
|
118
|
+
dobj.set_empty("Invalid data source pointer for ds name " + data_s_name)
|
|
119
|
+
logger.debug("Invalid data source pointer for ds name %s", data_s_name)
|
|
120
|
+
return dobj
|
|
121
|
+
else:
|
|
122
|
+
|
|
123
|
+
dobj = self.ds_list[data_s_name].get_data(**kwargs)
|
|
124
|
+
return dobj
|
|
125
|
+
else:
|
|
126
|
+
if data_s_name not in self.ds_list.keys():
|
|
127
|
+
logger.warning(" Invalid data source found %s ", data_s_name)
|
|
128
|
+
dobj = DataObj()
|
|
129
|
+
dobj.set_empty(f"Invalid data source name {data_s_name}")
|
|
130
|
+
|
|
131
|
+
return dobj
|
|
132
|
+
if self.default_ds is not None:
|
|
133
|
+
logger.info(" default source used ")
|
|
134
|
+
return self.default_ds.get_data(**kwargs)
|
|
135
|
+
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def start_subscription(self, data_s_name, **kwargs):
|
|
139
|
+
if data_s_name is not None and data_s_name in self.ds_list.keys():
|
|
140
|
+
self.ds_list[data_s_name].start_subscription(**kwargs)
|
|
141
|
+
|
|
142
|
+
def stop_subscription(self, data_s_name):
|
|
143
|
+
if data_s_name is not None and data_s_name in self.ds_list.keys():
|
|
144
|
+
logger.debug("stopSubscription A ")
|
|
145
|
+
self.ds_list[data_s_name].stop_subscription()
|
|
146
|
+
|
|
147
|
+
def get_next_data(self, data_s_name, vname):
|
|
148
|
+
if data_s_name is not None and data_s_name in self.ds_list.keys():
|
|
149
|
+
return self.ds_list[data_s_name].get_next_data(vname)
|
|
150
|
+
else:
|
|
151
|
+
dobj = DataObj()
|
|
152
|
+
dobj.set_empty(f"Invalid data source name {data_s_name}")
|
|
153
|
+
return dobj
|
|
154
|
+
|
|
155
|
+
def get_envelope(self, data_s_name, **kwargs):
|
|
156
|
+
if data_s_name is not None and data_s_name in self.ds_list.keys():
|
|
157
|
+
if self.ds_list[data_s_name] is None:
|
|
158
|
+
denv = DataEnvelope()
|
|
159
|
+
denv.set_empty(f"Invalid data source pointer for ds name {data_s_name}")
|
|
160
|
+
|
|
161
|
+
return denv
|
|
162
|
+
else:
|
|
163
|
+
return self.ds_list[data_s_name].get_envelope(**kwargs)
|
|
164
|
+
else:
|
|
165
|
+
if data_s_name not in self.ds_list.keys():
|
|
166
|
+
logger.warning(f"Invalid data source found {data_s_name}")
|
|
167
|
+
denv = DataEnvelope()
|
|
168
|
+
denv.set_empty(f"Invalid data source name {data_s_name}")
|
|
169
|
+
|
|
170
|
+
return denv
|
|
171
|
+
if self.default_ds is not None:
|
|
172
|
+
logger.info("default source used ")
|
|
173
|
+
return self.default_ds.get_envelope(**kwargs)
|
|
174
|
+
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
def get_connected_data_source_names(self) -> List[str]:
|
|
178
|
+
data_sources = [self.get_default_ds_name()]
|
|
179
|
+
for ds_name, ds in self.ds_list.items():
|
|
180
|
+
if ds_name not in data_sources and ds.is_connected():
|
|
181
|
+
data_sources.append(ds_name)
|
|
182
|
+
return data_sources
|
|
183
|
+
|
|
184
|
+
def get_connected_data_sources(self) -> List[DataSource]:
|
|
185
|
+
data_sources = []
|
|
186
|
+
for ds_name, ds in self.ds_list.items():
|
|
187
|
+
if ds.is_connected():
|
|
188
|
+
data_sources.append(ds)
|
|
189
|
+
return data_sources
|
|
190
|
+
|
|
191
|
+
# Clear cache of all the dataSources
|
|
192
|
+
def clear_cache(self):
|
|
193
|
+
for ds in self.ds_list.values():
|
|
194
|
+
if ds.is_connected():
|
|
195
|
+
ds.clear_cache()
|