neuronum 5.7.0__py3-none-any.whl → 5.8.1__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 neuronum might be problematic. Click here for more details.

cli/main.py CHANGED
@@ -10,6 +10,8 @@ import click
10
10
  import questionary
11
11
  from pathlib import Path
12
12
  import requests
13
+ import psutil
14
+ from datetime import datetime
13
15
 
14
16
 
15
17
  @click.group()
@@ -251,9 +253,13 @@ def delete_cell():
251
253
  @click.option('--stream', multiple=True, default=None, help="Optional stream ID for stream.")
252
254
  @click.option('--app', is_flag=True, help="Generate a Node with app template")
253
255
  def init_node(sync, stream, app):
254
- asyncio.run(async_init_node(sync, stream, app))
256
+ descr = click.prompt("Node description: Type up to 25 characters").strip()
257
+ if descr and len(descr) > 25:
258
+ click.echo("Description too long. Max 25 characters allowed.")
259
+ return
260
+ asyncio.run(async_init_node(sync, stream, app, descr))
255
261
 
256
- async def async_init_node(sync, stream, app):
262
+ async def async_init_node(sync, stream, app, descr):
257
263
  credentials_folder_path = Path.home() / ".neuronum"
258
264
  env_path = credentials_folder_path / ".env"
259
265
 
@@ -278,11 +284,16 @@ async def async_init_node(sync, stream, app):
278
284
  return
279
285
 
280
286
  url = f"https://{network}/api/init_node"
281
- node_payload = {"host": host, "password": password, "synapse": synapse}
287
+ node = {
288
+ "host": host,
289
+ "password": password,
290
+ "synapse": synapse,
291
+ "descr": descr,
292
+ }
282
293
 
283
294
  async with aiohttp.ClientSession() as session:
284
295
  try:
285
- async with session.post(url, json=node_payload) as response:
296
+ async with session.post(url, json=node) as response:
286
297
  response.raise_for_status()
287
298
  data = await response.json()
288
299
  nodeID = data["nodeID"]
@@ -316,16 +327,6 @@ async def async_init_node(sync, stream, app):
316
327
  env_path = project_path / ".env"
317
328
  await asyncio.to_thread(env_path.write_text, f"NODE={nodeID}\nHOST={host}\nPASSWORD={password}\nNETWORK={network}\nSYNAPSE={synapse}\n")
318
329
 
319
- gitignore_path = project_path / ".gitignore"
320
- await asyncio.to_thread(gitignore_path.write_text, ".env\n")
321
-
322
- requirements_path = project_path / "requirements.txt"
323
- requirements_content = """\
324
- # Please add additional packages below if your Node uses more
325
- neuronum
326
- """
327
- await asyncio.to_thread(requirements_path.write_text, requirements_content)
328
-
329
330
  nodemd_path = project_path / "NODE.md"
