threadlepy 0.0.1__tar.gz
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.
- threadlepy-0.0.1/LICENSE +0 -0
- threadlepy-0.0.1/PKG-INFO +20 -0
- threadlepy-0.0.1/README.md +5 -0
- threadlepy-0.0.1/pyproject.toml +19 -0
- threadlepy-0.0.1/setup.cfg +4 -0
- threadlepy-0.0.1/src/threadlepy/__init__.py +0 -0
- threadlepy-0.0.1/src/threadlepy/client.py +138 -0
- threadlepy-0.0.1/src/threadlepy/commands.py +372 -0
- threadlepy-0.0.1/src/threadlepy.egg-info/PKG-INFO +20 -0
- threadlepy-0.0.1/src/threadlepy.egg-info/SOURCES.txt +10 -0
- threadlepy-0.0.1/src/threadlepy.egg-info/dependency_links.txt +1 -0
- threadlepy-0.0.1/src/threadlepy.egg-info/top_level.txt +1 -0
threadlepy-0.0.1/LICENSE
ADDED
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: threadlepy
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A small example package
|
|
5
|
+
Author-email: Example Author <author@example.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pypa/sampleproject
|
|
8
|
+
Project-URL: Issues, https://github.com/pypa/sampleproject/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# Example Package
|
|
17
|
+
|
|
18
|
+
This is a simple example package. You can use
|
|
19
|
+
[GitHub-flavored Markdown](https://guides.github.com/features/mastering-markdown/)
|
|
20
|
+
to write your content.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "threadlepy"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name="Example Author", email="author@example.com" },
|
|
6
|
+
]
|
|
7
|
+
description = "A small example package"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.9"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Programming Language :: Python :: 3",
|
|
12
|
+
"Operating System :: OS Independent",
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
license-files = ["LICEN[CS]E*"]
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/pypa/sampleproject"
|
|
19
|
+
Issues = "https://github.com/pypa/sampleproject/issues"
|
|
File without changes
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import signal
|
|
5
|
+
import subprocess
|
|
6
|
+
import shutil
|
|
7
|
+
import json
|
|
8
|
+
import time
|
|
9
|
+
import re
|
|
10
|
+
import select
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any, Dict, Optional, Union
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class ThreadleStruct:
|
|
16
|
+
name: str
|
|
17
|
+
|
|
18
|
+
ThreadleName = Union[str, ThreadleStruct]
|
|
19
|
+
|
|
20
|
+
proc: Optional[subprocess.Popen[str]] = None
|
|
21
|
+
|
|
22
|
+
def stop():
|
|
23
|
+
global proc
|
|
24
|
+
if proc and proc.poll() is None:
|
|
25
|
+
print("threadle stopped")
|
|
26
|
+
print(f"threadle {os.getpgid(proc.pid)} killed")
|
|
27
|
+
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
|
|
28
|
+
proc = None
|
|
29
|
+
|
|
30
|
+
def start(path: str | None = None) -> subprocess.Popen[str]:
|
|
31
|
+
global proc
|
|
32
|
+
exe = path or shutil.which("threadle")
|
|
33
|
+
|
|
34
|
+
if not exe:
|
|
35
|
+
raise FileNotFoundError("Threadle path not found.")
|
|
36
|
+
exe = os.path.abspath(exe)
|
|
37
|
+
|
|
38
|
+
if proc and proc.poll() is None:
|
|
39
|
+
raise RuntimeError("Threadle already started.")
|
|
40
|
+
|
|
41
|
+
proc = subprocess.Popen(
|
|
42
|
+
[exe, "--json", "--silent"],
|
|
43
|
+
stdin=subprocess.PIPE,
|
|
44
|
+
stdout=subprocess.PIPE,
|
|
45
|
+
stderr=subprocess.DEVNULL,
|
|
46
|
+
text=True,
|
|
47
|
+
bufsize=1,
|
|
48
|
+
start_new_session=True,
|
|
49
|
+
)
|
|
50
|
+
print(f"threadle started pid={proc.pid} pgid={os.getpgid(proc.pid)}")
|
|
51
|
+
return proc
|
|
52
|
+
|
|
53
|
+
def collect_args(**kwargs):
|
|
54
|
+
return {k: v for k, v in kwargs.items() if v is not None}
|
|
55
|
+
|
|
56
|
+
def json_cmd(command: str, args: Optional[Dict[str, Any]] = None, assign: Optional[str] = None) -> str:
|
|
57
|
+
dto = {
|
|
58
|
+
"Assign": assign, # None -> JSON null
|
|
59
|
+
"Command": str(command),
|
|
60
|
+
"Args": args or {}, # 关键:保证是 {}
|
|
61
|
+
}
|
|
62
|
+
return json.dumps(dto, ensure_ascii=False)
|
|
63
|
+
|
|
64
|
+
def send_command(cmd_json: str, timeout: float = 30.0) -> Dict[str, Any]:
|
|
65
|
+
global proc
|
|
66
|
+
if proc is None or proc.poll() is not None:
|
|
67
|
+
raise RuntimeError("Threadle process is not running.")
|
|
68
|
+
assert proc.stdin is not None
|
|
69
|
+
assert proc.stdout is not None
|
|
70
|
+
|
|
71
|
+
proc.stdin.write(cmd_json.rstrip("\n") + "\n")
|
|
72
|
+
proc.stdin.flush()
|
|
73
|
+
|
|
74
|
+
out_lines: list[str] = []
|
|
75
|
+
end = time.time() + timeout
|
|
76
|
+
|
|
77
|
+
while time.time() < end:
|
|
78
|
+
r, _, _ = select.select([proc.stdout], [], [], 0.01)
|
|
79
|
+
if not r:
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
line = proc.stdout.readline()
|
|
83
|
+
if line == "":
|
|
84
|
+
raise RuntimeError("EOF from Threadle.")
|
|
85
|
+
|
|
86
|
+
out_lines.append(line)
|
|
87
|
+
|
|
88
|
+
s = re.sub(r"^\s*>\s*", "", line).strip()
|
|
89
|
+
|
|
90
|
+
if s.startswith("{") and s.endswith("}"):
|
|
91
|
+
try:
|
|
92
|
+
resp = json.loads(s)
|
|
93
|
+
if isinstance(resp, dict):
|
|
94
|
+
return resp
|
|
95
|
+
except json.JSONDecodeError:
|
|
96
|
+
pass
|
|
97
|
+
|
|
98
|
+
raise TimeoutError("Timed out waiting for JSON response.")
|
|
99
|
+
|
|
100
|
+
def unwrap(resp: Dict[str, Any], *, print_message: bool = True) -> Any:
|
|
101
|
+
if resp.get("Success") is not True:
|
|
102
|
+
code = resp.get("Code") or "Error"
|
|
103
|
+
msg = resp.get("Message") or "Threadle error"
|
|
104
|
+
raise RuntimeError(f"[{code}] {msg}")
|
|
105
|
+
|
|
106
|
+
if print_message:
|
|
107
|
+
msg = resp.get("Message")
|
|
108
|
+
if isinstance(msg, str) and msg.strip():
|
|
109
|
+
print(msg)
|
|
110
|
+
|
|
111
|
+
return resp.get("Payload")
|
|
112
|
+
|
|
113
|
+
def _norm(v: Any) -> Any:
|
|
114
|
+
if isinstance(v, ThreadleStruct):
|
|
115
|
+
return v.name
|
|
116
|
+
if isinstance(v, bool):
|
|
117
|
+
return "true" if v else "false"
|
|
118
|
+
if isinstance(v, (int, float)):
|
|
119
|
+
return str(v)
|
|
120
|
+
if isinstance(v, (list, tuple)):
|
|
121
|
+
return ";".join(str(x) for x in v)
|
|
122
|
+
return v
|
|
123
|
+
|
|
124
|
+
def call(cmd: str, locals_dict: dict, *, assign: Optional[str] = None, timeout: float = 30.0) -> Any:
|
|
125
|
+
args = collect_args(**locals_dict)
|
|
126
|
+
args.pop("cmd", None)
|
|
127
|
+
args.pop("assign", None)
|
|
128
|
+
|
|
129
|
+
if assign is not None:
|
|
130
|
+
for k, v in list(args.items()):
|
|
131
|
+
if v == assign:
|
|
132
|
+
args.pop(k, None)
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
args = {k: _norm(v) for k, v in args.items()}
|
|
136
|
+
cmd_json = json_cmd(cmd, args=args, assign=assign)
|
|
137
|
+
resp = send_command(cmd_json, timeout=timeout)
|
|
138
|
+
return unwrap(resp)
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from typing import Any, Optional, Union
|
|
3
|
+
|
|
4
|
+
from .client import call, ThreadleStruct, ThreadleName
|
|
5
|
+
|
|
6
|
+
NodeId = Union[int, str]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# --------------------
|
|
10
|
+
# Workdir
|
|
11
|
+
# --------------------
|
|
12
|
+
def get_workdir():
|
|
13
|
+
cmd = "getwd"
|
|
14
|
+
assign = None
|
|
15
|
+
return call(cmd, locals(), assign=assign)
|
|
16
|
+
|
|
17
|
+
def set_workdir(dir: str):
|
|
18
|
+
cmd = "setwd"
|
|
19
|
+
assign = None
|
|
20
|
+
return call(cmd, locals(), assign=assign)
|
|
21
|
+
|
|
22
|
+
# --------------------
|
|
23
|
+
# Meta
|
|
24
|
+
# --------------------
|
|
25
|
+
def inventory():
|
|
26
|
+
cmd = "i"
|
|
27
|
+
assign = None
|
|
28
|
+
return call(cmd, locals(), assign=assign)
|
|
29
|
+
|
|
30
|
+
def info(structure: Union[str, ThreadleStruct]):
|
|
31
|
+
cmd = "info"
|
|
32
|
+
assign = None
|
|
33
|
+
return call(cmd, locals(), assign=assign)
|
|
34
|
+
|
|
35
|
+
# ============================================
|
|
36
|
+
# Layer / Edge / Hyperedge / Affiliation ops
|
|
37
|
+
# ============================================
|
|
38
|
+
|
|
39
|
+
def add_aff(network: ThreadleName, layername: str, nodeid: NodeId, hypername: str,
|
|
40
|
+
addmissingnode: bool = True, addmissingaffiliation: bool = True):
|
|
41
|
+
cmd = "addaff"
|
|
42
|
+
assign = None
|
|
43
|
+
return call(cmd, locals(), assign=assign)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def remove_aff(network: ThreadleName, layername: str, nodeid: NodeId, hypername: str):
|
|
47
|
+
cmd = "removeaff"
|
|
48
|
+
assign = None
|
|
49
|
+
|
|
50
|
+
return call(cmd, locals(), assign=assign)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def add_edge(network: ThreadleName, layername: str, node1id: NodeId, node2id: NodeId,
|
|
54
|
+
value: int = 1, addmissingnodes: bool = True):
|
|
55
|
+
cmd = "addedge"
|
|
56
|
+
assign = None
|
|
57
|
+
return call(cmd, locals(), assign=assign)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def remove_edge(network: ThreadleName, layername: str, node1id: NodeId, node2id: NodeId):
|
|
61
|
+
cmd = "removeedge"
|
|
62
|
+
assign = None
|
|
63
|
+
return call(cmd, locals(), assign=assign)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def check_edge(network: ThreadleName, layername: str, node1id: NodeId, node2id: NodeId):
|
|
67
|
+
cmd = "checkedge"
|
|
68
|
+
assign = None
|
|
69
|
+
return call(cmd, locals(), assign=assign)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def clear_layer(network: ThreadleName, layername: str):
|
|
73
|
+
cmd = "clearlayer"
|
|
74
|
+
assign = None
|
|
75
|
+
return call(cmd, locals(), assign=assign)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def add_hyper(network: ThreadleName, layername: str, hypername: str,
|
|
79
|
+
nodes: Optional[list[Any]] = None, addmissingnodes: bool = True):
|
|
80
|
+
"""
|
|
81
|
+
Notes:
|
|
82
|
+
- ThreadleR collapses node vectors with ';' and uses "" when None.
|
|
83
|
+
"""
|
|
84
|
+
cmd = "addhyper"
|
|
85
|
+
assign = None
|
|
86
|
+
nodes = "" if nodes is None else ";".join(map(str, nodes))
|
|
87
|
+
return call(cmd, locals(), assign=assign)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def remove_hyper(network: ThreadleName, layername: str, hypername: str):
|
|
91
|
+
cmd = "removehyper"
|
|
92
|
+
assign = None
|
|
93
|
+
return call(cmd, locals(), assign=assign)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def add_layer(network: ThreadleName, layername: str, mode: int,
|
|
97
|
+
directed: bool = False, valuetype: str = "binary", selfties: bool = False):
|
|
98
|
+
"""
|
|
99
|
+
valuetype: "binary" or "valued"
|
|
100
|
+
"""
|
|
101
|
+
if valuetype not in ("binary", "valued"):
|
|
102
|
+
raise ValueError('valuetype must be "binary" or "valued".')
|
|
103
|
+
cmd = "addlayer"
|
|
104
|
+
assign = None
|
|
105
|
+
return call(cmd, locals(), assign=assign)
|
|
106
|
+
|
|
107
|
+
def remove_layer(network: Union[str, ThreadleStruct], layername: str):
|
|
108
|
+
cmd = "removelayer"
|
|
109
|
+
assign = None
|
|
110
|
+
return call(cmd, locals(), assign=assign)
|
|
111
|
+
|
|
112
|
+
# ============================================
|
|
113
|
+
# Nodeset / Node ops
|
|
114
|
+
# ============================================
|
|
115
|
+
|
|
116
|
+
def add_node(structure: ThreadleName, id: NodeId):
|
|
117
|
+
cmd = "addnode"
|
|
118
|
+
assign = None
|
|
119
|
+
return call(cmd, locals(), assign=assign)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def remove_node(structure: ThreadleName, nodeid: NodeId):
|
|
123
|
+
cmd = "removenode"
|
|
124
|
+
assign = None
|
|
125
|
+
return call(cmd, locals(), assign=assign)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def get_nbr_nodes(structure: ThreadleName):
|
|
129
|
+
cmd = "getnbrnodes"
|
|
130
|
+
assign = None
|
|
131
|
+
return call(cmd, locals(), assign=assign)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_nodeid_by_index(structure: ThreadleName, index: int):
|
|
135
|
+
cmd = "getnodeidbyindex"
|
|
136
|
+
assign = None
|
|
137
|
+
return call(cmd, locals(), assign=assign)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def get_random_node(structure: ThreadleName):
|
|
141
|
+
cmd = "getrandomnode"
|
|
142
|
+
assign = None
|
|
143
|
+
return call(cmd, locals(), assign=assign)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ============================================
|
|
147
|
+
# Attributes
|
|
148
|
+
# ============================================
|
|
149
|
+
|
|
150
|
+
def define_attr(structure: ThreadleName, attrname: str, attrtype: str = "int"):
|
|
151
|
+
"""
|
|
152
|
+
attrtype: "int", "char", "float", or "bool"
|
|
153
|
+
"""
|
|
154
|
+
if attrtype not in ("int", "char", "float", "bool"):
|
|
155
|
+
raise ValueError('attrtype must be one of: "int", "char", "float", "bool".')
|
|
156
|
+
cmd = "defineattr"
|
|
157
|
+
assign = None
|
|
158
|
+
return call(cmd, locals(), assign=assign)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def undefine_attr(structure: ThreadleName, attrname: str):
|
|
162
|
+
cmd = "undefineattr"
|
|
163
|
+
assign = None
|
|
164
|
+
return call(cmd, locals(), assign=assign)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def get_attr(structure: ThreadleName, nodeid: NodeId, attrname: str):
|
|
168
|
+
cmd = "getattr"
|
|
169
|
+
assign = None
|
|
170
|
+
return call(cmd, locals(), assign=assign)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def set_attr(structure: ThreadleName, nodeid: NodeId, attrname: str, attrvalue: Any):
|
|
174
|
+
cmd = "setattr"
|
|
175
|
+
assign = None
|
|
176
|
+
return call(cmd, locals(), assign=assign)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def remove_attr(structure: ThreadleName, nodeid: NodeId, attrname: str):
|
|
180
|
+
cmd = "removeattr"
|
|
181
|
+
assign = None
|
|
182
|
+
return call(cmd, locals(), assign=assign)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# ============================================
|
|
186
|
+
# Edge queries / paths / neighborhoods
|
|
187
|
+
# ============================================
|
|
188
|
+
|
|
189
|
+
def get_edge(network: ThreadleName, layername: str, node1id: NodeId, node2id: NodeId):
|
|
190
|
+
cmd = "getedge"
|
|
191
|
+
assign = None
|
|
192
|
+
return call(cmd, locals(), assign=assign)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def get_node_alters(network: ThreadleName, nodeid: NodeId, layername: str = "",
|
|
196
|
+
direction: str = "both", unique: bool = False):
|
|
197
|
+
"""
|
|
198
|
+
direction: "both", "in", "out"
|
|
199
|
+
layername:
|
|
200
|
+
- "" for all layers
|
|
201
|
+
- if you pass multiple layer names, pre-join with ';' before calling this wrapper
|
|
202
|
+
"""
|
|
203
|
+
if direction not in ("both", "in", "out"):
|
|
204
|
+
raise ValueError('direction must be one of: "both", "in", "out".')
|
|
205
|
+
cmd = "getnodealters"
|
|
206
|
+
assign = None
|
|
207
|
+
layername = "" if (layername is None or layername == "") else layername
|
|
208
|
+
return call(cmd, locals(), assign=assign)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def get_random_alter(network: ThreadleName, nodeid: NodeId, layername: str = "",
|
|
212
|
+
direction: str = "both", balanced: bool = False):
|
|
213
|
+
if direction not in ("both", "in", "out"):
|
|
214
|
+
raise ValueError('direction must be one of: "both", "in", "out".')
|
|
215
|
+
cmd = "getrandomalter"
|
|
216
|
+
assign = None
|
|
217
|
+
return call(cmd, locals(), assign=assign)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def shortest_path(network: ThreadleName, node1id: NodeId, node2id: NodeId, layername: str = ""):
|
|
221
|
+
cmd = "shortestpath"
|
|
222
|
+
assign = None
|
|
223
|
+
layername = "" if layername is None else layername
|
|
224
|
+
return call(cmd, locals(), assign=assign)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# ============================================
|
|
228
|
+
# Network measures / transforms
|
|
229
|
+
# ============================================
|
|
230
|
+
|
|
231
|
+
def degree(network: ThreadleName, layername: str, attrname: Optional[str] = None, direction: str = "in"):
|
|
232
|
+
if direction not in ("in", "out", "both"):
|
|
233
|
+
raise ValueError('direction must be one of: "in", "out", "both".')
|
|
234
|
+
cmd = "degree"
|
|
235
|
+
assign = None
|
|
236
|
+
return call(cmd, locals(), assign=assign)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def density(network: ThreadleName, layername: str):
|
|
240
|
+
cmd = "density"
|
|
241
|
+
assign = None
|
|
242
|
+
return call(cmd, locals(), assign=assign)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def dichotomize(network: ThreadleName, layername: str,
|
|
246
|
+
cond: str = "ge", threshold: Any = 1,
|
|
247
|
+
truevalue: Any = 1, falsevalue: Any = 0,
|
|
248
|
+
newlayername: Optional[str] = None):
|
|
249
|
+
"""
|
|
250
|
+
cond: "ge","eq","ne","gt","lt","le","isnull","notnull"
|
|
251
|
+
"""
|
|
252
|
+
if cond not in ("ge", "eq", "ne", "gt", "lt", "le", "isnull", "notnull"):
|
|
253
|
+
raise ValueError('cond must be one of: ge, eq, ne, gt, lt, le, isnull, notnull')
|
|
254
|
+
cmd = "dichotomize"
|
|
255
|
+
assign = None
|
|
256
|
+
return call(cmd, locals(), assign=assign)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def components(network: ThreadleName, layname: str, attrname: str):
|
|
260
|
+
cmd = "components"
|
|
261
|
+
assign = None
|
|
262
|
+
return call(cmd, locals(), assign=assign)
|
|
263
|
+
|
|
264
|
+
def symmetrize(network: Union[str, ThreadleStruct], layername: str, newlayername: Optional[str] = None):
|
|
265
|
+
cmd = "symmetrize"
|
|
266
|
+
assign = None
|
|
267
|
+
return call(cmd, locals(), assign=assign)
|
|
268
|
+
|
|
269
|
+
# ============================================
|
|
270
|
+
# Creation / IO / lifecycle
|
|
271
|
+
# ============================================
|
|
272
|
+
|
|
273
|
+
def create_nodeset(name: str, createnodes: int = 0):
|
|
274
|
+
cmd = "createnodeset"
|
|
275
|
+
assign = name
|
|
276
|
+
call(cmd, locals(), assign=assign)
|
|
277
|
+
# Return a client-side handle (assumes you have Nodeset/ThreadleStruct-style wrappers)
|
|
278
|
+
return ThreadleStruct(name=name)
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def create_network(nodeset: ThreadleName, name: str):
|
|
282
|
+
cmd = "createnetwork"
|
|
283
|
+
assign = name
|
|
284
|
+
call(cmd, locals(), assign=assign)
|
|
285
|
+
return ThreadleStruct(name=name)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def load_file(name: str, file: str, type: str):
|
|
289
|
+
"""
|
|
290
|
+
type: "network" or "nodeset"
|
|
291
|
+
"""
|
|
292
|
+
cmd = "loadfile"
|
|
293
|
+
assign = name
|
|
294
|
+
call(cmd, locals(), assign=assign)
|
|
295
|
+
|
|
296
|
+
# Return a client-side handle (replace with Network/Nodeset classes if you have them)
|
|
297
|
+
if type == "network":
|
|
298
|
+
return ThreadleStruct(name=name)
|
|
299
|
+
if type == "nodeset":
|
|
300
|
+
return ThreadleStruct(name=name)
|
|
301
|
+
raise ValueError(f"Unknown type: {type}")
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def save_file(structure: ThreadleName, file: str = ""):
|
|
305
|
+
"""
|
|
306
|
+
If file is empty, ThreadleR defaults to "<structure>.tsv".
|
|
307
|
+
"""
|
|
308
|
+
cmd = "savefile"
|
|
309
|
+
assign = None
|
|
310
|
+
if not file:
|
|
311
|
+
file = f"{structure}.tsv"
|
|
312
|
+
return call(cmd, locals(), assign=assign)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def import_layer(network: ThreadleName, layername: str, file: str,
|
|
316
|
+
format: str = "edgelist", sep: str = "\t", addmissingnodes: bool = False):
|
|
317
|
+
"""
|
|
318
|
+
format: "edgelist" or "matrix"
|
|
319
|
+
"""
|
|
320
|
+
if format not in ("edgelist", "matrix"):
|
|
321
|
+
raise ValueError('format must be "edgelist" or "matrix".')
|
|
322
|
+
cmd = "importlayer"
|
|
323
|
+
assign = None
|
|
324
|
+
return call(cmd, locals(), assign=assign)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def delete(structure: ThreadleName):
|
|
328
|
+
cmd = "delete"
|
|
329
|
+
assign = None
|
|
330
|
+
return call(cmd, locals(), assign=assign)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def delete_all():
|
|
334
|
+
cmd = "deleteall"
|
|
335
|
+
assign = None
|
|
336
|
+
return call(cmd, locals(), assign=assign)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# ============================================
|
|
340
|
+
# Subsetting / filtering
|
|
341
|
+
# ============================================
|
|
342
|
+
|
|
343
|
+
def filter_nodeset(name: str, nodeset: ThreadleName, attrname: str, cond: str, attrvalue: Any):
|
|
344
|
+
cmd = "filter"
|
|
345
|
+
assign = name
|
|
346
|
+
call(cmd, locals(), assign=assign)
|
|
347
|
+
return ThreadleStruct(name=name)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def subnet(name: str, network: ThreadleName, nodeset: ThreadleName):
|
|
351
|
+
cmd = "subnet"
|
|
352
|
+
assign = name
|
|
353
|
+
call(cmd, locals(), assign=assign)
|
|
354
|
+
return ThreadleStruct(name=name)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
# ============================================
|
|
358
|
+
# Random generation / settings
|
|
359
|
+
# ============================================
|
|
360
|
+
|
|
361
|
+
def generate(network: ThreadleName, layername: str, type: str,
|
|
362
|
+
p: Optional[float] = None, k: Optional[int] = None,
|
|
363
|
+
beta: Optional[float] = None, m: Optional[int] = None):
|
|
364
|
+
cmd = "generate"
|
|
365
|
+
assign = None
|
|
366
|
+
return call(cmd, locals(), assign=assign)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def setting(name: str, value: Any):
|
|
370
|
+
cmd = "setting"
|
|
371
|
+
assign = None
|
|
372
|
+
return call(cmd, locals(), assign=assign)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: threadlepy
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A small example package
|
|
5
|
+
Author-email: Example Author <author@example.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/pypa/sampleproject
|
|
8
|
+
Project-URL: Issues, https://github.com/pypa/sampleproject/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Dynamic: license-file
|
|
15
|
+
|
|
16
|
+
# Example Package
|
|
17
|
+
|
|
18
|
+
This is a simple example package. You can use
|
|
19
|
+
[GitHub-flavored Markdown](https://guides.github.com/features/mastering-markdown/)
|
|
20
|
+
to write your content.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/threadlepy/__init__.py
|
|
5
|
+
src/threadlepy/client.py
|
|
6
|
+
src/threadlepy/commands.py
|
|
7
|
+
src/threadlepy.egg-info/PKG-INFO
|
|
8
|
+
src/threadlepy.egg-info/SOURCES.txt
|
|
9
|
+
src/threadlepy.egg-info/dependency_links.txt
|
|
10
|
+
src/threadlepy.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
threadlepy
|