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.
@@ -0,0 +1,469 @@
1
+ #=================================================================
2
+ # Created by: Jieming Ye
3
+ # Created on: June 2026
4
+ # Last Modified: June 2026
5
+ #=================================================================
6
+ # Copyright (c) 2026 [Jieming Ye]
7
+ #
8
+ # This Python source code is licensed under the
9
+ # Open Source Non-Commercial License (OSNCL) v1.0
10
+ # See LICENSE for details.
11
+ #=================================================================
12
+ """
13
+ Microsoft Graph API related functions
14
+
15
+ """
16
+ #=================================================================
17
+ # VERSION CONTROL
18
+ # V1.0 (Jieming Ye) - Initial Version
19
+ #=================================================================
20
+ # Set Information Variable
21
+ # N/A
22
+ #=================================================================
23
+
24
+ import os
25
+ import shutil
26
+ import subprocess
27
+ import msal
28
+ import requests
29
+ import urllib.parse
30
+ import tempfile
31
+
32
+ from PyELSSA.shared_contents import SharedVariables, SharedMethods
33
+
34
+ class AzureCloudID:
35
+ TENANT_ID = "c22cc3e1-5d7f-4f4d-be03-d5a158cc9409"
36
+ CLIENT_ID = "cd44fee0-76ce-422f-af29-386036f0eac5"
37
+ GRAPH_BASE = "https://graph.microsoft.com/v1.0"
38
+ AUTHORITY = f"https://login.microsoftonline.com/{TENANT_ID}"
39
+
40
+ # Delegated scopes for interactive/public-client login
41
+ SCOPES_RW = ["https://graph.microsoft.com/Sites.ReadWrite.All"]
42
+ # App-only scopes for confidential client login
43
+ SCOPES_APP = ["https://graph.microsoft.com/.default"]
44
+
45
+ PUBLIC_APP = None
46
+ CONFIDENTIAL_APP = None
47
+ ACCESS_TOKEN_CACHE = None
48
+
49
+ SECRET_KEY = "GraphAPISecret.key"
50
+
51
+ # ---- SharePoint details ----
52
+ SHAREPOINT_HOST = "networkrail.sharepoint.com" # Network Rail Host Base
53
+ SITE_PATH = "/sites/NRDDTDNS" # e.g. /sites/Engineering
54
+
55
+ DOC_LIBRARY = "Documents"
56
+ MANUAL_PATH = "05 - Traction Power Modelling/04 - Vision Oslo/04 - VISION OSLO Extension Tool/VISION-OSLO Extension User Guide A09.pdf"
57
+ BHTPBANK_PATH = "05 - Traction Power Modelling/02 - Asset Data/07 - Rolling Stock/01 - Master BHTPBANK Library"
58
+ ELSSA_CORE_PATH = "00 - E and P Systems Team/zz_DO_NOT_CHANGE - CONFIDENTIAL_ACCESS/PyELSSA_DO_NOT_CHANGE/Latest_Core"
59
+
60
+ APP = None # internal ms api holding within the python process
61
+
62
+ class AzureWorkflow:
63
+ # open document file from sharepoint
64
+ @staticmethod
65
+ def open_file_from_sharepoint(filename):
66
+ # create a temp directory and download the file there
67
+ temp_dir = tempfile.gettempdir()
68
+ temp_file = os.path.join(temp_dir, filename)
69
+
70
+ online_library = AzureCloudID.DOC_LIBRARY
71
+ online_file = AzureCloudID.MANUAL_PATH
72
+
73
+ try:
74
+ AzureWorkflow.download_tdns_sharepoint_file(online_library,online_file,temp_file)
75
+ except Exception as e:
76
+ SharedMethods.print_message(f"ERROR: Error acuqiring the manual: {e}","31")
77
+ SharedMethods.print_message(f"ERROR: Ensure all errors are cleared in sequence...","31")
78
+ return
79
+
80
+ try:
81
+ subprocess.Popen(['start', '', temp_file], shell=True,close_fds=True)
82
+ except Exception as e:
83
+ SharedMethods.print_message(f"ERROR: Error opening file with default app: {e}","31")
84
+ return
85
+ return
86
+
87
+ @staticmethod
88
+ def _read_secret():
89
+ secret_file = os.path.join(SharedVariables.configuration_path, AzureCloudID.SECRET_KEY)
90
+ if not os.path.isfile(secret_file):
91
+ SharedMethods.print_message(f"ATTENTION: ESDD advanced user could request secret token via email to 'traction.power@networkrail.co.uk'.", "33")
92
+ return None
93
+ try:
94
+ with open(secret_file, 'r') as file:
95
+ secret = file.read().strip()
96
+ return secret if secret else None
97
+ except Exception as e:
98
+ SharedMethods.print_message(f"WARNING: Failed to read secret file. {e}", "33")
99
+ return None
100
+
101
+ @staticmethod
102
+ def _get_confidential_app():
103
+ secret = AzureWorkflow._read_secret()
104
+ if not secret:
105
+ return None
106
+ try:
107
+ if AzureCloudID.CONFIDENTIAL_APP is None:
108
+ AzureCloudID.CONFIDENTIAL_APP = msal.ConfidentialClientApplication(
109
+ AzureCloudID.CLIENT_ID,
110
+ authority=AzureCloudID.AUTHORITY,
111
+ client_credential=secret
112
+ )
113
+ return AzureCloudID.CONFIDENTIAL_APP
114
+ except Exception as e:
115
+ SharedMethods.print_message(f"WARNING: Failed to initialise confidential client. {e}", "33")
116
+ return None
117
+
118
+ @staticmethod
119
+ def _acquire_confidential_token():
120
+ app = AzureWorkflow._get_confidential_app()
121
+ if app is None:
122
+ return None
123
+ try:
124
+ result = app.acquire_token_for_client(scopes=AzureCloudID.SCOPES_APP)
125
+ except Exception as e:
126
+ SharedMethods.print_message(f"WARNING: Confidential login failed. {e}", "33")
127
+ return None
128
+
129
+ if result and "access_token" in result:
130
+ SharedMethods.print_message("INFO: Access token acquired using confidential client.", "32")
131
+ return result["access_token"]
132
+
133
+ SharedMethods.print_message(f"WARNING: Confidential token error: {result.get('error')} | {result.get('error_description')}.","33")
134
+ SharedMethods.print_message(f"\nWARNING: Please report screenshot back via 'Help - Email to Support' action...\n","33")
135
+ return None
136
+
137
+ @staticmethod
138
+ def _get_public_app():
139
+ if AzureCloudID.PUBLIC_APP is None:
140
+ AzureCloudID.PUBLIC_APP = msal.PublicClientApplication(
141
+ AzureCloudID.CLIENT_ID,
142
+ authority=AzureCloudID.AUTHORITY
143
+ )
144
+ return AzureCloudID.PUBLIC_APP
145
+
146
+ @staticmethod
147
+ def _acquire_public_token():
148
+ app = AzureWorkflow._get_public_app()
149
+
150
+ # Try silent token first
151
+ try:
152
+ accounts = app.get_accounts()
153
+ if accounts:
154
+ result = app.acquire_token_silent(
155
+ AzureCloudID.SCOPES_RW,
156
+ account=accounts[0]
157
+ )
158
+ if result and "access_token" in result:
159
+ SharedMethods.print_message("INFO: Access token acquired silently using public client.", "32")
160
+ return result["access_token"]
161
+ except Exception as e:
162
+ SharedMethods.print_message(f"WARNING: Silent token acquisition failed. {e}", "33")
163
+
164
+ # Interactive fallback
165
+ try:
166
+ SharedMethods.print_message(f"ATTENTION: Please sign in via system brower in two minutes. (Application awaiting for two minutes)...", "33")
167
+ result = app.acquire_token_interactive(
168
+ scopes=AzureCloudID.SCOPES_RW,
169
+ timeout=120
170
+ )
171
+ except Exception as e:
172
+ SharedMethods.print_message(f"ERROR: Interactive login failed. {e}", "31")
173
+ return None
174
+
175
+ if result and "access_token" in result:
176
+ SharedMethods.print_message("INFO: Access token acquired interactively using public client.", "32")
177
+ return result["access_token"]
178
+
179
+ SharedMethods.print_message(f"ERROR: Public token error: {result.get('error')} | {result.get('error_description')}","31")
180
+ return None
181
+
182
+ @staticmethod
183
+ def get_access_token():
184
+ # 1) Try confidential client first
185
+ token = AzureWorkflow._acquire_confidential_token()
186
+ if token:
187
+ AzureCloudID.ACCESS_TOKEN_CACHE = token
188
+ return token
189
+
190
+ # 2) Fall back to public client flow
191
+ token = AzureWorkflow._acquire_public_token()
192
+ if token:
193
+ AzureCloudID.ACCESS_TOKEN_CACHE = token
194
+ return token
195
+
196
+ return False
197
+
198
+ def graph_get(url, token, **kwargs):
199
+ headers = kwargs.pop("headers", {})
200
+ headers["Authorization"] = f"Bearer {token}"
201
+ response = requests.get(url, headers=headers, **kwargs)
202
+ response.raise_for_status()
203
+ return response
204
+
205
+ def download_tdns_sharepoint_file(online_library,online_file,output_file):
206
+ token = AzureWorkflow.get_access_token()
207
+ if not token:
208
+ SharedMethods.print_message(f"ERROR: Authorisation Fail...","31")
209
+ return False
210
+ # 1) Resolve site ID
211
+ # GET /sites/{hostname}:/{server-relative-path}
212
+ site_url = f"{AzureCloudID.GRAPH_BASE}/sites/{AzureCloudID.SHAREPOINT_HOST}:{AzureCloudID.SITE_PATH}"
213
+ site_resp = AzureWorkflow.graph_get(site_url, token)
214
+ site = site_resp.json()
215
+ site_id = site["id"]
216
+ print(f"Site: {AzureCloudID.SITE_PATH} -- ID: {site_id}")
217
+
218
+ # 2) Get all drives (document libraries) in the site
219
+ # GET /sites/{site-id}/drives
220
+ drives_url = f"{AzureCloudID.GRAPH_BASE}/sites/{site_id}/drives"
221
+ drives_resp = AzureWorkflow.graph_get(drives_url, token)
222
+ drives = drives_resp.json()["value"]
223
+
224
+ drive = next((d for d in drives if d["name"] == online_library), None)
225
+ if not drive:
226
+ SharedMethods.print_message(f"Library '{online_library}' not found. Available: {[d['name'] for d in drives]}","31")
227
+ drive_id = drive["id"]
228
+ print(f"Drive: {online_library} -- ID: {drive_id}")
229
+
230
+ # 3) Resolve the file by path relative to the library root
231
+ # GET /drives/{drive-id}/root:/{item-path}
232
+ encoded_path = urllib.parse.quote(online_file)
233
+ item_url = f"{AzureCloudID.GRAPH_BASE}/drives/{drive_id}/root:/{encoded_path}"
234
+ item_resp = AzureWorkflow.graph_get(item_url, token)
235
+ item = item_resp.json()
236
+ item_id = item["id"]
237
+ print(f"Item: {online_file} -- ID: {item_id}")
238
+
239
+ # 4) Download file content
240
+ # GET /drives/{drive-id}/items/{item-id}/content
241
+ download_url = f"{AzureCloudID.GRAPH_BASE}/drives/{drive_id}/items/{item_id}/content"
242
+ print("Downloading in process...")
243
+ with requests.get(
244
+ download_url,
245
+ headers={"Authorization": f"Bearer {token}"},
246
+ stream=True,
247
+ allow_redirects=True
248
+ ) as r:
249
+ r.raise_for_status()
250
+ with open(output_file, "wb") as f:
251
+ for chunk in r.iter_content(chunk_size=8192):
252
+ if chunk:
253
+ f.write(chunk)
254
+
255
+ print(f"Downloaded to: {output_file}")
256
+
257
+ return True
258
+
259
+
260
+ def download_tdns_sharepoint_folder(online_library, online_folder, local_folder, delete_extra_local=False):
261
+ """
262
+ Download all files from a SharePoint folder (and subfolders) into a local folder.
263
+
264
+ Parameters
265
+ ----------
266
+ online_library : str
267
+ SharePoint document library name, e.g. "Documents"
268
+ online_folder : str
269
+ Folder path relative to the library root, e.g. "FolderA/SubFolderB"
270
+ Use "" or "/" for the library root.
271
+ local_folder : str
272
+ Local destination folder.
273
+ delete_extra_local : bool
274
+ If True, remove local files/folders that do not exist in SharePoint,
275
+ making the local folder a mirror of the online folder.
276
+
277
+ Returns
278
+ -------
279
+ bool
280
+ True if successful, False otherwise.
281
+ """
282
+
283
+ token = AzureWorkflow.get_access_token()
284
+ if not token:
285
+ SharedMethods.print_message("ERROR: Authorisation Fail...", "31")
286
+ return False
287
+
288
+ session = requests.Session()
289
+ session.headers.update({"Authorization": f"Bearer {token}"})
290
+
291
+ try:
292
+ # 1) Resolve site ID
293
+ # GET /sites/{hostname}:/{server-relative-path}
294
+ site_url = f"{AzureCloudID.GRAPH_BASE}/sites/{AzureCloudID.SHAREPOINT_HOST}:{AzureCloudID.SITE_PATH}"
295
+ site_resp = AzureWorkflow.graph_get(site_url, token)
296
+ site_resp.raise_for_status()
297
+ site = site_resp.json()
298
+ site_id = site["id"]
299
+ print(f"Site {AzureCloudID.SITE_PATH} -- ID: {site_id}")
300
+
301
+ # 2) Get all drives (document libraries) in the site
302
+ # GET /sites/{site-id}/drives
303
+ drives_url = f"{AzureCloudID.GRAPH_BASE}/sites/{site_id}/drives"
304
+ drives_resp = AzureWorkflow.graph_get(drives_url, token)
305
+ drives_resp.raise_for_status()
306
+ drives = drives_resp.json().get("value", [])
307
+
308
+ drive = next((d for d in drives if d["name"] == online_library), None)
309
+ if not drive:
310
+ SharedMethods.print_message(
311
+ f"Library '{online_library}' not found. Available: {[d['name'] for d in drives]}",
312
+ "31"
313
+ )
314
+ return False
315
+
316
+ drive_id = drive["id"]
317
+ print(f"Drive {online_library} -- ID: {drive_id}")
318
+
319
+ # Normalise online folder
320
+ online_folder = (online_folder or "").strip("/").strip()
321
+
322
+ # 3) Resolve folder item ID
323
+ if online_folder == "":
324
+ # Library root
325
+ folder_item_id = "root"
326
+ print(f"Using library root folder")
327
+ else:
328
+ # IMPORTANT: preserve "/" so the path stays hierarchical
329
+ encoded_folder = urllib.parse.quote(online_folder, safe="/")
330
+ folder_url = f"{AzureCloudID.GRAPH_BASE}/drives/{drive_id}/root:/{encoded_folder}"
331
+ folder_resp = AzureWorkflow.graph_get(folder_url, token)
332
+ folder_resp.raise_for_status()
333
+ folder_item = folder_resp.json()
334
+
335
+ if "folder" not in folder_item:
336
+ SharedMethods.print_message(f"Path '{online_folder}' exists but is not a folder.","31")
337
+ return False
338
+
339
+ folder_item_id = folder_item["id"]
340
+ print(f"Folder {online_folder} -- ID: {folder_item_id}")
341
+
342
+ # Ensure local root exists
343
+ os.makedirs(local_folder, exist_ok=True)
344
+
345
+ # Keep track of downloaded relative paths so we can optionally delete extra local files
346
+ remote_relative_paths = set()
347
+
348
+ def list_children(drive_id, item_id):
349
+ """
350
+ Get all children for a folder, including pagination.
351
+ """
352
+ if item_id == "root":
353
+ url = f"{AzureCloudID.GRAPH_BASE}/drives/{drive_id}/root/children"
354
+ else:
355
+ url = f"{AzureCloudID.GRAPH_BASE}/drives/{drive_id}/items/{item_id}/children"
356
+
357
+ items = []
358
+ while url:
359
+ resp = session.get(url)
360
+ resp.raise_for_status()
361
+ data = resp.json()
362
+ items.extend(data.get("value", []))
363
+ url = data.get("@odata.nextLink")
364
+
365
+ return items
366
+
367
+ def download_file_by_item_id(drive_id, item_id, local_path):
368
+ """
369
+ Download one file and overwrite if it already exists.
370
+ """
371
+ download_url = f"{AzureCloudID.GRAPH_BASE}/drives/{drive_id}/items/{item_id}/content"
372
+ print(f"Downloading: {local_path}")
373
+
374
+ os.makedirs(os.path.dirname(local_path), exist_ok=True)
375
+
376
+ with session.get(download_url, stream=True, allow_redirects=True) as r:
377
+ r.raise_for_status()
378
+ with open(local_path, "wb") as f:
379
+ for chunk in r.iter_content(chunk_size=8192):
380
+ if chunk:
381
+ f.write(chunk)
382
+
383
+ def walk_and_download(current_item_id, current_local_folder, current_relative_folder=""):
384
+ """
385
+ Recursively walk SharePoint folder and download all files/subfolders.
386
+ """
387
+ children = list_children(drive_id, current_item_id)
388
+
389
+ for item in children:
390
+ item_name = item["name"]
391
+ item_id = item["id"]
392
+
393
+ # Build relative path for tracking/mirroring
394
+ if current_relative_folder:
395
+ relative_path = f"{current_relative_folder}/{item_name}"
396
+ else:
397
+ relative_path = item_name
398
+
399
+ relative_path_norm = relative_path.replace("\\", "/")
400
+
401
+ if "folder" in item:
402
+ # Subfolder
403
+ next_local_folder = os.path.join(current_local_folder, item_name)
404
+ os.makedirs(next_local_folder, exist_ok=True)
405
+
406
+ remote_relative_paths.add(relative_path_norm + "/")
407
+ walk_and_download(item_id, next_local_folder, relative_path_norm)
408
+
409
+ elif "file" in item:
410
+ # File
411
+ local_file_path = os.path.join(current_local_folder, item_name)
412
+ download_file_by_item_id(drive_id, item_id, local_file_path)
413
+ remote_relative_paths.add(relative_path_norm)
414
+
415
+ else:
416
+ print(f"Skipping unknown item type: {relative_path_norm}")
417
+
418
+ print("Downloading folder contents...")
419
+ walk_and_download(folder_item_id, local_folder)
420
+
421
+ if delete_extra_local:
422
+ print("Deleting extra local files/folders not found online...")
423
+
424
+ # Walk local tree bottom-up so folders can be removed after files
425
+ for root, dirs, files in os.walk(local_folder, topdown=False):
426
+ for file_name in files:
427
+ full_path = os.path.join(root, file_name)
428
+ rel_path = os.path.relpath(full_path, local_folder).replace("\\", "/")
429
+ if rel_path not in remote_relative_paths:
430
+ print(f"Removing local extra file: {full_path}")
431
+ os.remove(full_path)
432
+
433
+ for dir_name in dirs:
434
+ full_dir = os.path.join(root, dir_name)
435
+ rel_dir = os.path.relpath(full_dir, local_folder).replace("\\", "/") + "/"
436
+ if rel_dir not in remote_relative_paths:
437
+ print(f"Removing local extra folder: {full_dir}")
438
+ shutil.rmtree(full_dir, ignore_errors=True)
439
+
440
+ print(f"Download complete. Local folder: {local_folder}")
441
+ return True
442
+
443
+ except requests.HTTPError as e:
444
+ SharedMethods.print_message(f"ERROR: HTTP ERROR: {str(e)}", "31")
445
+ try:
446
+ resp_text = e.response.text
447
+ print(resp_text)
448
+ except Exception:
449
+ pass
450
+ return False
451
+
452
+ except Exception as e:
453
+ SharedMethods.print_message(f"ERROR: {str(e)}", "31")
454
+ return False
455
+
456
+
457
+ if __name__ == "__main__":
458
+ print("TBC")
459
+ LIBRARY_NAME = "Documents" # e.g. Documents or Shared Documents
460
+ FILE_PATH_IN_LIBRARY = "05 - Traction Power Modelling/04 - Vision Oslo/04 - VISION OSLO Extension Tool/VISION-OSLO Extension User Guide A09.pdf"
461
+ LOCAL_OUTPUT = "Manual.pdf"
462
+
463
+ AzureWorkflow.download_tdns_sharepoint_file(LIBRARY_NAME,FILE_PATH_IN_LIBRARY,LOCAL_OUTPUT)
464
+
465
+ # AzureWorkflow.download_tdns_sharepoint_folder(
466
+ # online_library="Documents",
467
+ # online_folder="05 - Traction Power Modelling/02 - Asset Data/07 - Rolling Stock/01 - Master BHTPBANK Library",
468
+ # local_folder=r"C:\Users\JYe1\Downloads\tractiontest"
469
+ # )
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: PyELSSA
3
+ Version: 0.1.0
4
+ Summary: This is 'ELSSA in Python' package owned by Network Rail Infrastructure Limited.
5
+ License: Open Source Non-Commercial License (OSNCL) v2.0
6
+ License-File: LICENSE
7
+ Author: Jieming Ye
8
+ Author-email: Jieming.Ye@networkrail.co.uk
9
+ Requires-Python: >=3.10
10
+ Classifier: License :: Other/Proprietary License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Requires-Dist: matplotlib
19
+ Requires-Dist: numpy
20
+ Requires-Dist: pandas
21
+ Requires-Dist: psutil
22
+ Requires-Dist: scipy
23
+ Requires-Dist: tk
24
+ Project-URL: Homepage, https://pypi.org/project/PyELSSA/
25
+ Description-Content-Type: text/markdown
26
+
27
+ # ELSSA_python
28
+ ELSSA (Electrification System Simulation Analysis) was written in Matlab. The last supporting Matlab version is 2014a which is out of official support for a fairly long time.
29
+ Some of the source file is p coded (encrypted) which makes the support extremly difficult.
30
+
31
+ This repository looks to mordenlize the software suite to a new era, giving the technology involvement in the last decade.
32
+
33
+ The code will be rewritten from scratch in Python aiming for better user interface and program maintenance.
34
+
35
+ ## ELSSA Python Migration Plan
36
+ - Phase 1: Reverse engineering the p coded section.
37
+ - Phase 2: Replicate the ELSSA Matlab functionality.
38
+ - Phase 3: Enhance the tool package and user interface.
39
+
40
+
41
+ ## ELLSA Matlab
42
+ Please refer to: https://github.com/NR-ESTractionPower/ELSSA_matlab
43
+
44
+
@@ -0,0 +1,18 @@
1
+ PyELSSA/__init__.py,sha256=7_2h1M-IR8oVQlfeJ9byqKqDcmtCYDWQzZRUoJ-QWmY,956
2
+ PyELSSA/configuration.py,sha256=cvtpvz-0lerODKOFjkH-y8VjmG5xJWH0z42taq7eeJ8,13726
3
+ PyELSSA/data/TestFile.txt,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ PyELSSA/data/TestFile1.csv,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ PyELSSA/data/TestFile2.csv,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ PyELSSA/gui/gui_base_frame.py,sha256=RMhsrJ2S6ZDmc88DdKknX0-0fOJA_3ve6CDFfGpvvds,9185
7
+ PyELSSA/gui/gui_main_page.py,sha256=aitWGd1BdNpErlFvN335vADOxHSi7gHNCmduFkkTRRs,7510
8
+ PyELSSA/gui/gui_start.py,sha256=UiJ1e3OQHkuzVZiyvujm9vlDu5kyYlO6dOdjJtx0DBk,20734
9
+ PyELSSA/gui/gui_sub_frame.py,sha256=VRC_cVnIAM1-HofmD4Fdo65jultjzpl_1ETfGjfzDtM,22579
10
+ PyELSSA/licensing.py,sha256=6s-VnUqg7pQZ1QMcKxpVySrkZrXP8RWuZL1Sd1e_WXM,9088
11
+ PyELSSA/master.py,sha256=4Xlq1JmMtHeDdbmyDKuTG7pf8Z76RdBBaZhOawk6ZVo,2756
12
+ PyELSSA/release_update.py,sha256=iEk-L7LMwoYD7_Ia-vOrY9G_w-B7RpuuYB5Ya-Frek8,2162
13
+ PyELSSA/shared_contents.py,sha256=GDlQuypmtdP7SaVhfus-xZpfTLPmmIOuxxKVT-sJ9Rw,21630
14
+ PyELSSA/shared_msazure_api.py,sha256=34V2ka68ASvO93BTdQH_Zpi57P3k124b6-BPavagUKI,20025
15
+ pyelssa-0.1.0.dist-info/licenses/LICENSE,sha256=WRXXPQ74IB1kCBAbJBgciruEL10LYdL7z29TUatP1Lc,4580
16
+ pyelssa-0.1.0.dist-info/METADATA,sha256=1DY7Qmwd2hUew8K1RxCH4AAHVYF_2BPyECzCzJ3y11Q,1728
17
+ pyelssa-0.1.0.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
18
+ pyelssa-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.3.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,88 @@
1
+ Copyright (c) 2026 [Jieming Ye, Engineering Services Design]
2
+
3
+ Open Source Non-Commercial License (OSNCL) v2.0
4
+
5
+ Preamble
6
+ The purpose of this license is to ensure that the software remains free for non-commercial use, while also ensuring
7
+ that any modifications to the software are made publicly available. Commercial use of this software is not allowed
8
+ without prior written permission from the original author(s).
9
+
10
+ The precise terms and conditions for copying, distribution, and modification follow.
11
+
12
+ TERMS AND CONDITIONS
13
+ Section 0: Definitions
14
+
15
+ “This License” refers to version 2.0 of the Open Source Non-Commercial License.
16
+ “The Software” refers to the Python package and all associated files, including but not limited to source code,
17
+ documentation, and configuration files, distributed under this License.
18
+ “Modification” means any work derived from or based on the Software, such as bug fixes, enhancements, or other
19
+ alterations to the original work.
20
+ "E&P Team" refers to the E&P team under Engineering Services, Route Services, Network Rail Infrastructure Ltd,
21
+ structured at September 2024.
22
+ “You” refers to the individual or entity using, modifying, or distributing the Software.
23
+
24
+ “Public Distribution” refers to the act of making any modifications of the Software available to the general
25
+ public.
26
+
27
+ Section 1: Source Code and Modifications
28
+
29
+ 1.1 You may modify the Software and distribute your modified version, provided that:
30
+ You make the source code of the modified version available to the public under the same terms as this License.
31
+ The modified version must be clearly marked to indicate that changes have been made, including the date of changes.
32
+
33
+ 1.2 All modifications must be publicly available, in a manner that makes them easily accessible, such as posting
34
+ on a public repository (e.g., GitHub, GitLab).
35
+
36
+ Section 2: Non-Commercial Use
37
+
38
+ 2.1 You may use, distribute, and modify the Software for non-commercial purposes only, including:
39
+ Personal projects.
40
+ Educational purposes.
41
+ Research purposes.
42
+
43
+ 2.2 Commercial use of the Software is prohibited unless you have received explicit, prior written permission
44
+ from the original author(s).
45
+
46
+ 2.3 If you wish to make commercial use of the Software, including any modifications, you must contact the original
47
+ author(s) to seek permission and negotiate terms for such use.
48
+
49
+ Section 3: Conveying Modified Source Code
50
+
51
+ 3.1 If you modify the Software, you must:
52
+ Ensure that the modifications are licensed under this License.
53
+ Make the modified source code publicly available.
54
+ Provide information in any modified version about the changes made, including a notice stating the date of the
55
+ modification and who made the changes.
56
+
57
+ Section 4: Conveying Non-Source Forms
58
+
59
+ 4.1 You may convey non-source forms of the Software, such as compiled binaries, provided that:
60
+ You also provide access to the corresponding source code.
61
+ The source code is made available under the same terms as this License.
62
+
63
+ Section 5: Commercial Use Exception
64
+
65
+ 5.1 Commercial use of the Software or any modified versions is strictly prohibited unless permission is granted
66
+ in writing by the original author(s). For commercial inquiries, please contact [Traction.Power@networkrail.co.uk].
67
+
68
+ 5.2 Commercial use of the Software or any modified versions is allowed by [E&P Team] without any restriction.
69
+
70
+ Section 6: No Warranty
71
+
72
+ 6.1 There is no warranty for the Software, to the extent permitted by applicable law. The Software is provided
73
+ “as is” without warranty of any kind, either expressed or implied, including but not limited to the implied
74
+ warranties of merchantability and fitness for a particular purpose. The entire risk as to the quality and
75
+ performance of the Software is with you.
76
+
77
+ 6.2 In no event, unless required by law or agreed to in writing, shall the author(s) be liable for any damages,
78
+ including any general, special, incidental, or consequential damages arising from the use or inability to use the
79
+ Software.
80
+
81
+ Section 7: Termination
82
+
83
+ 7.1 You may not propagate or modify the Software except as expressly provided under this License. Any attempt
84
+ otherwise to propagate or modify the Software is void and will automatically terminate your rights under this
85
+ License.
86
+
87
+ 7.2 However, parties who have received copies, or rights, from you under this License will not have their
88
+ licenses terminated so long as they remain in full compliance.