Skip to content

Commit

Permalink
feat(pexpect): introduce the pexpect python library
Browse files Browse the repository at this point in the history
A pure Python module for spawning
child applications; controlling them; and responding to expected patterns in
their output. Pexpect works like Don Libes’ Expect. Pexpect allows your script
to spawn a child application and control it as if a human were typing commands.

style(prompt_toolkit): correct links and indentations

fix(python_snippets): explain how to show the message in custom exceptions

feat(python_snippets): explain how to import a module or object from within a python program
  • Loading branch information
lyz-code committed Jan 30, 2021
1 parent f411b1b commit 53f7b1f
Show file tree
Hide file tree
Showing 6 changed files with 112 additions and 11 deletions.
1 change: 0 additions & 1 deletion docs/coding/python/click.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ date: 20200827
author: Lyz
---


[Click](https://click.palletsprojects.com/) is a Python package for creating
beautiful command line interfaces in a composable way with as little code as
necessary. It’s the “Command Line Interface Creation Kit”. It’s highly
Expand Down
19 changes: 11 additions & 8 deletions docs/coding/python/prompt_toolkit.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ pip install prompt_toolkit

# Usage

## [A simple
prompt](https://python-prompt-toolkit.readthedocs.io/en/master/pages/getting_started.html#a-simple-prompt)
## [A simple prompt](https://python-prompt-toolkit.readthedocs.io/en/master/pages/getting_started.html#a-simple-prompt)

The following snippet is the most simple example, it uses the `prompt()` function
to asks the user for input and returns the text. Just like `(raw_)input`.
Expand All @@ -36,7 +35,7 @@ interface](https://en.wikipedia.org/wiki/Text-based_user_interface) (TUI) with
python is not well documented. Some of the main developers suggest [mocking
it](https://github.com/prompt-toolkit/python-prompt-toolkit/issues/477) while
[others](https://github.com/copier-org/copier/pull/260/files#diff-4e8715c7a425ee52e74b7df4d34efd32e8c92f3e60bd51bc2e1ad5943b82032e)
use [pexpect](https://pexpect.readthedocs.io/en/stable/overview.html).
use [pexpect](pexpect.md).

With the first approach you can test python functions and methods internally but
it can lead you to the over mocking problem. The second will limit you to test
Expand Down Expand Up @@ -71,13 +70,12 @@ internally.
```

!!! note "File: test_source.py"
```python
from pexpect.popen_spawn import PopenSpawn
```python
import pexpect


def test_tui() -> None:
tui = PopenSpawn(["python", "source.py"], timeout=1)
tui = pexpect.spawn("python source.py", timeout=5)
tui.expect("Give me .*")
tui.sendline("HI")
tui.expect_exact(pexpect.EOF)
Expand All @@ -86,8 +84,12 @@ internally.
assert f.read() == "HI"
```
The `tui.expect_exact(pexpect.EOF)` line is required so that the tests aren't
run before the process has ended, otherwise the file might not exist yet.
Where:
* The `tui.expect_exact(pexpect.EOF)` line is required so that the tests aren't
run before the process has ended, otherwise the file might not exist yet.
* The `timeout=5` is required in case that the `pexpect` interaction is not well
defined, so that the test is not hung forever.
!!! note "Thank you [Jairo Llopis](https://github.com/Yajo) for this solution."
I've deduced the solution from his
Expand All @@ -98,4 +100,5 @@ run before the process has ended, otherwise the file might not exist yet.
# References
* [Docs](https://python-prompt-toolkit.readthedocs.io/en/master/)
* [Git](https://github.com/prompt-toolkit/python-prompt-toolkit)
* [Projects using prompt_toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/PROJECTS.rst)
1 change: 1 addition & 0 deletions docs/coding/python/pydantic.md
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ need to use
[`root_validators`](https://pydantic-docs.helpmanual.io/usage/validators/#root-validators).

```python
from pydantic import root_validator

class PypikaRepository(BaseModel):
"""Implement the repository pattern using the Pypika query builder."""
Expand Down
25 changes: 23 additions & 2 deletions docs/coding/python/python_snippets.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,14 +184,35 @@ class Error(Exception):
"""Base class for exceptions in this module."""



class ConceptNotFoundError(Error):
"""Transactions with unmatched concept."""

def __init__(self, transactions: List[Transaction]) -> None:
def __init__(self, message: str, transactions: List[Transaction]) -> None:
"""Initialize the exception."""
super().__init__()
self.message = message
self.transactions = transactions
super().__init__(self.message)
```

Most exceptions are defined with names that end in “Error”, similar to the
naming of the standard exceptions.

## [Import a module or it's objects from within a python program](https://docs.python.org/3/library/importlib.html)

```python
import importlib

module = importlib.import_module('os')
module_class = module.getcwd

relative_module = importlib.import_module('.model', package='mypackage')
class_to_extract = 'MyModel'
extracted_class = geattr(relative_module, class_to_extract)
```

The first argument specifies what module to import in absolute or relative terms
(e.g. either `pkg.mod` or `..mod`). If the name is specified in relative terms, then
the package argument must be set to the name of the package which is to act as
the anchor for resolving the package name (e.g. `import_module('..mod',
'pkg.subpkg')` will `import pkg.mod`).
76 changes: 76 additions & 0 deletions docs/pexpect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
title: pexpect
date: 20210130
author: Lyz
---

[pexpect](https://pexpect.readthedocs.io) is a pure Python module for spawning
child applications; controlling them; and responding to expected patterns in
their output. Pexpect works like Don Libes’ Expect. Pexpect allows your script
to spawn a child application and control it as if a human were typing commands.

# [Installation](https://pexpect.readthedocs.io/en/stable/install.html)

```bash
pip install pexpect
```

# Usage

```python
import pexpect

child = pexpect.spawn('ftp ftp.openbsd.org')
child.expect('Name .*: ')
child.sendline('anonymous')
```

If you're using it to spawn a program that asks something and then ends, you
can catch the end with `.expect_exact(pexpect.EOF)`.

```python
tui = pexpect.spawn("python source.py", timeout=5)
tui.expect("Give me .*")
tui.sendline("HI")
tui.expect_exact(pexpect.EOF)
```

The `timeout=5` is useful if the `pexpect` interaction is not well
defined, so that the script is not hung forever.

## Send key presses

To simulate key presses, you can use [prompt_toolkit](prompt_toolkit.md)
[keys](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/keys.py)
with
[REVERSE_ANSI_SEQUENCES](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/input/ansi_escape_sequences.py#L335).

```python
from prompt_toolkit.input.ansi_escape_sequences import REVERSE_ANSI_SEQUENCES
from prompt_toolkit.keys import Keys

tui = pexpect.spawn("python source.py", timeout=5)
tui.send(REVERSE_ANSI_SEQUENCES[Keys.ControlC])
```

To make your code cleaner you can use [a helper
class](https://github.com/copier-org/copier/blob/66d34d1dd35a55ad2a230dd1b0ce3c820089c971/tests/helpers.py):

```python
from prompt_toolkit.input.ansi_escape_sequences import REVERSE_ANSI_SEQUENCES
from prompt_toolkit.keys import Keys

class Keyboard(str, Enum):
ControlH = REVERSE_ANSI_SEQUENCES[Keys.ControlH]
Enter = "\r"
Esc = REVERSE_ANSI_SEQUENCES[Keys.Escape]

# Equivalent keystrokes in terminals; see python-prompt-toolkit for
# further explanations
Alt = Esc
Backspace = ControlH
```

# References

* [Docs](https://pexpect.readthedocs.io)
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ nav:
- mkdocstrings: coding/python/mkdocstrings.md
- Pandas: coding/python/pandas.md
- Passpy: coding/python/passpy.md
- pexpect: pexpect.md
- Prompt Toolkit: coding/python/prompt_toolkit.md
- Pydantic:
- Pydantic: coding/python/pydantic.md
Expand Down

0 comments on commit 53f7b1f

Please sign in to comment.