bfabric-web-apps 0.1.3__py3-none-any.whl → 0.1.5__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,414 @@
1
+ import redis
2
+ from rq import Queue
3
+ import os
4
+ import shutil
5
+ import subprocess
6
+ from pathlib import Path
7
+
8
+ from .get_logger import get_logger
9
+ from .get_power_user_wrapper import get_power_user_wrapper
10
+ from .callbacks import process_url_and_token
11
+ from bfabric_web_apps.objects import BfabricInterface
12
+ from .resource_utilities import (
13
+ create_workunit,
14
+ create_resource,
15
+ create_workunits,
16
+ create_resources
17
+ )
18
+
19
+ from .config import settings as config
20
+ from datetime import datetime as dt
21
+
22
+ GSTORE_REMOTE_PATH = config.GSTORE_REMOTE_PATH
23
+ SCRATCH_PATH = config.SCRATCH_PATH
24
+ TRX_LOGIN = config.TRX_LOGIN
25
+ TRX_SSH_KEY = config.TRX_SSH_KEY
26
+ URL = config.URL
27
+
28
+ def run_main_job(files_as_byte_strings: dict,
29
+ bash_commands: list[str],
30
+ resource_paths: dict,
31
+ attachment_paths: list[dict],
32
+ token: str):
33
+ """
34
+ Main function to handle:
35
+ 1) Save Files on Server
36
+ 2) Execute local bash commands
37
+ 3) Create workunits in B-Fabric
38
+ 4) Register resources in B-Fabric
39
+ 5) Attach additional gstore files (logs/reports/etc.) to entities in B-Fabric
40
+
41
+ :param files_as_byte_strings: {destination_path: file as byte strings}
42
+ :param bash_commands: List of bash commands to execute
43
+ :param resource_paths: dict, {resource_path: container_id}
44
+ :param attachment_paths: Dictionary mapping source file paths to their corresponding file names ({"path/test.txt": "name.txt"})
45
+ for attachment to a B-Fabric entity (e.g., logs, final reports, etc.)
46
+ :param token: Authentication token
47
+
48
+
49
+ Dev Notes:
50
+ !!! All exceptions get logged (make sure to log the exception message i.e. "except Exception as e: log(e)") !!!
51
+ !!! If an exception doesn't occur, log that some step ran successfully to the job object !!!
52
+ """
53
+
54
+ # STEP 0: Parse token, logger, etc.
55
+ token, token_data, entity_data, app_data, page_title, session_details, job_link = process_url_and_token(token)
56
+
57
+ if token is None:
58
+ raise ValueError("Error: 'token' is None")
59
+ if token_data is None:
60
+ raise ValueError("Error: 'token_data' is None")
61
+ if entity_data is None:
62
+ raise ValueError("Error: 'entity_data' is None")
63
+ if app_data is None:
64
+ raise ValueError("Error: 'app_data' is None")
65
+
66
+
67
+ L = get_logger(token_data)
68
+ print("Token Data:", token_data)
69
+ print("Entity Data:", entity_data)
70
+ print("App Data:", app_data)
71
+
72
+
73
+ # Step 1: Save files to the server
74
+ try:
75
+ summary = save_files_from_bytes(files_as_byte_strings, L)
76
+ L.log_operation("Success", f"File copy summary: {summary}", params=None, flush_logs=True)
77
+ print("Summary:", summary)
78
+ except Exception as e:
79
+ # If something unexpected blows up the entire process
80
+ L.log_operation("Error", f"Failed to copy files: {e}", params=None, flush_logs=True)
81
+ print("Error copying files:", e)
82
+
83
+
84
+ # STEP 2: Execute bash commands
85
+ try:
86
+ bash_log = execute_and_log_bash_commands(bash_commands, L)
87
+ L.log_operation("Success", f"Bash commands executed successfully:\n{bash_log}",
88
+ params=None, flush_logs=True)
89
+ except Exception as e:
90
+ L.log_operation("Error", f"Failed to execute bash commands: {e}",
91
+ params=None, flush_logs=True)
92
+ print("Error executing bash commands:", e)
93
+
94
+
95
+ # STEP 3: Create Workunits
96
+ try:
97
+ workunit_map = create_workunits_step(token_data, app_data, resource_paths, L)
98
+ except Exception as e:
99
+ L.log_operation("Error", f"Failed to create workunits in B-Fabric: {e}",
100
+ params=None, flush_logs=True)
101
+ print("Error creating workunits:", e)
102
+ workunit_map = []
103
+
104
+ # STEP 4: Register Resources (Refactored)
105
+ try:
106
+ attach_resources_to_workunits(token_data, L, workunit_map)
107
+ except Exception as e:
108
+ L.log_operation("Error", f"Failed to register resources: {e}", params=None, flush_logs=True)
109
+ print("Error registering resources:", e)
110
+
111
+ # STEP 5: Attach gstore files (logs, reports, etc.) to B-Fabric entity as a Link
112
+ try:
113
+ attach_gstore_files_to_entities_as_link(token_data, L, attachment_paths)
114
+ print("Attachment Paths:", attachment_paths)
115
+ except Exception as e:
116
+ L.log_operation("Error", f"Failed to attach extra files: {e}", params=None, flush_logs=True)
117
+ print("Error attaching extra files:", e)
118
+
119
+
120
+
121
+ #---------------------------------------------------------------------------------------------------------------------
122
+ #---------------------------------------------------------------------------------------------------------------------
123
+
124
+
125
+ # -----------------------------------------------------------------------------
126
+ # Step 1: Save Files from bytes
127
+ # -----------------------------------------------------------------------------
128
+
129
+ import os
130
+
131
+ def save_files_from_bytes(files_as_byte_strings: dict, logger):
132
+ """
133
+ Saves byte string files to their respective paths.
134
+
135
+ :param files_as_byte_strings: Dictionary where keys are destination paths and values are byte strings
136
+ :param logger: Logging instance
137
+ :return: Summary indicating how many files succeeded vs. failed
138
+ """
139
+ results = {} # Store results: (destination) -> True (if success) or error message (if failure)
140
+
141
+ # First pass: attempt to write all files
142
+ for destination, file_bytes in files_as_byte_strings.items():
143
+ try:
144
+ # Ensure the directory exists
145
+ os.makedirs(os.path.dirname(destination), exist_ok=True)
146
+
147
+ # Write file from byte string
148
+ with open(destination, "wb") as f:
149
+ f.write(file_bytes)
150
+ logger.log_operation("Files saved", "All files saved successfully.", params=None, flush_logs=True)
151
+ return "All files saved successfully."
152
+
153
+ except Exception as e:
154
+ error_msg = f"Error saving file: {destination}, Error: {str(e)}"
155
+ logger.log_operation("Error", error_msg, params=None, flush_logs=True)
156
+ print(error_msg)
157
+ raise RuntimeError(error_msg)
158
+
159
+
160
+ # -----------------------------------------------------------------------------
161
+ # Step 2: Execute Bash Commands
162
+ # -----------------------------------------------------------------------------
163
+
164
+ def execute_and_log_bash_commands(bash_commands: list[str], logger):
165
+ """
166
+ Executes a list of bash commands locally, logs and returns the output.
167
+
168
+ :param bash_commands: List of commands to execute
169
+ :param logger: Logging instance
170
+ :return: A single string containing logs for all commands
171
+ """
172
+ logstring = ""
173
+
174
+ for cmd in bash_commands:
175
+ logstring += "---------------------------------------------------------\n"
176
+ logstring += f"Executing Command: {cmd}\n"
177
+
178
+ try:
179
+ # Execute the command and capture both stdout and stderr
180
+ result = subprocess.run(cmd, shell=True, text=True, capture_output=True)
181
+ output = result.stdout.strip()
182
+ error_output = result.stderr.strip()
183
+
184
+ # Check if command executed successfully
185
+ if result.returncode == 0:
186
+ status = "SUCCESS"
187
+ log_entry = f"Command: {cmd}\nStatus: {status}\nOutput:\n{output}\n"
188
+ logger.log_operation("Info", log_entry, params=None, flush_logs=True)
189
+ else:
190
+ status = "FAILURE"
191
+ log_entry = f"Command: {cmd}\nStatus: {status}\nError Output:\n{error_output}\n"
192
+ logger.log_operation("Error", log_entry, params=None, flush_logs=True)
193
+
194
+ logstring += log_entry
195
+ print(log_entry)
196
+
197
+ except Exception as e:
198
+ logstring += f"Command: {cmd}\nStatus: ERROR\nException: {str(e)}\n"
199
+ logger.log_operation("Error", f"Command: {cmd} failed with Exception: {str(e)}",
200
+ params=None, flush_logs=True)
201
+
202
+ return logstring
203
+
204
+
205
+ # -----------------------------------------------------------------------------
206
+ # Step 3: Create Workunits in B-Fabric
207
+ # -----------------------------------------------------------------------------
208
+
209
+ def create_workunits_step(token_data, app_data, resource_paths, logger):
210
+ """
211
+ Creates multiple workunits in B-Fabric based on unique order IDs found in resource_paths.
212
+
213
+ :param token_data: dict with token/auth info
214
+ :param app_data: dict with fields like {"id": <app_id>} or other app info
215
+ :param resource_paths: Dictionary {file_path: container_id}
216
+ :param logger: a logger instance
217
+ :return: A dictionary mapping file_paths to workunit objects {file_path: workunit}
218
+ """
219
+ app_id = app_data["id"] # Extract the application ID
220
+
221
+ # Extract unique order IDs from resource_paths
222
+ container_ids = list(set(resource_paths.values()))
223
+
224
+ if not container_ids:
225
+ raise ValueError("No order IDs found in resource_paths; cannot create workunits.")
226
+
227
+ # Create all workunits in one API call
228
+ created_workunits = create_workunits(
229
+ token_data=token_data,
230
+ application_name="Test Workunit",
231
+ application_description="Workunits for batch processing",
232
+ application_id=app_id,
233
+ container_ids=container_ids
234
+ )
235
+
236
+ if not created_workunits or len(created_workunits) != len(container_ids):
237
+ raise ValueError(f"Mismatch in workunit creation: Expected {len(container_ids)} workunits, got {len(created_workunits)}.")
238
+
239
+ workunit_map = {
240
+ file_path: wu["id"]
241
+ for wu in created_workunits
242
+ for file_path, container_id in resource_paths.items()
243
+ if container_id == wu["container"]["id"]
244
+ }
245
+
246
+ logger.log_operation("Success", f"Total created Workunits: {list(workunit_map.values())}", params=None, flush_logs=True)
247
+ print(f"Total created Workunits: {list(workunit_map.values())}")
248
+
249
+ print(workunit_map)
250
+ return workunit_map # Returning {file_path: workunit}
251
+
252
+
253
+
254
+ # -----------------------------------------------------------------------------
255
+ # Step 4: Attach Resources in B-Fabric
256
+ # -----------------------------------------------------------------------------
257
+
258
+ def attach_resources_to_workunits(token_data, logger, workunit_map):
259
+ """
260
+ Attaches each file to its corresponding workunit.
261
+
262
+ Uses `create_resource` to upload files one by one.
263
+
264
+ :param token_data: B-Fabric token data
265
+ :param logger: Logger instance
266
+ :param workunit_map: Dictionary mapping file_path to workunit_id {file_path: workunit_id}
267
+ """
268
+ if not workunit_map:
269
+ logger.log_operation("Info", "No workunits found, skipping resource registration.",
270
+ params=None, flush_logs=True)
271
+ print("No workunits found, skipping resource registration.")
272
+ return
273
+
274
+ print("Workunit Map:", workunit_map)
275
+
276
+ for file_path, workunit_id in workunit_map.items():
277
+ print(f"Processing file: {file_path}, Workunit ID: {workunit_id}") # Corrected print statement
278
+ # Upload the file as a resource
279
+ resource = create_resource(token_data, workunit_id, file_path)
280
+ resource_id = resource.get("id")
281
+ print("Resource ID:", resource_id)
282
+
283
+ if resource_id:
284
+ logger.log_operation("Success", f"Resource {resource_id} attached to Workunit {workunit_id}",
285
+ params=None, flush_logs=True)
286
+ print(f"Resource {resource_id} attached to Workunit {workunit_id}")
287
+ else:
288
+ logger.log_operation("Error", f"Failed to attach resource {file_path} for Workunit {workunit_id}",
289
+ params=None, flush_logs=True)
290
+ print(f"Failed to attach resource {file_path} for Workunit {workunit_id}")
291
+
292
+
293
+
294
+ # -----------------------------------------------------------------------------
295
+ # Step 5: Attachments of gstore in B-Fabric as a Link
296
+ # -----------------------------------------------------------------------------
297
+
298
+ def attach_gstore_files_to_entities_as_link(token_data, logger, attachment_paths: dict):
299
+
300
+
301
+ """
302
+ Attaches files to a B-Fabric entity by copying them to the FGCZ storage and creating an API link.
303
+
304
+ Args:
305
+ token_data (dict): Authentication token data.
306
+ logger: Logger instance for logging operations.
307
+ attachment_paths (dict): Dictionary mapping source file paths to their corresponding file names.
308
+
309
+ Returns:
310
+ None
311
+ """
312
+
313
+ # Extract entity details from token data
314
+ entity_class = token_data.get("entityClass_data", None)
315
+ entity_id = token_data.get("entity_id_data", None)
316
+
317
+ # Check if we have access to the FGCZ server
318
+ local = local_access(GSTORE_REMOTE_PATH)
319
+
320
+ # Process each attachment
321
+ for source_path, file_name in attachment_paths.items():
322
+ if not source_path or not file_name:
323
+ logger.log_operation("Error", f"Missing required attachment details: {source_path} -> {file_name}", params=None, flush_logs=True)
324
+ print(f"Error: Missing required attachment details: {source_path} -> {file_name}")
325
+ continue
326
+
327
+ try:
328
+ # Define entity folder
329
+ entity_folder = f"{entity_class}_{entity_id}" if entity_class and entity_id else "unknown_entity"
330
+ final_remote_path = f"{GSTORE_REMOTE_PATH}/{entity_folder}/"
331
+
332
+ print("local access:", local)
333
+ print("source path:", source_path)
334
+ print("file name:", file_name)
335
+ print("final remote path:", final_remote_path)
336
+
337
+ if local: # We have direct access → Copy directly
338
+ g_req_copy(source_path, final_remote_path)
339
+
340
+ else: # We don't have direct access → Send to migration folder first
341
+ remote_tmp_path = f"{SCRATCH_PATH}/{file_name}"
342
+ scp_copy(source_path, TRX_LOGIN, TRX_SSH_KEY, remote_tmp_path)
343
+
344
+ # Move to final location
345
+ ssh_move(TRX_LOGIN, TRX_SSH_KEY, remote_tmp_path, final_remote_path)
346
+
347
+ # Log success
348
+ success_msg = f"Successfully attached '{file_name}' to {entity_class} (ID={entity_id})"
349
+ logger.log_operation("Success", success_msg, params=None, flush_logs=True)
350
+ print(success_msg)
351
+
352
+ # Step 3: Create API link
353
+ create_api_link(token_data, logger, entity_class, entity_id, file_name, entity_folder)
354
+
355
+ except Exception as e:
356
+ error_msg = f"Exception while processing '{file_name}': {e}"
357
+ logger.log_operation("Error", error_msg, params=None, flush_logs=True)
358
+ print(error_msg)
359
+
360
+ def local_access(remote_path):
361
+ """Checks if the remote gstore path (i.e. /srv/gstore/projects/) exists locally"""
362
+ result = os.path.exists(remote_path)
363
+ print("Remote Path Exists:", result)
364
+ return result
365
+
366
+
367
+ def scp_copy(source_path, ssh_user, ssh_key, remote_path):
368
+ """Copies a file to a remote location using SCP with the correct FGCZ server address."""
369
+ cmd = ["scp", "-i", ssh_key, source_path, f"{ssh_user}:{remote_path}"]
370
+ subprocess.run(cmd, check=True)
371
+ print(f"Copied {source_path} to {remote_path}")
372
+
373
+
374
+ def ssh_move(ssh_user, ssh_key, remote_tmp_path, final_remote_path):
375
+ """Moves a file on the remote server to its final location using SSH."""
376
+ cmd = ["ssh", "-i", ssh_key, ssh_user, f"/usr/local/ngseq/bin/g-req copynow -f {remote_tmp_path} {final_remote_path}"]
377
+
378
+ subprocess.run(cmd, check=True)
379
+ print(f"Moved {remote_tmp_path} to {final_remote_path}")
380
+
381
+
382
+ def g_req_copy(source_path, destination_path):
383
+ """Copies a file using g-req command when direct access is available."""
384
+ cmd = ["/usr/local/ngseq/bin/g-req", "copynow", "-f", source_path, destination_path]
385
+ subprocess.run(cmd, check=True)
386
+ print(f"Copied {source_path} using g-req")
387
+
388
+
389
+ def create_api_link(token_data, logger, entity_class, entity_id, file_name, folder_name):
390
+ """Creates an API link in B-Fabric for the attached file."""
391
+ wrapper = get_power_user_wrapper(token_data)
392
+ url = f"{URL}/{folder_name}/{file_name}"
393
+ timestamped_filename = f"{dt.now().strftime('%Y-%m-%d_%H:%M:%S')}_{file_name}"
394
+
395
+ data = {
396
+ "name": timestamped_filename,
397
+ "parentclassname": entity_class,
398
+ "parentid": entity_id,
399
+ "url": url
400
+ }
401
+
402
+ try:
403
+ link_result = wrapper.save("link", data)
404
+ if link_result:
405
+ success_msg = f"API link created for '{file_name}': {url}"
406
+ logger.log_operation("Success", success_msg, params=None, flush_logs=True)
407
+ print(success_msg)
408
+ else:
409
+ raise ValueError("API link creation failed")
410
+ except Exception as e:
411
+ error_msg = f"Failed to create API link for '{file_name}': {e}"
412
+ logger.log_operation("Error", error_msg, params=None, flush_logs=True)
413
+ print(error_msg)
414
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bfabric-web-apps
3
- Version: 0.1.3
3
+ Version: 0.1.5
4
4
  Summary: A package containing handy boilerplate utilities for developing bfabric web-applications
