PyDPEET 0.2.0__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.
- pydpeet/__init__.py +56 -0
- pydpeet/citations/__init__.py +7 -0
- pydpeet/citations/citeme.py +397 -0
- pydpeet/citations/html_writer.py +137 -0
- pydpeet/dev_utils/__init__.py +7 -0
- pydpeet/dev_utils/generate_inits/__init__.py +7 -0
- pydpeet/dev_utils/generate_inits/generate_inits.py +327 -0
- pydpeet/io/__init__.py +7 -0
- pydpeet/io/configs/__init__.py +7 -0
- pydpeet/io/configs/config.py +227 -0
- pydpeet/io/configs/const.py +1 -0
- pydpeet/io/convert.py +594 -0
- pydpeet/io/device/__init__.py +7 -0
- pydpeet/io/device/arbin_4_23_PV090331/__init__.py +7 -0
- pydpeet/io/device/arbin_4_23_PV090331/formatter.py +22 -0
- pydpeet/io/device/arbin_4_23_PV090331/mapper.py +18 -0
- pydpeet/io/device/arbin_4_23_PV090331/reader.py +68 -0
- pydpeet/io/device/arbin_8_00_PV221201/__init__.py +7 -0
- pydpeet/io/device/arbin_8_00_PV221201/formatter.py +26 -0
- pydpeet/io/device/arbin_8_00_PV221201/mapper.py +18 -0
- pydpeet/io/device/arbin_8_00_PV221201/reader.py +66 -0
- pydpeet/io/device/basytec_6_3_1_0/__init__.py +7 -0
- pydpeet/io/device/basytec_6_3_1_0/formatter.py +18 -0
- pydpeet/io/device/basytec_6_3_1_0/mapper.py +18 -0
- pydpeet/io/device/basytec_6_3_1_0/reader.py +54 -0
- pydpeet/io/device/digatron_4_20_6_236/__init__.py +7 -0
- pydpeet/io/device/digatron_4_20_6_236/formatter.py +27 -0
- pydpeet/io/device/digatron_4_20_6_236/mapper.py +18 -0
- pydpeet/io/device/digatron_4_20_6_236/reader.py +45 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/__init__.py +7 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/formatter.py +39 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/mapper.py +18 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/reader.py +42 -0
- pydpeet/io/device/neware_8_0_0_516/__init__.py +7 -0
- pydpeet/io/device/neware_8_0_0_516/formatter.py +31 -0
- pydpeet/io/device/neware_8_0_0_516/mapper.py +18 -0
- pydpeet/io/device/neware_8_0_0_516/reader.py +397 -0
- pydpeet/io/device/parstat_2_63_3/__init__.py +7 -0
- pydpeet/io/device/parstat_2_63_3/formatter.py +34 -0
- pydpeet/io/device/parstat_2_63_3/mapper.py +18 -0
- pydpeet/io/device/parstat_2_63_3/reader.py +34 -0
- pydpeet/io/device/safion_1_9/__init__.py +7 -0
- pydpeet/io/device/safion_1_9/formatter.py +25 -0
- pydpeet/io/device/safion_1_9/mapper.py +18 -0
- pydpeet/io/device/safion_1_9/reader.py +89 -0
- pydpeet/io/device/zahner/__init__.py +7 -0
- pydpeet/io/device/zahner/formatter.py +73 -0
- pydpeet/io/device/zahner/mapper.py +34 -0
- pydpeet/io/device/zahner/reader.py +46 -0
- pydpeet/io/device/zahner_new/__init__.py +7 -0
- pydpeet/io/device/zahner_new/formatter.py +72 -0
- pydpeet/io/device/zahner_new/mapper.py +50 -0
- pydpeet/io/device/zahner_new/reader.py +46 -0
- pydpeet/io/map.py +69 -0
- pydpeet/io/read.py +50 -0
- pydpeet/io/utils/__init__.py +7 -0
- pydpeet/io/utils/ext_path.py +34 -0
- pydpeet/io/utils/formatter_utils.py +429 -0
- pydpeet/io/utils/load_custom_module.py +33 -0
- pydpeet/io/utils/timing.py +36 -0
- pydpeet/io/write.py +99 -0
- pydpeet/process/__init__.py +7 -0
- pydpeet/process/analyze/__init__.py +7 -0
- pydpeet/process/analyze/average.py +297 -0
- pydpeet/process/analyze/capacity.py +208 -0
- pydpeet/process/analyze/configs/__init__.py +7 -0
- pydpeet/process/analyze/configs/battery_config.py +38 -0
- pydpeet/process/analyze/configs/ocv_config.py +177 -0
- pydpeet/process/analyze/configs/step_analyzer_config.py +178 -0
- pydpeet/process/analyze/cycle.py +63 -0
- pydpeet/process/analyze/efficiency.py +182 -0
- pydpeet/process/analyze/energy.py +51 -0
- pydpeet/process/analyze/extract/__init__.py +7 -0
- pydpeet/process/analyze/extract/dva_ica.py +224 -0
- pydpeet/process/analyze/extract/ocv.py +196 -0
- pydpeet/process/analyze/power.py +32 -0
- pydpeet/process/analyze/resistance.py +87 -0
- pydpeet/process/analyze/soc.py +418 -0
- pydpeet/process/analyze/soh.py +72 -0
- pydpeet/process/analyze/utils.py +122 -0
- pydpeet/process/merge/__init__.py +7 -0
- pydpeet/process/merge/series.py +274 -0
- pydpeet/process/sequence/__init__.py +7 -0
- pydpeet/process/sequence/configs/__init__.py +7 -0
- pydpeet/process/sequence/configs/config.py +17 -0
- pydpeet/process/sequence/step_analyzer.py +440 -0
- pydpeet/process/sequence/utils/__init__.py +7 -0
- pydpeet/process/sequence/utils/annotate/__init__.py +7 -0
- pydpeet/process/sequence/utils/annotate/annotate_primitives.py +419 -0
- pydpeet/process/sequence/utils/configs/CONFIG_Fallback.py +231 -0
- pydpeet/process/sequence/utils/configs/CONFIG_preprocessing.py +243 -0
- pydpeet/process/sequence/utils/configs/__init__.py +7 -0
- pydpeet/process/sequence/utils/console_prints/__init__.py +7 -0
- pydpeet/process/sequence/utils/console_prints/log_time.py +30 -0
- pydpeet/process/sequence/utils/postprocessing/__init__.py +7 -0
- pydpeet/process/sequence/utils/postprocessing/df_primitives_correction.py +166 -0
- pydpeet/process/sequence/utils/postprocessing/filter_df.py +168 -0
- pydpeet/process/sequence/utils/postprocessing/generate_instructions.py +297 -0
- pydpeet/process/sequence/utils/preprocessing/__init__.py +7 -0
- pydpeet/process/sequence/utils/preprocessing/calculate_thresholds.py +27 -0
- pydpeet/process/sequence/utils/processing/__init__.py +7 -0
- pydpeet/process/sequence/utils/processing/analyze_segments.py +368 -0
- pydpeet/process/sequence/utils/processing/attempt_to_merge_neighboring_segments.py +122 -0
- pydpeet/process/sequence/utils/processing/check_CV_results.py +226 -0
- pydpeet/process/sequence/utils/processing/check_power_zero_watt_segments.py +85 -0
- pydpeet/process/sequence/utils/processing/check_zero_length.py +117 -0
- pydpeet/process/sequence/utils/processing/split_in_segments.py +97 -0
- pydpeet/process/sequence/utils/processing/supress_smaller_segments.py +99 -0
- pydpeet/process/sequence/utils/processing/widen_constant_segments.py +185 -0
- pydpeet/process/sequence/utils/visualize/__init__.py +7 -0
- pydpeet/process/sequence/utils/visualize/visualize_data.py +288 -0
- pydpeet/res/__init__.py +7 -0
- pydpeet/settings.py +0 -0
- pydpeet/utils/__init__.py +7 -0
- pydpeet/utils/logging_style.py +28 -0
- pydpeet/version.py +0 -0
- pydpeet-0.2.0.dist-info/METADATA +85 -0
- pydpeet-0.2.0.dist-info/RECORD +122 -0
- pydpeet-0.2.0.dist-info/WHEEL +5 -0
- pydpeet-0.2.0.dist-info/licenses/AUTHORS.md +0 -0
- pydpeet-0.2.0.dist-info/licenses/LICENCE.md +27 -0
- pydpeet-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Init generator
|
|
4
|
+
- Deletes all existing __init__.py under the package root
|
|
5
|
+
- Creates restrictive __init__.py everywhere
|
|
6
|
+
- Applies config.json to expose only the listed functions at the requested package depth
|
|
7
|
+
|
|
8
|
+
expects config.json in format:
|
|
9
|
+
{
|
|
10
|
+
"src/pydpeet/convert/convert.py": {
|
|
11
|
+
"depth": 4,
|
|
12
|
+
"exports": ["convert_file"]
|
|
13
|
+
},
|
|
14
|
+
...
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this will enable this import:
|
|
18
|
+
import pydpeet as eet
|
|
19
|
+
eet.convert.convert_file(...)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import datetime
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
CONFIG_FILENAME = "config.json"
|
|
28
|
+
LOG_FILENAME = "generation_log.txt"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_script_path():
|
|
32
|
+
return os.path.abspath(__file__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_script_dir():
|
|
36
|
+
return os.path.dirname(get_script_path())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def header_text():
|
|
40
|
+
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
41
|
+
return f'"""\nAuto-generated __init__ file.\nCreated: {timestamp}\n"""\n\n'
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Logger:
|
|
45
|
+
def __init__(self, filename):
|
|
46
|
+
self.filepath = os.path.join(get_script_dir(), filename)
|
|
47
|
+
with open(self.filepath, "w", encoding="utf-8") as f:
|
|
48
|
+
f.write(f"--- Init Generation Log: {datetime.datetime.now()} ---\n\n")
|
|
49
|
+
|
|
50
|
+
def log(self, message):
|
|
51
|
+
print(message)
|
|
52
|
+
with open(self.filepath, "a", encoding="utf-8") as f:
|
|
53
|
+
f.write(message + "\n")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def find_project_and_package_root(start_path, logger):
|
|
57
|
+
"""
|
|
58
|
+
Walk up until we find a pyproject.toml (project root).
|
|
59
|
+
If a 'src' folder exists in the project root we treat project_root/src as package_root.
|
|
60
|
+
Otherwise package_root == project_root.
|
|
61
|
+
"""
|
|
62
|
+
current = start_path
|
|
63
|
+
while True:
|
|
64
|
+
if os.path.exists(os.path.join(current, "pyproject.toml")):
|
|
65
|
+
project_root = current
|
|
66
|
+
src_path = os.path.join(project_root, "src")
|
|
67
|
+
package_root = src_path if os.path.isdir(src_path) else project_root
|
|
68
|
+
logger.log(f"Project root: {project_root}")
|
|
69
|
+
logger.log(f"Package root: {package_root}")
|
|
70
|
+
return project_root, package_root
|
|
71
|
+
parent = os.path.dirname(current)
|
|
72
|
+
if parent == current:
|
|
73
|
+
# reached filesystem root, fallback
|
|
74
|
+
logger.log("No pyproject.toml found; using script dir as project_root/package_root")
|
|
75
|
+
return start_path, start_path
|
|
76
|
+
current = parent
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def remove_all_inits(root_dir, logger):
|
|
80
|
+
"""Delete every existing __init__.py under root_dir"""
|
|
81
|
+
logger.log("--- PHASE 1: Deleting all existing __init__.py files ---")
|
|
82
|
+
removed = 0
|
|
83
|
+
for dirpath, _, filenames in os.walk(root_dir):
|
|
84
|
+
for fn in filenames:
|
|
85
|
+
if fn == "__init__.py":
|
|
86
|
+
p = os.path.join(dirpath, fn)
|
|
87
|
+
try:
|
|
88
|
+
os.remove(p)
|
|
89
|
+
removed += 1
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.log(f"Failed to remove {p}: {e}")
|
|
92
|
+
logger.log(f"Removed {removed} existing __init__.py files.")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def create_empty_inits(root_dir, logger):
|
|
96
|
+
"""
|
|
97
|
+
Create an __init__.py for every folder under root_dir with strict empty __all__.
|
|
98
|
+
This ensures no accidental exports by existing modules.
|
|
99
|
+
"""
|
|
100
|
+
logger.log("--- PHASE 2: Creating restrictive empty __init__.py across tree ---")
|
|
101
|
+
created = 0
|
|
102
|
+
for dirpath, dirnames, _ in os.walk(root_dir):
|
|
103
|
+
# skip hidden dirs and __pycache__
|
|
104
|
+
dirnames[:] = [d for d in dirnames if not d.startswith(".") and d != "__pycache__"]
|
|
105
|
+
init_path = os.path.join(dirpath, "__init__.py")
|
|
106
|
+
try:
|
|
107
|
+
with open(init_path, "w", encoding="utf-8") as f:
|
|
108
|
+
f.write(header_text())
|
|
109
|
+
f.write("# Restrictive package init: start with no public API\n")
|
|
110
|
+
f.write("__all__ = []\n")
|
|
111
|
+
created += 1
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logger.log(f"Failed to create {init_path}: {e}")
|
|
114
|
+
logger.log(f"Created {created} new restrictive __init__.py files.")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def module_dotted_from_path(abs_path, project_root, package_root):
|
|
118
|
+
"""
|
|
119
|
+
Return the full dotted module path for a .py file relative to package_root.
|
|
120
|
+
e.g. abs_path -> /.../project/src/pydpeet/convert/directory_standardization.py
|
|
121
|
+
package_root -> /.../project/src
|
|
122
|
+
returns -> 'pydpeet.convert.directory_standardization'
|
|
123
|
+
"""
|
|
124
|
+
rel = os.path.relpath(abs_path, package_root)
|
|
125
|
+
parts = rel.split(os.sep)
|
|
126
|
+
if parts[-1].endswith(".py"):
|
|
127
|
+
parts[-1] = parts[-1][:-3]
|
|
128
|
+
return ".".join(parts)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def apply_config(project_root, package_root, config_path, logger):
|
|
132
|
+
logger.log("--- PHASE 3: Applying config.json to build the public API ---")
|
|
133
|
+
with open(config_path, encoding="utf-8") as f:
|
|
134
|
+
config = json.load(f)
|
|
135
|
+
|
|
136
|
+
# mapping: dir_path -> { 'child_packages': set(child_name),
|
|
137
|
+
# 'module_imports': { full_module_dotted: set(export_names) },
|
|
138
|
+
# 'exports': set(function_and_child_names) }
|
|
139
|
+
dir_data = {}
|
|
140
|
+
|
|
141
|
+
def get_data_for_dir(dir_path):
|
|
142
|
+
if dir_path not in dir_data:
|
|
143
|
+
dir_data[dir_path] = {
|
|
144
|
+
"child_packages": set(),
|
|
145
|
+
"module_imports": {}, # module_dotted -> set(names)
|
|
146
|
+
"exports": set(),
|
|
147
|
+
}
|
|
148
|
+
return dir_data[dir_path]
|
|
149
|
+
|
|
150
|
+
for rel_path, settings in config.items():
|
|
151
|
+
exports = settings.get("exports", [])
|
|
152
|
+
depth = settings.get("depth")
|
|
153
|
+
if not isinstance(depth, int) or depth < 1:
|
|
154
|
+
logger.log(f"Invalid depth for {rel_path}: {depth} (must be int >=1); skipping")
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
source_abs_path = os.path.abspath(os.path.join(project_root, rel_path))
|
|
158
|
+
if not os.path.exists(source_abs_path):
|
|
159
|
+
logger.log(f"WARNING: file not found: {source_abs_path}; skipping")
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
# Build rel parts from project root (so depth param matches your config's notion)
|
|
163
|
+
rel_parts_from_project = os.path.relpath(source_abs_path, project_root).split(os.sep)
|
|
164
|
+
# target folder parts are first (depth - 1) entries
|
|
165
|
+
target_folder_parts = rel_parts_from_project[: max(1, depth - 1)]
|
|
166
|
+
# if package_root is project_root/src, drop that first element if it equals basename(package_root)
|
|
167
|
+
if os.path.normpath(package_root).endswith(os.path.sep + target_folder_parts[0]) or (
|
|
168
|
+
target_folder_parts[0] == os.path.basename(package_root)
|
|
169
|
+
):
|
|
170
|
+
# remove the leading package root element if present (common case with 'src')
|
|
171
|
+
pkg_parts = target_folder_parts[1:]
|
|
172
|
+
else:
|
|
173
|
+
# general case: target package parts are target_folder_parts starting at 0,
|
|
174
|
+
# but if the first entry was 'src' (rare if package_root != project_root) handle below
|
|
175
|
+
if target_folder_parts[0] == "src":
|
|
176
|
+
pkg_parts = target_folder_parts[1:]
|
|
177
|
+
else:
|
|
178
|
+
pkg_parts = target_folder_parts
|
|
179
|
+
|
|
180
|
+
if not pkg_parts:
|
|
181
|
+
# exporting at the package root
|
|
182
|
+
target_dir = package_root
|
|
183
|
+
else:
|
|
184
|
+
target_dir = os.path.join(package_root, *pkg_parts)
|
|
185
|
+
|
|
186
|
+
# Determine full module dotted name relative to package root
|
|
187
|
+
module_dotted = module_dotted_from_path(source_abs_path, project_root, package_root)
|
|
188
|
+
|
|
189
|
+
# Add the function exports to the target_dir data
|
|
190
|
+
ddata = get_data_for_dir(target_dir)
|
|
191
|
+
ddata["module_imports"].setdefault(module_dotted, set()).update(exports)
|
|
192
|
+
ddata["exports"].update(exports)
|
|
193
|
+
|
|
194
|
+
# Bubble up: mark parent dirs to import child packages (so top-level 'pydpeet' exposes 'convert', etc.)
|
|
195
|
+
# walk parents until package_root (inclusive)
|
|
196
|
+
curr = target_dir
|
|
197
|
+
# if target_dir is package_root, still possibly export module names directly at package_root
|
|
198
|
+
while True:
|
|
199
|
+
parent = os.path.dirname(curr)
|
|
200
|
+
if parent == curr:
|
|
201
|
+
break
|
|
202
|
+
if os.path.commonpath([package_root, curr]) != os.path.normpath(package_root) and not curr.startswith(
|
|
203
|
+
os.path.normpath(package_root)
|
|
204
|
+
):
|
|
205
|
+
# outside of package_root, stop
|
|
206
|
+
break
|
|
207
|
+
# compute child name (basename of curr) relative to parent
|
|
208
|
+
child_name = os.path.basename(curr)
|
|
209
|
+
if child_name == "":
|
|
210
|
+
break
|
|
211
|
+
if parent.startswith(package_root) or os.path.normpath(parent) == os.path.normpath(package_root):
|
|
212
|
+
# ensure parent has data
|
|
213
|
+
p_data = get_data_for_dir(parent)
|
|
214
|
+
# Parent should expose the child package name (so importers can reference pydpeet.convert)
|
|
215
|
+
p_data["child_packages"].add(child_name)
|
|
216
|
+
p_data["exports"].add(child_name)
|
|
217
|
+
if os.path.normpath(curr) == os.path.normpath(package_root):
|
|
218
|
+
break
|
|
219
|
+
curr = parent
|
|
220
|
+
if os.path.normpath(curr) == os.path.normpath(os.path.dirname(package_root)):
|
|
221
|
+
break
|
|
222
|
+
|
|
223
|
+
# Now write the __init__.py files for each dir in dir_data (overwrite the existing restrictive ones)
|
|
224
|
+
for dir_path, data in dir_data.items():
|
|
225
|
+
init_path = os.path.join(dir_path, "__init__.py")
|
|
226
|
+
# ensure the dir exists
|
|
227
|
+
os.makedirs(dir_path, exist_ok=True)
|
|
228
|
+
try:
|
|
229
|
+
with open(init_path, "w", encoding="utf-8") as f:
|
|
230
|
+
f.write(header_text())
|
|
231
|
+
# child package imports (so parent exposes child as attribute)
|
|
232
|
+
if data["child_packages"]:
|
|
233
|
+
f.write("# Child packages exported by this package\n")
|
|
234
|
+
for child in sorted(data["child_packages"]):
|
|
235
|
+
f.write(f"from . import {child}\n")
|
|
236
|
+
f.write("\n")
|
|
237
|
+
|
|
238
|
+
# module imports -> bind functions into this package WITHOUT leaving the submodule object as an attribute
|
|
239
|
+
# inside the loop that writes the __init__.py file for dir_path:
|
|
240
|
+
|
|
241
|
+
# --- static re-export approach ---
|
|
242
|
+
if data["module_imports"]:
|
|
243
|
+
f.write("# Re-export selected names from source modules\n\n")
|
|
244
|
+
# module_imports is mapping full_module_dotted -> set(names)
|
|
245
|
+
for mod in sorted(data["module_imports"].keys()):
|
|
246
|
+
names = sorted(data["module_imports"][mod])
|
|
247
|
+
# Write a single `from <module> import a, b, c` line per module
|
|
248
|
+
# Use explicit imports only for the configured names
|
|
249
|
+
f.write(f"from {mod} import {', '.join(names)}\n")
|
|
250
|
+
f.write("\n")
|
|
251
|
+
|
|
252
|
+
# Write __all__
|
|
253
|
+
all_entries = sorted(data["exports"])
|
|
254
|
+
f.write("# Public API for this package\n")
|
|
255
|
+
f.write("__all__ = [\n")
|
|
256
|
+
for ent in all_entries:
|
|
257
|
+
f.write(f" '{ent}',\n")
|
|
258
|
+
f.write("]\n")
|
|
259
|
+
logger.log(f"Written public API for: {os.path.relpath(dir_path, package_root)}")
|
|
260
|
+
except Exception as e:
|
|
261
|
+
logger.log(f"Failed to write {init_path}: {e}")
|
|
262
|
+
|
|
263
|
+
# TODO: Obsolete?
|
|
264
|
+
# Ensure package_root has an __init__ that exports top-level packages (e.g., 'pydpeet')
|
|
265
|
+
# root_data = get_data_for_dir(package_root)
|
|
266
|
+
|
|
267
|
+
# If package root did not get any auto exports (possible), build sensible top-level __init__
|
|
268
|
+
# but don't overwrite if a specific config already created explicit exports
|
|
269
|
+
init_root = os.path.join(package_root, "__init__.py")
|
|
270
|
+
if os.path.exists(init_root):
|
|
271
|
+
# augment: ensure __all__ includes folders with exports present under package_root
|
|
272
|
+
# find immediate child dirs with dir_data entries
|
|
273
|
+
child_packages = set()
|
|
274
|
+
for p in dir_data.keys():
|
|
275
|
+
try:
|
|
276
|
+
rel = os.path.relpath(p, package_root).split(os.sep)
|
|
277
|
+
if len(rel) >= 1 and rel[0] != ".":
|
|
278
|
+
child_packages.add(rel[0])
|
|
279
|
+
except Exception:
|
|
280
|
+
continue
|
|
281
|
+
# read the file and update __all__ if needed
|
|
282
|
+
try:
|
|
283
|
+
# TODO: Obsolete?
|
|
284
|
+
# with open(init_root, encoding="utf-8") as f:
|
|
285
|
+
# content = f.read()
|
|
286
|
+
|
|
287
|
+
# simplistic append if not present
|
|
288
|
+
if child_packages:
|
|
289
|
+
# create an augmented root __init__ that imports child packages and sets __all__
|
|
290
|
+
with open(init_root, "w", encoding="utf-8") as f:
|
|
291
|
+
f.write(header_text())
|
|
292
|
+
f.write("# Expose immediate child packages at package root\n")
|
|
293
|
+
for child in sorted(child_packages):
|
|
294
|
+
f.write(f"from . import {child}\n")
|
|
295
|
+
f.write("\n")
|
|
296
|
+
f.write("__all__ = [\n")
|
|
297
|
+
for child in sorted(child_packages):
|
|
298
|
+
f.write(f" '{child}',\n")
|
|
299
|
+
f.write("]\n")
|
|
300
|
+
logger.log("Updated package root __init__ to export immediate child packages.")
|
|
301
|
+
except Exception as e:
|
|
302
|
+
logger.log(f"Failed updating package root __init__: {e}")
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def main():
|
|
306
|
+
logger = Logger(LOG_FILENAME)
|
|
307
|
+
script_dir = get_script_dir()
|
|
308
|
+
project_root, package_root = find_project_and_package_root(script_dir, logger)
|
|
309
|
+
|
|
310
|
+
# Phase 1: delete all old inits
|
|
311
|
+
remove_all_inits(package_root, logger)
|
|
312
|
+
|
|
313
|
+
# Phase 2: create empty restrictive inits everywhere
|
|
314
|
+
create_empty_inits(package_root, logger)
|
|
315
|
+
|
|
316
|
+
# Phase 3: apply config
|
|
317
|
+
config_path = os.path.join(script_dir, CONFIG_FILENAME)
|
|
318
|
+
if not os.path.exists(config_path):
|
|
319
|
+
logger.log(f"Config file not found at {config_path}. Aborting.")
|
|
320
|
+
sys.exit(1)
|
|
321
|
+
|
|
322
|
+
apply_config(project_root, package_root, config_path, logger)
|
|
323
|
+
logger.log("\n--- Done ---\n")
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
if __name__ == "__main__":
|
|
327
|
+
main()
|
pydpeet/io/__init__.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from enum import Enum, auto
|
|
3
|
+
|
|
4
|
+
from pandas import DataFrame
|
|
5
|
+
|
|
6
|
+
# Arbin MITS Pro
|
|
7
|
+
# v4.23 (090331), schedule version 3.03
|
|
8
|
+
import pydpeet.io.device.arbin_4_23_PV090331.formatter as arbin_4_23_PV090331_formatter
|
|
9
|
+
import pydpeet.io.device.arbin_4_23_PV090331.mapper as arbin_4_23_PV090331_mapper
|
|
10
|
+
import pydpeet.io.device.arbin_4_23_PV090331.reader as arbin_4_23_PV090331_reader
|
|
11
|
+
|
|
12
|
+
# Arbin MITS Pro
|
|
13
|
+
# v8.00 (PV221201), schedule version 8.00.13
|
|
14
|
+
import pydpeet.io.device.arbin_8_00_PV221201.formatter as arbin_8_00_PV221201_formatter
|
|
15
|
+
import pydpeet.io.device.arbin_8_00_PV221201.mapper as arbin_8_00_PV221201_mapper
|
|
16
|
+
import pydpeet.io.device.arbin_8_00_PV221201.reader as arbin_8_00_PV221201_reader
|
|
17
|
+
|
|
18
|
+
# BaSyTec Battery Test Software
|
|
19
|
+
# File version 6.3.1.0, product version 6.2
|
|
20
|
+
import pydpeet.io.device.basytec_6_3_1_0.formatter as basytec_6_3_1_0_formatter
|
|
21
|
+
import pydpeet.io.device.basytec_6_3_1_0.mapper as basytec_6_3_1_0_mapper
|
|
22
|
+
import pydpeet.io.device.basytec_6_3_1_0.reader as basytec_6_3_1_0_reader
|
|
23
|
+
|
|
24
|
+
# Digatron Battery Manager and Battery Manager Workstation
|
|
25
|
+
# v4.20.6.236 (2018-09-28)
|
|
26
|
+
import pydpeet.io.device.digatron_4_20_6_236.formatter as digatron_4_20_6_236_formatter
|
|
27
|
+
import pydpeet.io.device.digatron_4_20_6_236.mapper as digatron_4_20_6_236_mapper
|
|
28
|
+
import pydpeet.io.device.digatron_4_20_6_236.reader as digatron_4_20_6_236_reader
|
|
29
|
+
|
|
30
|
+
# Digatron (EIS) Battery Manager and Battery Manager Workstation
|
|
31
|
+
# v4.20.6.236 (2018-09-28)
|
|
32
|
+
import pydpeet.io.device.digatron_eis_4_20_6_236.formatter as digatron_eis_4_20_6_236_formatter
|
|
33
|
+
import pydpeet.io.device.digatron_eis_4_20_6_236.mapper as digatron_eis_4_20_6_236_mapper
|
|
34
|
+
import pydpeet.io.device.digatron_eis_4_20_6_236.reader as digatron_eis_4_20_6_236_reader
|
|
35
|
+
|
|
36
|
+
# Neware BTS
|
|
37
|
+
# Client version 8.0.0.516 (2023-05-13 R3), server version 8.0.0.323 (2023-05-13 R3)
|
|
38
|
+
import pydpeet.io.device.neware_8_0_0_516.formatter as neware_8_0_0_516_formatter
|
|
39
|
+
import pydpeet.io.device.neware_8_0_0_516.mapper as neware_8_0_0_516_mapper
|
|
40
|
+
import pydpeet.io.device.neware_8_0_0_516.reader as neware_8_0_0_516_reader
|
|
41
|
+
|
|
42
|
+
# Parstat VersaStudio
|
|
43
|
+
# v2.63.3, firmware version 2.63.1
|
|
44
|
+
import pydpeet.io.device.parstat_2_63_3.formatter as parstat_2_63_3_formatter
|
|
45
|
+
import pydpeet.io.device.parstat_2_63_3.mapper as parstat_2_63_3_mapper
|
|
46
|
+
import pydpeet.io.device.parstat_2_63_3.reader as parstat_2_63_3_reader
|
|
47
|
+
|
|
48
|
+
# Safion Inspectrum Suite
|
|
49
|
+
# v1.9 (2023-10-18)
|
|
50
|
+
import pydpeet.io.device.safion_1_9.formatter as safion_1_9_formatter
|
|
51
|
+
import pydpeet.io.device.safion_1_9.mapper as safion_1_9_mapper
|
|
52
|
+
import pydpeet.io.device.safion_1_9.reader as safion_1_9_reader
|
|
53
|
+
|
|
54
|
+
# Zahner
|
|
55
|
+
# TODO: Add specs
|
|
56
|
+
import pydpeet.io.device.zahner.formatter as zahner_formatter
|
|
57
|
+
import pydpeet.io.device.zahner.mapper as zahner_mapper
|
|
58
|
+
import pydpeet.io.device.zahner.reader as zahner_reader
|
|
59
|
+
|
|
60
|
+
# Zahner (new)
|
|
61
|
+
# TODO: Add specs
|
|
62
|
+
import pydpeet.io.device.zahner_new.formatter as zahner_new_formatter
|
|
63
|
+
import pydpeet.io.device.zahner_new.mapper as zahner_new_mapper
|
|
64
|
+
import pydpeet.io.device.zahner_new.reader as zahner_new_reader
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class Config(Enum):
|
|
68
|
+
Zahner_1 = auto()
|
|
69
|
+
Zahner_2 = auto()
|
|
70
|
+
Zahner_new_1 = auto()
|
|
71
|
+
Zahner_new_2 = auto()
|
|
72
|
+
Zahner_new_3 = auto()
|
|
73
|
+
Safion_1_9 = auto()
|
|
74
|
+
Parstat_2_63_3 = auto()
|
|
75
|
+
Neware_8_0_0_516 = auto()
|
|
76
|
+
Digatron_4_20_6_236 = auto()
|
|
77
|
+
Digatron_EIS_4_20_6_236 = auto()
|
|
78
|
+
BaSyTec_6_3_1_0 = auto()
|
|
79
|
+
Arbin_8_00_PV221201 = auto()
|
|
80
|
+
Arbin_4_23_PV090331 = auto()
|
|
81
|
+
Custom = auto()
|
|
82
|
+
|
|
83
|
+
@classmethod
|
|
84
|
+
def from_string(cls, value: str) -> "Config":
|
|
85
|
+
if not isinstance(value, str):
|
|
86
|
+
raise TypeError("Config must be str or Config enum")
|
|
87
|
+
|
|
88
|
+
key = value.strip().lower()
|
|
89
|
+
|
|
90
|
+
aliases = {
|
|
91
|
+
"arbin_4_23_pv090331": cls.Arbin_4_23_PV090331,
|
|
92
|
+
"arbin_8_00_pv221201": cls.Arbin_8_00_PV221201,
|
|
93
|
+
"basytec_6_3_1_0": cls.BaSyTec_6_3_1_0,
|
|
94
|
+
"digatron_4_20_6_236": cls.Digatron_4_20_6_236,
|
|
95
|
+
"digatron_eis_4_20_6_236": cls.Digatron_EIS_4_20_6_236,
|
|
96
|
+
"neware_8_0_0_516": cls.Neware_8_0_0_516,
|
|
97
|
+
"parstat_2_63_3": cls.Parstat_2_63_3,
|
|
98
|
+
"safion_1_9": cls.Safion_1_9,
|
|
99
|
+
"zahner1": cls.Zahner_1,
|
|
100
|
+
"zahner2": cls.Zahner_2,
|
|
101
|
+
"zahner_new_1": cls.Zahner_new_1,
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
return aliases[key]
|
|
106
|
+
except KeyError as k:
|
|
107
|
+
known = ", ".join(aliases.keys())
|
|
108
|
+
raise ValueError(f"Unknown config '{value}'. Known: {known}") from k
|
|
109
|
+
|
|
110
|
+
@staticmethod
|
|
111
|
+
def exists(maybe_config: any) -> bool:
|
|
112
|
+
"""
|
|
113
|
+
Checks if a given configuration is a member of the Config enum.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
maybe_config (any): The configuration to check, which can be an enum member or its value.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
bool: True if maybe_config is a member of the Config enum or its value; False otherwise.
|
|
120
|
+
"""
|
|
121
|
+
try:
|
|
122
|
+
return maybe_config in Config.__members__ or maybe_config.value in {config.value for config in Config}
|
|
123
|
+
except Exception:
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
@staticmethod
|
|
127
|
+
def not_exists(value: any) -> bool:
|
|
128
|
+
"""
|
|
129
|
+
Checks if a given configuration does not exist.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
value (any): The configuration to check, which can be an enum member or its value.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
bool: True if maybe_config is not a member of the Config enum or its value; False otherwise.
|
|
136
|
+
"""
|
|
137
|
+
return not Config.exists(value)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
STANDARD_COLUMNS = [
|
|
141
|
+
"Meta_Data",
|
|
142
|
+
"Step_Count",
|
|
143
|
+
"Voltage[V]",
|
|
144
|
+
"Current[A]",
|
|
145
|
+
"Temperature[°C]",
|
|
146
|
+
"Test_Time[s]",
|
|
147
|
+
"Date_Time",
|
|
148
|
+
"EIS_f[Hz]",
|
|
149
|
+
"EIS_Z_Real[Ohm]",
|
|
150
|
+
"EIS_Z_Imag[Ohm]",
|
|
151
|
+
"EIS_DC[A]",
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
READER_CONFIGS: dict[Config, Callable[[str], DataFrame]] = {
|
|
155
|
+
# zahner readers
|
|
156
|
+
Config.Zahner_1: zahner_reader.to_dataframe,
|
|
157
|
+
Config.Zahner_2: zahner_reader.to_dataframe,
|
|
158
|
+
# zahner New readers
|
|
159
|
+
Config.Zahner_new_1: zahner_new_reader.to_dataframe,
|
|
160
|
+
Config.Zahner_new_2: zahner_new_reader.to_dataframe,
|
|
161
|
+
Config.Zahner_new_3: zahner_new_reader.to_dataframe,
|
|
162
|
+
# Other readers
|
|
163
|
+
Config.Safion_1_9: safion_1_9_reader.to_dataframe,
|
|
164
|
+
Config.Parstat_2_63_3: parstat_2_63_3_reader.to_dataframe,
|
|
165
|
+
Config.Neware_8_0_0_516: neware_8_0_0_516_reader.to_dataframe,
|
|
166
|
+
Config.Digatron_4_20_6_236: digatron_4_20_6_236_reader.to_dataframe,
|
|
167
|
+
Config.Digatron_EIS_4_20_6_236: digatron_eis_4_20_6_236_reader.to_dataframe,
|
|
168
|
+
Config.BaSyTec_6_3_1_0: basytec_6_3_1_0_reader.to_dataframe,
|
|
169
|
+
Config.Arbin_8_00_PV221201: arbin_8_00_PV221201_reader.to_dataframe,
|
|
170
|
+
Config.Arbin_4_23_PV090331: arbin_4_23_PV090331_reader.to_dataframe,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
MAPPER_CONFIGS: dict[Config, tuple[dict[str, str], list[str]]] = {
|
|
174
|
+
# zahner mappers
|
|
175
|
+
Config.Zahner_1: (zahner_mapper.COLUMN_MAP_1, zahner_mapper.MISSING_REQUIRED_COLUMNS_1),
|
|
176
|
+
Config.Zahner_2: (zahner_mapper.COLUMN_MAP_2, zahner_mapper.MISSING_REQUIRED_COLUMNS_2),
|
|
177
|
+
# zahner New mappers
|
|
178
|
+
Config.Zahner_new_1: (zahner_new_mapper.COLUMN_MAP_1, zahner_new_mapper.MISSING_REQUIRED_COLUMNS_1),
|
|
179
|
+
Config.Zahner_new_2: (zahner_new_mapper.COLUMN_MAP_2, zahner_new_mapper.MISSING_REQUIRED_COLUMNS_2),
|
|
180
|
+
Config.Zahner_new_3: (zahner_new_mapper.COLUMN_MAP_3, zahner_new_mapper.MISSING_REQUIRED_COLUMNS_3),
|
|
181
|
+
# Other mappers
|
|
182
|
+
Config.Safion_1_9: (safion_1_9_mapper.COLUMN_MAP, safion_1_9_mapper.MISSING_REQUIRED_COLUMNS),
|
|
183
|
+
Config.Parstat_2_63_3: (parstat_2_63_3_mapper.COLUMN_MAP, parstat_2_63_3_mapper.MISSING_REQUIRED_COLUMNS),
|
|
184
|
+
Config.Neware_8_0_0_516: (neware_8_0_0_516_mapper.COLUMN_MAP, neware_8_0_0_516_mapper.MISSING_REQUIRED_COLUMNS),
|
|
185
|
+
Config.Digatron_4_20_6_236: (
|
|
186
|
+
digatron_4_20_6_236_mapper.COLUMN_MAP,
|
|
187
|
+
digatron_4_20_6_236_mapper.MISSING_REQUIRED_COLUMNS,
|
|
188
|
+
),
|
|
189
|
+
Config.Digatron_EIS_4_20_6_236: (
|
|
190
|
+
digatron_eis_4_20_6_236_mapper.COLUMN_MAP,
|
|
191
|
+
digatron_eis_4_20_6_236_mapper.MISSING_REQUIRED_COLUMNS,
|
|
192
|
+
),
|
|
193
|
+
Config.BaSyTec_6_3_1_0: (basytec_6_3_1_0_mapper.COLUMN_MAP, basytec_6_3_1_0_mapper.MISSING_REQUIRED_COLUMNS),
|
|
194
|
+
Config.Arbin_8_00_PV221201: (
|
|
195
|
+
arbin_8_00_PV221201_mapper.COLUMN_MAP,
|
|
196
|
+
arbin_8_00_PV221201_mapper.MISSING_REQUIRED_COLUMNS,
|
|
197
|
+
),
|
|
198
|
+
Config.Arbin_4_23_PV090331: (
|
|
199
|
+
arbin_4_23_PV090331_mapper.COLUMN_MAP,
|
|
200
|
+
arbin_4_23_PV090331_mapper.MISSING_REQUIRED_COLUMNS,
|
|
201
|
+
),
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
FORMATTER_CONFIGS: dict[Config, Callable[[DataFrame], DataFrame]] = {
|
|
205
|
+
# zahner formatters
|
|
206
|
+
Config.Zahner_1: zahner_formatter.get_data_into_format_zahner_1,
|
|
207
|
+
Config.Zahner_2: zahner_formatter.get_data_into_format_zahner_2,
|
|
208
|
+
# zahner New formatters
|
|
209
|
+
Config.Zahner_new_1: zahner_new_formatter.get_data_into_format_zahner_1,
|
|
210
|
+
Config.Zahner_new_2: zahner_new_formatter.get_data_into_format_zahner_2,
|
|
211
|
+
Config.Zahner_new_3: zahner_new_formatter.get_data_into_format_zahner_3,
|
|
212
|
+
# Other formatters
|
|
213
|
+
Config.Safion_1_9: safion_1_9_formatter.get_data_into_format,
|
|
214
|
+
Config.Parstat_2_63_3: parstat_2_63_3_formatter.get_data_into_format,
|
|
215
|
+
Config.Neware_8_0_0_516: neware_8_0_0_516_formatter.get_data_into_format,
|
|
216
|
+
Config.Digatron_4_20_6_236: digatron_4_20_6_236_formatter.get_data_into_format,
|
|
217
|
+
Config.Digatron_EIS_4_20_6_236: digatron_eis_4_20_6_236_formatter.get_data_into_format,
|
|
218
|
+
Config.BaSyTec_6_3_1_0: basytec_6_3_1_0_formatter.get_data_into_format,
|
|
219
|
+
Config.Arbin_8_00_PV221201: arbin_8_00_PV221201_formatter.get_data_into_format,
|
|
220
|
+
Config.Arbin_4_23_PV090331: arbin_4_23_PV090331_formatter.get_data_into_format,
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class DataOutputFiletype(Enum):
|
|
225
|
+
parquet = auto()
|
|
226
|
+
csv = auto()
|
|
227
|
+
xlsx = auto()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
PANDAS_EXCEL_ENGINE = "calamine"
|