threadlepy 0.0.2__tar.gz → 0.1.0__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 (36) hide show
  1. threadlepy-0.1.0/CHANGELOG.md +19 -0
  2. threadlepy-0.1.0/LICENSE +21 -0
  3. threadlepy-0.1.0/MANIFEST.in +13 -0
  4. threadlepy-0.1.0/PKG-INFO +278 -0
  5. threadlepy-0.1.0/README.md +261 -0
  6. threadlepy-0.1.0/docs/threadlepy_tutorial.ipynb +407 -0
  7. threadlepy-0.1.0/pyproject.toml +30 -0
  8. threadlepy-0.1.0/scripts/test_commands.py +248 -0
  9. threadlepy-0.1.0/scripts/test_session.py +59 -0
  10. threadlepy-0.1.0/src/threadlepy/Examples/Scripts/create.txt +186 -0
  11. threadlepy-0.1.0/src/threadlepy/Examples/dscw.tsv +21 -0
  12. threadlepy-0.1.0/src/threadlepy/Examples/dscw_nodeset.tsv +19 -0
  13. threadlepy-0.1.0/src/threadlepy/Examples/lazega.tsv +209 -0
  14. threadlepy-0.1.0/src/threadlepy/Examples/lazega_female.tsv +65 -0
  15. threadlepy-0.1.0/src/threadlepy/Examples/lazega_female_nodeset.tsv +19 -0
  16. threadlepy-0.1.0/src/threadlepy/Examples/lazega_nodes.tsv +72 -0
  17. threadlepy-0.1.0/src/threadlepy/Examples/mynet.tsv +38 -0
  18. threadlepy-0.1.0/src/threadlepy/Examples/mynet_nodesetfile.tsv +9 -0
  19. threadlepy-0.1.0/src/threadlepy/Examples/mynodes.tsv +6 -0
  20. threadlepy-0.1.0/src/threadlepy/__init__.py +16 -0
  21. threadlepy-0.1.0/src/threadlepy/client.py +240 -0
  22. threadlepy-0.1.0/src/threadlepy/commands.py +678 -0
  23. threadlepy-0.1.0/src/threadlepy.egg-info/PKG-INFO +278 -0
  24. threadlepy-0.1.0/src/threadlepy.egg-info/SOURCES.txt +25 -0
  25. threadlepy-0.0.2/LICENSE +0 -0
  26. threadlepy-0.0.2/PKG-INFO +0 -70
  27. threadlepy-0.0.2/README.md +0 -55
  28. threadlepy-0.0.2/pyproject.toml +0 -19
  29. threadlepy-0.0.2/src/threadlepy/__init__.py +0 -0
  30. threadlepy-0.0.2/src/threadlepy/client.py +0 -138
  31. threadlepy-0.0.2/src/threadlepy/commands.py +0 -372
  32. threadlepy-0.0.2/src/threadlepy.egg-info/PKG-INFO +0 -70
  33. threadlepy-0.0.2/src/threadlepy.egg-info/SOURCES.txt +0 -10
  34. {threadlepy-0.0.2 → threadlepy-0.1.0}/setup.cfg +0 -0
  35. {threadlepy-0.0.2 → threadlepy-0.1.0}/src/threadlepy.egg-info/dependency_links.txt +0 -0
  36. {threadlepy-0.0.2 → threadlepy-0.1.0}/src/threadlepy.egg-info/top_level.txt +0 -0
