PyELSSA 0.1.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.
PyELSSA/__init__.py ADDED
@@ -0,0 +1,28 @@
1
+ #
2
+ # -*- coding: utf-8 -*-
3
+ #=================================================================
4
+ # Created by: Jieming Ye
5
+ # Created on: Sep 2026
6
+ # Last Modified: XX/XX/2026
7
+ #=================================================================
8
+ # Copyright (c) 2026 [Jieming Ye]
9
+ #
10
+ # This Python source code is licensed under the
11
+ # Open Source Non-Commercial License (OSNCL) v1.0
12
+ # See LICENSE for details.
13
+ #=================================================================
14
+ """
15
+ Python on ELSSA: PyELSSA Package
16
+
17
+ """
18
+ #=================================================================
19
+ # VERSION CONTROL
20
+ # V1.0.0 (Jieming Ye) - Initial Version
21
+ #=================================================================
22
+ # Set Information Variable
23
+ __author__ = "Jieming Ye"
24
+ __copyright__ = "Copyright 2026, Engineering Services"
25
+ __credits__ = "Jieming Ye"
26
+ __version__ = "0.1.0"
27
+ __email__ = "jieming.ye@networkrail.co.uk"
28
+ __status__ = "Beta Version"
@@ -0,0 +1,325 @@
1
+ #
2
+ # -*- coding: utf-8 -*-
3
+ #=================================================================
4
+ # Created by: Jieming Ye
5
+ # Created on: July 2026
6
+ # Last Modified: July 2026
7
+ #=================================================================
8
+ # Copyright (c) 2026 [Jieming Ye]
9
+ #
10
+ # This Python source code is licensed under the
11
+ # Open Source Non-Commercial License (OSNCL) v1.0
12
+ # See LICENSE for details.
13
+ #=================================================================
14
+ """
15
+ This scripts contains the functions that control the configuration file settings
16
+
17
+ """
18
+ #=================================================================
19
+ # VERSION CONTROL
20
+ # V1.0 (Jieming Ye) - Initial Version
21
+ #
22
+ #=================================================================
23
+ # Set Information Variable
24
+ # N/A
25
+ #=================================================================
26
+
27
+ import os
28
+ import json
29
+ import importlib
30
+ import requests
31
+ import shutil
32
+
33
+ from PyELSSA.shared_contents import SharedVariables, SharedMethods, DataFilenames, InternetValidation
34
+ from PyELSSA.shared_msazure_api import AzureWorkflow, AzureCloudID
35
+
36
+ CONFIG_FILE = SharedVariables.config_file
37
+
38
+ # initial database system check
39
+ def initial_missing_user_database_check():
40
+ '''
41
+ This function doing application database entering checking and returing:
42
+ FALSE: if the initial database check passed i.e. no missing files.
43
+ Filenamelist: if the initial database check failed.
44
+ '''
45
+ filenamelist = []
46
+ # check if the database path exist.
47
+ if not os.path.exists(SharedVariables.database_path_user):
48
+ for filename in DataFilenames.private_file.values():
49
+ filenamelist.append(filename)
50
+ for filename in DataFilenames.public_file.values():
51
+ filenamelist.append(filename)
52
+
53
+ # loop the database folder and add the missing files into the filename list
54
+ else:
55
+ for filename in DataFilenames.private_file.values():
56
+ file_in_package = os.path.join(SharedVariables.database_path_user, filename)
57
+ if not os.path.isfile(file_in_package):
58
+ filenamelist.append(filename)
59
+ for filename in DataFilenames.public_file.values():
60
+ file_in_package = os.path.join(SharedVariables.database_path_user, filename)
61
+ if not os.path.isfile(file_in_package):
62
+ filenamelist.append(filename)
63
+
64
+ # if not empty filename list return filenamelist otherwise False
65
+ if filenamelist:
66
+ return filenamelist
67
+ else:
68
+ return False
69
+
70
+ # update the customised database library
71
+ @staticmethod
72
+ def update_database_library(filenamelist: list=None, library: int = 0):
73
+ '''
74
+ This function updates the customised database library from GitHub Repo online (if public)
75
+ or data folder (if private).
76
+ This will overwrite the existing database file blindly.
77
+
78
+ if no input is not provided, this will restore all default database library
79
+
80
+ library: (int)
81
+ default to 0: default library
82
+ 1: user library
83
+ '''
84
+ if library == 0:
85
+ controlled_data_path = SharedVariables.database_path_default
86
+ else:
87
+ controlled_data_path = SharedVariables.database_path_user
88
+
89
+ print(f"Config Path: '{controlled_data_path}'")
90
+
91
+ os.makedirs(controlled_data_path, exist_ok=True)
92
+
93
+ private_file_list = []
94
+ public_file_list = []
95
+
96
+ if filenamelist:
97
+ for filename in filenamelist:
98
+ # check if the filename belongs public or private dataset, and update accordingly
99
+ if filename in DataFilenames.private_file.values():
100
+ private_file_list.append(filename)
101
+ elif filename in DataFilenames.public_file.values():
102
+ public_file_list.append(filename)
103
+ else:
104
+ SharedMethods.print_message(f"ERROR: File '{filename}' is not recognised as a valid database file. Please contact support using this error message...", "31")
105
+ return False
106
+ else: # default to updating all database files
107
+ for file in DataFilenames.private_file.values():
108
+ private_file_list.append(file)
109
+ for file in DataFilenames.public_file.values():
110
+ public_file_list.append(file)
111
+
112
+ # copy the private data from the packaged data folder
113
+ distribution = importlib.resources.files(SharedVariables.package_name)
114
+ package_path = os.path.join(str(distribution), SharedVariables.private_data_name)
115
+
116
+ # Get the absolute path of the file in the package location
117
+ for file in private_file_list:
118
+ file_in_package = os.path.join(package_path, file)
119
+ target_file = os.path.join(controlled_data_path, file)
120
+ try:
121
+ shutil.copy(file_in_package, target_file)
122
+ SharedMethods.print_message(f"ATTENTION: '{file}' database updated to default dataset.","33")
123
+ except Exception as e:
124
+ SharedMethods.print_message(f"ERROR: Error copying file {file} to {target_file}: {e}. Please contact support using this error message...", "31")
125
+ return False
126
+
127
+ # check online resources and download the public data to the same folder
128
+ # Check internet connection
129
+ if not InternetValidation.internet_check(SharedVariables.public_data_path):
130
+ SharedMethods.print_message(f"WARNING: Online databse updated cannot proceed due to not allowed network. Skipping...","33")
131
+ return False
132
+
133
+ for file in public_file_list:
134
+ file_url = SharedVariables.raw_data_url_begin + file
135
+ # download the csv file to the target folder
136
+ target_file = os.path.join(controlled_data_path, file)
137
+ try:
138
+ response = requests.get(file_url)
139
+ response.raise_for_status() # Raise an error for bad responses
140
+ with open(target_file, 'wb') as f:
141
+ f.write(response.content)
142
+ SharedMethods.print_message(f"ATTENTION: '{file}' database updated to default dataset.","33")
143
+ except Exception as e:
144
+ SharedMethods.print_message(f"ERROR: Error downloading file {file} from {file_url}: {e}. Please contact support using this error message...", "31")
145
+ return False
146
+
147
+ return True
148
+
149
+ # reset configuraiton file varile to default
150
+ def reset_configuration_file_varible(key: str = "DEFAULT_ALL"):
151
+ """
152
+ This function reset configuration varible to default
153
+ return True of False
154
+ """
155
+ try:
156
+ if not os.path.exists(SharedVariables.config_file):
157
+ SharedMethods.print_message(f"ERROR: Configuration file not found. ","31")
158
+ return False
159
+
160
+ config_data = DataFilenames.default_config
161
+
162
+ if key == "DEFAULT_ALL":
163
+ with open(SharedVariables.config_file, "w") as file:
164
+ json.dump(config_data, file, indent=4)
165
+ return True
166
+ else:
167
+ return set_configuration_file_variable(key,config_data[key])
168
+
169
+ except Exception as e:
170
+ SharedMethods.print_message(f"ERROR: Error resetting configuration: {e}","31")
171
+ return False
172
+
173
+
174
+ # set configuration file
175
+ def set_configuration_file_variable(key:str, value):
176
+ """
177
+ This function handle the configuration file value update
178
+ return True or False
179
+ """
180
+ try:
181
+ if not os.path.exists(CONFIG_FILE):
182
+ SharedMethods.print_message(f"ERROR: Configuration file not found. ","31")
183
+ return False
184
+
185
+ with open(CONFIG_FILE, "r") as file:
186
+ config_data = json.load(file)
187
+
188
+ if config_data.get(key) != value:
189
+ config_data[key] = value
190
+ with open(CONFIG_FILE, "w") as file:
191
+ json.dump(config_data, file, indent=4)
192
+ return True
193
+
194
+ except Exception as e:
195
+ SharedMethods.print_message(f"ERROR: Error setting configuration: {e}","31")
196
+ return False
197
+
198
+ # read configuration file varible
199
+ def read_configuration_file_variable(key:str, update_required: bool = False):
200
+ """
201
+ This function read the configuration file and return value
202
+ return Value or False / None
203
+ """
204
+ try:
205
+ if not os.path.exists(CONFIG_FILE):
206
+ SharedMethods.print_message(f"ERROR: Configuration file not found. ","31")
207
+ return False
208
+
209
+ config_data = {}
210
+ with open(CONFIG_FILE, "r") as file:
211
+ config_data = json.load(file)
212
+
213
+ if key not in config_data:
214
+ SharedMethods.print_message(f"WARNING: Configuration does not contain setting of {key}. ","33")
215
+ if update_required:
216
+ SharedMethods.print_message(f"WARNING: {key} will be set to default value. ","33")
217
+ return False
218
+
219
+ return config_data[key]
220
+
221
+ except Exception as e:
222
+ SharedMethods.print_message(f"ERROR: Error reading configuration: {e}","31")
223
+ return False
224
+
225
+ def version_updated():
226
+ try:
227
+ if os.path.exists(SharedVariables.config_file):
228
+ with open(SharedVariables.config_file, 'r') as file:
229
+ config_data = json.load(file)
230
+ if not config_data["package_version_number"] == SharedVariables.installed_version:
231
+ return True
232
+ return False
233
+ except Exception as e:
234
+ SharedMethods.print_message(f"ERROR: Error reading configuration: {e}","31")
235
+ return False
236
+
237
+ def update_elssa_core():
238
+ try:
239
+ # ensure the installation folder location exist
240
+ if os.path.exists(SharedVariables.package_path):
241
+ # validate if configuration path folder exist if not, create it
242
+ os.makedirs(SharedVariables.elssa_core_path, exist_ok=True)
243
+ # download the latest
244
+ if not AzureWorkflow.download_tdns_sharepoint_folder(AzureCloudID.DOC_LIBRARY,AzureCloudID.ELSSA_CORE_PATH,SharedVariables.elssa_core_path,True):
245
+ return False
246
+ return True
247
+
248
+ else:
249
+ SharedMethods.print_message(f"ERROR: {SharedVariables.package_name} has not been installed yet...","31")
250
+ return False
251
+ except Exception as e:
252
+ SharedMethods.print_message(f"ERROR: Error updating the PyELSSA core: {e}","31")
253
+ SharedMethods.print_message(f"ERROR: Please check the message above. Try again with proper internet or contact support...","31")
254
+ return False
255
+
256
+ # processing of checking configuraiton file and set up approprite action
257
+ def configuration_main():
258
+ # disable in debug mode (bypass the process in debug mode):
259
+ if SharedMethods.is_debug_mode():
260
+ return True
261
+
262
+ # validate if configuration path folder exist if not, create it
263
+ os.makedirs(SharedVariables.configuration_path, exist_ok=True)
264
+
265
+ # update default database if configuration file does not exist (1st run only)
266
+ if not os.path.exists(CONFIG_FILE):
267
+ # create empty configuration file.
268
+ try:
269
+ with open(CONFIG_FILE, "w") as file:
270
+ json.dump({}, file)
271
+ except Exception as e:
272
+ SharedMethods.print_message(f"ERROR: Error creating configuration: {e}","31")
273
+ return False
274
+
275
+ # version update steps
276
+ try:
277
+ if version_updated():
278
+ # after the version udpate, elssa core is forced to be update, set to false
279
+ set_configuration_file_variable(key = 'elssa_core_updated',value = False)
280
+
281
+ # download the ELSSA core from the SharePoint
282
+ if not read_configuration_file_variable(key = 'elssa_core_updated'):
283
+ # trying the update the default library if success or not.
284
+ if not update_elssa_core():
285
+ # update not successful, force exit until update completed to ensure consistent version
286
+ return False
287
+ else:
288
+ # write the status to json configuraiton file
289
+ set_configuration_file_variable(key = 'elssa_core_updated',value = True)
290
+ SharedMethods.print_message(f"INFO: ELSSA Core Package Updating Succesfully. Consider relauch the application for proper reloading.","32")
291
+
292
+ if not read_configuration_file_variable(key = 'default_database_updated', update_required = True):
293
+ # trying the update the default library if success or not.
294
+ if not update_database_library():
295
+ # update not successful
296
+ database_library = False
297
+ else:
298
+ # update succesfully
299
+ database_library = True
300
+ # write the status to json configuraiton file
301
+ set_configuration_file_variable(key = 'default_database_updated',value = database_library)
302
+
303
+ set_configuration_file_variable(key = 'package_version_number', value = SharedVariables.installed_version)
304
+ return True
305
+
306
+ except Exception as e:
307
+ SharedMethods.print_message(f"ERROR: Error reading / writing configuration: {e}","31")
308
+ return False
309
+
310
+
311
+ def main():
312
+ if not configuration_main():
313
+ SharedMethods.print_message(f"ERROR: Configuration environment is not set properly. Close and try it again...","31")
314
+ SharedMethods.print_message(f"ERROR: Contact support {SharedVariables.contacts} if the issue persist...","31")
315
+ return False
316
+ else:
317
+ # database folder check: This is on customised user database only
318
+ filenamelist = initial_missing_user_database_check()
319
+ if filenamelist:
320
+ update_database_library(filenamelist=filenamelist,library=1)
321
+ return True
322
+
323
+ # Programme running
324
+ if __name__ == '__main__':
325
+ main()
File without changes
File without changes
File without changes
@@ -0,0 +1,221 @@
1
+ # -*- coding: utf-8 -*-
2
+ #=================================================================
3
+ # Created by: Jieming Ye
4
+ # Created on: Aug 2026
5
+ # Last Modified: Aug 2026
6
+ #=================================================================
7
+ # Copyright (c) 2026 [Jieming Ye]
8
+ #
9
+ # This Python source code is licensed under the
10
+ # Open Source Non-Commercial License (OSNCL) v1.0
11
+ # See LICENSE for details.
12
+ #=================================================================
13
+ """
14
+ This the base frame for this GUI
15
+ """
16
+ #=================================================================
17
+ # VERSION CONTROL
18
+ # V1.0 (Jieming Ye) - Initial Version
19
+ #
20
+ #
21
+ #=================================================================
22
+
23
+ import os
24
+ import tkinter as tk
25
+ from tkinter import filedialog, ttk
26
+ from threading import Thread
27
+ from multiprocessing import Process
28
+ from PyELSSA.shared_contents import SharedMethods, SharedVariables
29
+
30
+
31
+ class BasePage(tk.Frame):
32
+ def __init__(self, parent, controller):
33
+ super().__init__(parent)
34
+ self.controller = controller
35
+ self.style = ttk.Style()
36
+ self._configure_styles()
37
+
38
+ self.headframe = self.create_frame(padding=(16, 16))
39
+ self.contentframe = self.create_frame(padding=(12, 8))
40
+ self.footerframe = self.create_frame(padding=(10, 6))
41
+
42
+ def _configure_styles(self):
43
+ self.style.configure('PageTitle.TLabel', font=('Helvetica', 16, 'bold'))
44
+ self.style.configure('PageSubtitle.TLabel', font=('Helvetica', 12, 'bold'))
45
+ self.style.configure('PageText.TLabel', font=('Helvetica', 10), foreground='#333333')
46
+ self.style.configure('Card.TLabelframe', borderwidth=1, relief='solid')
47
+ self.style.configure('Card.TLabelframe.Label', font=('Helvetica', 12, 'bold'))
48
+ self.style.configure('Action.TButton', font=('Helvetica', 10, 'bold'), padding=8)
49
+ self.style.configure('Secondary.TButton', font=('Helvetica', 10), padding=6)
50
+ self.style.configure('OptionText.TLabel', font=('Helvetica', 10), wraplength=660, foreground='#222222')
51
+ self.style.configure('Info.TLabel', font=('Helvetica', 9), foreground='#555555', wraplength=660)
52
+ self.style.configure('SmallText.TLabel', font=('Helvetica', 8), foreground='#444444', wraplength=660)
53
+
54
+ def create_frame(self, padding=(0, 0), row_weights=None, column_weights=None, **kwargs):
55
+ pack_options = {}
56
+ for option in ('fill', 'expand', 'side', 'anchor', 'padx', 'pady'):
57
+ if option in kwargs:
58
+ pack_options[option] = kwargs.pop(option)
59
+
60
+ frame = ttk.Frame(self, padding=padding, **kwargs)
61
+ if not pack_options:
62
+ pack_options = {'fill': tk.BOTH, 'expand': True}
63
+ frame.pack(**pack_options)
64
+
65
+ if row_weights is not None:
66
+ for index, weight in enumerate(row_weights):
67
+ frame.rowconfigure(index, weight=weight)
68
+ if column_weights is not None:
69
+ for index, weight in enumerate(column_weights):
70
+ frame.columnconfigure(index, weight=weight)
71
+
72
+ return frame
73
+
74
+ def create_section(self, parent, title=None, padding=(8, 6), pack=True, pack_options=None):
75
+ if title:
76
+ section = ttk.Labelframe(parent, text=title, style='Card.TLabelframe', padding=padding)
77
+ else:
78
+ section = ttk.Frame(parent, padding=padding)
79
+ if pack:
80
+ options = {'fill': tk.BOTH, 'expand': True, 'pady': (0, 8)}
81
+ if pack_options:
82
+ options.update(pack_options)
83
+ section.pack(**options)
84
+ return section
85
+
86
+ def create_scrollable_section(self, parent, title=None, height=280, padding=(8, 8)):
87
+ section = self.create_section(parent, title=title, padding=padding)
88
+ bg_color = '#f4f6f9'
89
+ try:
90
+ bg_color = section.cget('background')
91
+ except tk.TclError:
92
+ pass
93
+
94
+ canvas = tk.Canvas(section, borderwidth=0, highlightthickness=0, background=bg_color)
95
+ scrollbar = ttk.Scrollbar(section, orient='vertical', command=canvas.yview)
96
+ content = ttk.Frame(canvas)
97
+
98
+ canvas.configure(yscrollcommand=scrollbar.set)
99
+ scrollbar.pack(side='right', fill='y')
100
+ canvas.pack(side='left', fill='both', expand=True)
101
+ window_id = canvas.create_window((0, 0), window=content, anchor='nw')
102
+
103
+ def on_configure(event):
104
+ canvas.configure(scrollregion=canvas.bbox('all'))
105
+
106
+ def on_canvas_resize(event):
107
+ canvas.itemconfigure(window_id, width=event.width)
108
+
109
+ content.bind('<Configure>', on_configure)
110
+ canvas.bind('<Configure>', on_canvas_resize)
111
+
112
+ def on_mousewheel(event):
113
+ delta = -1 if event.delta > 0 else 1
114
+ canvas.yview_scroll(delta, 'units')
115
+
116
+ content.bind('<Enter>', lambda _: content.bind_all('<MouseWheel>', on_mousewheel))
117
+ content.bind('<Leave>', lambda _: content.unbind_all('<MouseWheel>'))
118
+ section.configure(height=height)
119
+ return content
120
+
121
+ def create_label(self, parent, text, style='PageText.TLabel', **kwargs):
122
+ label = ttk.Label(parent, text=text, style=style, **kwargs)
123
+ return label
124
+
125
+ def create_button(self, parent, text, command, style='Action.TButton', **kwargs):
126
+ button = ttk.Button(parent, text=text, command=command, style=style, **kwargs)
127
+ return button
128
+
129
+ def create_entry(self, parent, variable, width=32, **kwargs):
130
+ entry = ttk.Entry(parent, textvariable=variable, width=width, **kwargs)
131
+ return entry
132
+
133
+ def create_radio(self, parent, text, variable, value, **kwargs):
134
+ radio = ttk.Radiobutton(parent, text=text, variable=variable, value=value, **kwargs)
135
+ return radio
136
+
137
+ def auto_excel_select(self, variable):
138
+ initial_dir = os.getcwd()
139
+ file_path = filedialog.askopenfilename(title='Select File...', initialdir=initial_dir)
140
+ if not file_path:
141
+ return
142
+
143
+ variable.set(file_path)
144
+ selected_dir = os.path.dirname(file_path)
145
+ selected_dir = os.path.normpath(selected_dir)
146
+ if selected_dir != os.getcwd():
147
+ os.chdir(selected_dir)
148
+ SharedVariables.current_path = selected_dir
149
+ self.controller.working_directory.set(selected_dir)
150
+ SharedMethods.print_message(f'ATTENTION: Working directory set to: {selected_dir}','33')
151
+
152
+ def auto_file_select(self, variable):
153
+ initial_dir = os.getcwd()
154
+ file_path = filedialog.askopenfilename(title='Select File...', initialdir=initial_dir)
155
+ if not file_path:
156
+ return
157
+
158
+ variable.set(file_path)
159
+ selected_dir = os.path.dirname(file_path)
160
+ selected_dir = os.path.normpath(selected_dir)
161
+ if selected_dir != os.getcwd():
162
+ os.chdir(selected_dir)
163
+ SharedVariables.current_path = selected_dir
164
+ self.controller.working_directory.set(selected_dir)
165
+ SharedMethods.print_message(f'ATTENTION: Working directory set to: {selected_dir}','33')
166
+
167
+ def run_new_thread_or_process(self, target_script, **inputs):
168
+ try:
169
+ args = [
170
+ target_script,
171
+ None,
172
+ None,
173
+ None,
174
+ None,
175
+ None,
176
+ None,
177
+ None,
178
+ None,
179
+ None,
180
+ None,
181
+ SharedVariables.current_path,
182
+ ]
183
+
184
+ input_mapping = {
185
+ 'text_input1': 1,
186
+ 'text_input2': 2,
187
+ 'option_input1': 3,
188
+ 'option_input2': 4,
189
+ 'time_input1': 5,
190
+ 'time_input2': 6,
191
+ 'add_text': 7,
192
+ 'add_input1': 8,
193
+ 'add_input2': 9,
194
+ 'config_input': 10,
195
+ }
196
+
197
+ independent_process = False
198
+ for key, value in inputs.items():
199
+ if key in input_mapping:
200
+ index = input_mapping[key]
201
+ if isinstance(value, tk.Variable):
202
+ args[index] = value.get()
203
+ else:
204
+ args[index] = value
205
+ elif key == 'independent_process':
206
+ independent_process = bool(value)
207
+ else:
208
+ SharedMethods.print_message(f'WARNING: Unrecognized input key "{key}" provided. Skipping...','33')
209
+
210
+ if independent_process:
211
+ process = Process(target=SharedMethods.launch_new_thread_or_process, args=tuple(args))
212
+ process.start()
213
+ else:
214
+ thread = Thread(target=SharedMethods.launch_new_thread_or_process, args=tuple(args))
215
+ thread.start()
216
+
217
+ except Exception as e:
218
+ SharedMethods.print_message(
219
+ f'ERROR: Error in threading/processing...{e}. Contact Support / Do not carry out multiple tasking at the same time.',
220
+ '31'
221
+ )