pytest-language-server 0.7.1

A blazingly fast Language Server Protocol implementation for pytest
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# pytest-language-server 🔥

[![CI](https://github.com/bellini666/pytest-language-server/workflows/CI/badge.svg)](https://github.com/bellini666/pytest-language-server/actions/workflows/ci.yml)
[![Security Audit](https://github.com/bellini666/pytest-language-server/workflows/Security%20Audit/badge.svg)](https://github.com/bellini666/pytest-language-server/actions/workflows/security.yml)
[![PyPI version](https://badge.fury.io/py/pytest-language-server.svg)](https://badge.fury.io/py/pytest-language-server)
[![Downloads](https://static.pepy.tech/badge/pytest-language-server)](https://pepy.tech/project/pytest-language-server)
[![Crates.io](https://img.shields.io/crates/v/pytest-language-server.svg)](https://crates.io/crates/pytest-language-server)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python Version](https://img.shields.io/pypi/pyversions/pytest-language-server.svg)](https://pypi.org/project/pytest-language-server/)

> **Shamelessly vibed into existence** 🤖✨
>
> This entire LSP implementation was built from scratch in a single AI-assisted coding session.
> No template. No boilerplate. Just pure vibes and Rust. That's right - a complete, working
> Language Server Protocol implementation for pytest, vibed into reality through the power of
> modern AI tooling. Even this message about vibing was vibed into existence.

A blazingly fast Language Server Protocol (LSP) implementation for pytest, built with Rust.

## Demo

![pytest-language-server demo](demo.gif)

*Showcasing go-to-definition, code completion, hover documentation, and code actions. Demo also vibed into existence.* ✨

## Features

### 🎯 Go to Definition
Jump directly to fixture definitions from anywhere they're used:
- Local fixtures in the same file
- Fixtures in `conftest.py` files
- Third-party fixtures from pytest plugins (pytest-mock, pytest-asyncio, etc.)
- Respects pytest's fixture shadowing/priority rules

### ✨ Code Completion
Smart auto-completion for pytest fixtures:
- **Context-aware**: Only triggers inside test functions and fixture functions
- **Hierarchy-respecting**: Suggests fixtures based on pytest's priority rules (same file > conftest.py > third-party)
- **Rich information**: Shows fixture source file and docstring
- **No duplicates**: Automatically filters out shadowed fixtures
- **Works everywhere**: Completions available in both function parameters and function bodies
- Supports both sync and async functions

### 🔍 Find References
Find all usages of a fixture across your entire test suite:
- Works from fixture definitions or usage sites
- Character-position aware (distinguishes between fixture name and parameters)
- Shows references in all test files
- Correctly handles fixture overriding and hierarchies
- **LSP spec compliant**: Always includes the current position in results

### 📚 Hover Documentation
View fixture information on hover:
- Fixture signature
- Source file location
- Docstring (with proper formatting and dedenting)
- Markdown support in docstrings

### 💡 Code Actions (Quick Fixes)
One-click fixes for common pytest issues:
- **Add missing fixture parameters**: Automatically add undeclared fixtures to function signatures
- **Smart insertion**: Handles both empty and existing parameter lists
- **Editor integration**: Works with any LSP-compatible editor's quick fix menu
- **LSP compliant**: Full support for `CodeActionKind::QUICKFIX`

### ⚠️ Diagnostics & Quick Fixes
Detect and fix common pytest fixture issues with intelligent code actions:

**Undeclared Fixture Detection:**
- Detects when fixtures are used in function bodies but not declared as parameters
- **Line-aware scoping**: Correctly handles local variables assigned later in the function
- **Hierarchy-aware**: Only reports fixtures that are actually available in the current file's scope
- **Works in tests and fixtures**: Detects undeclared usage in both test functions and fixture functions
- Excludes built-in names (`self`, `request`) and actual local variables

**One-Click Quick Fixes:**
- **Code actions** to automatically add missing fixture parameters
- Intelligent parameter insertion (handles both empty and existing parameter lists)
- Works with both single-line and multi-line function signatures
- Triggered directly from diagnostic warnings

Example:
```python
@pytest.fixture
def user_db():
    return Database()

def test_user(user_db):  # ✅ user_db properly declared
    user = user_db.get_user(1)
    assert user.name == "Alice"

def test_broken():  # ⚠️ Warning: 'user_db' used but not declared
    user = user_db.get_user(1)  # 💡 Quick fix: Add 'user_db' fixture parameter
    assert user.name == "Alice"
```

**How to use quick fixes:**
1. Place cursor on the warning squiggle
2. Trigger code actions menu (usually Cmd+. or Ctrl+. in most editors)
3. Select "Add 'fixture_name' fixture parameter"
4. The parameter is automatically added to your function signature

### ⚡️ Performance
Built with Rust for maximum performance:
- Fast workspace scanning with concurrent file processing
- Efficient AST parsing using rustpython-parser
- Lock-free data structures with DashMap
- Minimal memory footprint

## Installation

Choose your preferred installation method:

### 📦 PyPI (Recommended)

The easiest way to install for Python projects:

```bash
# Using uv (recommended)
uv tool install pytest-language-server

# Or with pip
pip install pytest-language-server

# Or with pipx (isolated environment)
pipx install pytest-language-server
```

### 🍺 Homebrew (macOS/Linux)

Install via Homebrew for system-wide availability:

```bash
brew install bellini666/tap/pytest-language-server
```

To add the tap first:
```bash
brew tap bellini666/tap https://github.com/bellini666/pytest-language-server
brew install pytest-language-server
```

### 🦀 Cargo (Rust)

Install from crates.io if you have Rust installed:

```bash
cargo install pytest-language-server
```

### 📥 Pre-built Binaries

Download pre-built binaries from the [GitHub Releases](https://github.com/bellini666/pytest-language-server/releases) page.

Available for:
- **Linux**: x86_64, aarch64, armv7 (glibc and musl)
- **macOS**: Intel and Apple Silicon
- **Windows**: x64 and x86

### 🔨 From Source

Build from source for development or customization:

```bash
git clone https://github.com/bellini666/pytest-language-server
cd pytest-language-server
cargo build --release
```

The binary will be at `target/release/pytest-language-server`.

## Setup

### Neovim (with nvim-lspconfig)

```lua
require'lspconfig'.pytest_lsp.setup{
  cmd = { "pytest-language-server" },
  filetypes = { "python" },
  root_dir = function(fname)
    return require'lspconfig'.util.root_pattern('pyproject.toml', 'setup.py', 'setup.cfg', 'pytest.ini')(fname)
  end,
}
```

### Zed

Install the extension from the extensions marketplace:

1. Open Zed
2. Open the command palette (Cmd+Shift+P / Ctrl+Shift+P)
3. Search for "zed: extensions"
4. Search for "pytest Language Server"
5. Click "Install"

The extension will automatically detect `pytest-language-server` if it's in your PATH.

### VS Code

Install the extension from the marketplace (coming soon) or configure manually:

```json
{
  "pytest-language-server.enable": true,
  "pytest-language-server.path": "pytest-language-server"
}
```

### Other Editors

Any editor with LSP support can use pytest-language-server. Configure it to run the `pytest-language-server` command.

## Configuration

### Logging

Control log verbosity with the `RUST_LOG` environment variable:

```bash
# Minimal logging (default)
RUST_LOG=warn pytest-language-server

# Info level
RUST_LOG=info pytest-language-server

# Debug level (verbose)
RUST_LOG=debug pytest-language-server

# Trace level (very verbose)
RUST_LOG=trace pytest-language-server
```

Logs are written to stderr, so they won't interfere with LSP communication.

### Virtual Environment Detection

The server automatically detects your Python virtual environment:
1. Checks for `.venv/`, `venv/`, or `env/` in your project root
2. Falls back to `$VIRTUAL_ENV` environment variable
3. Scans third-party pytest plugins for fixtures

### Code Actions / Quick Fixes

Code actions are automatically available on diagnostic warnings. If code actions don't appear in your editor:

1. **Check LSP capabilities**: Ensure your editor supports code actions (most modern editors do)
2. **Enable debug logging**: Use `RUST_LOG=info` to see if actions are being created
3. **Verify diagnostics**: Code actions only appear where there are warnings
4. **Trigger manually**: Use your editor's code action keybinding (Cmd+. / Ctrl+.)

For detailed troubleshooting, see [CODE_ACTION_TESTING.md](CODE_ACTION_TESTING.md).

## Supported Fixture Patterns

### Decorator Style
```python
@pytest.fixture
def my_fixture():
    """Fixture docstring."""
    return 42
```

### Assignment Style (pytest-mock)
```python
mocker = pytest.fixture()(_mocker)
```

### Async Fixtures
```python
@pytest.fixture
async def async_fixture():
    return await some_async_operation()
```

### Fixture Dependencies
```python
@pytest.fixture
def fixture_a():
    return "a"

@pytest.fixture
def fixture_b(fixture_a):  # Go to definition works on fixture_a
    return fixture_a + "b"
```

## Fixture Priority Rules

pytest-language-server correctly implements pytest's fixture shadowing rules:
1. **Same file**: Fixtures defined in the same file have highest priority
2. **Closest conftest.py**: Searches parent directories for conftest.py files
3. **Virtual environment**: Third-party plugin fixtures

### Fixture Overriding

The LSP correctly handles complex fixture overriding scenarios:

```python
# conftest.py (parent)
@pytest.fixture
def cli_runner():
    return "parent runner"

# tests/conftest.py (child)
@pytest.fixture
def cli_runner(cli_runner):  # Overrides parent
    return cli_runner  # Uses parent

# tests/test_example.py
def test_example(cli_runner):  # Uses child
    pass
```

When using find-references:
- Clicking on the **function name** `def cli_runner(...)` shows references to the child fixture
- Clicking on the **parameter** `cli_runner(cli_runner)` shows references to the parent fixture
- Character-position aware to distinguish between the two

## Supported Third-Party Fixtures

Automatically discovers fixtures from popular pytest plugins:
- **pytest-mock**: `mocker`, `class_mocker`
- **pytest-asyncio**: `event_loop`
- **pytest-django**: Database fixtures
- **pytest-cov**: Coverage fixtures
- And any other pytest plugin in your environment

## Architecture

- **Language**: Rust 🦀
- **LSP Framework**: tower-lsp
- **Parser**: rustpython-parser
- **Concurrency**: tokio async runtime
- **Data Structures**: DashMap for lock-free concurrent access

## Development

### Prerequisites

- Rust 1.83+ (2021 edition)
- Python 3.10+ (for testing)

### Building

```bash
cargo build --release
```

### Running Tests

```bash
cargo test
```

### Logging During Development

```bash
RUST_LOG=debug cargo run
```

## Security

Security is a priority. This project includes:
- Automated dependency vulnerability scanning (cargo-audit)
- License compliance checking (cargo-deny)
- Daily security audits in CI/CD
- Dependency review on pull requests
- Pre-commit security hooks

See [SECURITY.md](SECURITY.md) for our security policy and how to report vulnerabilities.

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

### Development Setup

1. Install pre-commit hooks:
   ```bash
   pre-commit install
   ```

2. Run security checks locally:
   ```bash
   cargo audit
   cargo clippy
   cargo test
   ```

## License

MIT License - see LICENSE file for details.

## Acknowledgments

Built with:
- [tower-lsp]https://github.com/ebkalderon/tower-lsp - LSP framework
- [rustpython-parser]https://github.com/RustPython/RustPython - Python AST parsing
- [tokio]https://tokio.rs/ - Async runtime

Special thanks to the pytest team for creating such an amazing testing framework.

---

**Made with ❤️ and Rust. Shamelessly vibed into existence. Blazingly fast. 🔥**

*When you need a pytest LSP and the vibes are just right.* ✨