5
5
  Author: Marc Zuber, Griffin White, GWC GmbH
6
6
  Requires-Python: >=3.8,<4.0
@@ -9,3 +9,6 @@ Classifier: Programming Language :: Python :: 3.8
9
9
  Classifier: Programming Language :: Python :: 3.9
10
10
  Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Programming Language :: Python :: 3.11
12
+ Requires-Dist: pydantic-settings (>=2.8.1,<3.0.0)
13
+ Requires-Dist: pydantic[email] (>=2.10.6,<3.0.0)
14
+ Requires-Dist: rq (==1.15.1)
@@ -0,0 +1,20 @@
1
+ bfabric_web_apps/__init__.py,sha256=D2jTCEYedVnp6yQxNGLRsjihupDTQxNkPVFQAUYXQys,1409
2
+ bfabric_web_apps/layouts/layouts.py,sha256=z8gL4n4wwLdpLGomO9CftBLnGpc3r6OpmUc2-wBg8uo,14661
3
+ bfabric_web_apps/objects/BfabricInterface.py,sha256=wmcL9JuSC0QEopgImvkZxmtCIS7izt6bwb6y_ch0zus,10178
4
+ bfabric_web_apps/objects/Logger.py,sha256=62LC94xhm7YG5LUw3yH46NqvJQsAX7wnc9D4zbY16rA,5224
5
+ bfabric_web_apps/utils/app_init.py,sha256=RCdpCXp19cF74bouYJLPe-KSETZ0Vwqtd02Ta2VXEF8,428
6
+ bfabric_web_apps/utils/callbacks.py,sha256=m5d6IPiYX77-kJN8I2OptZN-GPxZgrI76o1DGFxjpPU,12686
7
+ bfabric_web_apps/utils/components.py,sha256=V7ECGmF2XYy5O9ciDJVH1nofJYP2a_ELQF3z3X_ADbo,844
8
+ bfabric_web_apps/utils/config.py,sha256=i93fe49Ak4Z7cm_G80m2cBCPp-5qCYLAJEtEr-mYSwQ,1044
9
+ bfabric_web_apps/utils/create_app_in_bfabric.py,sha256=eVk3cQDXxW-yo9b9n_zzGO6kLg_SLxYbIDECyvEPJXU,2752
10
+ bfabric_web_apps/utils/get_logger.py,sha256=0Y3SrXW93--eglS0_ZOc34NOriAt6buFPik5n0ltzRA,434
11
+ bfabric_web_apps/utils/get_power_user_wrapper.py,sha256=T33z64XjmJ0KSlmfEmrEP8eYpbpINCVD6Xld_V7PR2g,1027
12
+ bfabric_web_apps/utils/redis_connection.py,sha256=qXSPxW6m55Ogv44BhmPCl9ACuvzmpfZNU73UJhHRXL4,133
13
+ bfabric_web_apps/utils/redis_queue.py,sha256=MCx7z_I2NusJ4P42mcLvV7STtXBFMIIvun83fM8zOGI,168
14
+ bfabric_web_apps/utils/redis_worker_init.py,sha256=9SUc9bbgBeMbUdqJD9EkWPA4wcJjvyX6Tzanv5JfqEg,691
15
+ bfabric_web_apps/utils/resource_utilities.py,sha256=cJTak0sXAiMSQ7VwJ4ImDUCmW8tAKGObBZCSr5uARBg,5931
16
+ bfabric_web_apps/utils/run_main_pipeline.py,sha256=1YSbk3uP_T3tL6mZZXGv7a7FJc8exro_Eb49gnJjdrs,16864
17
+ bfabric_web_apps-0.1.5.dist-info/LICENSE,sha256=k0O_i2k13i9e35aO-j7FerJafAqzzu8x0kkBs0OWF3c,1065
18
+ bfabric_web_apps-0.1.5.dist-info/METADATA,sha256=Cg8oOpeNHTs2EpD_CLp5LoKmomHml-ermak1AzF14tA,608
19
+ bfabric_web_apps-0.1.5.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
20
+ bfabric_web_apps-0.1.5.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- # defaults.py
2
- CONFIG_FILE_PATH = "~/.bfabricpy.yml"
3
-
4
- # Default values for application settings
5
- HOST = "0.0.0.0"
6
- PORT = 8050
7
- DEV = False
8
-
9
- # Developer and bug report email addresses
10
- DEVELOPER_EMAIL_ADDRESS = "griffin@gwcustom.com"
11
- BUG_REPORT_EMAIL_ADDRESS = "gwtools@fgcz.system"
@@ -1,16 +0,0 @@
1
- bfabric_web_apps/__init__.py,sha256=xqh-kddXaV4xblnMcDA73bTuy3zn5viucueuuIx41HM,2495
2
- bfabric_web_apps/layouts/layouts.py,sha256=XoSLcQPcgMBhQz2VfxkUzNL23FLBXFRFvbCL2mNLfnk,11636
3
- bfabric_web_apps/objects/BfabricInterface.py,sha256=YC6VimARZfG2t90TK5xhQDsNzgMNljwqbCOT8Qz_Uhs,10247
4
- bfabric_web_apps/objects/Logger.py,sha256=62LC94xhm7YG5LUw3yH46NqvJQsAX7wnc9D4zbY16rA,5224
5
- bfabric_web_apps/utils/app_init.py,sha256=RCdpCXp19cF74bouYJLPe-KSETZ0Vwqtd02Ta2VXEF8,428
6
- bfabric_web_apps/utils/callbacks.py,sha256=PiP1ZJ-QxdrOAZ-Mt-MN-g9wJLSOoLkWkXwPq_TLqDI,6472
7
- bfabric_web_apps/utils/components.py,sha256=V7ECGmF2XYy5O9ciDJVH1nofJYP2a_ELQF3z3X_ADbo,844
8
- bfabric_web_apps/utils/create_app_in_bfabric.py,sha256=eVk3cQDXxW-yo9b9n_zzGO6kLg_SLxYbIDECyvEPJXU,2752
9
- bfabric_web_apps/utils/defaults.py,sha256=B82j3JEbysLEU9JDZgoDBTX7WGvW3Hn5YMZaWAcjZew,278
10
- bfabric_web_apps/utils/get_logger.py,sha256=0Y3SrXW93--eglS0_ZOc34NOriAt6buFPik5n0ltzRA,434
11
- bfabric_web_apps/utils/get_power_user_wrapper.py,sha256=T33z64XjmJ0KSlmfEmrEP8eYpbpINCVD6Xld_V7PR2g,1027
12
- bfabric_web_apps/utils/resource_utilities.py,sha256=AVaqIXEfmCdWZfXDt32QfkZ9ChTL8D8cm1lCHXfT4Nc,7317
13
- bfabric_web_apps-0.1.3.dist-info/LICENSE,sha256=k0O_i2k13i9e35aO-j7FerJafAqzzu8x0kkBs0OWF3c,1065
14
- bfabric_web_apps-0.1.3.dist-info/METADATA,sha256=gTd86dYPrHKfypKFPry0SKjYtydW1z17fA-tSxz8vuM,480
15
- bfabric_web_apps-0.1.3.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
16
- bfabric_web_apps-0.1.3.dist-info/RECORD,,