sl-shared-assets 3.1.1__py3-none-any.whl → 3.1.3__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.

Potentially problematic release.


This version of sl-shared-assets might be problematic. Click here for more details.

sl_shared_assets/cli.py CHANGED
@@ -26,7 +26,7 @@ from .data_classes import SessionData, ProcessingTracker
26
26
  default=False,
27
27
  help=(
28
28
  "Determines whether to create the processed data hierarchy. This flag should be disabled for most runtimes. "
29
- "Primarily, it is used by lab acquisition system code to generate processed data directories on the remote "
29
+ "Primarily, it is used by acquisition systems to generate processed data directories on the remote "
30
30
  "compute servers as part of the data preprocessing pipeline."
31
31
  ),
32
32
  )
@@ -105,18 +105,21 @@ def verify_session_integrity(
105
105
  help="The absolute path to the directory where to store the generated project manifest file.",
106
106
  )
107
107
  @click.option(
108
- "-ppp",
109
- "--project_processed_path",
108
+ "-pdr",
109
+ "--processed_data_root",
110
110
  type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
111
111
  required=False,
112
112
  help=(
113
- "The absolute path to the project directory where processed session data is stored, if different from the "
114
- "directory used to store raw session data. Typically, this extra argument is only used when processing data "
115
- "stored on remote compute server(s)."
113
+ "The absolute path to the directory where processed data from all projects is stored on the machine that runs "
114
+ "this command. This argument is used when calling the CLI on the BioHPC server, which uses different data "
115
+ "volumes for raw and processed data. Note, the input path must point to the root directory, as it will be "
116
+ "automatically modified to include the project name. Note, if the system cannot properly resolve the path to "
117
+ "the processed data, the generated manifest will indicate that no data processing has been performed for the "
118
+ "project."
116
119
  ),
117
120
  )
118
121
  def generate_project_manifest_file(
119
- project_path: Path, output_directory: Path, project_processed_path: Path | None
122
+ project_path: Path, output_directory: Path, processed_data_root: Path | None
120
123
  ) -> None:
121
124
  """Generates the manifest .feather file that provides information about the data-processing state of all available
122
125
  project sessions.
@@ -128,7 +131,7 @@ def generate_project_manifest_file(
128
131
  generate_project_manifest(
129
132
  raw_project_directory=Path(project_path),
130
133
  output_directory=Path(output_directory),
131
- processed_project_directory=Path(project_processed_path) if project_processed_path else None,
134
+ processed_data_root=Path(processed_data_root) if processed_data_root else None,
132
135
  )
133
136
  # noinspection PyTypeChecker
134
137
  console.echo(message=f"Project {Path(project_path).stem} data manifest file: generated.", level=LogLevel.SUCCESS)
@@ -428,14 +431,15 @@ def start_jupyter_server(
428
431
  help="Determines whether to create the processed data hierarchy. This flag should be disabled for most runtimes.",
429
432
  )
430
433
  @click.option(
431
- "-ppp",
432
- "--project_processed_path",
434
+ "-pdr",
435
+ "--processed_data_root",
433
436
  type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path),
434
437
  required=False,
435
438
  help=(
436
- "The absolute path to the project directory where processed session data is stored, if different from the "
437
- "directory used to store raw session data. Typically, this extra argument is only used when processing data "
438
- "stored on remote compute server(s)."
439
+ "The absolute path to the directory where processed data from all projects is stored on the machine that runs "
440
+ "this command. This argument is used when calling the CLI on the BioHPC server, which uses different data "
441
+ "volumes for raw and processed data. Note, the input path must point to the root directory, as it will be "
442
+ "automatically modified to include the project name."
439
443
  ),
440
444
  )
441
445
  @click.option(
@@ -463,7 +467,7 @@ def start_jupyter_server(
463
467
  def resolve_dataset_marker(
464
468
  session_path: Path,
465
469
  create_processed_directories: bool,
466
- project_processed_path: Path | None,
470
+ processed_data_root: Path | None,
467
471
  remove: bool,
468
472
  update_manifest: bool,
469
473
  ) -> None:
@@ -477,7 +481,7 @@ def resolve_dataset_marker(
477
481
  resolve_p53_marker(
478
482
  session_path=session_path,
479
483
  create_processed_data_directory=create_processed_directories,
480
- processed_data_root=project_processed_path,
484
+ processed_data_root=processed_data_root,
481
485
  remove=remove,
482
486
  update_manifest=update_manifest,
483
487
  )
sl_shared_assets/cli.pyi CHANGED
@@ -33,7 +33,7 @@ def verify_session_integrity(
33
33
  """