@@ -0,0 +1,19 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Expanded Python command wrappers to better correspond to Threadle.
6
+ - Added `th_*` aliases for users moving between threadleR and threadlepy.
7
+ - Added bundled example-data workflow through `load_examples()`.
8
+ - Added `scripts/test_commands.py` integration smoke test.
9
+ - Added `scripts/test_session.py` smoke test for context-managed sessions.
10
+ - Added `docs/threadlepy_tutorial.ipynb` tutorial notebook.
11
+ - Added `threadlepy.session()` for context-managed Threadle process lifecycle.
12
+ - Improved Threadle executable checks and process-exit error reporting.
13
+ - Improved API compatibility for `layername` / `layernames`, `th_cmd(cmd=...)`, `addmissingaffiliation`, and related legacy arguments.
14
+ - Added package-data configuration for example TSV and script files.
15
+ - Cleaned generated artifacts from the repository and updated `.gitignore`.
16
+
17
+ threadlepy follows its own PyPI versioning. Its API is designed to correspond
18
+ to Threadle CLI commands and is inspired by threadleR, but version numbers are
19
+ not synchronized with threadleR releases.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yukun Jiao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ include README.md
2
+ include LICENSE
3
+ include CHANGELOG.md
4
+ include pyproject.toml
5
+
6
+ recursive-include src/threadlepy/Examples *.tsv *.txt
7
+ recursive-include docs *.ipynb
8
+ recursive-include scripts *.py
9
+
10
+ global-exclude __pycache__
11
+ global-exclude *.py[cod]
12
+ global-exclude .DS_Store
13
+ global-exclude .Rhistory
@@ -0,0 +1,278 @@
1
+ Metadata-Version: 2.4
2
+ Name: threadlepy
3
+ Version: 0.1.0
4
+ Summary: Python wrapper for the Threadle command-line interface
5
+ Author: Yukun Jiao
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/YukunJiao/threadlepy
8
+ Project-URL: Issues, https://github.com/YukunJiao/threadlepy/issues
9
+ Project-URL: Documentation, https://github.com/YukunJiao/threadlepy#readme
10
+ Project-URL: Source, https://github.com/YukunJiao/threadlepy
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.9
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Dynamic: license-file
17
+
18
+ # threadlepy
19
+
20
+ `threadlepy` is a Python client for the **Threadle CLI** JSON interface.
21
+ It starts a Threadle subprocess, sends commands over stdin/stdout, and exposes
22
+ Python wrappers that mirror the core workflow of
23
+ [`YukunJiao/threadleR`](https://github.com/YukunJiao/threadleR).
24
+
25
+ > Status: experimental. The wrapper names and returned payload shapes follow the
26
+ > Threadle CLI and may evolve with the backend.
27
+
28
+ `threadlepy` follows its own PyPI versioning. Its API is designed to correspond
29
+ to Threadle CLI commands and is inspired by `threadleR`, but version numbers are
30
+ not synchronized with `threadleR` releases.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install threadlepy
36
+ ```
37
+
38
+ For local development:
39
+
40
+ ```bash
41
+ pip install -e .
42
+ ```
43
+
44
+ Requirements:
45
+
46
+ - Python 3.9+
47
+ - A working Threadle CLI executable named `threadle` on `PATH`, or a full path
48
+ passed to `threadlepy.start()`
49
+
50
+ ## Quick Start
51
+
52
+ ```python
53
+ import threadlepy
54
+ import threadlepy.commands as th
55
+
56
+ threadlepy.configure(timeout=3600)
57
+ threadlepy.start()
58
+
59
+ examples = th.load_examples("lazega")
60
+ lazega = examples["lazega"]
61
+
62
+ th.info(lazega)
63
+ th.get_node_alters(lazega, nodeid=23, layernames="friends", direction="out")
64
+ th.shortest_path(lazega, node1id=1, node2id=23, layernames="friends")
65
+
66
+ threadlepy.stop()
67
+ ```
68
+
69
+ You can also manage the Threadle process with a context manager:
70
+
71
+ ```python
72
+ import threadlepy
73
+ import threadlepy.commands as th
74
+
75
+ with threadlepy.session(timeout=3600):
76
+ examples = th.load_examples("mynet")
77
+ print(th.info(examples["mynet"]))
78
+ ```
79
+
80
+ If `threadle` is not on `PATH`, pass the full executable path to
81
+ `threadlepy.start("/full/path/to/threadle")` or
82
+ `threadlepy.session("/full/path/to/threadle")`.
83
+
84
+ ## Integration Test Script
85
+
86
+ The repository includes a threadleR-style smoke test that exercises the public
87
+ command wrappers against bundled example data:
88
+
89
+ ```bash
90
+ python scripts/test_commands.py
91
+ ```
92
+
93
+ If `threadle` is not on `PATH`, pass the executable explicitly:
94
+
95
+ ```bash
96
+ python scripts/test_commands.py --threadle /full/path/to/threadle
97
+ ```
98
+
99
+ There is also a small lifecycle smoke test for the recommended scripting style
100
+ with `threadlepy.session()`:
101
+
102
+ ```bash
103
+ python scripts/test_session.py
104
+ ```
105
+
106
+ or:
107
+
108
+ ```bash
109
+ python scripts/test_session.py --threadle /full/path/to/threadle
110
+ ```
111
+
112
+ The script:
113
+
114
+ - starts and stops the Threadle CLI process
115
+ - loads `mynet` from the bundled example data
116
+ - runs inventory, metadata, node, attribute, edge, path, degree, density,
117
+ component, two-mode, transformation, import/export, raw-command, and cleanup
118
+ commands
119
+ - creates a scratch network for mutating commands so the example workflow stays
120
+ easy to inspect
121
+
122
+ When the Threadle executable is unavailable, the script exits with code `77`
123
+ and prints a skip message.
124
+
125
+ ## Tutorial Notebook
126
+
127
+ A runnable tutorial notebook is available at:
128
+
129
+ ```text
130
+ docs/threadlepy_tutorial.ipynb
131
+ ```
132
+
133
+ Open it with Jupyter, VS Code, or another notebook UI:
134
+
135
+ ```bash
136
+ jupyter notebook docs/threadlepy_tutorial.ipynb
137
+ ```
138
+
139
+ The notebook follows the same design as the threadleR vignette: start Threadle,
140
+ load example data, inspect nodes and attributes, work with one-mode and two-mode
141
+ layers, derive structures, edit a scratch network, import/export files, and
142
+ clean up.
143
+
144
+ ## Handles
145
+
146
+ Commands that create or load structures return lightweight Python handles:
147
+
148
+ ```python
149
+ ns = th.create_nodeset("ns", createnodes=5)
150
+ net = th.create_network("net", ns)
151
+ ```
152
+
153
+ A handle stores the backend variable name. Any wrapper that expects a network or
154
+ nodeset also accepts the raw backend name as a string.
155
+
156
+ Most wrappers are available in two styles: Pythonic names such as
157
+ `create_nodeset()` and `shortest_path()`, plus `threadleR`-style aliases such as
158
+ `th_create_nodeset()` and `th_shortest_path()`.
159
+
160
+ ## Working Directory and Examples
161
+
162
+ ```python
163
+ th.get_workdir()
164
+ th.set_workdir("~/my_threadle_workspace")
165
+ th.sync_wd()
166
+
167
+ example_dir = th.stage_examples_to_wd("threadle_examples")
168
+ th.set_workdir(example_dir)
169
+ ```
170
+
171
+ Bundled datasets can be loaded directly:
172
+
173
+ ```python
174
+ objs = th.load_examples(["mynet", "lazega"])
175
+ mynet = objs["mynet"]
176
+ ```
177
+
178
+ ## Creating and Editing Networks
179
+
180
+ ```python
181
+ ns = th.create_nodeset("people", createnodes=10)
182
+ net = th.create_network("relations", ns)
183
+
184
+ th.add_layer(net, "friends", mode=1, directed=False, valuetype="binary")
185
+ th.add_edge(net, "friends", 1, 2)
186
+ th.check_edge(net, "friends", 1, 2)
187
+ th.remove_edge(net, "friends", 1, 2)
188
+
189
+ th.add_layer(net, "clubs", mode=2)
190
+ th.add_hyper(net, "clubs", "club_a", nodes=[1, 2, 3])
191
+ th.add_aff(net, "clubs", nodeid=4, hypername="club_a")
192
+ th.get_hyperedge_nodes(net, "clubs", "club_a")
193
+ ```
194
+
195
+ ## Attributes
196
+
197
+ ```python
198
+ th.define_attr(ns, "group", "string")
199
+ th.set_attr(ns, nodeid=1, attrname="group", attrvalue="A")
200
+ th.get_attr(ns, nodeid=1, attrname="group")
201
+
202
+ th.generate_attr(ns, "score", attrtype="int", min=1, max=10)
203
+ th.get_attr_summary(ns, "score")
204
+ th.get_attrs(ns, nodes=[1, 2, 3], attrname="score")
205
+ ```
206
+
207
+ ## Queries and Measures
208
+
209
+ ```python
210
+ th.get_all_nodes(ns, offset=0, limit=100)
211
+ th.get_all_edges(net, "friends", offset=0, limit=1000)
212
+ th.get_degree(net, nodeid=1, layernames="friends", direction="both")
213
+ th.degree(net, "friends", attrname="friends_degree", direction="both")
214
+ th.density(net, "friends", samplesize=500)
215
+ th.components(net, "friends", attrname="component")
216
+ ```
217
+
218
+ Multiple layer names can be passed as a Python list; the client sends the
219
+ semicolon-separated format expected by Threadle:
220
+
221
+ ```python
222
+ th.shortest_path(net, 1, 8, layernames=["friends", "coworkers"])
223
+ ```
224
+
225
+ ## Transformations and Random Walks
226
+
227
+ ```python
228
+ th.symmetrize(net, "advice", method="max", newlayername="advice_sym")
229
+ th.project_two_mode(net, "clubs", method="count", newlayername="club_projection")
230
+ th.pack(net, "friends")
231
+ th.unpack(net, "friends")
232
+
233
+ rw = th.rwdistances("rw_dist", net, attrname="group", maxsteps=100)
234
+ mfpt = th.rwfpt("mfpt", net, attrname="group", maxsteps=100, minpairobs=10)
235
+ ```
236
+
237
+ ## File IO
238
+
239
+ ```python
240
+ th.save_file(ns, "people.tsv")
241
+ ns2 = th.load_file("people2", "people.tsv", type="nodeset")
242
+
243
+ th.export_layer(net, "friends", "friends.tsv", header=True, sep="\t")
244
+ th.import_layer(net, "friends", "friends.tsv", format="edgelist", header=True)
245
+ th.export(net, format="gexf", file="friends.gexf", layername="friends")
246
+ ```
247
+
248
+ ## Raw Commands
249
+
250
+ Use `threadlepy.raw_cmd()` or `th.cmd()` for backend commands that do not yet
251
+ have a typed wrapper:
252
+
253
+ ```python
254
+ payload = threadlepy.raw_cmd("i")
255
+ handle = th.cmd("createnodeset", {"createnodes": 3}, assign="tmp", type="nodeset")
256
+ ```
257
+
258
+ ## Function Coverage
259
+
260
+ `threadlepy.commands` includes wrappers for Threadle process workflow,
261
+ inventory, file IO, layer editing, node editing, attributes, shortest paths,
262
+ degree and density, components, random generation, random walks, packing,
263
+ projection, filtering, subnetwork creation, and export utilities.
264
+
265
+ ## Release Checklist
266
+
267
+ Before publishing a new PyPI release:
268
+
269
+ ```bash
270
+ python3 -m compileall src/threadlepy scripts/test_commands.py
271
+ python3 -m json.tool docs/threadlepy_tutorial.ipynb > /tmp/threadlepy_tutorial_checked.json
272
+ python3 scripts/test_commands.py
273
+ python3 scripts/test_session.py
274
+ python3 -m build
275
+ python3 -m twine check dist/*
276
+ ```
277
+
278
+ Use TestPyPI first when publishing a new release line.
@@ -0,0 +1,261 @@
1
+ # threadlepy
2
+
3
+ `threadlepy` is a Python client for the **Threadle CLI** JSON interface.
4
+ It starts a Threadle subprocess, sends commands over stdin/stdout, and exposes
5
+ Python wrappers that mirror the core workflow of
6
+ [`YukunJiao/threadleR`](https://github.com/YukunJiao/threadleR).
7
+
8
+ > Status: experimental. The wrapper names and returned payload shapes follow the
9
+ > Threadle CLI and may evolve with the backend.
10
+
11
+ `threadlepy` follows its own PyPI versioning. Its API is designed to correspond
12
+ to Threadle CLI commands and is inspired by `threadleR`, but version numbers are
13
+ not synchronized with `threadleR` releases.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pip install threadlepy
19
+ ```
20
+
21
+ For local development:
22
+
23
+ ```bash
24
+ pip install -e .
25
+ ```
26
+
27
+ Requirements:
28
+
29
+ - Python 3.9+
30
+ - A working Threadle CLI executable named `threadle` on `PATH`, or a full path
31
+ passed to `threadlepy.start()`
32
+
33
+ ## Quick Start
34
+
35
+ ```python
36
+ import threadlepy
37
+ import threadlepy.commands as th
38
+
39
+ threadlepy.configure(timeout=3600)
40
+ threadlepy.start()
41
+
42
+ examples = th.load_examples("lazega")
43
+ lazega = examples["lazega"]
44
+
45
+ th.info(lazega)
46
+ th.get_node_alters(lazega, nodeid=23, layernames="friends", direction="out")
47
+ th.shortest_path(lazega, node1id=1, node2id=23, layernames="friends")
48
+
49
+ threadlepy.stop()
50
+ ```
51
+
52
+ You can also manage the Threadle process with a context manager:
53
+
54
+ ```python
55
+ import threadlepy
56
+ import threadlepy.commands as th
57
+
58
+ with threadlepy.session(timeout=3600):
59
+ examples = th.load_examples("mynet")
60
+ print(th.info(examples["mynet"]))
61
+ ```
62
+
63
+ If `threadle` is not on `PATH`, pass the full executable path to
64
+ `threadlepy.start("/full/path/to/threadle")` or
65
+ `threadlepy.session("/full/path/to/threadle")`.
66
+
67
+ ## Integration Test Script
68
+
69
+ The repository includes a threadleR-style smoke test that exercises the public
70
+ command wrappers against bundled example data:
71
+
72
+ ```bash
73
+ python scripts/test_commands.py
74
+ ```
75
+
76
+ If `threadle` is not on `PATH`, pass the executable explicitly:
77
+
78
+ ```bash
79
+ python scripts/test_commands.py --threadle /full/path/to/threadle
80
+ ```
81
+
82
+ There is also a small lifecycle smoke test for the recommended scripting style
83
+ with `threadlepy.session()`:
84
+
85
+ ```bash
86
+ python scripts/test_session.py
87
+ ```
88
+
89
+ or:
90
+
91
+ ```bash
92
+ python scripts/test_session.py --threadle /full/path/to/threadle
93
+ ```
94
+
95
+ The script:
96
+
97
+ - starts and stops the Threadle CLI process
98
+ - loads `mynet` from the bundled example data
99
+ - runs inventory, metadata, node, attribute, edge, path, degree, density,
100
+ component, two-mode, transformation, import/export, raw-command, and cleanup
101
+ commands
102
+ - creates a scratch network for mutating commands so the example workflow stays
103
+ easy to inspect
104
+
105
+ When the Threadle executable is unavailable, the script exits with code `77`
106
+ and prints a skip message.
107
+
108
+ ## Tutorial Notebook
109
+
110
+ A runnable tutorial notebook is available at:
111
+
112
+ ```text
113
+ docs/threadlepy_tutorial.ipynb
114
+ ```
115
+
116
+ Open it with Jupyter, VS Code, or another notebook UI:
117
+
118
+ ```bash
119
+ jupyter notebook docs/threadlepy_tutorial.ipynb
120
+ ```
121
+
122
+ The notebook follows the same design as the threadleR vignette: start Threadle,
123
+ load example data, inspect nodes and attributes, work with one-mode and two-mode
124
+ layers, derive structures, edit a scratch network, import/export files, and
125
+ clean up.
126
+
127
+ ## Handles
128
+
129
+ Commands that create or load structures return lightweight Python handles:
130
+
131
+ ```python
132
+ ns = th.create_nodeset("ns", createnodes=5)
133
+ net = th.create_network("net", ns)
134
+ ```
135
+
136
+ A handle stores the backend variable name. Any wrapper that expects a network or
137
+ nodeset also accepts the raw backend name as a string.
138
+
139
+ Most wrappers are available in two styles: Pythonic names such as
140
+ `create_nodeset()` and `shortest_path()`, plus `threadleR`-style aliases such as
141
+ `th_create_nodeset()` and `th_shortest_path()`.
142
+
143
+ ## Working Directory and Examples
144
+
145
+ ```python
146
+ th.get_workdir()
147
+ th.set_workdir("~/my_threadle_workspace")
148
+ th.sync_wd()
149
+
150
+ example_dir = th.stage_examples_to_wd("threadle_examples")
151
+ th.set_workdir(example_dir)
152
+ ```
153
+
154
+ Bundled datasets can be loaded directly:
155
+
156
+ ```python
157
+ objs = th.load_examples(["mynet", "lazega"])
158
+ mynet = objs["mynet"]
159
+ ```
160
+
161
+ ## Creating and Editing Networks
162
+
163
+ ```python
164
+ ns = th.create_nodeset("people", createnodes=10)
165
+ net = th.create_network("relations", ns)
166
+
167
+ th.add_layer(net, "friends", mode=1, directed=False, valuetype="binary")
168
+ th.add_edge(net, "friends", 1, 2)
169
+ th.check_edge(net, "friends", 1, 2)
170
+ th.remove_edge(net, "friends", 1, 2)
171
+
172
+ th.add_layer(net, "clubs", mode=2)
173
+ th.add_hyper(net, "clubs", "club_a", nodes=[1, 2, 3])
174
+ th.add_aff(net, "clubs", nodeid=4, hypername="club_a")
175
+ th.get_hyperedge_nodes(net, "clubs", "club_a")
176
+ ```
177
+
178
+ ## Attributes
179
+
180
+ ```python
181
+ th.define_attr(ns, "group", "string")
182
+ th.set_attr(ns, nodeid=1, attrname="group", attrvalue="A")
183
+ th.get_attr(ns, nodeid=1, attrname="group")
184
+
185
+ th.generate_attr(ns, "score", attrtype="int", min=1, max=10)
186
+ th.get_attr_summary(ns, "score")
187
+ th.get_attrs(ns, nodes=[1, 2, 3], attrname="score")
188
+ ```
189
+
190
+ ## Queries and Measures
191
+
192
+ ```python
193
+ th.get_all_nodes(ns, offset=0, limit=100)
194
+ th.get_all_edges(net, "friends", offset=0, limit=1000)
195
+ th.get_degree(net, nodeid=1, layernames="friends", direction="both")
196
+ th.degree(net, "friends", attrname="friends_degree", direction="both")
197
+ th.density(net, "friends", samplesize=500)
198
+ th.components(net, "friends", attrname="component")
199
+ ```
200
+
201
+ Multiple layer names can be passed as a Python list; the client sends the
202
+ semicolon-separated format expected by Threadle:
203
+
204
+ ```python
205
+ th.shortest_path(net, 1, 8, layernames=["friends", "coworkers"])
206
+ ```
207
+
208
+ ## Transformations and Random Walks
209
+
210
+ ```python
211
+ th.symmetrize(net, "advice", method="max", newlayername="advice_sym")
212
+ th.project_two_mode(net, "clubs", method="count", newlayername="club_projection")
213
+ th.pack(net, "friends")
214
+ th.unpack(net, "friends")
215
+
216
+ rw = th.rwdistances("rw_dist", net, attrname="group", maxsteps=100)
217
+ mfpt = th.rwfpt("mfpt", net, attrname="group", maxsteps=100, minpairobs=10)
218
+ ```
219
+
220
+ ## File IO
221
+
222
+ ```python
223
+ th.save_file(ns, "people.tsv")
224
+ ns2 = th.load_file("people2", "people.tsv", type="nodeset")
225
+
226
+ th.export_layer(net, "friends", "friends.tsv", header=True, sep="\t")
227
+ th.import_layer(net, "friends", "friends.tsv", format="edgelist", header=True)
228
+ th.export(net, format="gexf", file="friends.gexf", layername="friends")
229
+ ```
230
+
231
+ ## Raw Commands
232
+
233
+ Use `threadlepy.raw_cmd()` or `th.cmd()` for backend commands that do not yet
234
+ have a typed wrapper:
235
+
236
+ ```python
237
+ payload = threadlepy.raw_cmd("i")
238
+ handle = th.cmd("createnodeset", {"createnodes": 3}, assign="tmp", type="nodeset")
239
+ ```
240
+
241
+ ## Function Coverage
242
+
243
+ `threadlepy.commands` includes wrappers for Threadle process workflow,
244
+ inventory, file IO, layer editing, node editing, attributes, shortest paths,
245
+ degree and density, components, random generation, random walks, packing,
246
+ projection, filtering, subnetwork creation, and export utilities.
247
+
248
+ ## Release Checklist
249
+
250
+ Before publishing a new PyPI release:
251
+
252
+ ```bash
253
+ python3 -m compileall src/threadlepy scripts/test_commands.py
254
+ python3 -m json.tool docs/threadlepy_tutorial.ipynb > /tmp/threadlepy_tutorial_checked.json
255
+ python3 scripts/test_commands.py
256
+ python3 scripts/test_session.py
257
+ python3 -m build
258
+ python3 -m twine check dist/*
259
+ ```
260
+
261
+ Use TestPyPI first when publishing a new release line.