Name | pytest-hot-reloading JSON |
Version |
0.1.0a19
JSON |
| download |
home_page | None |
Summary | None |
upload_time | 2024-09-23 18:37:37 |
maintainer | None |
docs_url | None |
author | James Hutchison |
requires_python | <4.0,>=3.10 |
license | None |
keywords |
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# A PyTest Hot Reloading Plugin
![versions](https://img.shields.io/pypi/pyversions/pytest-hot-reloading)
A hot reloading pytest daemon, implemented as a plugin.
## Notice - Busted VS Code behavior - 5/28/2024
It was noted that VS Code does not properly detect tests finishing. This is due to buggy experimental behavior that you opt-in to. To opt-out, add to your settings:
```json
"python.experiments.optOutFrom": [
"pythonTestAdapter"
],
```
## Features
- Uses the [jurigged](https://github.com/breuleux/jurigged) library to watch and hot reload files
- Caches test discovery in many situations
- Improved performance by not having to import libraries again and again and skipping initialization logic
- System for registering workarounds in case something doesn't work out of the box
## Trade-offs
- First time imports are slower (measured < 10% to > 100% slower depending on the repo)
- May not work with some libraries
- Sometimes gets in a bad state and needs to be restarted
- Requires starting the daemon separately or adding a command line option to automatically start it
If it takes less than 5 seconds to do all of the imports
necessary to run a unit test, then you probably don't need this.
If you're using Django, recommended to use `--keep-db` to preserve the test database.
The minimum Python version is 3.10
## Demo
### With the hot reloading daemon
Faster subsequent runs
![Hot reloading demo](docs/with-daemon.gif)
### Without the hot reloading daemon
![Not hot reloading demo](docs/no-daemon.gif)
## Installation
Do not install in production code. This is exclusively for the developer environment.
**pip**: Add `pytest-hot-reloading` to your `dev-requirements.txt` file and `pip install -r dev-requirements.txt`
**poetry**: `poetry add --group=dev pytest-hot-reloading`
## Usage
Add the plugin to the pytest arguments.
Example using pyproject.toml:
```toml
[tool.pytest.ini_options]
addopts = "-p pytest_hot_reloading.plugin"
```
When running pytest, the plugin will detect whether the daemon is running.
If the daemon is not running, it will error unless the `--daemon-start-if-needed` argument is passed.
The current version of the VS Code Python extension is not, by default, compatible with automatically starting the daemon. The
test runner will hang. However, you can revert to legacy behavior which will allow for using the automatic starting.
See the VS Code section below for more information.
The recommended way to run the daemon is to give it its own run run profile so you can easily use the debugger in tests. As a convenience
you can also, if you're using a dev container, add this to your postStartCommand: `pytest --daemon &`. If the daemon doesn't start from your postStartCommand, see: https://github.com/microsoft/vscode-remote-release/issues/8536
Note that a pid file is created to track the pid.
Imports and in many cases initialization logic are not reran on subsequent runs, which can be a huge time saver.
Currently, if you want to debug, you will want to run the daemon manually with debugging.
### JetBrains (IDEA, PyCharm, etc)
Create a REGULAR Python run configuration, with pytest as the *module*. For parameters, add `--daemon`. Strongly consider storing
in the project so it is shared with other developers. Note that you most likely also need to set the working directory to the
project root where the pytest configuration is located so that it knows to use the plugin you configured earlier.
![JetBrains Example](docs/jetbrains-hot-reloading-example.png)
For more information on parameters, see the VS Code section below.
### VS Code
![Debugging demo](docs/hot-reloading-debug.gif)
This can easily be done in VS Code with the following launch profile:
```json
{
"name": "Pytest Daemon",
"type": "python",
"request": "launch",
"module": "pytest",
"justMyCode": false,
"args": [
"--daemon",
//
// everything below this is optional
//
"--daemon-port",
"4852", // the default value
"--daemon-watch-globs",
"./*.py" // the default value
// "./my-project/*.py:./some-thing-else/*.py", // example of colon separated globs
"--daemon-ignore-watch-globs",
"./.venv/*" // this is the default value, also colon separated globs
]
},
```
The daemon can be configured to use either file system polling or OS-based file system events.
The polling behavior is used by default and has higher compatibility. For example, if you're using
Docker for Windows with WSL2, you're going to have a bad time with inotify.
If the daemon is already running and you run pytest with `--daemon`, then the old one will be stopped
and a new one will be started. Note that `pytest --daemon` is NOT how you run tests. It is only used to start
the daemon.
The daemon can be stopped with `pytest --stop-daemon`. This can be used if it gets into a bad state.
To enable automatically starting the server, you have to, currently, disable the new Python Test Adapter:
In your devcontainer.json or user settings:
```json
"python.experiments.optOutFrom": [
"pythonTestAdapter"
],
```
Then enable automatically starting the daemon in your settings:
```json
"python.testing.pytestArgs": [
"--daemon-start-if-needed",
"tests"
],
```
## Arguments and Env Variables
- `PYTEST_DAEMON_USE_OS_EVENTS`
- Instead of polling the file system, use OS events such as inotify to check for file changes (recommended if your system supports it)
- Default: `False`
- Command line: `--daemon-use-os-events`
- `PYTEST_DAEMON_POLL_THROTTLE`
- A multipler for how aggressive the daemon does file system polling. This is not used if OS events are used.
- 2.0 = twice as slow, less CPU usage
- Default: `1.0`
- Command line: `--daemon-poll-throttle`
- `PYTEST_DAEMON_PORT`
- The port the daemon listens on.
- Default: `4852`.
- Command line: `--daemon-port`
- `PYTEST_DAEMON_PYTEST_NAME`
- The name of the pytest executable. Used for spawning the daemon.
- Default: `pytest`.
- Command line: `--pytest-name`
- `PYTEST_DAEMON_WATCH_GLOBS`
- The colon separated globs to watch.
- Default: `./**/*.py`.
- Command line: `--daemon-watch-globs`
- `PYTEST_DAEMON_IGNORE_WATCH_GLOBS`
- The colon separated globs to ignore.
- Default: `./.venv/*`.
- Command line: `--daemon-ignore-watch-globs`
- `PYTEST_DAEMON_START_IF_NEEDED`
- Start the pytest daemon if it is not running.
- Default: `False`
- Command line: `--daemon-start-if-needed`
- `PYTEST_DAEMON_DISABLE`
- Disable the pytest plugin for this test run.
- Default: `False`
- Command line: `--daemon-disable`
- `PYTEST_DAEMON_DO_NOT_AUTOWATCH_FIXTURES`
- Disable automatically watching files containing fixtures
- Default: `False`
- Command line: `--daemon-do-not-autowatch-fixtures`
## Workarounds
Libraries that use mutated globals may need a workaround to work with this plugin. The preferred
route is to have the library update its code to not mutate globals in a test environment, or to
restore them after a test suite has ran. In some cases, that isn't possible, usually because
the person with the problem doesn't own the library and can't wait around for a fix.
To register a workaround, create a function that is decorated by the
`pytest_hot_reloading.workaround.register_workaround` decorator. It may optionally yield. If it does,
then code after the yield is executed after the test suite has ran.
Example:
```python
from pytest_hot_reloading.workaround import register_workaround
@register_workaround("my_library")
def my_library_workaround():
import my_library
yield
my_library.some_global = BackToOriginalValue()
```
If you are a library author, you can disable any workarounds for your library by creating an empty
module `_clear_hot_reload_workarounds.py`. If this is successfully imported, then workarounds for
the given module will not be executed.
## Need More Speed?
- If using docker, run everything out of a named volume. Do not use bind mounts (sharing with the host file system)
- Set `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1` in the environment to disable automatic search and loading of plugins
- Note: You will need to specify explicitly specify the plugins to use if you do this.
- Example:
```toml
[tool.pytest.ini_options]
asyncio_mode = "auto"
addopts = "-p pytest_asyncio.plugin -p megamock.plugins.pytest -p pytest_hot_reloading.plugin"
```
- Run out of a Github Codespace or similar dedicated external environment
- Prefer using OS events, if your system works well with it. It uses less CPU and can pick up changes faster. Enable it with the environment variable `PYTEST_DAEMON_USE_OS_EVENTS=1`. It is only disabled by default for maximum compatibility.
## Known Issues
- This is alpha, although it's getting closer to where it can be called beta
- If you run out of docker, prefer named volumes. Bind mounds and cached file systems can create random issues where changes aren't reflected.
- The jurigged library is not perfect and sometimes it gets in a bad state
- Some libraries were not written with hot reloading in mind, and will not work without some changes.
- Many systems have conservative inotify limits. Consider bumping this up if you see errors about hitting the inotify limit.
- Possible command to see current limit: `cat /proc/sys/fs/inotify/max_user_instances`
- `sudo sysctl fs.inotify.max_user_instances=4096` in the `postStartCommand` will for example, help with dev containers. Increase as needed. Consult with ChatGPT if you need assistance with your OS.
- Use the `PYTEST_DAEMON_WATCH_GLOBS` env variable when there are simply too many files.
## Notes
- pytest-xdist will have its logic disabled, even if args are passed in to enable it
- pytest-django will not create test database suffixes for multiworker runs such as tox.
- Extreme example using import perf test from megamock:
- Without hot reloading: 0.71s
- With hot reloading (1st run): 1.79s
- With hot reloading (2nd run): 0.00s
Raw data
{
"_id": null,
"home_page": null,
"name": "pytest-hot-reloading",
"maintainer": null,
"docs_url": null,
"requires_python": "<4.0,>=3.10",
"maintainer_email": null,
"keywords": null,
"author": "James Hutchison",
"author_email": "jamesghutchison@proton.me",
"download_url": "https://files.pythonhosted.org/packages/cc/2d/239fda2a291cf56f72ea987dba20b7e3f79d668f1a605a4711c9fe15ce80/pytest_hot_reloading-0.1.0a19.tar.gz",
"platform": null,
"description": "# A PyTest Hot Reloading Plugin\n![versions](https://img.shields.io/pypi/pyversions/pytest-hot-reloading)\n\nA hot reloading pytest daemon, implemented as a plugin.\n\n## Notice - Busted VS Code behavior - 5/28/2024\nIt was noted that VS Code does not properly detect tests finishing. This is due to buggy experimental behavior that you opt-in to. To opt-out, add to your settings:\n\n```json\n\"python.experiments.optOutFrom\": [\n \"pythonTestAdapter\"\n],\n```\n\n## Features\n- Uses the [jurigged](https://github.com/breuleux/jurigged) library to watch and hot reload files\n- Caches test discovery in many situations\n- Improved performance by not having to import libraries again and again and skipping initialization logic\n- System for registering workarounds in case something doesn't work out of the box\n\n## Trade-offs\n- First time imports are slower (measured < 10% to > 100% slower depending on the repo)\n- May not work with some libraries\n- Sometimes gets in a bad state and needs to be restarted\n- Requires starting the daemon separately or adding a command line option to automatically start it\n\nIf it takes less than 5 seconds to do all of the imports\nnecessary to run a unit test, then you probably don't need this.\n\nIf you're using Django, recommended to use `--keep-db` to preserve the test database.\n\nThe minimum Python version is 3.10\n\n## Demo\n\n### With the hot reloading daemon\nFaster subsequent runs\n\n![Hot reloading demo](docs/with-daemon.gif)\n\n### Without the hot reloading daemon\n![Not hot reloading demo](docs/no-daemon.gif)\n\n## Installation\nDo not install in production code. This is exclusively for the developer environment.\n\n**pip**: Add `pytest-hot-reloading` to your `dev-requirements.txt` file and `pip install -r dev-requirements.txt`\n\n**poetry**: `poetry add --group=dev pytest-hot-reloading`\n\n\n## Usage\nAdd the plugin to the pytest arguments.\n\nExample using pyproject.toml:\n```toml\n[tool.pytest.ini_options]\naddopts = \"-p pytest_hot_reloading.plugin\"\n```\n\nWhen running pytest, the plugin will detect whether the daemon is running.\nIf the daemon is not running, it will error unless the `--daemon-start-if-needed` argument is passed.\n\nThe current version of the VS Code Python extension is not, by default, compatible with automatically starting the daemon. The\ntest runner will hang. However, you can revert to legacy behavior which will allow for using the automatic starting.\nSee the VS Code section below for more information.\n\nThe recommended way to run the daemon is to give it its own run run profile so you can easily use the debugger in tests. As a convenience\nyou can also, if you're using a dev container, add this to your postStartCommand: `pytest --daemon &`. If the daemon doesn't start from your postStartCommand, see: https://github.com/microsoft/vscode-remote-release/issues/8536\n\nNote that a pid file is created to track the pid.\n\nImports and in many cases initialization logic are not reran on subsequent runs, which can be a huge time saver.\n\nCurrently, if you want to debug, you will want to run the daemon manually with debugging.\n\n### JetBrains (IDEA, PyCharm, etc)\n\nCreate a REGULAR Python run configuration, with pytest as the *module*. For parameters, add `--daemon`. Strongly consider storing\nin the project so it is shared with other developers. Note that you most likely also need to set the working directory to the\nproject root where the pytest configuration is located so that it knows to use the plugin you configured earlier.\n\n![JetBrains Example](docs/jetbrains-hot-reloading-example.png)\n\nFor more information on parameters, see the VS Code section below.\n\n### VS Code\n\n![Debugging demo](docs/hot-reloading-debug.gif)\n\nThis can easily be done in VS Code with the following launch profile:\n\n```json\n {\n \"name\": \"Pytest Daemon\",\n \"type\": \"python\",\n \"request\": \"launch\",\n \"module\": \"pytest\",\n \"justMyCode\": false,\n \"args\": [\n \"--daemon\",\n //\n // everything below this is optional\n //\n \"--daemon-port\",\n \"4852\", // the default value\n \"--daemon-watch-globs\",\n \"./*.py\" // the default value\n // \"./my-project/*.py:./some-thing-else/*.py\", // example of colon separated globs\n \"--daemon-ignore-watch-globs\",\n \"./.venv/*\" // this is the default value, also colon separated globs\n ]\n },\n```\n\nThe daemon can be configured to use either file system polling or OS-based file system events.\nThe polling behavior is used by default and has higher compatibility. For example, if you're using\nDocker for Windows with WSL2, you're going to have a bad time with inotify.\n\nIf the daemon is already running and you run pytest with `--daemon`, then the old one will be stopped\nand a new one will be started. Note that `pytest --daemon` is NOT how you run tests. It is only used to start\nthe daemon.\n\nThe daemon can be stopped with `pytest --stop-daemon`. This can be used if it gets into a bad state.\n\nTo enable automatically starting the server, you have to, currently, disable the new Python Test Adapter:\n\nIn your devcontainer.json or user settings:\n```json\n\"python.experiments.optOutFrom\": [\n \"pythonTestAdapter\"\n],\n```\n\nThen enable automatically starting the daemon in your settings:\n```json\n\"python.testing.pytestArgs\": [\n \"--daemon-start-if-needed\",\n \"tests\"\n],\n```\n\n## Arguments and Env Variables\n- `PYTEST_DAEMON_USE_OS_EVENTS`\n - Instead of polling the file system, use OS events such as inotify to check for file changes (recommended if your system supports it)\n - Default: `False`\n - Command line: `--daemon-use-os-events`\n- `PYTEST_DAEMON_POLL_THROTTLE`\n - A multipler for how aggressive the daemon does file system polling. This is not used if OS events are used.\n - 2.0 = twice as slow, less CPU usage\n - Default: `1.0`\n - Command line: `--daemon-poll-throttle`\n- `PYTEST_DAEMON_PORT`\n - The port the daemon listens on.\n - Default: `4852`.\n - Command line: `--daemon-port`\n- `PYTEST_DAEMON_PYTEST_NAME`\n - The name of the pytest executable. Used for spawning the daemon.\n - Default: `pytest`.\n - Command line: `--pytest-name`\n- `PYTEST_DAEMON_WATCH_GLOBS`\n - The colon separated globs to watch.\n - Default: `./**/*.py`.\n - Command line: `--daemon-watch-globs`\n- `PYTEST_DAEMON_IGNORE_WATCH_GLOBS`\n - The colon separated globs to ignore.\n - Default: `./.venv/*`.\n - Command line: `--daemon-ignore-watch-globs`\n- `PYTEST_DAEMON_START_IF_NEEDED`\n - Start the pytest daemon if it is not running.\n - Default: `False`\n - Command line: `--daemon-start-if-needed`\n- `PYTEST_DAEMON_DISABLE`\n - Disable the pytest plugin for this test run.\n - Default: `False`\n - Command line: `--daemon-disable`\n- `PYTEST_DAEMON_DO_NOT_AUTOWATCH_FIXTURES`\n - Disable automatically watching files containing fixtures\n - Default: `False`\n - Command line: `--daemon-do-not-autowatch-fixtures`\n\n## Workarounds\nLibraries that use mutated globals may need a workaround to work with this plugin. The preferred\nroute is to have the library update its code to not mutate globals in a test environment, or to\nrestore them after a test suite has ran. In some cases, that isn't possible, usually because\nthe person with the problem doesn't own the library and can't wait around for a fix.\n\nTo register a workaround, create a function that is decorated by the\n`pytest_hot_reloading.workaround.register_workaround` decorator. It may optionally yield. If it does,\nthen code after the yield is executed after the test suite has ran.\n\nExample:\n```python\nfrom pytest_hot_reloading.workaround import register_workaround\n\n@register_workaround(\"my_library\")\ndef my_library_workaround():\n import my_library\n\n yield\n\n my_library.some_global = BackToOriginalValue()\n```\n\nIf you are a library author, you can disable any workarounds for your library by creating an empty\nmodule `_clear_hot_reload_workarounds.py`. If this is successfully imported, then workarounds for\nthe given module will not be executed.\n\n## Need More Speed?\n- If using docker, run everything out of a named volume. Do not use bind mounts (sharing with the host file system)\n- Set `PYTEST_DISABLE_PLUGIN_AUTOLOAD=1` in the environment to disable automatic search and loading of plugins\n - Note: You will need to specify explicitly specify the plugins to use if you do this.\n - Example:\n ```toml\n [tool.pytest.ini_options]\n asyncio_mode = \"auto\"\n addopts = \"-p pytest_asyncio.plugin -p megamock.plugins.pytest -p pytest_hot_reloading.plugin\"\n ```\n- Run out of a Github Codespace or similar dedicated external environment\n- Prefer using OS events, if your system works well with it. It uses less CPU and can pick up changes faster. Enable it with the environment variable `PYTEST_DAEMON_USE_OS_EVENTS=1`. It is only disabled by default for maximum compatibility.\n\n## Known Issues\n- This is alpha, although it's getting closer to where it can be called beta\n- If you run out of docker, prefer named volumes. Bind mounds and cached file systems can create random issues where changes aren't reflected.\n- The jurigged library is not perfect and sometimes it gets in a bad state\n- Some libraries were not written with hot reloading in mind, and will not work without some changes.\n- Many systems have conservative inotify limits. Consider bumping this up if you see errors about hitting the inotify limit.\n - Possible command to see current limit: `cat /proc/sys/fs/inotify/max_user_instances`\n - `sudo sysctl fs.inotify.max_user_instances=4096` in the `postStartCommand` will for example, help with dev containers. Increase as needed. Consult with ChatGPT if you need assistance with your OS.\n - Use the `PYTEST_DAEMON_WATCH_GLOBS` env variable when there are simply too many files.\n\n## Notes\n- pytest-xdist will have its logic disabled, even if args are passed in to enable it\n- pytest-django will not create test database suffixes for multiworker runs such as tox.\n- Extreme example using import perf test from megamock:\n - Without hot reloading: 0.71s\n - With hot reloading (1st run): 1.79s\n - With hot reloading (2nd run): 0.00s\n\n",
"bugtrack_url": null,
"license": null,
"summary": null,
"version": "0.1.0a19",
"project_urls": null,
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "1b7a1982991fe2a78633097ef80be85e48fecbc1aeae714f1ab4e13bbb91e9ef",
"md5": "cff1203fa62cc4975da894dbbc5692e9",
"sha256": "0e44c5032028757b231c83a633edcf5fe58714aa096dfafca49cf0c349b1f1ed"
},
"downloads": -1,
"filename": "pytest_hot_reloading-0.1.0a19-py3-none-any.whl",
"has_sig": false,
"md5_digest": "cff1203fa62cc4975da894dbbc5692e9",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<4.0,>=3.10",
"size": 19206,
"upload_time": "2024-09-23T18:37:36",
"upload_time_iso_8601": "2024-09-23T18:37:36.165070Z",
"url": "https://files.pythonhosted.org/packages/1b/7a/1982991fe2a78633097ef80be85e48fecbc1aeae714f1ab4e13bbb91e9ef/pytest_hot_reloading-0.1.0a19-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "cc2d239fda2a291cf56f72ea987dba20b7e3f79d668f1a605a4711c9fe15ce80",
"md5": "2beb049ceb3e7ab235081a983a194086",
"sha256": "2221ab547bf82f4b12cc4e15d166df064acd2265464d87195b6b95441dd68d30"
},
"downloads": -1,
"filename": "pytest_hot_reloading-0.1.0a19.tar.gz",
"has_sig": false,
"md5_digest": "2beb049ceb3e7ab235081a983a194086",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<4.0,>=3.10",
"size": 20148,
"upload_time": "2024-09-23T18:37:37",
"upload_time_iso_8601": "2024-09-23T18:37:37.882765Z",
"url": "https://files.pythonhosted.org/packages/cc/2d/239fda2a291cf56f72ea987dba20b7e3f79d668f1a605a4711c9fe15ce80/pytest_hot_reloading-0.1.0a19.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-09-23 18:37:37",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "pytest-hot-reloading"
}