threadlepy 0.0.2__tar.gz → 0.1.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.
Files changed (36) hide show
  1. threadlepy-0.1.1/CHANGELOG.md +24 -0
  2. threadlepy-0.1.1/LICENSE +21 -0
  3. threadlepy-0.1.1/MANIFEST.in +13 -0
  4. threadlepy-0.1.1/PKG-INFO +69 -0
  5. threadlepy-0.1.1/README.md +52 -0
  6. threadlepy-0.1.1/docs/threadlepy_tutorial.ipynb +407 -0
  7. threadlepy-0.1.1/pyproject.toml +30 -0
  8. threadlepy-0.1.1/scripts/test_commands.py +248 -0
  9. threadlepy-0.1.1/scripts/test_session.py +59 -0
  10. threadlepy-0.1.1/src/threadlepy/Examples/Scripts/create.txt +186 -0
  11. threadlepy-0.1.1/src/threadlepy/Examples/dscw.tsv +21 -0
  12. threadlepy-0.1.1/src/threadlepy/Examples/dscw_nodeset.tsv +19 -0
  13. threadlepy-0.1.1/src/threadlepy/Examples/lazega.tsv +209 -0
  14. threadlepy-0.1.1/src/threadlepy/Examples/lazega_female.tsv +65 -0
  15. threadlepy-0.1.1/src/threadlepy/Examples/lazega_female_nodeset.tsv +19 -0
  16. threadlepy-0.1.1/src/threadlepy/Examples/lazega_nodes.tsv +72 -0
  17. threadlepy-0.1.1/src/threadlepy/Examples/mynet.tsv +38 -0
  18. threadlepy-0.1.1/src/threadlepy/Examples/mynet_nodesetfile.tsv +9 -0
  19. threadlepy-0.1.1/src/threadlepy/Examples/mynodes.tsv +6 -0
  20. threadlepy-0.1.1/src/threadlepy/__init__.py +16 -0
  21. threadlepy-0.1.1/src/threadlepy/client.py +240 -0
  22. threadlepy-0.1.1/src/threadlepy/commands.py +678 -0
  23. threadlepy-0.1.1/src/threadlepy.egg-info/PKG-INFO +69 -0
  24. threadlepy-0.1.1/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.1}/setup.cfg +0 -0
  35. {threadlepy-0.0.2 → threadlepy-0.1.1}/src/threadlepy.egg-info/dependency_links.txt +0 -0
  36. {threadlepy-0.0.2 → threadlepy-0.1.1}/src/threadlepy.egg-info/top_level.txt +0 -0
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ ## 0.1.1
4
+
5
+ - Updated PyPI project description to match README.
6
+ - Synchronized package metadata version with the 0.1.1 release.
7
+
8
+ ## 0.1.0
9
+
10
+ - Expanded Python command wrappers to better correspond to Threadle.
11
+ - Added `th_*` aliases for users moving between threadleR and threadlepy.
12
+ - Added bundled example-data workflow through `load_examples()`.
13
+ - Added `scripts/test_commands.py` integration smoke test.
14
+ - Added `scripts/test_session.py` smoke test for context-managed sessions.
15
+ - Added `docs/threadlepy_tutorial.ipynb` tutorial notebook.
16
+ - Added `threadlepy.session()` for context-managed Threadle process lifecycle.
17
+ - Improved Threadle executable checks and process-exit error reporting.
18
+ - Improved API compatibility for `layername` / `layernames`, `th_cmd(cmd=...)`, `addmissingaffiliation`, and related legacy arguments.
19
+ - Added package-data configuration for example TSV and script files.
20
+ - Cleaned generated artifacts from the repository and updated `.gitignore`.
21
+
22
+ threadlepy follows its own PyPI versioning. Its API is designed to correspond
23
+ to Threadle CLI commands and is inspired by threadleR, but version numbers are
24
+ 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,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: threadlepy
3
+ Version: 0.1.1
4
+ Summary: Python client for the Threadle CLI JSON 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
+ Python client for the Threadle CLI JSON interface.
21
+
22
+ `threadlepy` starts a Threadle subprocess and exposes Python wrappers for core Threadle workflows.
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install threadlepy
28
+ ```
29
+
30
+ Requirements:
31
+
32
+ - Python 3.9+
33
+ - A working `threadle` executable available on `PATH`
34
+
35
+ ## Quick Start
36
+
37
+ ```python
38
+ import threadlepy
39
+ import threadlepy.commands as th
40
+
41
+ with threadlepy.session(timeout=3600):
42
+ examples = th.load_examples("lazega")
43
+ lazega = examples["lazega"]
44
+
45
+ print(th.info(lazega))
46
+
47
+ path = th.shortest_path(
48
+ lazega,
49
+ node1id=1,
50
+ node2id=23,
51
+ layernames="friends",
52
+ )
53
+
54
+ print(path)
55
+ ```
56
+
57
+ If `threadle` is not on `PATH`, pass the executable path explicitly:
58
+
59
+ ```python
60
+ threadlepy.start("/full/path/to/threadle")
61
+ ```
62
+
63
+ ## Documentation
64
+
65
+ For more examples and workflows, see:
66
+
67
+ ```text
68
+ docs/threadlepy_tutorial.ipynb
69
+ ```
@@ -0,0 +1,52 @@
1
+ # threadlepy
2
+
3
+ Python client for the Threadle CLI JSON interface.
4
+
5
+ `threadlepy` starts a Threadle subprocess and exposes Python wrappers for core Threadle workflows.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install threadlepy
11
+ ```
12
+
13
+ Requirements:
14
+
15
+ - Python 3.9+
16
+ - A working `threadle` executable available on `PATH`
17
+
18
+ ## Quick Start
19
+
20
+ ```python
21
+ import threadlepy
22
+ import threadlepy.commands as th
23
+
24
+ with threadlepy.session(timeout=3600):
25
+ examples = th.load_examples("lazega")
26
+ lazega = examples["lazega"]
27
+
28
+ print(th.info(lazega))
29
+
30
+ path = th.shortest_path(
31
+ lazega,
32
+ node1id=1,
33
+ node2id=23,
34
+ layernames="friends",
35
+ )
36
+
37
+ print(path)
38
+ ```
39
+
40
+ If `threadle` is not on `PATH`, pass the executable path explicitly:
41
+
42
+ ```python
43
+ threadlepy.start("/full/path/to/threadle")
44
+ ```
45
+
46
+ ## Documentation
47
+
48
+ For more examples and workflows, see:
49
+
50
+ ```text
51
+ docs/threadlepy_tutorial.ipynb
52
+ ```
@@ -0,0 +1,407 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# threadlepy tutorial\n",
8
+ "\n",
9
+ "This notebook mirrors the public workflow of `YukunJiao/threadleR`, but uses Python wrappers from `threadlepy`. It loads bundled example data, explores nodes and layers, edits a scratch network, and demonstrates import/export.\n",
10
+ "\n",
11
+ "You need a working Threadle CLI executable. This tutorial assumes `threadle` is already on `PATH`."
12
+ ]
13
+ },
14
+ {
15
+ "cell_type": "code",
16
+ "execution_count": null,
17
+ "id": "a211e5be",
18
+ "metadata": {},
19
+ "outputs": [],
20
+ "source": [
21
+ "import os\n",
22
+ "\n",
23
+ "import threadlepy\n",
24
+ "import threadlepy.commands as th\n",
25
+ "\n",
26
+ "threadlepy.__version__, threadlepy.is_available()"
27
+ ]
28
+ },
29
+ {
30
+ "cell_type": "markdown",
31
+ "id": "e7e3e731",
32
+ "metadata": {},
33
+ "source": [
34
+ "## Start Threadle\n",
35
+ "\n",
36
+ "Start a background Threadle process in JSON mode. The client sends one JSON command per wrapper call and returns the backend payload.\n",
37
+ "\n",
38
+ "For scripts, `with threadlepy.session():` is the most convenient lifecycle helper. In a notebook, explicit `start()` / `stop()` cells make it easier to rerun sections interactively."
39
+ ]
40
+ },
41
+ {
42
+ "cell_type": "code",
43
+ "execution_count": null,
44
+ "id": "45f9c4d3",
45
+ "metadata": {},
46
+ "outputs": [],
47
+ "source": [
48
+ "threadlepy.configure(print_messages=False, timeout=1800)\n",
49
+ "threadlepy.start()\n",
50
+ "tutorial_workdir = th.get_workdir()\n",
51
+ "tutorial_workdir"
52
+ ]
53
+ },
54
+ {
55
+ "cell_type": "markdown",
56
+ "id": "77370a1b",
57
+ "metadata": {},
58
+ "source": [
59
+ "## Load example data\n",
60
+ "\n",
61
+ "`load_examples()` loads the package data under `src/threadlepy/Examples`. The returned objects are lightweight handles that store the backend variable name."
62
+ ]
63
+ },
64
+ {
65
+ "cell_type": "code",
66
+ "execution_count": null,
67
+ "id": "324095ef",
68
+ "metadata": {},
69
+ "outputs": [],
70
+ "source": [
71
+ "examples = th.load_examples(\"mynet\")\n",
72
+ "mynet = examples[\"mynet\"]\n",
73
+ "mynet_nodeset = examples[\"mynet_nodeset\"]\n",
74
+ "\n",
75
+ "# `load_examples()` switches Threadle to the example-data folder.\n",
76
+ "# Switch back so Threadle file exports and Python open() use the same folder.\n",
77
+ "th.set_workdir(tutorial_workdir)\n",
78
+ "\n",
79
+ "th.i(), th.info(mynet), th.info(mynet_nodeset)"
80
+ ]
81
+ },
82
+ {
83
+ "cell_type": "code",
84
+ "execution_count": null,
85
+ "id": "9518caff",
86
+ "metadata": {},
87
+ "outputs": [],
88
+ "source": [
89
+ "th.get_workdir()\n",
90
+ "th.dir()"
91
+ ]
92
+ },
93
+ {
94
+ "cell_type": "markdown",
95
+ "id": "b8a49716",
96
+ "metadata": {},
97
+ "source": [
98
+ "## Nodes and attributes\n",
99
+ "\n",
100
+ "Node commands work on either a nodeset handle or a network handle, since Threadle networks carry an underlying nodeset."
101
+ ]
102
+ },
103
+ {
104
+ "cell_type": "code",
105
+ "execution_count": null,
106
+ "id": "aa877fd0",
107
+ "metadata": {},
108
+ "outputs": [],
109
+ "source": [
110
+ "th.get_nbr_nodes(mynet)\n",
111
+ "th.get_all_nodes(mynet, offset=0, limit=8)\n",
112
+ "th.get_nodeid_by_index(mynet, index=0)\n",
113
+ "th.get_random_node(mynet)"
114
+ ]
115
+ },
116
+ {
117
+ "cell_type": "code",
118
+ "execution_count": null,
119
+ "id": "91099c34",
120
+ "metadata": {},
121
+ "outputs": [],
122
+ "source": [
123
+ "th.get_attr(mynet_nodeset, nodeid=123, attrname=\"gender\")\n",
124
+ "th.get_attrs(mynet_nodeset, nodes=[123, 234, 345], attrname=\"gender\")\n",
125
+ "th.get_attr_summary(mynet_nodeset, attrname=\"gender\")"
126
+ ]
127
+ },
128
+ {
129
+ "cell_type": "markdown",
130
+ "id": "e1dfd3ef",
131
+ "metadata": {},
132
+ "source": [
133
+ "## One-mode layers\n",
134
+ "\n",
135
+ "`mynet` includes one-mode layers named `kinship` and `trade`. Use them for edge, alter, degree, density, component, and shortest-path commands."
136
+ ]
137
+ },
138
+ {
139
+ "cell_type": "code",
140
+ "execution_count": null,
141
+ "id": "8ef3910f",
142
+ "metadata": {},
143
+ "outputs": [],
144
+ "source": [
145
+ "th.get_edge(mynet, \"trade\", node1id=123, node2id=345)\n",
146
+ "th.check_edge(mynet, \"kinship\", node1id=123, node2id=345)\n",
147
+ "th.get_all_edges(mynet, \"trade\", offset=0, limit=10)\n",
148
+ "th.get_random_edge(mynet, \"trade\")"
149
+ ]
150
+ },
151
+ {
152
+ "cell_type": "code",
153
+ "execution_count": null,
154
+ "id": "47b059f8",
155
+ "metadata": {},
156
+ "outputs": [],
157
+ "source": [
158
+ "th.get_node_alters(mynet, nodeid=345, layernames=\"trade\", direction=\"both\")\n",
159
+ "th.get_degree(mynet, nodeid=345, layernames=\"trade\", direction=\"both\")\n",
160
+ "th.get_random_alter(mynet, nodeid=345, layernames=\"trade\", direction=\"both\")\n",
161
+ "th.density(mynet, \"kinship\")"
162
+ ]
163
+ },
164
+ {
165
+ "cell_type": "code",
166
+ "execution_count": null,
167
+ "id": "2d4ed293",
168
+ "metadata": {},
169
+ "outputs": [],
170
+ "source": [
171
+ "th.components(mynet, \"kinship\", attrname=\"kinship_component\")\n",
172
+ "th.degree(mynet, \"kinship\", attrname=\"kinship_degree\", direction=\"both\")\n",
173
+ "th.get_attr_summary(mynet, \"kinship_degree\")"
174
+ ]
175
+ },
176
+ {
177
+ "cell_type": "code",
178
+ "execution_count": null,
179
+ "id": "3f9f6401",
180
+ "metadata": {},
181
+ "outputs": [],
182
+ "source": [
183
+ "th.shortest_path(mynet, node1id=123, node2id=567, layernames=\"kinship\")\n",
184
+ "paths_by_gender = th.shortest_paths(\"paths_by_gender\", mynet, attrname=\"gender\", layernames=\"kinship\")\n",
185
+ "th.info(paths_by_gender)"
186
+ ]
187
+ },
188
+ {
189
+ "cell_type": "markdown",
190
+ "id": "c850d95b",
191
+ "metadata": {},
192
+ "source": [
193
+ "## Two-mode layers\n",
194
+ "\n",
195
+ "The `work` layer is a two-mode affiliation layer. Hyperedge helpers use `hypername` for the affiliation name."
196
+ ]
197
+ },
198
+ {
199
+ "cell_type": "code",
200
+ "execution_count": null,
201
+ "id": "799c7042",
202
+ "metadata": {},
203
+ "outputs": [],
204
+ "source": [
205
+ "th.get_all_hyperedges(mynet, \"work\", offset=0, limit=10)\n",
206
+ "th.get_hyperedge_nodes(mynet, \"work\", hypername=\"ias\")\n",
207
+ "th.get_node_hyperedges(mynet, \"work\", nodeid=123)"
208
+ ]
209
+ },
210
+ {
211
+ "cell_type": "code",
212
+ "execution_count": null,
213
+ "id": "eea3beb8",
214
+ "metadata": {},
215
+ "outputs": [],
216
+ "source": [
217
+ "th.add_hyper(mynet, \"work\", hypername=\"py_temp\", nodes=[123, 234])\n",
218
+ "th.add_aff(mynet, \"work\", nodeid=345, hypername=\"py_temp\")\n",
219
+ "th.get_hyperedge_nodes(mynet, \"work\", hypername=\"py_temp\")\n",
220
+ "\n",
221
+ "th.remove_aff(mynet, \"work\", nodeid=345, hypername=\"py_temp\")\n",
222
+ "th.remove_hyper(mynet, \"work\", hypername=\"py_temp\")"
223
+ ]
224
+ },
225
+ {
226
+ "cell_type": "markdown",
227
+ "id": "e31d7274",
228
+ "metadata": {},
229
+ "source": [
230
+ "## Derived structures and transformations"
231
+ ]
232
+ },
233
+ {
234
+ "cell_type": "code",
235
+ "execution_count": null,
236
+ "id": "65e52e2f",
237
+ "metadata": {},
238
+ "outputs": [],
239
+ "source": [
240
+ "female_nodeset = th.filter(\"female_nodeset\", mynet_nodeset, attrname=\"gender\", cond=\"eq\", attrvalue=\"f\")\n",
241
+ "female_mynet = th.subnet(\"female_mynet\", mynet, female_nodeset)\n",
242
+ "th.info(female_nodeset), th.info(female_mynet)"
243
+ ]
244
+ },
245
+ {
246
+ "cell_type": "code",
247
+ "execution_count": null,
248
+ "id": "70453631",
249
+ "metadata": {},
250
+ "outputs": [],
251
+ "source": [
252
+ "th.symmetrize(mynet, \"trade\", method=\"max\", newlayername=\"trade_sym\")\n",
253
+ "th.dichotomize(mynet, \"trade\", cond=\"ge\", threshold=1000, truevalue=1, falsevalue=0, newlayername=\"trade_hi\")\n",
254
+ "th.project_two_mode(mynet, \"work\", method=\"count\", newlayername=\"work_projected\")\n",
255
+ "\n",
256
+ "th.get_edge(mynet, \"trade_sym\", 123, 345)\n",
257
+ "th.get_edge(mynet, \"trade_hi\", 890, 234)\n",
258
+ "th.get_edge(mynet, \"work_projected\", 123, 456)"
259
+ ]
260
+ },
261
+ {
262
+ "cell_type": "markdown",
263
+ "id": "d893d41e",
264
+ "metadata": {},
265
+ "source": [
266
+ "## Scratch network editing\n",
267
+ "\n",
268
+ "Use scratch structures when learning mutating commands. This keeps the bundled example network easy to inspect."
269
+ ]
270
+ },
271
+ {
272
+ "cell_type": "code",
273
+ "execution_count": null,
274
+ "id": "6f6cf7c2",
275
+ "metadata": {},
276
+ "outputs": [],
277
+ "source": [
278
+ "scratch_nodes = th.create_nodeset(\"scratch_nodes\", name=\"Scratch nodes\", createnodes=5)\n",
279
+ "scratch_net = th.create_network(\"scratch_net\", scratch_nodes, name=\"Scratch network\")\n",
280
+ "\n",
281
+ "th.add_layer(scratch_net, \"friendship\", mode=1, directed=False, valuetype=\"binary\")\n",
282
+ "th.add_edge(scratch_net, \"friendship\", node1id=1, node2id=2)\n",
283
+ "th.check_edge(scratch_net, \"friendship\", node1id=1, node2id=2)\n",
284
+ "th.remove_edge(scratch_net, \"friendship\", node1id=1, node2id=2)\n",
285
+ "th.clear_layer(scratch_net, \"friendship\")"
286
+ ]
287
+ },
288
+ {
289
+ "cell_type": "code",
290
+ "execution_count": null,
291
+ "id": "65f4f4a8",
292
+ "metadata": {},
293
+ "outputs": [],
294
+ "source": [
295
+ "th.add_node(scratch_nodes, nodeid=10)\n",
296
+ "th.get_all_nodes(scratch_nodes, offset=0, limit=20)\n",
297
+ "th.remove_node(scratch_nodes, nodeid=10)\n",
298
+ "\n",
299
+ "th.define_attr(scratch_nodes, \"score\", \"int\")\n",
300
+ "th.set_attr(scratch_nodes, nodeid=1, attrname=\"score\", attrvalue=7)\n",
301
+ "th.get_attr(scratch_nodes, nodeid=1, attrname=\"score\")\n",
302
+ "th.remove_attr(scratch_nodes, nodeid=1, attrname=\"score\")\n",
303
+ "th.undefine_attr(scratch_nodes, \"score\")"
304
+ ]
305
+ },
306
+ {
307
+ "cell_type": "markdown",
308
+ "id": "eaa65364",
309
+ "metadata": {},
310
+ "source": [
311
+ "## Import and export"
312
+ ]
313
+ },
314
+ {
315
+ "cell_type": "code",
316
+ "execution_count": null,
317
+ "id": "9fc188d4",
318
+ "metadata": {},
319
+ "outputs": [],
320
+ "source": [
321
+ "exported_file = os.path.join(tutorial_workdir, \"kinship_export.tsv\")\n",
322
+ "import_file = os.path.join(tutorial_workdir, \"imported_edges.tsv\")\n",
323
+ "saved_nodes_file = os.path.join(tutorial_workdir, \"scratch_nodes_saved.tsv\")\n",
324
+ "\n",
325
+ "th.export_layer(mynet, \"kinship\", file=exported_file, header=True, sep=\"\\t\")\n",
326
+ "with open(exported_file, encoding=\"utf-8\") as f:\n",
327
+ " print(f.read().splitlines()[:3])\n",
328
+ "\n",
329
+ "with open(import_file, \"w\", encoding=\"utf-8\") as f:\n",
330
+ " f.write(\"1\\t2\\t4\\n2\\t3\\t5\\n\")\n",
331
+ "\n",
332
+ "th.add_layer(scratch_net, \"imported_edges\", mode=1, directed=False, valuetype=\"valued\")\n",
333
+ "th.import_layer(scratch_net, \"imported_edges\", file=import_file, format=\"edgelist\", header=False, addmissingnodes=True)\n",
334
+ "print(th.get_edge(scratch_net, \"imported_edges\", node1id=1, node2id=2))\n",
335
+ "\n",
336
+ "th.save_file(scratch_nodes, file=saved_nodes_file)\n",
337
+ "scratch_copy = th.load_file(\"scratch_nodes_copy\", file=saved_nodes_file, type=\"nodeset\")\n",
338
+ "print(th.get_nbr_nodes(scratch_copy))"
339
+ ]
340
+ },
341
+ {
342
+ "cell_type": "code",
343
+ "execution_count": null,
344
+ "metadata": {},
345
+ "outputs": [],
346
+ "source": [
347
+ "# Remove files created by the import/export example.\n",
348
+ "for file in [exported_file, import_file, saved_nodes_file]:\n",
349
+ " try:\n",
350
+ " os.remove(file)\n",
351
+ " except FileNotFoundError:\n",
352
+ " pass"
353
+ ]
354
+ },
355
+ {
356
+ "cell_type": "markdown",
357
+ "id": "8334003a",
358
+ "metadata": {},
359
+ "source": [
360
+ "## Low-level commands and cleanup"
361
+ ]
362
+ },
363
+ {
364
+ "cell_type": "code",
365
+ "execution_count": null,
366
+ "id": "d0e2ee3c",
367
+ "metadata": {},
368
+ "outputs": [],
369
+ "source": [
370
+ "th.cmd(\"info\", args={\"structure\": mynet})\n",
371
+ "th.delete_all()\n",
372
+ "th.i()"
373
+ ]
374
+ },
375
+ {
376
+ "cell_type": "code",
377
+ "execution_count": null,
378
+ "id": "81c36469",
379
+ "metadata": {},
380
+ "outputs": [],
381
+ "source": [
382
+ "threadlepy.stop()"
383
+ ]
384
+ }
385
+ ],
386
+ "metadata": {
387
+ "kernelspec": {
388
+ "display_name": "Python 3",
389
+ "language": "python",
390
+ "name": "python3"
391
+ },
392
+ "language_info": {
393
+ "codemirror_mode": {
394
+ "name": "ipython",
395
+ "version": 3
396
+ },
397
+ "file_extension": ".py",
398
+ "mimetype": "text/x-python",
399
+ "name": "python",
400
+ "nbconvert_exporter": "python",
401
+ "pygments_lexer": "ipython3",
402
+ "version": "3.9"
403
+ }
404
+ },
405
+ "nbformat": 4,
406
+ "nbformat_minor": 5
407
+ }
@@ -0,0 +1,30 @@
1
+ [build-system]
2
+ requires = ["setuptools >= 77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "threadlepy"
7
+ version = "0.1.1"
8
+ description = "Python client for the Threadle CLI JSON interface."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [
13
+ { name = "Yukun Jiao" }
14
+ ]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Operating System :: OS Independent",
18
+ ]
19
+
20
+ [project.urls]
21
+ Homepage = "https://github.com/YukunJiao/threadlepy"
22
+ Issues = "https://github.com/YukunJiao/threadlepy/issues"
23
+ Documentation = "https://github.com/YukunJiao/threadlepy#readme"
24
+ Source = "https://github.com/YukunJiao/threadlepy"
25
+
26
+ [tool.setuptools.packages.find]
27
+ where = ["src"]
28
+
29
+ [tool.setuptools.package-data]
30
+ threadlepy = ["Examples/*.tsv", "Examples/Scripts/*.txt"]