330
331
  await asyncio.to_thread(nodemd_path.write_text, """### NODE.md: How to interact with this Node
331
332
 
@@ -565,13 +566,38 @@ asyncio.run(main())
565
566
  @click.command()
566
567
  @click.option('--d', is_flag=True, help="Start node in detached mode")
567
568
  def start_node(d):
569
+ pid_file = Path.cwd() / "status.txt"
570
+ system_name = platform.system()
571
+ active_pids = []
572
+
573
+ start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
574
+
575
+ if pid_file.exists():
576
+ try:
577
+ with open(pid_file, "r") as f:
578
+ pids = [int(line.strip()) for line in f if line.strip().isdigit()]
579
+ for pid in pids:
580
+ if system_name == "Windows":
581
+ if psutil.pid_exists(pid):
582
+ active_pids.append(pid)
583
+ else:
584
+ try:
585
+ os.kill(pid, 0)
586
+ active_pids.append(pid)
587
+ except OSError:
588
+ continue
589
+ except Exception as e:
590
+ click.echo(f"Failed to read PID file: {e}")
591
+
592
+ if active_pids:
593
+ click.echo(f"Node is already running. Active PIDs: {', '.join(map(str, active_pids))}")
594
+ return
595
+
568
596
  click.echo("Starting Node...")
569
597
 
570
598
  project_path = Path.cwd()
571
599
  script_files = glob.glob("sync_*.py") + glob.glob("stream_*.py") + glob.glob("app.py")
572
-
573
600
  processes = []
574
- system_name = platform.system()
575
601
 
576
602
  for script in script_files:
577
603
  script_path = project_path / script
@@ -587,7 +613,10 @@ def start_node(d):
587
613
  start_new_session=True
588
614
  )
589
615
  else:
590
- process = subprocess.Popen(["python", str(script_path)], start_new_session=True)
616
+ process = subprocess.Popen(
617
+ [python_cmd, str(script_path)],
618
+ start_new_session=True
619
+ )
591
620
 
592
621
  processes.append(process.pid)
593
622
 
@@ -595,55 +624,172 @@ def start_node(d):
595
624
  click.echo("Error: No valid node script found. Ensure the node is set up correctly.")
596
625
  return
597
626
 
598
- with open("node_pid.txt", "w") as f:
627
+ with open(pid_file, "w") as f:
628
+ f.write(f"Started at: {start_time}\n")
599
629
  f.write("\n".join(map(str, processes)))
600
630
 
601
- click.echo("Node started successfully!")
631
+ click.echo(f"Node started successfully with PIDs: {', '.join(map(str, processes))}")
602
632
 
603
633
 
604
634
  @click.command()
605
- def stop_node():
606
- asyncio.run(async_stop_node())
635
+ def check_node():
636
+ click.echo("Checking Node status...")
607
637
 
608
- async def async_stop_node():
609
- click.echo("Stopping Node...")
638
+ env_data = {}
639
+ try:
640
+ with open(".env", "r") as f:
641
+ for line in f:
642
+ if "=" in line:
643
+ key, value = line.strip().split("=", 1)
644
+ env_data[key] = value
645
+ nodeID = env_data.get("NODE", "")
646
+ except FileNotFoundError:
647
+ click.echo("Error: .env with credentials not found")
648
+ return
649
+ except Exception as e:
650
+ click.echo(f"Error reading .env file: {e}")
651
+ return
652
+
653
+ pid_file = Path.cwd() / "status.txt"
610
654
 
611
- node_pid_path = Path("node_pid.txt")
655
+ if not pid_file.exists():
656
+ click.echo(f"Node {nodeID} is not running. Status file missing.")
657
+ return
612
658
 
613
659
  try:
614
- with open("node_pid.txt", "r") as f:
615
- pids = [int(pid.strip()) for pid in f.readlines()]
660
+ with open(pid_file, "r") as f:
661
+ lines = f.readlines()
662
+ timestamp_line = next((line for line in lines if line.startswith("Started at:")), None)
663
+ pids = [int(line.strip()) for line in lines if line.strip().isdigit()]
664
+
665
+ if timestamp_line:
666
+ click.echo(timestamp_line.strip())
667
+ start_time = datetime.strptime(timestamp_line.split(":", 1)[1].strip(), "%Y-%m-%d %H:%M:%S")
668
+ now = datetime.now()
669
+ uptime = now - start_time
670
+ click.echo(f"Uptime: {str(uptime).split('.')[0]}")
671
+ except Exception as e:
672
+ click.echo(f"Failed to read PID file: {e}")
673
+ return
616
674
 
617
- system_name = platform.system()
675
+ system_name = platform.system()
676
+ running_pids = []
618
677
 
619
- for pid in pids:
678
+ for pid in pids:
679
+ if system_name == "Windows":
680
+ if psutil.pid_exists(pid):
681
+ running_pids.append(pid)
682
+ else:
620
683
  try:
684
+ os.kill(pid, 0)
685
+ running_pids.append(pid)
686
+ except OSError:
687
+ continue
688
+
689
+ if running_pids:
690
+ for pid in running_pids:
691
+ proc = psutil.Process(pid)
692
+ mem = proc.memory_info().rss / (1024 * 1024) # in MB
693
+ cpu = proc.cpu_percent(interval=0.1)
694
+ click.echo(f"PID {pid} → Memory: {mem:.2f} MB | CPU: {cpu:.1f}%")
695
+ click.echo(f"Node {nodeID} is running. Active PIDs: {', '.join(map(str, running_pids))}")
696
+ else:
697
+ click.echo(f"Node {nodeID} is not running.")
698
+
699
+
700
+ @click.command()
701
+ @click.option('--d', is_flag=True, help="Restart node in detached mode")
702
+ def restart_node(d):
703
+ pid_file = Path.cwd() / "status.txt"
704
+ system_name = platform.system()
705
+
706
+ start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
707
+
708
+ env_data = {}
709
+ try:
710
+ with open(".env", "r") as f:
711
+ for line in f:
712
+ key, value = line.strip().split("=")
713
+ env_data[key] = value
714
+
715
+ nodeID = env_data.get("NODE", "")
716
+
717
+ except FileNotFoundError:
718
+ print("Error: .env with credentials not found")
719
+ return
720
+ except Exception as e:
721
+ print(f"Error reading .env file: {e}")
722
+ return
723
+
724
+ if pid_file.exists():
725
+ try:
726
+ with open(pid_file, "r") as f:
727
+ pids = [int(line.strip()) for line in f if line.strip().isdigit()]
728
+
729
+ for pid in pids:
621
730
  if system_name == "Windows":
622
- await asyncio.to_thread(subprocess.run, ["taskkill", "/F", "/PID", str(pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
731
+ if psutil.pid_exists(pid):
732
+ proc = psutil.Process(pid)
733
+ proc.terminate()
623
734
  else:
624
- await asyncio.to_thread(os.kill, pid, 9)
625
- except ProcessLookupError:
626
- click.echo(f"Warning: Process {pid} already stopped or does not exist.")
735
+ try:
736
+ os.kill(pid, 15)
737
+ except OSError:
738
+ continue
627
739
 
628
- await asyncio.to_thread(os.remove, node_pid_path)
629
- click.echo("Node stopped successfully!")
740
+ pid_file.unlink()
630
741
 
631
- except FileNotFoundError:
632
- click.echo("Error: No active node process found.")
633
- except subprocess.CalledProcessError:
634
- click.echo("Error: Unable to stop some node processes.")
742
+ click.echo(f"Terminated existing {nodeID} processes: {', '.join(map(str, pids))}")
743
+
744
+ except Exception as e:
745
+ click.echo(f"Failed to terminate processes: {e}")
746
+ return
747
+ else:
748
+ click.echo(f"Node {nodeID} is not running")
749
+
750
+ click.echo(f"Starting Node {nodeID}...")
751
+ project_path = Path.cwd()
752
+ script_files = glob.glob("sync_*.py") + glob.glob("stream_*.py") + glob.glob("app.py")
753
+ processes = []
754
+
755
+ python_cmd = "pythonw" if system_name == "Windows" else "python"
756
+
757
+ for script in script_files:
758
+ script_path = project_path / script
759
+ if script_path.exists():
760
+ if d:
761
+ process = subprocess.Popen(
762
+ ["nohup", python_cmd, str(script_path), "&"] if system_name != "Windows"
763
+ else [python_cmd, str(script_path)],
764
+ stdout=subprocess.DEVNULL,
765
+ stderr=subprocess.DEVNULL,
766
+ start_new_session=True
767
+ )
768
+ else:
769
+ process = subprocess.Popen([python_cmd, str(script_path)], start_new_session=True)
770
+
771
+ processes.append(process.pid)
772
+
773
+ if not processes:
774
+ click.echo("Error: No valid node script found.")
775
+ return
776
+
777
+ with open(pid_file, "w") as f:
778
+ f.write(f"Started at: {start_time}\n")
779
+ f.write("\n".join(map(str, processes)))
780
+
781
+ click.echo(f"Node {nodeID} started with new PIDs: {', '.join(map(str, processes))}")
635
782
 
636
783
 
637
784
  @click.command()
638
- def connect_node():
639
- node_type = questionary.select(
640
- "Choose Node type:",
641
- choices=["public", "private"]
642
- ).ask()
643
- descr = click.prompt("Node description (max. 25 characters)")
644
- asyncio.run(async_connect_node(descr, node_type))
785
+ def stop_node():
786
+ asyncio.run(async_stop_node())
787
+
788
+ async def async_stop_node():
789
+ click.echo("Stopping Node...")
790
+
791
+ node_pid_path = Path("status.txt")
645
792
 
646
- async def async_connect_node(descr, node_type):
647
793
  env_data = {}
648
794
  try:
649
795
  with open(".env", "r") as f:
@@ -652,10 +798,6 @@ async def async_connect_node(descr, node_type):
652
798
  env_data[key] = value
653
799
 
654
800
  nodeID = env_data.get("NODE", "")
655
- host = env_data.get("HOST", "")
656
- password = env_data.get("PASSWORD", "")
657
- network = env_data.get("NETWORK", "")
658
- synapse = env_data.get("SYNAPSE", "")
659
801
 
660
802
  except FileNotFoundError:
661
803
  print("Error: .env with credentials not found")
@@ -665,52 +807,46 @@ async def async_connect_node(descr, node_type):
665
807
  return
666
808
 
667
809
  try:
668
- with open("NODE.md", "r") as f:
669
- nodemd_file = f.read()
810
+ with open("status.txt", "r") as f:
811
+ pids = [int(line.strip()) for line in f if line.strip().isdigit()]
670
812
 
671
- except FileNotFoundError:
672
- print("Error: NODE.md file not found")
673
- return
674
- except Exception as e:
675
- print(f"Error reading NODE.md file: {e}")
676
- return
813
+ system_name = platform.system()
677
814
 
678
- url = f"https://{network}/api/connect_node/{node_type}"
815
+ for pid in pids:
816
+ try:
817
+ if system_name == "Windows":
818
+ await asyncio.to_thread(subprocess.run, ["taskkill", "/F", "/PID", str(pid)], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
819
+ else:
820
+ await asyncio.to_thread(os.kill, pid, 9)
821
+ except ProcessLookupError:
822
+ click.echo(f"Warning: Process {pid} already stopped or does not exist.")
679
823
 
680
- node = {
681
- "nodeID": nodeID,
682
- "descr": descr,
683
- "host": host,
684
- "password": password,
685
- "synapse": synapse,
686
- "nodemd_file": nodemd_file
687
- }
824
+ await asyncio.to_thread(os.remove, node_pid_path)
825
+ click.echo(f"Node {nodeID} stopped successfully!")
688
826
 
689
- async with aiohttp.ClientSession() as session:
690
- try:
691
- async with session.post(url, json=node) as response:
692
- response.raise_for_status()
693
- data = await response.json()
694
- nodeID = data["nodeID"]
695
- node_url = data["node_url"]
696
- except aiohttp.ClientError as e:
697
- click.echo(f"Error sending request: {e}")
698
- return
699
-
700
- if nodeID == "Node does not exist":
701
- click.echo(f"Neuronum Node not found! Make sure you initialized your Node correctly")
702
- else:
703
- if node_type == "public":
704
- click.echo(f"Public Neuronum Node '{nodeID}' connected! Visit: {node_url}")
705
- else:
706
- click.echo(f"Private Neuronum Node '{nodeID}' connected!")
827
+ except FileNotFoundError:
828
+ click.echo("Error: No active node process found.")
829
+ except subprocess.CalledProcessError:
830
+ click.echo("Error: Unable to stop some node processes.")
707
831
 
708
832
 
709
833
  @click.command()
710
834
  def update_node():
711
- asyncio.run(async_update_node())
835
+ node_type = questionary.select(
836
+ "Update Node type:",
837
+ choices=["public", "private"]
838
+ ).ask()
839
+ descr = click.prompt(
840
+ "Update Node description: Type up to 25 characters, or press Enter to leave it unchanged",
841
+ default="None",
842
+ show_default=False
843
+ ).strip()
844
+ if descr and len(descr) > 25:
845
+ click.echo("Description too long. Max 25 characters allowed.")
846
+ return
847
+ asyncio.run(async_update_node(node_type, descr))
712
848
 
713
- async def async_update_node():
849
+ async def async_update_node(node_type: str, descr: str) -> None:
714
850
  env_data = {}
715
851
 
716
852
  try:
@@ -744,17 +880,19 @@ async def async_update_node():
744
880
  return
745
881
 
746
882
  url = f"https://{network}/api/update_node"
747
- node_payload = {
883
+ node = {
748
884
  "nodeID": nodeID,
749
885
  "host": host,
750
886
  "password": password,
751
887
  "synapse": synapse,
752
- "nodemd_file": nodemd_file
888
+ "node_type": node_type,
889
+ "nodemd_file": nodemd_file,
890
+ "descr": descr,
753
891
  }
754
892
 
755
893
  async with aiohttp.ClientSession() as session:
756
894
  try:
757
- async with session.post(url, json=node_payload) as response:
895
+ async with session.post(url, json=node) as response:
758
896
  response.raise_for_status()
759
897
  data = await response.json()
760
898
  nodeID = data["nodeID"]
@@ -782,54 +920,10 @@ async def async_update_node():
782
920
  await asyncio.to_thread(Path("streams.json").write_text, json.dumps(stx, indent=4))
783
921
  await asyncio.to_thread(Path("nodes.json").write_text, json.dumps(nodes, indent=4))
784
922
 
785
- click.echo(f"Neuronum Node '{nodeID}' updated! Visit: {node_url}")
786
-
787
-
788
- @click.command()
789
- def disconnect_node():
790
- asyncio.run(async_disconnect_node())
791
-
792
- async def async_disconnect_node():
793
- env_data = {}
794
-
795
- try:
796
- with open(".env", "r") as f:
797
- for line in f:
798
- key, value = line.strip().split("=")
799
- env_data[key] = value
800
-
801
- nodeID = env_data.get("NODE", "")
802
- host = env_data.get("HOST", "")
803
- password = env_data.get("PASSWORD", "")
804
- network = env_data.get("NETWORK", "")
805
- synapse = env_data.get("SYNAPSE", "")
806
-
807
- except FileNotFoundError:
808
- click.echo("Error: .env with credentials not found")
809
- return
810
- except Exception as e:
811
- click.echo(f"Error reading .env file: {e}")
812
- return
813
-
814
- url = f"https://{network}/api/disconnect_node"
815
- node_payload = {
816
- "nodeID": nodeID,
817
- "host": host,
818
- "password": password,
819
- "synapse": synapse
820
- }
821
-
822
- async with aiohttp.ClientSession() as session:
823
- try:
824
- async with session.post(url, json=node_payload) as response:
825
- response.raise_for_status()
826
- data = await response.json()
827
- nodeID = data["nodeID"]
828
- except aiohttp.ClientError as e:
829
- click.echo(f"Error sending request: {e}")
830
- return
831
-
832
- click.echo(f"Neuronum Node '{nodeID}' disconnected!")
923
+ if node_type == "public":
924
+ click.echo(f"Neuronum Node '{nodeID}' updated! Visit: {node_url}")
925
+ else:
926
+ click.echo(f"Neuronum Node '{nodeID}' updated!")
833
927
 
834
928
 
835
929
  @click.command()
@@ -1004,16 +1098,15 @@ async def async_sync(stx):
1004
1098
 
1005
1099
 
1006
1100
  cli.add_command(create_cell)
1007
- cli.add_command(connect_cell)
1008
1101
  cli.add_command(view_cell)
1009
1102
  cli.add_command(disconnect_cell)
1010
1103
  cli.add_command(delete_cell)
1011
1104
  cli.add_command(init_node)
1012
1105
  cli.add_command(start_node)
1106
+ cli.add_command(restart_node)
1013
1107
  cli.add_command(stop_node)
1014
- cli.add_command(connect_node)
1108
+ cli.add_command(check_node)
1015
1109
  cli.add_command(update_node)
1016
- cli.add_command(disconnect_node)
1017
1110
  cli.add_command(delete_node)
1018
1111
  cli.add_command(activate)
1019
1112
  cli.add_command(load)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: neuronum
3
- Version: 5.7.0
3
+ Version: 5.8.1
4
4
  Summary: Official client library to interact with the Neuronum Network
5
5
  Home-page: https://neuronum.net
6
6
  Author: Neuronum Cybernetics
@@ -18,6 +18,7 @@ Requires-Dist: click
18
18
  Requires-Dist: questionary
19
19
  Requires-Dist: python-dotenv
20
20
  Requires-Dist: requests
21
+ Requires-Dist: psutil
21
22
  Dynamic: author
22
23
  Dynamic: author-email
23
24
  Dynamic: classifier
@@ -54,10 +55,11 @@ Dynamic: summary
54
55
  ------------------
55
56
 
56
57
  ### **A Getting Started into the Neuronum Network**
57
- - Learn about Neuronum
58
- - Connect to the Network
59
- - Build a Neuronum Node
60
- - Interact with your Node
58
+ In this brief getting started guide, you will:
59
+ - [Learn about Neuronum](#about-neuronum)
60
+ - [Connect to the Network](#connect-to-neuronum)
61
+ - [Build a Neuronum Node](#build-on-neuronum)
62
+ - [Interact with your Node](#interact-with-neuronum)
61
63
 
62
64
  ------------------
63
65
 
@@ -101,10 +103,13 @@ neuronum connect-cell # connect Cell
101
103
  ------------------
102
104
 
103
105
 
104
- ### **Build On Neuronum** **[(Build with Node Examples)](https://github.com/neuronumcybernetics/neuronum/tree/main/features/nodes)**
106
+ ### **Build On Neuronum**
107
+ [View Node Examples](https://github.com/neuronumcybernetics/neuronum/tree/main/features/nodes)
108
+
109
+
105
110
  Initialize a Node (app template):
106
111
  ```sh
107
- neuronum init-node --app # initialize a Node with app template -> creates a folder named node_<node_id> containing all relevant files
112
+ neuronum init-node --app # initialize a Node with app template
108
113
  ```
109
114
 
110
115
  Change into Node folder
@@ -0,0 +1,10 @@
1
+ cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ cli/main.py,sha256=HPkD9tH-XszD2mLzpfV8VnPCWU8evRUUWvo4-3Ql9fQ,34810
3
+ neuronum/__init__.py,sha256=Drsm263_w3_VWgl1YsKLUr8WwVodqV3TSjqpxLjyq_M,46
4
+ neuronum/neuronum.py,sha256=L8Oz1qcTyOiYMTGfiSKgN5Nu9jcl25jFeJOh0XItPjw,17201
5
+ neuronum-5.8.1.dist-info/licenses/LICENSE.md,sha256=zGst0rjgnp6oFuRVwFwB1Ql4sDXt_nw9xbUR49Gf99A,2008
6
+ neuronum-5.8.1.dist-info/METADATA,sha256=I-4PL0Nsl6SZ1D1k92AnkV8RfJEda3j5QwHkgHdq1HU,5374
7
+ neuronum-5.8.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ neuronum-5.8.1.dist-info/entry_points.txt,sha256=XKYBcRNxGeJpZZkDPsa8HA_RaJ7Km_R_JaUq5T9Nk2U,42
9
+ neuronum-5.8.1.dist-info/top_level.txt,sha256=ru8Fr84cHm6oHr_DcJ8-uaq3RTiuCRFIr6AC8V0zPu4,13
10
+ neuronum-5.8.1.dist-info/RECORD,,
@@ -1,10 +0,0 @@
1
- cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- cli/main.py,sha256=nishUFMtkKJqpCnmSTats30kymNYDIfknKLoesiztVs,31371
3
- neuronum/__init__.py,sha256=Drsm263_w3_VWgl1YsKLUr8WwVodqV3TSjqpxLjyq_M,46
4
- neuronum/neuronum.py,sha256=L8Oz1qcTyOiYMTGfiSKgN5Nu9jcl25jFeJOh0XItPjw,17201
5
- neuronum-5.7.0.dist-info/licenses/LICENSE.md,sha256=zGst0rjgnp6oFuRVwFwB1Ql4sDXt_nw9xbUR49Gf99A,2008
6
- neuronum-5.7.0.dist-info/METADATA,sha256=M7Q5CKsUO2uHGbPXQk43zq2FYFDPfWDnV29rZEW7A-8,5288
7
- neuronum-5.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- neuronum-5.7.0.dist-info/entry_points.txt,sha256=XKYBcRNxGeJpZZkDPsa8HA_RaJ7Km_R_JaUq5T9Nk2U,42
9
- neuronum-5.7.0.dist-info/top_level.txt,sha256=ru8Fr84cHm6oHr_DcJ8-uaq3RTiuCRFIr6AC8V0zPu4,13
10
- neuronum-5.7.0.dist-info/RECORD,,