34
34
 
35
35
  def generate_project_manifest_file(
36
- project_path: Path, output_directory: Path, project_processed_path: Path | None
36
+ project_path: Path, output_directory: Path, processed_data_root: Path | None
37
37
  ) -> None:
38
38
  """Generates the manifest .feather file that provides information about the data-processing state of all available
39
39
  project sessions.
@@ -88,7 +88,7 @@ def start_jupyter_server(
88
88
  def resolve_dataset_marker(
89
89
  session_path: Path,
90
90
  create_processed_directories: bool,
91
- project_processed_path: Path | None,
91
+ processed_data_root: Path | None,
92
92
  remove: bool,
93
93
  update_manifest: bool,
94
94
  ) -> None:
@@ -264,8 +264,8 @@ class SessionData(YamlConfig):
264
264
  Notes:
265
265
  This class is specifically designed for working with the data from a single session, performed by a single
266
266
  animal under the specific experiment. The class is used to manage both raw and processed data. It follows the
267
- data through acquisition, preprocessing and processing stages of the Sun lab data workflow. This class serves as
268
- an entry point for all interactions with the managed session's data.
267
+ data through acquisition, preprocessing, and processing stages of the Sun lab data workflow. This class serves
268
+ as an entry point for all interactions with the managed session's data.
269
269
  """
270
270
 
271
271
  project_name: str
@@ -125,8 +125,8 @@ class SessionData(YamlConfig):
125
125
  Notes:
126
126
  This class is specifically designed for working with the data from a single session, performed by a single
127
127
  animal under the specific experiment. The class is used to manage both raw and processed data. It follows the
128
- data through acquisition, preprocessing and processing stages of the Sun lab data workflow. This class serves as
129
- an entry point for all interactions with the managed session's data.
128
+ data through acquisition, preprocessing, and processing stages of the Sun lab data workflow. This class serves
129
+ as an entry point for all interactions with the managed session's data.
130
130
  """
131
131
 
132
132
  project_name: str
@@ -552,22 +552,59 @@ class Server:
552
552
  finally:
553
553
  sftp.close()
554
554
 
555
- def remove(self, remote_path: Path, is_dir: bool) -> None:
555
+ def remove(self, remote_path: Path, is_dir: bool, recursive: bool = False) -> None:
556
556
  """Removes the specified file or directory from the remote server.
557
557
 
558
558
  Args:
559
559
  remote_path: The path to the file or directory on the remote server to be removed.
560
560
  is_dir: Determines whether the input path represents a directory or a file.
561
+ recursive: If True and is_dir is True, recursively deletes all contents of the directory
562
+ before removing it. If False, only removes empty directories (standard rmdir behavior).
561
563
  """
562
564
  sftp = self._client.open_sftp()
563
565
  try:
564
566
  if is_dir:
565
- sftp.rmdir(path=str(remote_path))
567
+ if recursive:
568
+ # Recursively deletes all contents first and then removes the top-level (now empty) directory
569
+ self._recursive_remove(sftp, remote_path)
570
+ else:
571
+ # Only removes empty directories
572
+ sftp.rmdir(path=str(remote_path))
566
573
  else:
567
574
  sftp.unlink(path=str(remote_path))
568
575
  finally:
569
576
  sftp.close()
570
577
 
578
+ def _recursive_remove(self, sftp: paramiko.SFTPClient, remote_path: Path) -> None:
579
+ """Recursively removes a directory and all its contents.
580
+
581
+ This worker method is used by the user-facing remove() method to recursively remove non-empty directories.
582
+
583
+ Args:
584
+ sftp: The SFTP client instance to use for remove operations.
585
+ remote_path: The path to the remote directory to recursively remove.
586
+ """
587
+ try:
588
+ # Lists all items in the directory
589
+ items = sftp.listdir_attr(str(remote_path))
590
+
591
+ for item in items:
592
+ item_path = remote_path / item.filename
593
+
594
+ # Checks if the item is a directory
595
+ if stat.S_ISDIR(item.st_mode): # type: ignore
596
+ # Recursively removes subdirectories
597
+ self._recursive_remove(sftp, item_path)
598
+ else:
599
+ # Recursively removes files
600
+ sftp.unlink(str(item_path))
601
+
602
+ # After all contents are removed, removes the empty directory
603
+ sftp.rmdir(str(remote_path))
604
+
605
+ except Exception as e:
606
+ console.echo(f"Unable to remove the specified directory {remote_path}: {str(e)}", level=LogLevel.WARNING)
607
+
571
608
  def create_directory(self, remote_path: Path, parents: bool = True) -> None:
572
609
  """Creates the specified directory tree on the managed remote server via SFTP.
