pynteract 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 (41) hide show
  1. pynteract-0.1.0/LICENSE +21 -0
  2. pynteract-0.1.0/MANIFEST.in +8 -0
  3. pynteract-0.1.0/PKG-INFO +386 -0
  4. pynteract-0.1.0/README.md +371 -0
  5. pynteract-0.1.0/pynteract/__init__.py +6 -0
  6. pynteract-0.1.0/pynteract/builtin_magics.py +260 -0
  7. pynteract-0.1.0/pynteract/cli.py +112 -0
  8. pynteract-0.1.0/pynteract/collector.py +109 -0
  9. pynteract-0.1.0/pynteract/context.py +35 -0
  10. pynteract-0.1.0/pynteract/future_utils.py +60 -0
  11. pynteract-0.1.0/pynteract/magics.py +229 -0
  12. pynteract-0.1.0/pynteract/namespace_utils.py +142 -0
  13. pynteract-0.1.0/pynteract/ptk.py +484 -0
  14. pynteract-0.1.0/pynteract/shell.py +822 -0
  15. pynteract-0.1.0/pynteract/streams.py +461 -0
  16. pynteract-0.1.0/pynteract/traceback_utils.py +168 -0
  17. pynteract-0.1.0/pynteract/user_config.py +32 -0
  18. pynteract-0.1.0/pynteract/utils.py +37 -0
  19. pynteract-0.1.0/pynteract.egg-info/PKG-INFO +386 -0
  20. pynteract-0.1.0/pynteract.egg-info/SOURCES.txt +39 -0
  21. pynteract-0.1.0/pynteract.egg-info/dependency_links.txt +1 -0
  22. pynteract-0.1.0/pynteract.egg-info/entry_points.txt +2 -0
  23. pynteract-0.1.0/pynteract.egg-info/requires.txt +7 -0
  24. pynteract-0.1.0/pynteract.egg-info/top_level.txt +1 -0
  25. pynteract-0.1.0/pyproject.toml +29 -0
  26. pynteract-0.1.0/setup.cfg +4 -0
  27. pynteract-0.1.0/tests/conftest.py +9 -0
  28. pynteract-0.1.0/tests/test_builtin_magics_autoregister.py +11 -0
  29. pynteract-0.1.0/tests/test_cli.py +29 -0
  30. pynteract-0.1.0/tests/test_display.py +44 -0
  31. pynteract-0.1.0/tests/test_dynamic_stream_hooks.py +24 -0
  32. pynteract-0.1.0/tests/test_history_and_filenames.py +39 -0
  33. pynteract-0.1.0/tests/test_hooks.py +74 -0
  34. pynteract-0.1.0/tests/test_ipython_magics.py +59 -0
  35. pynteract-0.1.0/tests/test_magics.py +155 -0
  36. pynteract-0.1.0/tests/test_reset.py +18 -0
  37. pynteract-0.1.0/tests/test_run_hooks_and_capture_context.py +69 -0
  38. pynteract-0.1.0/tests/test_run_magic.py +43 -0
  39. pynteract-0.1.0/tests/test_shell.py +57 -0
  40. pynteract-0.1.0/tests/test_stream_compat.py +60 -0
  41. pynteract-0.1.0/tests/test_system_commands.py +40 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Baptiste
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,8 @@
1
+ include README.md
2
+ include LICENSE
3
+ include pyproject.toml
4
+
5
+ recursive-include pynteract *.py
6
+ recursive-include tests *.py
7
+
8
+ global-exclude __pycache__ *.py[cod]
@@ -0,0 +1,386 @@
1
+ Metadata-Version: 2.4
2
+ Name: pynteract
3
+ Version: 0.1.0
4
+ Summary: Lightweight embedded, cell-oriented Python shell (notebooks/agents) with minimal dependencies.
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: asttokens>=2.4
10
+ Provides-Extra: terminal
11
+ Requires-Dist: prompt-toolkit>=3.0; extra == "terminal"
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=7.0; extra == "dev"
14
+ Dynamic: license-file
15
+
16
+ # Pynteract
17
+
18
+ Pynteract is a lightweight, dependency-minimal, embeddable Python “cell shell” with IPython-style ergonomics:
19
+ magics (`%`, `%%`), system commands (`!`, `!!`), per-cell filenames for better tracebacks, and a hookable execution pipeline.
20
+
21
+ It is designed to be used inside notebooks, web apps, AI agents, CLIs, and other embedded environments where you want:
22
+ deterministic execution, controllable IO, and rich integration points.
23
+
24
+ ## Table of contents
25
+
26
+ - [Install](#install)
27
+ - [Quick start](#quick-start)
28
+ - [Core concepts](#core-concepts)
29
+ - [Execution + display](#execution--display)
30
+ - [Namespaces + filenames](#namespaces--filenames)
31
+ - [History](#history)
32
+ - [Magics and system commands](#magics-and-system-commands)
33
+ - [Registering magics](#registering-magics)
34
+ - [Magic forms](#magic-forms)
35
+ - [Templates](#templates)
36
+ - [System commands](#system-commands)
37
+ - [Built-in magics](#built-in-magics)
38
+ - [Hooks](#hooks)
39
+ - [Interactive terminal mode](#interactive-terminal-mode)
40
+ - [Persistent history](#persistent-history)
41
+ - [Startup script](#startup-script)
42
+ - [Restarting a session](#restarting-a-session)
43
+ - [CLI](#cli)
44
+ - [Threads and late output routing](#threads-and-late-output-routing)
45
+ - [API reference](#api-reference)
46
+ - [`Shell`](#shell)
47
+ - [`ShellResponse`](#shellresponse)
48
+ - [`RunContext` and context capture](#runcontext-and-context-capture)
49
+ - [Hook reference](#hook-reference)
50
+ - [Configuration files](#configuration-files)
51
+ - [Development](#development)
52
+ - [License](#license)
53
+
54
+ ## Install
55
+
56
+ ```bash
57
+ pip install pynteract
58
+ ```
59
+
60
+ Optional terminal interactive mode (adds prompt_toolkit):
61
+
62
+ ```bash
63
+ pip install "pynteract[terminal]"
64
+ ```
65
+
66
+ ## Quick start
67
+
68
+ ```python
69
+ from pynteract import Shell
70
+
71
+ shell = Shell(display_mode="none")
72
+ resp = shell.run("x = 41 + 1\nx")
73
+
74
+ assert resp.result == 42
75
+ assert resp.stdout == ""
76
+ assert resp.stderr == ""
77
+ assert resp.exception is None
78
+ ```
79
+
80
+ ## Core concepts
81
+
82
+ ### Execution + display
83
+
84
+ - Code is executed node-by-node (AST-level), enabling pre/post hooks per statement/expression.
85
+ - `display_mode` controls what expression results are displayed:
86
+ - `"last"` (default): display only the last expression value
87
+ - `"all"`: display every expression value
88
+ - `"none"`: never display expression values
89
+ - A semicolon after an expression suppresses display, mirroring IPython: `x;`.
90
+
91
+ ### Namespaces + filenames
92
+
93
+ Pynteract runs in a module-backed namespace by default (good `__module__` behavior, better tracebacks).
94
+
95
+ You can embed it in an existing namespace:
96
+
97
+ ```python
98
+ import sys
99
+ from pynteract import Shell
100
+
101
+ shell = Shell(module_name="__main__", namespace=sys.modules["__main__"].__dict__)
102
+ ```
103
+
104
+ Each `run()` is assigned a synthetic filename like `<shell-input-3>` (or a custom one via `filename=...`).
105
+ This filename is used for:
106
+
107
+ - tracebacks
108
+ - `Shell.history` keys
109
+ - hook routing via `RunContext.name` (handy in notebook-like UIs)
110
+
111
+ ### History
112
+
113
+ `Shell.history` is an `OrderedDict` of recent `ShellResponse` objects keyed by synthetic/custom filename.
114
+ The size is capped by `history_size`.
115
+
116
+ ## Magics and system commands
117
+
118
+ ### Registering magics
119
+
120
+ ```python
121
+ from pynteract import Shell
122
+
123
+ shell = Shell(display_mode="none")
124
+
125
+ @shell.register_magic(name="caps", mode="both") # "line" | "cell" | "both"
126
+ def caps(text: str) -> str:
127
+ return text.upper()
128
+
129
+ assert shell.run("%caps hello").result == "HELLO"
130
+ ```
131
+
132
+ ### Magic forms
133
+
134
+ - Line magic: `%name rest of line`
135
+ - Cell magic: `%%name` on the first line; the remaining cell body is passed as a string
136
+ - Inline magic: `x = %name rest of line` (also works after `;`)
137
+
138
+ ### Templates
139
+
140
+ Inside magics and system commands, `{expr}` is evaluated in the current namespace and replaced with `str(value)`.
141
+
142
+ - Escape literal braces with `{{` / `}}`.
143
+
144
+ ### System commands
145
+
146
+ - `!cmd ...` runs a system command and streams stdout/stderr into the captured streams.
147
+ - `!!cmd ...` runs a system command, captures stdout, and returns it as the cell result (stderr still streams).
148
+
149
+ ```python
150
+ shell = Shell(display_mode="none")
151
+ shell.run('!python3 -c "print(123)"').stdout
152
+ shell.run('!!python3 -c "print(456)"').result
153
+ ```
154
+
155
+ ### Built-in magics
156
+
157
+ Pynteract ships a small set of “IPython-like” convenience magics (registered automatically by `Shell.ensure_builtins()`):
158
+
159
+ - `%pwd`, `%cd [path|-]`, `%ls [path]`
160
+ - `%env`, `%env KEY`, `%env KEY=value`, `%env -u KEY`
161
+ - `%who`, `%whos`
162
+ - `%run [-i] script.py [args...]`
163
+ - `%time <code>`, `%timeit [-n N] [-r R] <code>`
164
+ - `%%bash`
165
+
166
+ ## Hooks
167
+
168
+ Hooks are stored in `shell.hooks` (a dict) and are looked up dynamically during execution. Updating a hook
169
+ while the shell is running takes effect immediately.
170
+
171
+ ```python
172
+ from pynteract import Shell
173
+
174
+ def stdout_hook(data: str, buffer: str) -> None:
175
+ print("STDOUT:", data, end="")
176
+
177
+ shell = Shell(display_mode="none", stdout_hook=stdout_hook)
178
+ shell.hooks["stdout_hook"] = stdout_hook # can be swapped dynamically
179
+ ```
180
+
181
+ Hooks may optionally accept a final `ctx` argument (`RunContext`) for routing. `ctx.name` matches the synthetic/custom
182
+ `filename` of the current run (or another routing name if you override it).
183
+
184
+ ## Interactive terminal mode
185
+
186
+ ```python
187
+ from pynteract import Shell
188
+ Shell().interact()
189
+ ```
190
+
191
+ ### Persistent history
192
+
193
+ Interactive sessions store prompt histories under `~/.pynteract/` (or `PYNTERACT_CONFIG_DIR`):
194
+
195
+ - `history_python.txt`: interactive `>>>` input history
196
+ - `history_text.txt`: stdin “text” history used for `input()` reads
197
+
198
+ ### Startup script
199
+
200
+ If present and non-empty, `~/.pynteract/startup.py` is executed at the start of interactive sessions.
201
+
202
+ - The startup file is executed via `Shell.run(..., silent=True)` so magics/system commands are supported.
203
+ - If the startup fails, Pynteract prints the enriched traceback and exits (non-zero status in the CLI).
204
+ - When using `pynteract -i script.py`, the startup script runs before the script (same namespace kept for the REPL).
205
+
206
+ ### Restarting a session
207
+
208
+ In interactive mode, you can reset the namespace and rerun startup:
209
+
210
+ ```python
211
+ __shell__.restart_session()
212
+ ```
213
+
214
+ ## CLI
215
+
216
+ Installing the package provides a `pynteract` executable.
217
+
218
+ ```bash
219
+ # Interactive session
220
+ pynteract
221
+
222
+ # Run a script (similar to `python script.py`)
223
+ pynteract path/to/script.py [args...]
224
+
225
+ # Run a script, then enter interactive mode with the same namespace
226
+ pynteract -i path/to/script.py [args...]
227
+ ```
228
+
229
+ ## Threads and late output routing
230
+
231
+ To keep output from background threads routed to the originating cell/run, call `shell.enable_stdio_proxy()` once,
232
+ then capture and propagate a `contextvars.Context`:
233
+
234
+ ```python
235
+ import threading, time
236
+ from pynteract import Shell
237
+
238
+ shell = Shell(display_mode="none")
239
+ shell.enable_stdio_proxy()
240
+
241
+ def worker():
242
+ for i in range(3):
243
+ print(f"tick {i}")
244
+ time.sleep(0.1)
245
+
246
+ shell.run("print('cell start')", filename="<cell-1>")
247
+ ctx = shell.capture_context() # captures ctx.name == "<cell-1>"
248
+ threading.Thread(target=lambda: ctx.run(worker), daemon=True).start()
249
+ ```
250
+
251
+ ### Notebook-style “late streaming” example
252
+
253
+ In a notebook UI, you typically want each cell to own its own stdout/stderr widget, and you want background threads
254
+ spawned by a cell to keep streaming into that same widget even after the cell finished.
255
+
256
+ Use the run `filename=` (exposed as `ctx.name` to hooks) as your routing key:
257
+
258
+ ```python
259
+ from pynteract import Shell
260
+
261
+ cell_widgets = {} # e.g. {"cell-42": StdoutTextArea(...)}
262
+
263
+ def cell_id_from_ctx(ctx) -> str:
264
+ return ctx.name.split(":")[1] # e.g. "<nb:cell-42:run-7>" -> "cell-42"
265
+
266
+ def stdout_router(data: str, _buffer: str, ctx) -> None:
267
+ cell_widgets[cell_id_from_ctx(ctx)].append(data)
268
+
269
+ shell = Shell(display_mode="none", stdout_hook=stdout_router)
270
+ shell.enable_stdio_proxy()
271
+
272
+ def run_cell(cell_id: str, run_no: int, code: str) -> None:
273
+ shell.run(code, filename=f"<nb:{cell_id}:run-{run_no}>")
274
+ ```
275
+
276
+ ## API reference
277
+
278
+ ### `Shell`
279
+
280
+ Constructor:
281
+
282
+ ```python
283
+ Shell(
284
+ namespace: dict | None = None,
285
+ module_name: str | None = None,
286
+ ensure_cwd_on_syspath: bool = True,
287
+ display_mode: Literal["all", "last", "none"] = "last",
288
+ history_size: int = 200,
289
+ **hooks,
290
+ )
291
+ ```
292
+
293
+ Key methods:
294
+
295
+ | Method | Signature | Notes |
296
+ | --- | --- | --- |
297
+ | Execute | `run(code, globals=None, locals=None, silent=False, filename=None) -> ShellResponse` | `silent=True` suppresses stdout/stderr hooks (output still captured). |
298
+ | Interactive | `interact() -> int` | Terminal REPL; returns process-like exit code. |
299
+ | Restart | `restart_session(rerun_startup=True, announce=True) -> int` | Resets namespace and (optionally) reruns startup. |
300
+ | Namespace | `update_namespace(**kwargs)` | Adds symbols to the execution namespace. |
301
+ | Namespace | `set_namespace(namespace: dict)` | Switches to a different dict namespace. |
302
+ | Namespace | `reset_namespace()` | Clears user symbols and resets `__future__` flags. |
303
+ | Magics | `register_magic(func=None, *, name=None, mode="both")` | Decorator or direct call; mode `"line"|"cell"|"both"`. |
304
+ | Threads | `enable_stdio_proxy()` | Installs a routing proxy on `sys.stdout`/`sys.stderr` for late-thread capture. |
305
+ | Threads | `capture_context(name=None) -> contextvars.Context` | Capture routing context for another thread. |
306
+ | Builtins | `ensure_builtins()` | (Re)adds `__shell__`, `__magics__`, `display`, and built-in magics. |
307
+
308
+ Important public attributes:
309
+
310
+ | Attribute | Type | Meaning |
311
+ | --- | --- | --- |
312
+ | `namespace` | `dict` | Module-backed execution namespace. |
313
+ | `hooks` | `dict[str, Any]` | Hook registry; updated dynamically. |
314
+ | `magics` | `dict[str, Any]` | Registered magics. |
315
+ | `history` | `OrderedDict[str, ShellResponse]` | Recent run history keyed by filename. |
316
+ | `last_result` | `Any` | Last expression value. |
317
+
318
+ ### `ShellResponse`
319
+
320
+ Returned by `Shell.run(...)`.
321
+
322
+ | Field | Type | Meaning |
323
+ | --- | --- | --- |
324
+ | `input` | `str` | Original source string. |
325
+ | `processed_input` | `str` | Expanded source (magics/system commands). |
326
+ | `stdout` / `stderr` | `str` | Captured output for the run. |
327
+ | `result` | `Any` | Last expression value (depending on `display_mode`). |
328
+ | `exception` | `Exception | None` | Exception instance, with enriched traceback attached (when available). |
329
+
330
+ ### `RunContext` and context capture
331
+
332
+ Hooks may receive a `RunContext` object (`ctx`) with:
333
+
334
+ - `ctx.name`: routing name (by default the synthetic/custom `filename` of the current `run()`).
335
+
336
+ Use `shell.capture_context()` to propagate routing/capture context to a new thread:
337
+
338
+ ```python
339
+ ctx = shell.capture_context()
340
+ threading.Thread(target=lambda: ctx.run(worker)).start()
341
+ ```
342
+
343
+ ### Hook reference
344
+
345
+ All hooks are optional. Hook keys live in `shell.hooks` and are passed to `Shell(...)` via `**hooks` (kwargs must end with `_hook`).
346
+ Most hooks may optionally accept a final `ctx: RunContext`.
347
+
348
+ | Hook key | Signature | When it runs |
349
+ | --- | --- | --- |
350
+ | `input_hook` | `input_hook(code: str[, ctx]) -> None` | Before parsing. |
351
+ | `pre_run_hook` | `pre_run_hook(code: str[, ctx]) -> str` | Before tokenization/execution; can rewrite source. |
352
+ | `code_block_hook` | `code_block_hook(code_block: str[, ctx]) -> None` | For each executed AST block. |
353
+ | `pre_execute_hook` | `pre_execute_hook(node, source[, ctx]) -> ast.AST` | Before compiling a node. |
354
+ | `post_execute_hook` | `post_execute_hook(node, result[, ctx]) -> None` | After a node executes. |
355
+ | `display_hook` | `display_hook(obj, **kwargs)` or `display_hook(obj, ctx, **kwargs)` | When displaying expression values. |
356
+ | `stdout_hook` | `stdout_hook(data: str, buffer: str[, ctx]) -> None` | As stdout is flushed. |
357
+ | `stderr_hook` | `stderr_hook(data: str, buffer: str[, ctx]) -> None` | As stderr is flushed. |
358
+ | `stdin_hook` | `stdin_hook([ctx]) -> str | None` | For stdin reads (`None` = EOF). |
359
+ | `exception_hook` | `exception_hook(exc: Exception[, ctx]) -> None` | When a run finishes with an error. |
360
+ | `namespace_change_hook` | `namespace_change_hook(old, new, locals[, ctx]) -> None` | After a run, with before/after namespaces. |
361
+ | `post_run_hook` | `post_run_hook(resp: ShellResponse[, ctx]) -> ShellResponse` | Final response override/hook. |
362
+
363
+ ### Configuration files
364
+
365
+ Pynteract stores user-facing state under `~/.pynteract/` (override with `PYNTERACT_CONFIG_DIR`):
366
+
367
+ - `startup.py`: optional startup file (interactive + `-i` only)
368
+ - `history_python.txt`: persistent REPL history
369
+ - `history_text.txt`: persistent stdin history
370
+
371
+ ## Development
372
+
373
+ ```bash
374
+ pip install -e ".[dev]"
375
+ pytest -q
376
+ ```
377
+
378
+ To develop terminal interactive mode:
379
+
380
+ ```bash
381
+ pip install -e ".[dev,terminal]"
382
+ ```
383
+
384
+ ## License
385
+
386
+ MIT. See `LICENSE` file.