Skip to content

Commit fbbc7e7

Browse files
authored
Add support for using AI coding agents (#1672)
Includes AI policy so people understand what they can and cannot do with regards to AI in pyinfra
1 parent 0e8c348 commit fbbc7e7

5 files changed

Lines changed: 216 additions & 0 deletions

File tree

.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
[*]
2+
end_of_line = lf
3+
insert_final_newline = true
4+
max_line_length = 100
5+
6+
[*.py]
7+
indent_style = space
8+
indent_size = 4
9+
110
[*.json]
211
indent_style = space
312
indent_size = 4

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ docs/_deploy_globals.rst
3131

3232
.idea/
3333
.vscode/
34+
.claude/settings.local.json
3435

3536
pyinfra-debug.log
3637
Makefile

AGENTS.md

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to coding agents, like Claude Code (claude.ai/code), when working with
4+
code in this repository.
5+
6+
## About pyinfra
7+
8+
pyinfra turns Python code into shell commands and runs them on servers. Think Ansible, but Python
9+
instead of YAML, and much faster. It supports SSH, local machine, Docker, and more via connectors.
10+
11+
## Development Setup
12+
13+
```bash
14+
uv sync # Install all dependencies into managed venv
15+
```
16+
17+
## Common Commands
18+
19+
```bash
20+
# Run tests
21+
scripts/dev-test.sh
22+
# or directly:
23+
uv run pytest --cov --disable-warnings -m 'not end_to_end'
24+
25+
# Run fixtures for a single operation or fact
26+
uv run pytest tests/test_operations.py -k "apt.packages"
27+
uv run pytest tests/test_facts.py -k "LinuxHardware"
28+
29+
# End-to-end tests (require Docker/SSH/local targets)
30+
uv run pytest -m end_to_end_local
31+
uv run pytest -m end_to_end_docker
32+
33+
# Lint and type check
34+
scripts/dev-lint.sh
35+
# Individually:
36+
uv run ruff check
37+
uv run ruff format --check
38+
uv run mypy
39+
uv run python scripts/lint_arguments_sync.py
40+
41+
# Auto-format
42+
scripts/dev-format.sh
43+
```
44+
45+
## Architecture
46+
47+
The repo has two top-level packages under `src/`:
48+
49+
- **`pyinfra/`** — core library (operations, facts, connectors, API)
50+
- **`pyinfra_cli/`** — CLI wrapper using Click + gevent
51+
52+
### Core Concepts
53+
54+
**Operations** (`src/pyinfra/operations/`) — Declarative functions (e.g. `apt.packages`,
55+
`files.put`) that describe desired state. Each operation uses the `@operation` decorator from
56+
`api/operation.py`, generates a list of commands, and is idempotent. Operations call facts to
57+
read current state, then return commands to reach desired state.
58+
59+
**Facts** (`src/pyinfra/facts/`) — Read system state (e.g. `AptPackages`, `LinuxHardware`). Each
60+
fact is a class extending `FactBase` with a `command` attribute and a `process()` method that
61+
parses command output. Facts are cached per-host per-run.
62+
63+
**Connectors** (`src/pyinfra/connectors/`) — Abstractions for how to connect to and execute on a
64+
target (SSH, local, Docker, chroot, Terraform, Vagrant, etc.). New connectors should be separate
65+
packages, not added to this repo.
66+
67+
**API** (`src/pyinfra/api/`) — The core engine:
68+
- `state.py` — Global deploy state, callbacks, host grouping
69+
- `host.py` — Per-host metadata and fact access
70+
- `inventory.py` — Collection of hosts and groups
71+
- `operation.py``@operation` decorator that wraps functions into deploy operations
72+
- `operations.py` — Execution engine that runs operations across hosts
73+
- `command.py` — Command types: `StringCommand`, `FileUploadCommand`, `RsyncCommand`,
74+
`QuoteString`, `MaskString`, etc.
75+
- `facts.py` — Fact base classes and execution logic
76+
- `deploy.py``@deploy` decorator for grouping operations
77+
- `connect.py` — Connector lifecycle (connect/disconnect)
78+
- `config.py``Config` object with all configuration options
79+
- `arguments.py` / `arguments_typed.py` — Global operation arguments (e.g. `_sudo`, `_su_user`);
80+
**these two files must stay in sync** — CI enforces this via `scripts/lint_arguments_sync.py`,
81+
so touching one requires touching the other
82+
- `output.py` — Pluggable output functions (decoupled from Click for testability)
83+
84+
**Context** (`src/pyinfra/context.py`) — Thread-local (gevent-safe) context objects: `host`,
85+
`state`, `config`, `inventory`. Operations access the current host via `pyinfra.context.host`
86+
rather than explicit passing.
87+
88+
**Concurrency** — Uses gevent greenlets for parallel host execution. `pyinfra_cli/main.py`
89+
monkey-patches stdlib at startup.
90+
91+
### Adding Operations / Facts
92+
93+
Operations and facts are auto-discovered from their respective directories. A new
94+
`src/pyinfra/operations/mytool.py` is immediately available as `from pyinfra.operations import
95+
mytool`.
96+
97+
- Operations must be idempotent and use facts to check current state
98+
- Facts must implement `command` (shell command to run) and `process(output)` (parse result)
99+
- Both need corresponding tests (see fixture convention below)
100+
- Every operation/fact module must be registered in `pyinfra-metadata.toml` as a plugin with
101+
tags; omitting this won't break tests but will break docs generation
102+
103+
**Operation / fact tests are YAML or JSON fixtures, not Python tests.** Drop a file under
104+
`tests/operations/<module>.<op>/` or `tests/facts/<module>.<Fact>/` — it is auto-discovered by
105+
the `testgen` metaclass. Prefer YAML for new fixtures. To cover a new code path, add a fixture —
106+
do not write a new Python test.
107+
108+
Operation fixture structure (`tests/operations/<module>.<op>/<name>.yaml`):
109+
110+
```yaml
111+
args:
112+
- positional_arg
113+
kwargs:
114+
param: value
115+
facts:
116+
module.FactClass: {} # a dict of mock values keyed by object_id and attribute
117+
commands:
118+
- shell command that should be produced
119+
```
120+
121+
Optional keys: `exception` (e.g. `{name: OperationError, message: "..."}`), `noop_description`.
122+
123+
Fact fixture structure (`tests/facts/<module>.<Fact>/<name>.yaml`):
124+
125+
```yaml
126+
command: shell command the fact runs
127+
requires_command: binary # optional
128+
output: |
129+
raw stdout to parse
130+
fact:
131+
item:
132+
key: value # expected return value of process()
133+
```
134+
135+
**Docstring format** — pyinfra uses `+ param: description` bullets (parsed by
136+
`scripts/generate_operations_docs.py`). Do not use Google/NumPy/Sphinx style — it will silently
137+
break docs generation.
138+
139+
**Shell safety** — user-supplied values must be composed into shell commands using `StringCommand`
140+
+ `QuoteString` / `MaskString` from `pyinfra.api`. Do not use plain string formatting (e.g.
141+
`"rm -f {}".format(path)`) for user-controlled values.
142+
143+
**Optional parameter defaults** — optional parameters must default to `None`, not `""`. Older
144+
operations in the codebase use `""` defaults; do not replicate this pattern.
145+
146+
## Branch Strategy
147+
148+
PRs target the latest major branch (`3.x`). One branch per major version exists (`2.x`, `1.x`,
149+
etc.).
150+
151+
## PR Checklist
152+
153+
- Tests pass (`scripts/dev-test.sh`)
154+
- Lint/types pass (`scripts/dev-lint.sh`)
155+
- New operations/facts include tests and documentation

AI_POLICY.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# AI Usage Policy
2+
3+
The pyinfra project has strict rules for AI usage:
4+
5+
- **All AI usage in any form must be disclosed.** You must state the tool you used (e.g. Claude
6+
Code, Cursor, Amp) along with the extent that the work was AI-assisted.
7+
8+
- **The human-in-the-loop must fully understand all code.** If you can't explain what your changes
9+
do and how they interact with the greater system without the aid of AI tools, do not contribute
10+
to this project.
11+
12+
- **Issues and discussions can use AI assistance but must have a full human-in-the-loop.** This
13+
means that any content generated with AI must have been reviewed _and edited_ by a human before
14+
submission. AI is very good at being overly verbose and including noise that distracts from the
15+
main point. Humans must do their research and trim this down.
16+
17+
- **No AI-generated media is allowed (art, images, videos, audio, etc.).** Text and code are the
18+
only acceptable AI-generated content, per the other rules in this policy.
19+
20+
## There are Humans Here
21+
22+
Please remember that pyinfra is maintained by humans.
23+
24+
Every discussion, issue, and pull request is read and reviewed by humans (and sometimes machines,
25+
too). It is a boundary point at which people interact with each other and the work done. It is
26+
rude and disrespectful to approach this boundary with low-effort, unqualified work, since it puts
27+
the burden of validation on the maintainer.
28+
29+
In a perfect world, AI would produce high-quality, accurate work every time. But today, that
30+
reality depends on the driver of the AI. And today, most drivers of AI are just not good enough.
31+
So, until either the people get better, the AI gets better, or both, we have to have strict rules
32+
to protect maintainers.
33+
34+
## AI is Welcome Here
35+
36+
pyinfra is written with plenty of AI assistance, and many maintainers embrace AI tools as a
37+
productive tool in their workflow. As a project, we welcome AI as a tool!
38+
39+
**Our reason for the strict AI policy is not due to an anti-AI stance**, but instead due to the
40+
number of highly unqualified people using AI. It's the people, not the tools, that are the problem.
41+
42+
I include this section to be transparent about the project's usage about AI for people who may
43+
disagree with it, and to address the misconception that this policy is anti-AI in nature.
44+
45+
## Attribution
46+
47+
This policy is based on the AI Usage Policy of the
48+
[Ghostty project](https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md).

CLAUDE.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# CLAUDE.md
2+
3+
See @AGENTS.md for guidance.

0 commit comments

Comments
 (0)