573
610
 
@@ -1,6 +1,7 @@
1
1
  from pathlib import Path
2
2
  from dataclasses import field, dataclass
3
3
 
4
+ import paramiko
4
5
  from _typeshed import Incomplete
5
6
  from simple_slurm import Slurm as Slurm
6
7
  from paramiko.client import SSHClient as SSHClient
@@ -230,12 +231,23 @@ class Server:
230
231
  local_directory_path: The path to the local directory to be uploaded.
231
232
  remote_directory_path: The path on the remote server where the directory will be copied.
232
233
  """
233
- def remove(self, remote_path: Path, is_dir: bool) -> None:
234
+ def remove(self, remote_path: Path, is_dir: bool, recursive: bool = False) -> None:
234
235
  """Removes the specified file or directory from the remote server.
235
236
 
236
237
  Args:
237
238
  remote_path: The path to the file or directory on the remote server to be removed.
238
239
  is_dir: Determines whether the input path represents a directory or a file.
240
+ recursive: If True and is_dir is True, recursively deletes all contents of the directory
241
+ before removing it. If False, only removes empty directories (standard rmdir behavior).
242
+ """
243
+ def _recursive_remove(self, sftp: paramiko.SFTPClient, remote_path: Path) -> None:
244
+ """Recursively removes a directory and all its contents.
245
+
246
+ This worker method is used by the user-facing remove() method to recursively remove non-empty directories.
247
+
248
+ Args:
249
+ sftp: The SFTP client instance to use for remove operations.
250
+ remote_path: The path to the remote directory to recursively remove.
239
251
  """
240
252
  def create_directory(self, remote_path: Path, parents: bool = True) -> None:
241
253
  """Creates the specified directory tree on the managed remote server via SFTP.
@@ -8,7 +8,7 @@ from datetime import datetime
8
8
  import pytz
9
9
  import polars as pl
10
10
  from filelock import FileLock
11
- from ataraxis_base_utilities import console
11
+ from ataraxis_base_utilities import LogLevel, console
12
12
 
