threadlepy 0.1.1__tar.gz → 0.1.2__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.1.1 → threadlepy-0.1.2}/CHANGELOG.md +6 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/MANIFEST.in +1 -0
- {threadlepy-0.1.1/src/threadlepy.egg-info → threadlepy-0.1.2}/PKG-INFO +1 -1
- {threadlepy-0.1.1 → threadlepy-0.1.2}/pyproject.toml +2 -2
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/__init__.py +1 -1
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/client.py +7 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/commands.py +120 -34
- threadlepy-0.1.2/src/threadlepy/py.typed +1 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2/src/threadlepy.egg-info}/PKG-INFO +1 -1
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy.egg-info/SOURCES.txt +1 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/LICENSE +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/README.md +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/docs/threadlepy_tutorial.ipynb +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/scripts/test_commands.py +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/scripts/test_session.py +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/setup.cfg +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/Scripts/create.txt +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/dscw.tsv +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/dscw_nodeset.tsv +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/lazega.tsv +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/lazega_female.tsv +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/lazega_female_nodeset.tsv +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/lazega_nodes.tsv +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/mynet.tsv +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/mynet_nodesetfile.tsv +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/mynodes.tsv +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy.egg-info/dependency_links.txt +0 -0
- {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
- Added one-line docstrings for public command wrappers.
|
|
6
|
+
- Added `Literal` type hints for finite-choice parameters to improve editor completions.
|
|
7
|
+
- Marked the package as typed with `py.typed`.
|
|
8
|
+
|
|
3
9
|
## 0.1.1
|
|
4
10
|
|
|
5
11
|
- Updated PyPI project description to match README.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "threadlepy"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "Python client for the Threadle CLI JSON interface."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -27,4 +27,4 @@ Source = "https://github.com/YukunJiao/threadlepy"
|
|
|
27
27
|
where = ["src"]
|
|
28
28
|
|
|
29
29
|
[tool.setuptools.package-data]
|
|
30
|
-
threadlepy = ["Examples/*.tsv", "Examples/Scripts/*.txt"]
|
|
30
|
+
threadlepy = ["py.typed", "Examples/*.tsv", "Examples/Scripts/*.txt"]
|
|
@@ -48,6 +48,7 @@ def configure(
|
|
|
48
48
|
default_timeout = float(timeout)
|
|
49
49
|
|
|
50
50
|
def stop():
|
|
51
|
+
"""Stop the running Threadle subprocess, if one is active."""
|
|
51
52
|
global proc
|
|
52
53
|
if proc and proc.poll() is None:
|
|
53
54
|
print("threadle stopped")
|
|
@@ -56,6 +57,7 @@ def stop():
|
|
|
56
57
|
proc = None
|
|
57
58
|
|
|
58
59
|
def start(path: str | None = None) -> subprocess.Popen[str]:
|
|
60
|
+
"""Start the Threadle CLI JSON subprocess and return the process handle."""
|
|
59
61
|
global proc
|
|
60
62
|
if path is None or not str(path).strip():
|
|
61
63
|
exe = shutil.which("threadle")
|
|
@@ -112,9 +114,11 @@ def session(path: str | None = None, **config: Any) -> Iterator[subprocess.Popen
|
|
|
112
114
|
stop()
|
|
113
115
|
|
|
114
116
|
def collect_args(**kwargs):
|
|
117
|
+
"""Return keyword arguments whose values are not ``None``."""
|
|
115
118
|
return {k: v for k, v in kwargs.items() if v is not None}
|
|
116
119
|
|
|
117
120
|
def json_cmd(command: str, args: Optional[Dict[str, Any]] = None, assign: Optional[str] = None) -> str:
|
|
121
|
+
"""Build a JSON command payload for the Threadle CLI."""
|
|
118
122
|
dto = {
|
|
119
123
|
"Assign": assign,
|
|
120
124
|
"Command": str(command),
|
|
@@ -123,6 +127,7 @@ def json_cmd(command: str, args: Optional[Dict[str, Any]] = None, assign: Option
|
|
|
123
127
|
return json.dumps(dto, ensure_ascii=False)
|
|
124
128
|
|
|
125
129
|
def send_command(cmd_json: str, timeout: Optional[float] = None) -> Dict[str, Any]:
|
|
130
|
+
"""Send a JSON command to Threadle and wait for a JSON response."""
|
|
126
131
|
global proc
|
|
127
132
|
if proc is None or proc.poll() is not None:
|
|
128
133
|
raise RuntimeError("Threadle process is not running.")
|
|
@@ -177,6 +182,7 @@ def send_command(cmd_json: str, timeout: Optional[float] = None) -> Dict[str, An
|
|
|
177
182
|
raise TimeoutError(hint)
|
|
178
183
|
|
|
179
184
|
def unwrap(resp: Dict[str, Any], *, print_message: bool = True) -> Any:
|
|
185
|
+
"""Validate a Threadle response and return its payload."""
|
|
180
186
|
if resp.get("Success") is not True:
|
|
181
187
|
code = resp.get("Code") or "Error"
|
|
182
188
|
msg = resp.get("Message") or "Threadle error"
|
|
@@ -209,6 +215,7 @@ def call(
|
|
|
209
215
|
return_response: bool = False,
|
|
210
216
|
drop: tuple[str, ...] = (),
|
|
211
217
|
) -> Any:
|
|
218
|
+
"""Call a typed Threadle command wrapper using the caller's local variables."""
|
|
212
219
|
args = collect_args(**locals_dict)
|
|
213
220
|
args.pop("cmd", None)
|
|
214
221
|
args.pop("assign", None)
|
|
@@ -3,15 +3,33 @@ from __future__ import annotations
|
|
|
3
3
|
import os
|
|
4
4
|
import shutil
|
|
5
5
|
from importlib import resources
|
|
6
|
-
from typing import Any, Optional, Union
|
|
6
|
+
from typing import Any, Literal, Optional, Union
|
|
7
7
|
|
|
8
8
|
from .client import ThreadleName, ThreadleStruct, call, is_available as _client_is_available, raw_cmd, start, stop
|
|
9
9
|
|
|
10
10
|
NodeId = Union[int, str]
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
Condition = Literal["eq", "ne", "gt", "lt", "ge", "le", "isnull", "notnull"]
|
|
12
|
+
NumericCondition = Literal["eq", "ne", "gt", "lt", "ge", "le"]
|
|
13
|
+
Direction = Literal["both", "in", "out"]
|
|
14
|
+
AttrType = Literal["int", "char", "float", "bool", "string"]
|
|
15
|
+
LayerMode = Literal[1, 2, "1", "2"]
|
|
16
|
+
LayerValueType = Literal["binary", "valued"]
|
|
17
|
+
LayerFormat = Literal["edgelist", "matrix"]
|
|
18
|
+
StructureType = Literal["network", "nodeset"]
|
|
19
|
+
SymmetrizeMethod = Literal["max", "min", "minnonzero", "average", "sum", "product"]
|
|
20
|
+
ProjectMethod = Literal["count", "newman", "binary"]
|
|
21
|
+
GenerateType = Literal["er", "ws", "ba", "2mode"]
|
|
22
|
+
|
|
23
|
+
CONDITIONS: tuple[Condition, ...] = ("eq", "ne", "gt", "lt", "ge", "le", "isnull", "notnull")
|
|
24
|
+
NUMERIC_CONDITIONS: tuple[NumericCondition, ...] = ("eq", "ne", "gt", "lt", "ge", "le")
|
|
25
|
+
DIRECTIONS: tuple[Direction, ...] = ("both", "in", "out")
|
|
26
|
+
ATTR_TYPES: tuple[AttrType, ...] = ("int", "char", "float", "bool", "string")
|
|
27
|
+
LAYER_VALUE_TYPES: tuple[LayerValueType, ...] = ("binary", "valued")
|
|
28
|
+
LAYER_FORMATS: tuple[LayerFormat, ...] = ("edgelist", "matrix")
|
|
29
|
+
STRUCTURE_TYPES: tuple[StructureType, ...] = ("network", "nodeset")
|
|
30
|
+
SYMMETRIZE_METHODS: tuple[SymmetrizeMethod, ...] = ("max", "min", "minnonzero", "average", "sum", "product")
|
|
31
|
+
PROJECT_METHODS: tuple[ProjectMethod, ...] = ("count", "newman", "binary")
|
|
32
|
+
GENERATE_TYPES: tuple[GenerateType, ...] = ("er", "ws", "ba", "2mode")
|
|
15
33
|
|
|
16
34
|
|
|
17
35
|
def _check(value: str, allowed: tuple[str, ...], name: str) -> str:
|
|
@@ -37,12 +55,12 @@ def _handle(name: str) -> ThreadleStruct:
|
|
|
37
55
|
# Process escape hatch
|
|
38
56
|
# --------------------
|
|
39
57
|
def is_available(path: Optional[str] = "threadle") -> bool:
|
|
40
|
-
"""Return whether the Threadle executable can be found."""
|
|
58
|
+
"""Return whether the Threadle CLI executable can be found."""
|
|
41
59
|
return _client_is_available(path)
|
|
42
60
|
|
|
43
61
|
|
|
44
62
|
def start_threadle(path: Optional[str] = None):
|
|
45
|
-
"""Start the Threadle CLI process."""
|
|
63
|
+
"""Start the Threadle CLI process in JSON mode."""
|
|
46
64
|
return start(path)
|
|
47
65
|
|
|
48
66
|
|
|
@@ -56,10 +74,10 @@ def cmd(
|
|
|
56
74
|
args: Optional[dict[str, Any]] = None,
|
|
57
75
|
*,
|
|
58
76
|
assign: Optional[str] = None,
|
|
59
|
-
type: Optional[
|
|
77
|
+
type: Optional[StructureType] = None,
|
|
60
78
|
**kwargs: Any,
|
|
61
79
|
):
|
|
62
|
-
"""Send a raw Threadle command
|
|
80
|
+
"""Send a raw Threadle command to the backend."""
|
|
63
81
|
if command is None:
|
|
64
82
|
command = kwargs.pop("cmd", None)
|
|
65
83
|
elif "cmd" in kwargs:
|
|
@@ -79,10 +97,12 @@ def cmd(
|
|
|
79
97
|
# Workdir / filesystem
|
|
80
98
|
# --------------------
|
|
81
99
|
def get_workdir():
|
|
100
|
+
"""Return Threadle's current working directory."""
|
|
82
101
|
return call("getwd", locals())
|
|
83
102
|
|
|
84
103
|
|
|
85
104
|
def set_workdir(dir: str):
|
|
105
|
+
"""Set Threadle's current working directory."""
|
|
86
106
|
return call("setwd", locals())
|
|
87
107
|
|
|
88
108
|
|
|
@@ -92,11 +112,12 @@ def sync_wd():
|
|
|
92
112
|
|
|
93
113
|
|
|
94
114
|
def dir(path: Optional[str] = None):
|
|
115
|
+
"""List files in a Threadle-visible directory."""
|
|
95
116
|
return call("dir", locals())
|
|
96
117
|
|
|
97
118
|
|
|
98
119
|
def stage_examples_to_wd(folder: str = "threadle_examples", overwrite: bool = True) -> str:
|
|
99
|
-
"""Copy bundled example files into a folder
|
|
120
|
+
"""Copy bundled example files into a local folder."""
|
|
100
121
|
dest = os.path.expanduser(folder)
|
|
101
122
|
if not os.path.isabs(dest):
|
|
102
123
|
dest = os.path.join(os.getcwd(), dest)
|
|
@@ -116,18 +137,22 @@ def stage_examples_to_wd(folder: str = "threadle_examples", overwrite: bool = Tr
|
|
|
116
137
|
# Meta
|
|
117
138
|
# --------------------
|
|
118
139
|
def inventory():
|
|
140
|
+
"""Return the objects currently stored in the Threadle session."""
|
|
119
141
|
return call("i", locals())
|
|
120
142
|
|
|
121
143
|
|
|
122
144
|
def i():
|
|
145
|
+
"""Return the objects currently stored in the Threadle session."""
|
|
123
146
|
return inventory()
|
|
124
147
|
|
|
125
148
|
|
|
126
149
|
def info(structure: ThreadleName):
|
|
150
|
+
"""Return metadata for a Threadle network or nodeset."""
|
|
127
151
|
return call("info", locals())
|
|
128
152
|
|
|
129
153
|
|
|
130
154
|
def preview(structure: ThreadleName):
|
|
155
|
+
"""Return a human-readable preview of a Threadle network or nodeset."""
|
|
131
156
|
return call("preview", locals())
|
|
132
157
|
|
|
133
158
|
|
|
@@ -143,12 +168,14 @@ def add_aff(
|
|
|
143
168
|
addmissinghyperedge: bool = True,
|
|
144
169
|
addmissingaffiliation: Optional[bool] = None,
|
|
145
170
|
):
|
|
171
|
+
"""Add a node-to-hyperedge affiliation in a two-mode layer."""
|
|
146
172
|
if addmissingaffiliation is not None:
|
|
147
173
|
addmissinghyperedge = addmissingaffiliation
|
|
148
174
|
return call("addaff", locals(), drop=("addmissingaffiliation",))
|
|
149
175
|
|
|
150
176
|
|
|
151
177
|
def remove_aff(network: ThreadleName, layername: str, nodeid: NodeId, hypername: str):
|
|
178
|
+
"""Remove a node-to-hyperedge affiliation from a two-mode layer."""
|
|
152
179
|
return call("removeaff", locals())
|
|
153
180
|
|
|
154
181
|
|
|
@@ -160,30 +187,37 @@ def add_edge(
|
|
|
160
187
|
value: Any = 1,
|
|
161
188
|
addmissingnodes: bool = True,
|
|
162
189
|
):
|
|
190
|
+
"""Add an edge between two nodes in a one-mode layer."""
|
|
163
191
|
return call("addedge", locals())
|
|
164
192
|
|
|
165
193
|
|
|
166
194
|
def remove_edge(network: ThreadleName, layername: str, node1id: NodeId, node2id: NodeId):
|
|
195
|
+
"""Remove an edge between two nodes from a one-mode layer."""
|
|
167
196
|
return call("removeedge", locals())
|
|
168
197
|
|
|
169
198
|
|
|
170
199
|
def check_edge(network: ThreadleName, layername: str, node1id: NodeId, node2id: NodeId):
|
|
200
|
+
"""Return whether two nodes are connected in a layer."""
|
|
171
201
|
return call("checkedge", locals())
|
|
172
202
|
|
|
173
203
|
|
|
174
204
|
def get_edge(network: ThreadleName, layername: str, node1id: NodeId, node2id: NodeId):
|
|
205
|
+
"""Return the value of an edge between two nodes."""
|
|
175
206
|
return call("getedge", locals())
|
|
176
207
|
|
|
177
208
|
|
|
178
209
|
def get_random_edge(network: ThreadleName, layername: str, maxattempts: int = 100):
|
|
210
|
+
"""Return a random edge from a layer."""
|
|
179
211
|
return call("getrandomedge", locals())
|
|
180
212
|
|
|
181
213
|
|
|
182
214
|
def get_all_edges(network: ThreadleName, layername: str, offset: int = 0, limit: int = 1000):
|
|
215
|
+
"""Return edges from a one-mode layer with optional pagination."""
|
|
183
216
|
return call("getalledges", locals())
|
|
184
217
|
|
|
185
218
|
|
|
186
219
|
def clear_layer(network: ThreadleName, layername: str):
|
|
220
|
+
"""Remove all edges or hyperedges from a layer while keeping the layer."""
|
|
187
221
|
return call("clearlayer", locals())
|
|
188
222
|
|
|
189
223
|
|
|
@@ -194,41 +228,48 @@ def add_hyper(
|
|
|
194
228
|
nodes: Optional[Union[list[Any], tuple[Any, ...], str]] = None,
|
|
195
229
|
addmissingnodes: bool = True,
|
|
196
230
|
):
|
|
231
|
+
"""Add a hyperedge to a two-mode layer."""
|
|
197
232
|
nodes = _collapse(nodes)
|
|
198
233
|
return call("addhyper", locals())
|
|
199
234
|
|
|
200
235
|
|
|
201
236
|
def remove_hyper(network: ThreadleName, layername: str, hypername: str):
|
|
237
|
+
"""Remove a hyperedge from a two-mode layer."""
|
|
202
238
|
return call("removehyper", locals())
|
|
203
239
|
|
|
204
240
|
|
|
205
241
|
def get_all_hyperedges(network: ThreadleName, layername: str, offset: int = 0, limit: int = 1000):
|
|
242
|
+
"""Return hyperedges from a two-mode layer with optional pagination."""
|
|
206
243
|
return call("getallhyperedges", locals())
|
|
207
244
|
|
|
208
245
|
|
|
209
246
|
def get_hyperedge_nodes(network: ThreadleName, layername: str, hypername: str):
|
|
247
|
+
"""Return the nodes affiliated with a hyperedge."""
|
|
210
248
|
return call("gethyperedgenodes", locals())
|
|
211
249
|
|
|
212
250
|
|
|
213
251
|
def get_node_hyperedges(network: ThreadleName, layername: str, nodeid: NodeId):
|
|
252
|
+
"""Return the hyperedges affiliated with a node."""
|
|
214
253
|
return call("getnodehyperedges", locals())
|
|
215
254
|
|
|
216
255
|
|
|
217
256
|
def add_layer(
|
|
218
257
|
network: ThreadleName,
|
|
219
258
|
layername: str,
|
|
220
|
-
mode:
|
|
259
|
+
mode: LayerMode,
|
|
221
260
|
directed: bool = False,
|
|
222
|
-
valuetype:
|
|
261
|
+
valuetype: LayerValueType = "binary",
|
|
223
262
|
selfties: bool = False,
|
|
224
263
|
):
|
|
264
|
+
"""Add a one-mode or two-mode relational layer to a network."""
|
|
225
265
|
if str(mode) not in ("1", "2"):
|
|
226
266
|
raise ValueError("mode must be 1 or 2.")
|
|
227
|
-
_check(valuetype,
|
|
267
|
+
_check(valuetype, LAYER_VALUE_TYPES, "valuetype")
|
|
228
268
|
return call("addlayer", locals())
|
|
229
269
|
|
|
230
270
|
|
|
231
271
|
def remove_layer(network: ThreadleName, layername: str):
|
|
272
|
+
"""Remove a relational layer and its ties from a network."""
|
|
232
273
|
return call("removelayer", locals())
|
|
233
274
|
|
|
234
275
|
|
|
@@ -236,7 +277,7 @@ def import_layer(
|
|
|
236
277
|
network: ThreadleName,
|
|
237
278
|
layername: str,
|
|
238
279
|
file: str,
|
|
239
|
-
format:
|
|
280
|
+
format: LayerFormat = "edgelist",
|
|
240
281
|
node1col: int = 0,
|
|
241
282
|
node2col: int = 1,
|
|
242
283
|
valuecol: int = 2,
|
|
@@ -246,11 +287,13 @@ def import_layer(
|
|
|
246
287
|
sep: str = "\t",
|
|
247
288
|
addmissingnodes: bool = False,
|
|
248
289
|
):
|
|
249
|
-
|
|
290
|
+
"""Import ties into an existing network layer from a file."""
|
|
291
|
+
_check(format, LAYER_FORMATS, "format")
|
|
250
292
|
return call("importlayer", locals())
|
|
251
293
|
|
|
252
294
|
|
|
253
295
|
def export_layer(network: ThreadleName, layername: str, file: str, header: bool = True, sep: str = "\t"):
|
|
296
|
+
"""Export a network layer as an edge list file."""
|
|
254
297
|
return call("exportlayer", locals())
|
|
255
298
|
|
|
256
299
|
|
|
@@ -258,6 +301,7 @@ def export_layer(network: ThreadleName, layername: str, file: str, header: bool
|
|
|
258
301
|
# Nodeset / Node ops
|
|
259
302
|
# ============================================
|
|
260
303
|
def add_node(structure: ThreadleName, nodeid: Optional[NodeId] = None, id: Optional[NodeId] = None):
|
|
304
|
+
"""Add a node to a nodeset or to the nodeset of a network."""
|
|
261
305
|
if nodeid is None:
|
|
262
306
|
nodeid = id
|
|
263
307
|
if nodeid is None:
|
|
@@ -266,68 +310,81 @@ def add_node(structure: ThreadleName, nodeid: Optional[NodeId] = None, id: Optio
|
|
|
266
310
|
|
|
267
311
|
|
|
268
312
|
def remove_node(structure: ThreadleName, nodeid: NodeId):
|
|
313
|
+
"""Remove a node from a nodeset or network nodeset."""
|
|
269
314
|
return call("removenode", locals())
|
|
270
315
|
|
|
271
316
|
|
|
272
317
|
def get_nbr_nodes(structure: ThreadleName):
|
|
318
|
+
"""Return the number of nodes in a network or nodeset."""
|
|
273
319
|
return call("getnbrnodes", locals())
|
|
274
320
|
|
|
275
321
|
|
|
276
322
|
def get_all_nodes(structure: ThreadleName, offset: int = 0, limit: int = 1000):
|
|
323
|
+
"""Return nodes from a network or nodeset with optional pagination."""
|
|
277
324
|
return call("getallnodes", locals())
|
|
278
325
|
|
|
279
326
|
|
|
280
327
|
def get_nodeid_by_index(structure: ThreadleName, index: int):
|
|
328
|
+
"""Return the node id at an index position."""
|
|
281
329
|
return call("getnodeidbyindex", locals())
|
|
282
330
|
|
|
283
331
|
|
|
284
332
|
def get_random_node(structure: ThreadleName):
|
|
333
|
+
"""Return a random node id from a network or nodeset."""
|
|
285
334
|
return call("getrandomnode", locals())
|
|
286
335
|
|
|
287
336
|
|
|
288
337
|
# ============================================
|
|
289
338
|
# Attributes
|
|
290
339
|
# ============================================
|
|
291
|
-
def define_attr(structure: ThreadleName, attrname: str, attrtype:
|
|
340
|
+
def define_attr(structure: ThreadleName, attrname: str, attrtype: AttrType = "int"):
|
|
341
|
+
"""Define a node attribute on a network or nodeset."""
|
|
292
342
|
_check(attrtype, ATTR_TYPES, "attrtype")
|
|
293
343
|
return call("defineattr", locals())
|
|
294
344
|
|
|
295
345
|
|
|
296
346
|
def undefine_attr(structure: ThreadleName, attrname: str):
|
|
347
|
+
"""Remove a node attribute definition and its stored values."""
|
|
297
348
|
return call("undefineattr", locals())
|
|
298
349
|
|
|
299
350
|
|
|
300
351
|
def get_attr(structure: ThreadleName, nodeid: NodeId, attrname: str):
|
|
352
|
+
"""Return one node attribute value."""
|
|
301
353
|
return call("getattr", locals())
|
|
302
354
|
|
|
303
355
|
|
|
304
356
|
def get_attrs(structure: ThreadleName, nodes: Union[str, list[NodeId], tuple[NodeId, ...]], attrname: str):
|
|
357
|
+
"""Return one attribute value for multiple nodes."""
|
|
305
358
|
nodes = _collapse(nodes)
|
|
306
359
|
return call("getattrs", locals())
|
|
307
360
|
|
|
308
361
|
|
|
309
362
|
def get_attr_summary(structure: ThreadleName, attrname: str):
|
|
363
|
+
"""Return summary statistics for a node attribute."""
|
|
310
364
|
return call("getattrsummary", locals())
|
|
311
365
|
|
|
312
366
|
|
|
313
367
|
def set_attr(structure: ThreadleName, nodeid: NodeId, attrname: str, attrvalue: Any):
|
|
368
|
+
"""Set one node attribute value."""
|
|
314
369
|
return call("setattr", locals())
|
|
315
370
|
|
|
316
371
|
|
|
317
372
|
def remove_attr(structure: ThreadleName, nodeid: NodeId, attrname: str):
|
|
373
|
+
"""Remove one node attribute value."""
|
|
318
374
|
return call("removeattr", locals())
|
|
319
375
|
|
|
320
376
|
|
|
321
377
|
def generate_attr(
|
|
322
378
|
structure: ThreadleName,
|
|
323
379
|
attrname: str,
|
|
324
|
-
attrtype:
|
|
380
|
+
attrtype: AttrType = "int",
|
|
325
381
|
min: Optional[Any] = None,
|
|
326
382
|
max: Optional[Any] = None,
|
|
327
383
|
p: float = 0.5,
|
|
328
384
|
chars: str = "m;f;o",
|
|
329
385
|
values: Optional[Union[str, list[Any], tuple[Any, ...]]] = None,
|
|
330
386
|
):
|
|
387
|
+
"""Generate random values for a node attribute."""
|
|
331
388
|
_check(attrtype, ATTR_TYPES, "attrtype")
|
|
332
389
|
if attrtype == "int":
|
|
333
390
|
min = 0 if min is None else min
|
|
@@ -346,10 +403,11 @@ def get_node_alters(
|
|
|
346
403
|
network: ThreadleName,
|
|
347
404
|
nodeid: NodeId,
|
|
348
405
|
layernames: Optional[Union[str, list[str], tuple[str, ...]]] = "",
|
|
349
|
-
direction:
|
|
406
|
+
direction: Direction = "out",
|
|
350
407
|
unique: bool = True,
|
|
351
408
|
layername: Optional[Union[str, list[str], tuple[str, ...]]] = None,
|
|
352
409
|
):
|
|
410
|
+
"""Return alters of a node within one or more layers."""
|
|
353
411
|
_check(direction, DIRECTIONS, "direction")
|
|
354
412
|
if layername is not None and not layernames:
|
|
355
413
|
layernames = layername
|
|
@@ -361,11 +419,12 @@ def get_random_alter(
|
|
|
361
419
|
network: ThreadleName,
|
|
362
420
|
nodeid: NodeId,
|
|
363
421
|
layernames: Optional[Union[str, list[str], tuple[str, ...]]] = "",
|
|
364
|
-
direction:
|
|
422
|
+
direction: Direction = "both",
|
|
365
423
|
balanced: bool = False,
|
|
366
424
|
weighted: bool = False,
|
|
367
425
|
layername: Optional[Union[str, list[str], tuple[str, ...]]] = None,
|
|
368
426
|
):
|
|
427
|
+
"""Return a random alter of a node."""
|
|
369
428
|
_check(direction, DIRECTIONS, "direction")
|
|
370
429
|
if layername is not None and not layernames:
|
|
371
430
|
layernames = layername
|
|
@@ -377,10 +436,11 @@ def get_degree(
|
|
|
377
436
|
network: ThreadleName,
|
|
378
437
|
nodeid: NodeId,
|
|
379
438
|
layernames: Optional[Union[str, list[str], tuple[str, ...]]] = None,
|
|
380
|
-
direction:
|
|
439
|
+
direction: Direction = "out",
|
|
381
440
|
unique: bool = True,
|
|
382
441
|
layername: Optional[Union[str, list[str], tuple[str, ...]]] = None,
|
|
383
442
|
):
|
|
443
|
+
"""Return the degree of a node within one or more layers."""
|
|
384
444
|
_check(direction, DIRECTIONS, "direction")
|
|
385
445
|
if layername is not None and not layernames:
|
|
386
446
|
layernames = layername
|
|
@@ -395,6 +455,7 @@ def shortest_path(
|
|
|
395
455
|
layernames: Optional[Union[str, list[str], tuple[str, ...]]] = None,
|
|
396
456
|
layername: Optional[Union[str, list[str], tuple[str, ...]]] = None,
|
|
397
457
|
):
|
|
458
|
+
"""Return the shortest path distance between two nodes."""
|
|
398
459
|
if layername is not None and not layernames:
|
|
399
460
|
layernames = layername
|
|
400
461
|
layernames = _collapse(layernames)
|
|
@@ -408,6 +469,7 @@ def shortest_paths(
|
|
|
408
469
|
layernames: Optional[Union[str, list[str], tuple[str, ...]]] = None,
|
|
409
470
|
layername: Optional[Union[str, list[str], tuple[str, ...]]] = None,
|
|
410
471
|
):
|
|
472
|
+
"""Compute category-level average shortest paths and return a result network handle."""
|
|
411
473
|
if layername is not None and not layernames:
|
|
412
474
|
layernames = layername
|
|
413
475
|
layernames = _collapse(layernames)
|
|
@@ -418,25 +480,28 @@ def shortest_paths(
|
|
|
418
480
|
# ============================================
|
|
419
481
|
# Network measures / transforms
|
|
420
482
|
# ============================================
|
|
421
|
-
def degree(network: ThreadleName, layername: str, attrname: Optional[str] = None, direction:
|
|
483
|
+
def degree(network: ThreadleName, layername: str, attrname: Optional[str] = None, direction: Direction = "out"):
|
|
484
|
+
"""Compute node degree values for a layer."""
|
|
422
485
|
_check(direction, DIRECTIONS, "direction")
|
|
423
486
|
return call("degree", locals())
|
|
424
487
|
|
|
425
488
|
|
|
426
489
|
def density(network: ThreadleName, layername: str, samplesize: int = 200):
|
|
490
|
+
"""Compute or estimate the density of a layer."""
|
|
427
491
|
return call("density", locals())
|
|
428
492
|
|
|
429
493
|
|
|
430
494
|
def dichotomize(
|
|
431
495
|
network: ThreadleName,
|
|
432
496
|
layername: str,
|
|
433
|
-
cond:
|
|
497
|
+
cond: NumericCondition = "ge",
|
|
434
498
|
threshold: Any = 1,
|
|
435
499
|
truevalue: Any = 1,
|
|
436
500
|
falsevalue: Any = 0,
|
|
437
501
|
newlayername: Optional[str] = None,
|
|
438
502
|
):
|
|
439
|
-
|
|
503
|
+
"""Create a binary layer by thresholding a valued one-mode layer."""
|
|
504
|
+
_check(cond, NUMERIC_CONDITIONS, "cond")
|
|
440
505
|
return call("dichotomize", locals())
|
|
441
506
|
|
|
442
507
|
|
|
@@ -446,6 +511,7 @@ def components(
|
|
|
446
511
|
attrname: Optional[str] = None,
|
|
447
512
|
layname: Optional[str] = None,
|
|
448
513
|
):
|
|
514
|
+
"""Compute connected components for a network layer."""
|
|
449
515
|
if layername is None:
|
|
450
516
|
layername = layname
|
|
451
517
|
if layername is None:
|
|
@@ -456,28 +522,32 @@ def components(
|
|
|
456
522
|
def symmetrize(
|
|
457
523
|
network: ThreadleName,
|
|
458
524
|
layername: str,
|
|
459
|
-
method:
|
|
525
|
+
method: SymmetrizeMethod = "max",
|
|
460
526
|
newlayername: Optional[str] = None,
|
|
461
527
|
):
|
|
462
|
-
|
|
528
|
+
"""Create a symmetric version of a one-mode layer."""
|
|
529
|
+
_check(method, SYMMETRIZE_METHODS, "method")
|
|
463
530
|
return call("symmetrize", locals())
|
|
464
531
|
|
|
465
532
|
|
|
466
533
|
def project_two_mode(
|
|
467
534
|
network: ThreadleName,
|
|
468
535
|
layername: str,
|
|
469
|
-
method:
|
|
536
|
+
method: ProjectMethod = "count",
|
|
470
537
|
newlayername: Optional[str] = None,
|
|
471
538
|
):
|
|
472
|
-
|
|
539
|
+
"""Project a two-mode layer to a one-mode layer."""
|
|
540
|
+
_check(method, PROJECT_METHODS, "method")
|
|
473
541
|
return call("projecttwomode", locals())
|
|
474
542
|
|
|
475
543
|
|
|
476
544
|
def pack(network: ThreadleName, layername: Optional[str] = None):
|
|
545
|
+
"""Convert one or all dynamic layers to a static memory-efficient representation."""
|
|
477
546
|
return call("pack", locals())
|
|
478
547
|
|
|
479
548
|
|
|
480
549
|
def unpack(network: ThreadleName, layername: Optional[str] = None):
|
|
550
|
+
"""Convert one or all static layers back to an editable representation."""
|
|
481
551
|
return call("unpack", locals())
|
|
482
552
|
|
|
483
553
|
|
|
@@ -485,17 +555,20 @@ def unpack(network: ThreadleName, layername: Optional[str] = None):
|
|
|
485
555
|
# Creation / IO / lifecycle
|
|
486
556
|
# ============================================
|
|
487
557
|
def create_nodeset(var: str, name: Optional[str] = None, createnodes: int = 0):
|
|
558
|
+
"""Create a nodeset and return its Threadle handle."""
|
|
488
559
|
call("createnodeset", locals(), assign=var)
|
|
489
560
|
return _handle(var)
|
|
490
561
|
|
|
491
562
|
|
|
492
563
|
def create_network(var: str, nodeset: ThreadleName, name: Optional[str] = None):
|
|
564
|
+
"""Create a network from a nodeset and return its Threadle handle."""
|
|
493
565
|
call("createnetwork", locals(), assign=var)
|
|
494
566
|
return _handle(var)
|
|
495
567
|
|
|
496
568
|
|
|
497
|
-
def load_file(name: str, file: str, type:
|
|
498
|
-
|
|
569
|
+
def load_file(name: str, file: str, type: StructureType, pack: bool = False):
|
|
570
|
+
"""Load a nodeset or network file and return its Threadle handle."""
|
|
571
|
+
_check(type, STRUCTURE_TYPES, "type")
|
|
499
572
|
file2 = os.path.expanduser(file)
|
|
500
573
|
old_wd = None
|
|
501
574
|
if os.path.dirname(file2):
|
|
@@ -513,10 +586,12 @@ def load_file(name: str, file: str, type: str, pack: bool = False):
|
|
|
513
586
|
|
|
514
587
|
|
|
515
588
|
def save_file(structure: ThreadleName, file: str = ""):
|
|
589
|
+
"""Save a Threadle network or nodeset to disk."""
|
|
516
590
|
return call("savefile", locals())
|
|
517
591
|
|
|
518
592
|
|
|
519
593
|
def load_script(file: str):
|
|
594
|
+
"""Load and run a Threadle script file."""
|
|
520
595
|
return call("loadscript", locals())
|
|
521
596
|
|
|
522
597
|
|
|
@@ -526,7 +601,7 @@ def load_examples(
|
|
|
526
601
|
set_workdir_to_examples: bool = True,
|
|
527
602
|
set_workdir: Optional[bool] = None,
|
|
528
603
|
):
|
|
529
|
-
"""Load bundled
|
|
604
|
+
"""Load bundled example datasets and return their Threadle handles."""
|
|
530
605
|
if set_workdir is not None:
|
|
531
606
|
set_workdir_to_examples = set_workdir
|
|
532
607
|
|
|
@@ -560,31 +635,37 @@ def load_examples(
|
|
|
560
635
|
|
|
561
636
|
|
|
562
637
|
def export(network: ThreadleName, file: str, layername: str, format: str = "gexf"):
|
|
638
|
+
"""Export a network layer to an external graph format."""
|
|
563
639
|
return call("export", locals())
|
|
564
640
|
|
|
565
641
|
|
|
566
642
|
def delete(structure: ThreadleName):
|
|
643
|
+
"""Delete a Threadle structure from the current session."""
|
|
567
644
|
return call("delete", locals())
|
|
568
645
|
|
|
569
646
|
|
|
570
647
|
def delete_all():
|
|
648
|
+
"""Delete all Threadle structures from the current session."""
|
|
571
649
|
return call("deleteall", locals())
|
|
572
650
|
|
|
573
651
|
|
|
574
652
|
# ============================================
|
|
575
653
|
# Subsetting / filtering
|
|
576
654
|
# ============================================
|
|
577
|
-
def filter(name: str, nodeset: ThreadleName, attrname: str, cond:
|
|
655
|
+
def filter(name: str, nodeset: ThreadleName, attrname: str, cond: Condition, attrvalue: Any = None):
|
|
656
|
+
"""Create a filtered nodeset from node attribute values."""
|
|
578
657
|
_check(cond, CONDITIONS, "cond")
|
|
579
658
|
call("filter", locals(), assign=name)
|
|
580
659
|
return _handle(name)
|
|
581
660
|
|
|
582
661
|
|
|
583
|
-
def filter_nodeset(name: str, nodeset: ThreadleName, attrname: str, cond:
|
|
662
|
+
def filter_nodeset(name: str, nodeset: ThreadleName, attrname: str, cond: Condition, attrvalue: Any = None):
|
|
663
|
+
"""Alias for filter."""
|
|
584
664
|
return filter(name, nodeset, attrname, cond, attrvalue)
|
|
585
665
|
|
|
586
666
|
|
|
587
667
|
def subnet(name: str, network: ThreadleName, nodeset: ThreadleName):
|
|
668
|
+
"""Create a subnetwork induced by a nodeset."""
|
|
588
669
|
call("subnet", locals(), assign=name)
|
|
589
670
|
return _handle(name)
|
|
590
671
|
|
|
@@ -595,7 +676,7 @@ def subnet(name: str, network: ThreadleName, nodeset: ThreadleName):
|
|
|
595
676
|
def generate(
|
|
596
677
|
network: ThreadleName,
|
|
597
678
|
layername: str,
|
|
598
|
-
type:
|
|
679
|
+
type: GenerateType,
|
|
599
680
|
p: Optional[float] = None,
|
|
600
681
|
k: Optional[int] = None,
|
|
601
682
|
beta: Optional[float] = None,
|
|
@@ -603,7 +684,8 @@ def generate(
|
|
|
603
684
|
h: Optional[int] = None,
|
|
604
685
|
a: Optional[float] = None,
|
|
605
686
|
):
|
|
606
|
-
|
|
687
|
+
"""Generate random ties in a network layer."""
|
|
688
|
+
_check(type, GENERATE_TYPES, "type")
|
|
607
689
|
return call("generate", locals())
|
|
608
690
|
|
|
609
691
|
|
|
@@ -620,6 +702,7 @@ def rwdistances(
|
|
|
620
702
|
savesteps: bool = False,
|
|
621
703
|
layername: Optional[Union[str, list[str], tuple[str, ...]]] = None,
|
|
622
704
|
):
|
|
705
|
+
"""Estimate category distances with random walks and return a result network handle."""
|
|
623
706
|
if layername is not None and not layernames:
|
|
624
707
|
layernames = layername
|
|
625
708
|
layernames = _collapse(layernames)
|
|
@@ -639,6 +722,7 @@ def rwfpt(
|
|
|
639
722
|
weighted: bool = False,
|
|
640
723
|
layername: Optional[Union[str, list[str], tuple[str, ...]]] = None,
|
|
641
724
|
):
|
|
725
|
+
"""Estimate category mean first-passage times with random walks and return a result network handle."""
|
|
642
726
|
if layername is not None and not layernames:
|
|
643
727
|
layernames = layername
|
|
644
728
|
layernames = _collapse(layernames)
|
|
@@ -647,10 +731,12 @@ def rwfpt(
|
|
|
647
731
|
|
|
648
732
|
|
|
649
733
|
def random_seed(seed: int = 6031769):
|
|
734
|
+
"""Set Threadle's random seed."""
|
|
650
735
|
return call("randomseed", locals())
|
|
651
736
|
|
|
652
737
|
|
|
653
738
|
def setting(name: str, value: Any):
|
|
739
|
+
"""Set a Threadle backend setting or a threadlepy runtime setting."""
|
|
654
740
|
if name.lower() == "verbose":
|
|
655
741
|
from . import client
|
|
656
742
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|