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.
Files changed (28) hide show
  1. {threadlepy-0.1.1 → threadlepy-0.1.2}/CHANGELOG.md +6 -0
  2. {threadlepy-0.1.1 → threadlepy-0.1.2}/MANIFEST.in +1 -0
  3. {threadlepy-0.1.1/src/threadlepy.egg-info → threadlepy-0.1.2}/PKG-INFO +1 -1
  4. {threadlepy-0.1.1 → threadlepy-0.1.2}/pyproject.toml +2 -2
  5. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/__init__.py +1 -1
  6. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/client.py +7 -0
  7. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/commands.py +120 -34
  8. threadlepy-0.1.2/src/threadlepy/py.typed +1 -0
  9. {threadlepy-0.1.1 → threadlepy-0.1.2/src/threadlepy.egg-info}/PKG-INFO +1 -1
  10. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy.egg-info/SOURCES.txt +1 -0
  11. {threadlepy-0.1.1 → threadlepy-0.1.2}/LICENSE +0 -0
  12. {threadlepy-0.1.1 → threadlepy-0.1.2}/README.md +0 -0
  13. {threadlepy-0.1.1 → threadlepy-0.1.2}/docs/threadlepy_tutorial.ipynb +0 -0
  14. {threadlepy-0.1.1 → threadlepy-0.1.2}/scripts/test_commands.py +0 -0
  15. {threadlepy-0.1.1 → threadlepy-0.1.2}/scripts/test_session.py +0 -0
  16. {threadlepy-0.1.1 → threadlepy-0.1.2}/setup.cfg +0 -0
  17. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/Scripts/create.txt +0 -0
  18. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/dscw.tsv +0 -0
  19. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/dscw_nodeset.tsv +0 -0
  20. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/lazega.tsv +0 -0
  21. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/lazega_female.tsv +0 -0
  22. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/lazega_female_nodeset.tsv +0 -0
  23. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/lazega_nodes.tsv +0 -0
  24. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/mynet.tsv +0 -0
  25. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/mynet_nodesetfile.tsv +0 -0
  26. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy/Examples/mynodes.tsv +0 -0
  27. {threadlepy-0.1.1 → threadlepy-0.1.2}/src/threadlepy.egg-info/dependency_links.txt +0 -0
  28. {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,6 +4,7 @@ include CHANGELOG.md
4
4
  include pyproject.toml
5
5
 
6
6
  recursive-include src/threadlepy/Examples *.tsv *.txt
7
+ include src/threadlepy/py.typed
7
8
  recursive-include docs *.ipynb
8
9
  recursive-include scripts *.py
9
10
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: threadlepy
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Python client for the Threadle CLI JSON interface.
5
5
  Author: Yukun Jiao
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "threadlepy"
7
- version = "0.1.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"]
@@ -2,7 +2,7 @@
2
2
 
3
3
  from .client import ThreadleStruct, configure, is_available, raw_cmd, session, start, stop
4
4
 
5
- __version__ = "0.1.1"
5
+ __version__ = "0.1.2"
6
6
 
7
7
  __all__ = [
8
8
  "ThreadleStruct",
@@ -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
- CONDITIONS = ("eq", "ne", "gt", "lt", "ge", "le", "isnull", "notnull")
13
- DIRECTIONS = ("both", "in", "out")
14
- ATTR_TYPES = ("int", "char", "float", "bool", "string")
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[str] = None,
77
+ type: Optional[StructureType] = None,
60
78
  **kwargs: Any,
61
79
  ):
62
- """Send a raw Threadle command and optionally return an assigned handle."""
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 and return its absolute path."""
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: int,
259
+ mode: LayerMode,
221
260
  directed: bool = False,
222
- valuetype: str = "binary",
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, ("binary", "valued"), "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: str = "edgelist",
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
- _check(format, ("edgelist", "matrix"), "format")
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: str = "int"):
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: str = "int",
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: str = "out",
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: str = "both",
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: str = "out",
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: str = "out"):
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: str = "ge",
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
- _check(cond, CONDITIONS[:6], "cond")
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: str = "max",
525
+ method: SymmetrizeMethod = "max",
460
526
  newlayername: Optional[str] = None,
461
527
  ):
462
- _check(method, ("max", "min", "minnonzero", "average", "sum", "product"), "method")
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: str = "count",
536
+ method: ProjectMethod = "count",
470
537
  newlayername: Optional[str] = None,
471
538
  ):
472
- _check(method, ("count", "newman", "binary"), "method")
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: str, pack: bool = False):
498
- _check(type, ("network", "nodeset"), "type")
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 examples and return a dict of Threadle handles."""
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: str, attrvalue: Any = None):
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: str, attrvalue: Any = None):
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: str,
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
- _check(type, ("er", "ws", "ba", "2mode"), "type")
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
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: threadlepy
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Python client for the Threadle CLI JSON interface.
5
5
  Author: Yukun Jiao
6
6
  License-Expression: MIT
@@ -9,6 +9,7 @@ scripts/test_session.py
9
9
  src/threadlepy/__init__.py
10
10
  src/threadlepy/client.py
11
11
  src/threadlepy/commands.py
12
+ src/threadlepy/py.typed
12
13
  src/threadlepy.egg-info/PKG-INFO
13
14
  src/threadlepy.egg-info/SOURCES.txt
14
15
  src/threadlepy.egg-info/dependency_links.txt
File without changes
File without changes
File without changes