13
13
  from ..data_classes import (
14
14
  SessionData,
@@ -230,7 +230,7 @@ class ProjectManifest:
230
230
 
231
231
 
232
232
  def generate_project_manifest(
233
- raw_project_directory: Path, output_directory: Path, processed_project_directory: Path | None = None
233
+ raw_project_directory: Path, output_directory: Path, processed_data_root: Path | None = None
234
234
  ) -> None:
235
235
  """Builds and saves the project manifest .feather file under the specified output directory.
236
236
 
@@ -248,9 +248,9 @@ def generate_project_manifest(
248
248
  Args:
249
249
  raw_project_directory: The path to the root project directory used to store raw session data.
250
250
  output_directory: The path to the directory where to save the generated manifest file.
251
- processed_project_directory: The path to the root project directory used to store processed session data if it
252
- is different from the 'raw_project_directory'. Typically, this would be the case on remote compute server(s)
253
- and not on local machines.
251
+ processed_data_root: The path to the root directory (volume) used to store processed data for all Sun lab
252
+ projects if it is different from the parent of the 'raw_project_directory'. Typically, this would be the
253
+ case on remote compute server(s) and not on local machines.
254
254
  """
255
255
 
256
256
  if not raw_project_directory.exists():
@@ -306,7 +306,7 @@ def generate_project_manifest(
306
306
  # Instantiates the SessionData instance to resolve the paths to all session's data files and locations.
307
307
  session_data = SessionData.load(
308
308
  session_path=directory,
309
- processed_data_root=processed_project_directory,
309
+ processed_data_root=processed_data_root,
310
310
  make_processed_data_directory=False,
311
311
  )
312
312
 
@@ -362,6 +362,8 @@ def generate_project_manifest(
362
362
  manifest["notes"].append(descriptor.experimenter_notes)
363
363
  except Exception:
364
364
  manifest["notes"].append("N/A")
365
+ else:
366
+ manifest["notes"].append("N/A")
365
367
 
366
368
  # If the session raw_data folder contains the telomere.bin file, marks the session as complete.
367
369
  manifest["complete"].append(session_data.raw_data.telomere_path.exists())
@@ -519,7 +521,7 @@ def verify_session_checksum(
519
521
  # Generates the manifest file inside the root raw data project directory
520
522
  generate_project_manifest(
521
523
  raw_project_directory=session_path.parents[1],
522
- processed_project_directory=processed_data_root,
524
+ processed_data_root=processed_data_root,
523
525
  output_directory=raw_directory,
524
526
  )
525
527
 
@@ -573,12 +575,27 @@ def resolve_p53_marker(
573
575
  if session_data.processed_data.p53_path.exists():
574
576
  if remove:
575
577
  session_data.processed_data.p53_path.unlink()
578
+ message = (
579
+ f"Dataset marker for the session {session_data.session_name} acquired for the animal "
580
+ f"{session_data.animal_id} and {session_data.project_name} project: Removed."
581
+ )
582
+ console.echo(message=message, level=LogLevel.SUCCESS)
576
583
  return # Ends remove runtime
577
584
 
585
+ message = (
586
+ f"Dataset marker for the session {session_data.session_name} acquired for the animal "
587
+ f"{session_data.animal_id} and {session_data.project_name} project: Already exists. No actions taken."
588
+ )
589
+ console.echo(message=message, level=LogLevel.SUCCESS)
578
590
  return # Ends create runtime
579
591
 
580
592
  # If the marker does not exist and the function is called in 'remove' mode, aborts the runtime
581
593
  elif remove:
594
+ message = (
595
+ f"Dataset marker for the session {session_data.session_name} acquired for the animal "
596
+ f"{session_data.animal_id} and {session_data.project_name} project: Does not exist. No actions taken."
597
+ )
598
+ console.echo(message=message, level=LogLevel.SUCCESS)
582
599
  return # Ends remove runtime
583
600
 
584
601
  # The rest of the runtime deals with determining whether it is safe to create the marker file.
@@ -588,6 +605,13 @@ def resolve_p53_marker(
588
605
  # Window checking sessions are not designed to be integrated into datasets, so they cannot be marked with the
589
606
  # p53.bin file. Similarly, any incomplete session is automatically excluded from dataset formation.
590
607
  if session_type == SessionTypes.WINDOW_CHECKING or not session_data.raw_data.telomere_path.exists():
608
+ message = (
609
+ f"Unable to generate the dataset marker for the session {session_data.session_name} acquired for the "
610
+ f"animal {session_data.animal_id} and {session_data.project_name} project, as the session is incomplete or "
611
+ f"is of Window Checking type. These sessions must be manually evaluated and marked for dataset inclusion "
612
+ f"by the experimenter. "
613
+ )
614
+ console.echo(message=message, level=LogLevel.ERROR)
591
615
  return
592
616
 
593
617
  # Training sessions collect similar data and share processing pipeline requirements
@@ -597,6 +621,13 @@ def resolve_p53_marker(
597
621
  video_tracker = ProcessingTracker(file_path=session_data.processed_data.video_processing_tracker_path)
598
622
  if behavior_tracker.is_running or video_tracker.is_running:
599
623
  # Note, training runtimes do not require suite2p processing.
624
+ message = (
625
+ f"Unable to generate the dataset marker for the session {session_data.session_name} acquired for the "
626
+ f"animal {session_data.animal_id} and {session_data.project_name} project, as it is currently being "
627
+ f"processed by one of the data processing pipelines. Wait until the session is fully processed by all "
628
+ f"pipelines and repeat the command that encountered this error."
629
+ )
630
+ console.echo(message=message, level=LogLevel.ERROR)
600
631
  return
601
632
 
602
633
  # Mesoscope experiment sessions require additional processing with suite2p
@@ -607,11 +638,23 @@ def resolve_p53_marker(
607
638
 
608
639
  # Similar to the above, ensures that the session is not being processed with one of the supported pipelines.
609
640
  if behavior_tracker.is_running or suite2p_tracker.is_running or video_tracker.is_running:
641
+ message = (
642
+ f"Unable to generate the dataset marker for the session {session_data.session_name} acquired for the "
643
+ f"animal {session_data.animal_id} and {session_data.project_name} project, as it is currently being "
644
+ f"processed by one of the data processing pipelines. Wait until the session is fully processed by all "
645
+ f"pipelines and repeat the command that encountered this error."
646
+ )
647
+ console.echo(message=message, level=LogLevel.ERROR)
610
648
  return
611
649
 
612
650
  # If the runtime reached this point, the session is eligible for dataset integration. Creates the p53.bin marker
613
651
  # file, preventing the session from being processed again as long as the marker exists.
614
652
  session_data.processed_data.p53_path.touch()
653
+ message = (
654
+ f"Dataset marker for the session {session_data.session_name} acquired for the animal "
655
+ f"{session_data.animal_id} and {session_data.project_name} project: Created."
656
+ )
657
+ console.echo(message=message, level=LogLevel.SUCCESS)
615
658
 
616
659
  # If the runtime is configured to generate the project manifest file, attempts to generate and overwrite the
617
660
  # existing manifest file for the target project.
@@ -623,6 +666,6 @@ def resolve_p53_marker(
623
666
  # Generates the manifest file inside the root raw data project directory
624
667
  generate_project_manifest(
625
668
  raw_project_directory=session_path.parents[1],
626
- processed_project_directory=processed_data_root,
669
+ processed_data_root=processed_data_root,
627
670
  output_directory=raw_directory,
628
671
  )
@@ -107,7 +107,7 @@ class ProjectManifest:
107
107
  """
108
108
 
109
109
  def generate_project_manifest(
110
- raw_project_directory: Path, output_directory: Path, processed_project_directory: Path | None = None
110
+ raw_project_directory: Path, output_directory: Path, processed_data_root: Path | None = None
111
111
  ) -> None:
112
112
  """Builds and saves the project manifest .feather file under the specified output directory.
113
113
 
@@ -125,9 +125,9 @@ def generate_project_manifest(
125
125
  Args:
126
126
  raw_project_directory: The path to the root project directory used to store raw session data.
127
127
  output_directory: The path to the directory where to save the generated manifest file.
128
- processed_project_directory: The path to the root project directory used to store processed session data if it
129
- is different from the 'raw_project_directory'. Typically, this would be the case on remote compute server(s)
130
- and not on local machines.
128
+ processed_data_root: The path to the root directory (volume) used to store processed data for all Sun lab
129
+ projects if it is different from the parent of the 'raw_project_directory'. Typically, this would be the
130
+ case on remote compute server(s) and not on local machines.
131
131
  """
132
132
 
133
133
  def verify_session_checksum(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sl-shared-assets
3
- Version: 3.1.1
3
+ Version: 3.1.3
4
4
  Summary: Provides data acquisition and processing assets shared between Sun (NeuroAI) lab libraries.
5
5
  Project-URL: Homepage, https://github.com/Sun-Lab-NBB/sl-shared-assets
6
6
  Project-URL: Documentation, https://sl-shared-assets-api-docs.netlify.app/
@@ -700,7 +700,7 @@ Requires-Dist: natsort==8.4.0
700
700
  Requires-Dist: numpy==2.2.6
701
701
  Requires-Dist: paramiko==3.5.1
702
702
  Requires-Dist: polars==1.31.0
703
- Requires-Dist: pyarrow==20.0.0
703
+ Requires-Dist: pyarrow==21.0.0
704
704
  Requires-Dist: pytz==2025.2
705
705
  Requires-Dist: simple-slurm==0.3.6
706
706
  Requires-Dist: tqdm==4.67.1
@@ -1,7 +1,7 @@
1
1
  sl_shared_assets/__init__.py,sha256=ybThh0XDtijjwahKkSEnnQ44rxrN2SVyjB5dHaXts0E,2391
2
2
  sl_shared_assets/__init__.pyi,sha256=Cb-umRqvnynk2udbgqAJ6h5_tiJyvVtWmx0kLKrL2Yg,2678
3
- sl_shared_assets/cli.py,sha256=OIwXf6pNPnzqzUPL7mSmEw17KIa3yAOpP0Mpo1Zpf88,19087
4
- sl_shared_assets/cli.pyi,sha256=5hEbOnYaH4q5qdqJ-zhM9-ElzgcaBeMAX34tuHaUDos,5328
3
+ sl_shared_assets/cli.py,sha256=w7FJW93LkhWbLckonFKj8DFN4yAJLPxPLe0kaMBqFMk,19513
4
+ sl_shared_assets/cli.pyi,sha256=-2-rAGqkF3-EeLLijKsp1sWNxEhZ2zVmaAO4VdvGW6k,5322
5
5
  sl_shared_assets/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  sl_shared_assets/data_classes/__init__.py,sha256=mP__bBIIjMf0EETM4PgQzKy1ZKsjp6paRPNDWWbPRV4,1962
7
7
  sl_shared_assets/data_classes/__init__.pyi,sha256=J7ZCH9qQ4qz-3Wq9ILdihlmK9zFR3iU1cpLcSaN45Y8,2238
@@ -9,28 +9,28 @@ sl_shared_assets/data_classes/configuration_data.py,sha256=npMYu9WQMJnoanzC2_5rh
9
9
  sl_shared_assets/data_classes/configuration_data.pyi,sha256=1_kmBDPGkHmVwXEGYR_3uERBSsenQOTuquMBtjKVTA8,11068
10
10
  sl_shared_assets/data_classes/runtime_data.py,sha256=kmXTUk5rDAJBN3XrYLYgusJRfVJ5WiBBk0RPNiSk2pE,16725
11
11
  sl_shared_assets/data_classes/runtime_data.pyi,sha256=Hyc-dBePM0xIGgkSIoKmwwUUmdOokm1LUwy1OHHalyU,6771
12
- sl_shared_assets/data_classes/session_data.py,sha256=YKXako1sNB87LDkGEXx9WZFs6lG3aD619lga5g4L4Ks,49172
13
- sl_shared_assets/data_classes/session_data.pyi,sha256=CgB4nIDBl4bY1JvcIILfFTlos3ukl3WK4AOaku4CL3Y,15959
12
+ sl_shared_assets/data_classes/session_data.py,sha256=lni8TAAxkFukdRSkMSfcrLYgfYcKpPfMAqmM8EDGVYs,49173
13
+ sl_shared_assets/data_classes/session_data.pyi,sha256=5pk7PtZLooj4zmwLf1MImf5WfTIyTURgQVM9rRNoLJA,15960
14
14
  sl_shared_assets/data_classes/surgery_data.py,sha256=5B1OPKFq4bnzbAoe-_c5dFV3kbSD5YFzXbX2zXmfGs8,7485
15
15
  sl_shared_assets/data_classes/surgery_data.pyi,sha256=rf59lJ3tGSYKHQlEGXg75MnjajBwl0DYhL4TClAO4SM,2605
16
16
  sl_shared_assets/server/__init__.py,sha256=GOQ7wWjiS5Xg_WgTqeEqCTRF9ms9GXx0nffCr-BmKsA,453
17
17
  sl_shared_assets/server/__init__.pyi,sha256=Zc12G90fZdgEMwaVZbFzrRVV1wH_LEj3sxaV3lhk1Cw,316
18
18
  sl_shared_assets/server/job.py,sha256=wZbppMrv6fqch79bKLjOGQ9AYfjiDKDnTyUe7xgAT44,19461
19
19
  sl_shared_assets/server/job.pyi,sha256=wop4ulVY2u6eb3twajeA9MS0EAtNb89aA56pPoGF1Xc,11673
20
- sl_shared_assets/server/server.py,sha256=oEwdXisyel72Hdk7ZpEwTPq3Lu64UbQWfGHArV8Y6nI,32978
21
- sl_shared_assets/server/server.pyi,sha256=84XFtqU9fYbxu6Ldf-OMB2nFe6wdGneZM1MFtR9rz4s,15133
20
+ sl_shared_assets/server/server.py,sha256=AuoPy4LUPG19oYOnQ61z8s4S3TSXix_DjF7eTtVjDSY,34764
21
+ sl_shared_assets/server/server.pyi,sha256=K42zwzaZk13LH964fex3sK2kCL_Ulis_GualZkZG-NA,15828
22
22
  sl_shared_assets/tools/__init__.py,sha256=i-oUVw_un3lzyyII4Sc75s4BnUfZh_aUbQe6dP2Vrbc,743
23
23
  sl_shared_assets/tools/__init__.pyi,sha256=pi-5AJyQYeuqIFGWpJ_HhUpXLq6P_nItIqDhsdaIJFU,686
24
24
  sl_shared_assets/tools/ascension_tools.py,sha256=xI-hrkR9NIgb7lyhj-ntc8tCYQvDEv6YgYJXl1yvxCs,14639
25
25
  sl_shared_assets/tools/ascension_tools.pyi,sha256=fs5j7nbnZ4WpgK8D75A7WJcvFMwK_MUO9ULIYo1YkGo,3739
26
26
  sl_shared_assets/tools/packaging_tools.py,sha256=VxQoluGPDUWjPj1ftEt2dvUcdmj0g7T1frGZhZPM8NE,7541
27
27
  sl_shared_assets/tools/packaging_tools.pyi,sha256=vgGbAQCExwg-0A5F72MzEhzHxu97Nqg1yuz-5P89ycU,3118
28
- sl_shared_assets/tools/project_management_tools.py,sha256=9G9jdeUxs80yLkcFJsWUdVcAcuXDB8reqnTfxVa-FAw,32161
29
- sl_shared_assets/tools/project_management_tools.pyi,sha256=hdn0U9e3_j9McJH75Dzoas-FxcB9nVCTHEFHPofdLtg,11361
28
+ sl_shared_assets/tools/project_management_tools.py,sha256=Gyp_LrYi0H09-sSl-LenimEw-4lyXydlOE47wMEuUQg,34965
29
+ sl_shared_assets/tools/project_management_tools.pyi,sha256=r45nLPP51mrtn0ajm9iSVq-aR37CS71DGZuRXqd29Zc,11377
30
30
  sl_shared_assets/tools/transfer_tools.py,sha256=vqYO4sERZV0W1DFNFnTpJA6QBZ4QJA94a2TyUhZW2Qk,6605
31
31
  sl_shared_assets/tools/transfer_tools.pyi,sha256=WtUGfaKV9FP_CnhBg_UvclpuDvOlEESOSMlEDtWpOLg,3293
32
- sl_shared_assets-3.1.1.dist-info/METADATA,sha256=eBMBDc_ahWT4kEFQH0X_i3U1cthEtn5O23x5VBr3EOQ,56944
33
- sl_shared_assets-3.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
34
- sl_shared_assets-3.1.1.dist-info/entry_points.txt,sha256=UmO1rl7ly9N7HWPwWyP9E0b5KBUStpBo4TRoqNtizDY,430
35
- sl_shared_assets-3.1.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
36
- sl_shared_assets-3.1.1.dist-info/RECORD,,
32
+ sl_shared_assets-3.1.3.dist-info/METADATA,sha256=OW1w-PlNKnEDMaVh-mAANdk0cO_KZhZGxn_0-stSd2g,56944
33
+ sl_shared_assets-3.1.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
34
+ sl_shared_assets-3.1.3.dist-info/entry_points.txt,sha256=UmO1rl7ly9N7HWPwWyP9E0b5KBUStpBo4TRoqNtizDY,430
35
+ sl_shared_assets-3.1.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
36
+ sl_shared_assets-3.1.3.dist-info/RECORD,,