| # How To Contribute | |
| > [!IMPORTANT] | |
| > - This document is mainly to help you to get started by codifying tribal knowledge and expectations and make it more accessible to everyone. | |
| > But don't be afraid to open half-finished PRs and ask questions if something is unclear! | |
| > | |
| > - If you use LLM / "AI" tools for your contributions, please read and follow our [_Generative AI / LLM Policy_][llm]. | |
| ## Support | |
| In case you'd like to help out but don't want to deal with GitHub, there's a great opportunity: | |
| help your fellow developers on [Stack Overflow](https://stackoverflow.com/questions/tagged/python-attrs)! | |
| The official tag is `python-attrs` and helping out in support frees us up to improve *attrs* instead! | |
| ## Workflow | |
| First off, thank you for considering to contribute! | |
| It's people like *you* who make this project such a great tool for everyone. | |
| - **Only contribute code that you fully understand.** | |
| See also our [AI policy][llm]. | |
| - Very relatedly, our pull request checklist is our mandatory [Van Halen test](https://en.wikipedia.org/wiki/Van_Halen_test). | |
| Sadly, the current state of the world has forced us into being stricter about policies – sorry fellow humans! | |
| - No contribution is too small! | |
| Please submit as many fixes for typos and grammar bloopers as you can! | |
| They're your license to delete the checklist! | |
| - Before starting big contributions, **talk to us first**. | |
| Don't waste energy / tokens on something that we do not want. | |
| Rejecting a huge PR is unpleasant for everybody. | |
| - Try to limit each pull request to *one* change only. | |
| - Since we squash on merge, it's up to you how you handle updates to the `main` branch. | |
| Whether you prefer to rebase on `main` or merge `main` into your branch, do whatever is more comfortable for you. | |
| Just remember to [not use your own `main` branch for the pull request](https://hynek.me/articles/pull-requests-branch/). | |
| - *Always* add tests and docs for your code. | |
| This is a hard rule; patches with missing tests or documentation won't be merged. | |
| - Consider adding a news fragment to [`changelog.d`](../changelog.d/) to reflect the changes as observed by people *using* this library. | |
| - Make sure your changes pass our [CI](https://github.com/python-attrs/attrs/actions). | |
| You won't get any feedback until it's green unless you ask for it. | |
| For the CI to pass, the coverage must be 100%. | |
| If you have problems to test something, open anyway and ask for advice. | |
| In some situations, we may agree to add an `# pragma: no cover`. | |
| - Once you've addressed review feedback, make sure to bump the pull request with a short note, so we know you're done. | |
| - Don't break [backwards-compatibility](SECURITY.md). | |
| ## Local Development Environment | |
| First, **fork** the repository on GitHub. | |
| Make sure to **uncheck** the `Copy the main branch only` radio button on the `Create a new fork` page. | |
| If you don't, our test suite will fail because we use Git tags for packaging. | |
| Finally, **clone** your fork using one of the alternatives that you can copy-paste by pressing the big green button labeled `<> Code`. | |
| You can (and should) run our test suite using [*tox*](https://tox.wiki/) with the [*tox-uv*](https://github.com/tox-dev/tox-uv) plugin. | |
| The easiest way is to [install *uv*] which is needed in any case and then run `uv tool install --with tox-uv tox` to have it globally available or `uvx --with tox-uv tox` to use a temporary environment. | |
| --- | |
| However, you'll probably want a more traditional environment as well. | |
| We recommend using the Python version from the `.python-version-default` file in the project's root directory. | |
| We use a fully-locked development environment using [*uv*](https://docs.astral.sh/uv/) so the easiest way to get started is to [install *uv*] and you can run `uv run pytest` to run the tests immediately. | |
| I you'd like a traditional virtual environment, you can run `uv sync --python=$(cat .python-version-default)` and it will create a virtual environment named `.venv` with the correct Python version and install all the dependencies in the root directory. | |
| If you're using [*direnv*](https://direnv.net), you can automate the creation and activation of the project's virtual environment with the correct Python version by adding the following `.envrc` to the project root: | |
| ```bash | |
| uv sync --python=$(cat .python-version-default) | |
| . .venv/bin/activate | |
| ``` | |
| --- | |
| If you don't want to use *uv*, you can use Pip 25.1 (that added support for dependency groups) or newer and install the dependencies manually: | |
| ```console | |
| $ pip install -e . --group dev | |
| ``` | |
| --- | |
| If the test suite fails with errors in `test_packaging.py`, you're lacking Git tags. | |
| You can retroactively fetch them using: | |
| ```console | |
| $ git remote add upstream git@github.com:python-attrs/attrs.git | |
| $ git fetch upstream --tags | |
| ``` | |
| --- | |
| > [!WARNING] | |
| > - **Before** you start working on a new pull request, use the "*Sync fork*" button in GitHub's web UI to ensure your fork is up to date. | |
| > | |
| > - **Always create a new branch off `main` for each new pull request.** | |
| > Yes, you can work on `main` in your fork and submit pull requests. | |
| > But this will *inevitably* lead to you not being able to synchronize your fork with upstream and having to start over. | |
| --- | |
| When working on the documentation, use: | |
| ```console | |
| $ tox run -e docs-watch | |
| ``` | |
| This will build the documentation, watch for changes, and rebuild it whenever you save a file. | |
| To just build the documentation and exit immediately use: | |
| ```console | |
| $ tox run -e docs-build | |
| ``` | |
| You will find the built documentation in `docs/_build/html`. | |
| To run doctests: | |
| ```console | |
| $ tox run -e docs-doctests | |
| ``` | |
| ## Code | |
| - We follow [PEP 8](https://peps.python.org/pep-0008/) as enforced by [Ruff](https://ruff.rs/) with a line length of 79 characters. | |
| - As long as you run our full *tox* suite before committing, you won't have to spend any time on formatting your code at all. | |
| If you don't, CI will catch it for you – but that seems like a waste of your time! | |
| - If you've changed or added public APIs, please update our type stubs (files ending in `.pyi`). | |
| ## Tests | |
| - Write your asserts as `expected == actual` to line them up nicely, and leave an empty line before them: | |
| ```python | |
| x = f() | |
| assert 42 == x.some_attribute | |
| assert "foo" == x._a_private_attribute | |
| ``` | |
| - You can run the test suite with all dependencies against all supported Python versions - just as it will in our CI - by running `tox`. | |
| - Write [good test docstrings](https://jml.io/test-docstrings/). | |
| - To ensure new features work well with the rest of the system, they should be also added to our [Hypothesis](https://hypothesis.readthedocs.io/) testing strategy, which can be found in `tests/strategies.py`. | |
| ## Documentation | |
| - Use [semantic newlines] in [reStructuredText](https://www.sphinx-doc.org/en/stable/usage/restructuredtext/basics.html) (`*.rst`) and [Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) (`*.md`) files: | |
| ```markdown | |
| This is a sentence. | |
| This is another sentence. | |
| This is a new paragraph. | |
| ``` | |
| - If you start a new section, add two blank lines before and one blank line after the header except if two headers follow immediately after each other: | |
| ```markdown | |
| # Main Header | |
| Last line of previous section. | |
| ## Header of New Top Section | |
| ### Header of New Section | |
| First line of new section. | |
| ``` | |
| - If you add a new feature, demonstrate its awesomeness on the [examples page](https://github.com/python-attrs/attrs/blob/main/docs/examples.md)! | |
| - For docstrings, we follow [PEP 257](https://peps.python.org/pep-0257/), use the `"""`-on-separate-lines style, and [Napoleon](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html)-style API documentation: | |
| ```python | |
| def func(x: str, y: int) -> str: | |
| """ | |
| Do something. | |
| Args: | |
| x: A very important argument. | |
| y: | |
| Another very important argument, but its description is so long | |
| that it doesn't fit on one line. So, we start the whole block on a | |
| fresh new line to keep the block together. | |
| Returns: | |
| str: The result of doing something. | |
| Raises: | |
| ValueError: When an invalid value is passed. | |
| """ | |
| ``` | |
| Please note that the API docstrings are still reStructuredText. | |
| - If you add or change public APIs, tag the docstring using `.. versionadded:: 24.1.0 WHAT` or `.. versionchanged:: 24.1.0 WHAT`. | |
| We follow CalVer, so the next version will be the current with with the middle number incremented (for example, `24.1.0` -> `24.2.0`). | |
| ### Changelog | |
| If your change is interesting to end-users, there needs to be a changelog entry so they can learn about it! | |
| To avoid merge conflicts, we use the [Towncrier](https://pypi.org/project/towncrier) package to manage our changelog. | |
| *towncrier* uses independent Markdown files for each pull request - so called *news fragments* - instead of one monolithic changelog file. | |
| On release, those news fragments are compiled into our [`CHANGELOG.md`](../CHANGELOG.md). | |
| You don't need to install Towncrier yourself, you just have to abide by a few simple rules: | |
| - For each pull request, add a new file into `changelog.d` with a filename adhering to the `pr#.(change|deprecation|breaking).md` schema: | |
| For example, `changelog.d/42.change.md` for a non-breaking change that is proposed in pull request #42. | |
| - As with other docs, please use [semantic newlines] within news fragments. | |
| - Refer to all symbols by their fully-qualified names. | |
| For example, `attrs.Foo` – not just `Foo`. | |
| - Wrap symbols like modules, functions, or classes into backticks, so they are rendered in a `monospace font`. | |
| - Wrap arguments into asterisks so they are *italicized* like in API documentation: | |
| `Added new argument *an_argument*.` | |
| - If you mention functions or methods, add parentheses at the end of their names: | |
| `attrs.func()` or `attrs.Class.method()`. | |
| This makes the changelog a lot more readable. | |
| - Prefer simple past tense or constructions with "now". | |
| Example entries: | |
| ```md | |
| Added `attrs.validators.func()`. | |
| The feature really *is* awesome. | |
| ``` | |
| or: | |
| ```md | |
| `attrs.func()` now doesn't crash the Large Hadron Collider anymore when passed the *foobar* argument. | |
| The bug really *was* nasty. | |
| ``` | |
| --- | |
| If you want to reference multiple issues, copy the news fragment to another filename. | |
| Towncrier will merge all news fragments with identical contents into one entry with multiple links to the respective pull requests. | |
| `tox run -e changelog` will render the current changelog to the terminal if you have any doubts. | |
| ## Governance | |
| *attrs* is maintained by [team of volunteers](https://github.com/python-attrs) that is always open to new members that share our vision of a fast, lean, and magic-free library that empowers programmers to write better code with less effort. | |
| If you'd like to join, just get a pull request merged and ask to be added in the very same pull request! | |
| **The simple rule is that everyone is welcome to review/merge pull requests of others but nobody is allowed to merge their own code.** | |
| [Hynek Schlawack](https://hynek.me/about/) acts reluctantly as the [BDFL](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life) and has the final say over design decisions. | |
| ## See You on GitHub! | |
| Again, this whole file is mainly to help you to get started by codifying tribal knowledge and expectations to save you time and turnarounds. | |
| It is **not** meant to be a barrier to entry, so don't be afraid to open half-finished PRs and ask questions if something is unclear! | |
| Please note that this project is released with a Contributor [Code of Conduct](CODE_OF_CONDUCT.md). | |
| By participating in this project you agree to abide by its terms. | |
| Please report any harm to Hynek Schlawack in any way you find appropriate. | |
| [semantic newlines]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/ | |
| [install *uv*]: https://docs.astral.sh/uv/getting-started/installation/ | |
| [llm]: https://github.com/python-attrs/attrs/blob/main/.github/AI_POLICY.md |