# Contributing Thank you for your interest in contributing to usethis! There are issues labeled [Good First Issue](https://github.com/usethis-python/usethis-python/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22) which are good opportunities for new contributors. Since `usethis` is at early stages of development, please [ensure a GitHub Issue is opened](https://github.com/usethis-python/usethis-python/issues) before starting work on a new feature or bug fix. This helps to ensure that the feature is aligned with the project's goals and that there is no duplication of effort. Sometimes these Issues don't have enough guidance in them, so consider asking for some more guidance from the ticket creator before getting started. ## Setup ### Development Environment [uv](https://github.com/astral-sh/uv) is required to install the development environment. You can install it using the instructions here: Then with the current working directory set to the project root, run: ```shell uv sync ``` ### Git Hooks This project uses the `prek` framework (similar to `pre-commit`) to manage Git hooks. To install the hooks, run: ```shell uv run prek install ``` ## Testing ### Running the Test Suite To run the tests, simply run: ```shell uv run pytest ``` ### Dogfooding To run usethis against the development repository itself, use: ```shell uvx --from . usethis ``` This can be a useful data point for gauging the behaviour of changes or new commands on a moderately complex project repo. ### Writing Tests Tests are written using the `pytest` framework. The test suite is located in the `tests` directory. The tests are organized into subdirectories with a directory structure that mirrors the structure of the code being tested. This makes it easy to find the tests for a specific module or function. For example: `src/x/y/z.py` would be tested at `tests/x/y/test_z.py`. Tests are usually organized into classes centred around the objects being tested; either modules or classes. For example, tests for a class `MyClass` would be tested in a class `TestMyClass`. If testing a method of a class, the method tests would be nested within the class test. For example, tests for the `my_method` method of `MyClass` would be in a nested class `TestMyMethod` within `TestMyClass`. These classes, and the test functions themselves do not usually need docstrings, and their use is discouraged. The test function names are usually descriptive enough to make it clear what is being tested. PRs should ideally include tests for any new features or bug fixes. To diagnose slow test speeds, you can run ```shell uv run pyinstrument -m pytest ``` With any `pytest` options you wish to include, e.g. `-k` to run specific tests, or `--collect-only` to only profile test collection time. This will generate a CLI-friendly report of where time is being spent. For an interactive HTML report, you can run `pyinstrument` with the `-r=html` option before the `-m pytest` part. A common pattern in the test suite is to use a [pytest fixture](https://docs.pytest.org/en/7.1.x/how-to/fixtures.html) to get a temporary directory, and then use the `usethis._test.change_cwd` context manager in the test to simulate running usethis from within a project. If you're writing a new test and noticing unexpected creations or modifications of files, it's a sign that the working directory has not been properly configured for the test. ## Documentation Documentation is hosted at . It can be served locally with: ```shell uv run mkdocs serve ``` Docstrings use [Google Style](https://google.github.io/styleguide/pyguide.html#381-docstrings). Note that type annotations should not be repeated in the docstring, since these are already present in the function signature. ## Version Control Git is used for version control, using [Trunk-based development](https://trunkbaseddevelopment.com/). It is recommended that you use signed commits, although this is not a requirement. Please see this guide from the VS Code project for instructions on how to do this: ## Architecture This project uses [Import Linter](https://import-linter.readthedocs.io/en/stable/) to enforce a software architecture. Refer to the `[[tool.importlinter.contracts]]` sections in `pyproject.toml` to understand the structure of the project. ### Global Configuration State The `usethis._config.usethis_config` object manages global application state that affects behavior across the entire application. This design avoids the need for pass-through variables that would otherwise need to be threaded through many layers of function calls. It provides a context manager, `usethis_config.set()`, which temporarily overrides global settings: ```python # Temporarily suppress all output except warnings and errors with usethis_config.set(alert_only=True): # Code here runs with modified config do_something() # Original settings are automatically restored ``` ## Python Version Support This project supports all versions of Python until end of life. The development environment uses the oldest supported version, which is given in the `.python-version` file. The GitHub Actions pipeline tests all supported versions. ## Conventions ### Print statements To ensure the `--quiet` flag is always effective, avoid using simple `print` statements. Instead, use the purpose-built `usethis._console.plain_print` function. ### Branding The usethis name should not be capitalized (i.e. not Usethis or UseThis), even at the beginning of a sentence. It should only be styled in monospace as `usethis` when referring to the command itself. These colours are used in branding materials: - Green: #00c000 - Orange: #f26622 - Grey: #999999 - Darker Grey: #424242 Along with the fonts [EB Garamond](https://fonts.google.com/specimen/EB+Garamond) and [Cairo](https://fonts.google.com/specimen/Cairo). ## Guides ### Adding a new badge To add a new `usethis badge` interface, follow these steps: - Define a `get__badge` function in `src/usethis/_core/badge.py`. Try to keep the definitions in alphabetical order. - Declare the interface in `src/usethis/_ui/interface/badge.py`. Again, keep the declarations in alphabetical order. The pattern is basically just boilerplate with the other interfaces, but you need to give a description of your command for the `--help` option. - Add a test for your badge in `tests/usethis/_ui/interface/test_interface_badge.py`. Follow the pattern of the existing tests, although you only need the `test_add` case, which simply tests that the command runs without error. - Declare a recommended badge placement in the `get_badge_order` function in `src/usethis/_core/badge.py`. This helps ensure the badges are arranged in an opinionated way relative to existing badges. Finally, run the command on this project, to make sure the badge gets inserted correctly with valid Markdown syntax. Check it renders successfully and that any hyperlink works as expected. ### Adding a new tool Tool implementations are defined in classes in the `usethis._tool.impl` module. To add a new tool, follow these steps: #### Define the configuration file classes - If your tool uses bespoke configuration files (e.g. its configuration does not fit within standard files like `pyproject.toml`, or `setup.cfg`), you will need to define classes for these. - These classes should be added to `usethis._config_file` and should subclass an appropriate base class based on the file type (YAML, TOML, INI, etc.) e.g. `YAMLFileManager`. - Name the classes based on the filename of the configuration file, following the pattern of the other classes in the module. - Register the file in the `files_manager` function in the `usethis._config_file` module. #### Create the `Tool` and `ToolSpec` subclasses - Create a submodule in `usethis._tool.impl` for your tool, e.g. for a tool named Xyz name it `usethis._tool.impl.xyz`. - Declare this new submodule in the `.importlinter` configuration to architecturally describe its dependency relationships with other tools' submodules. For example, does your tool integrate with pre-commit? It should be in a higher layer module than the `pre-commit` submodule. - Define a `usethis._tool.base.ToolSpec` subclass, e.g. for a tool named Xyz, define a class `XyzToolSpec(ToolSpec)`. - Start by implementing its `meta` property method (which supplies the tool's name, managed files, and rule configuration), then work through the other methods. Most methods have default implementations, but even in those cases you will need to consider them individually and determine an appropriate implementation. For example, methods which specify the tool's dependencies default to empty dependencies, but you shouldn't rely on this. - Mark all methods in your `ToolSpec` subclass with the `@typing.final` decorator. This prevents the methods from being accidentally overridden in the `Tool` subclass. - Then, define a subclass of the `ToolSpec` subclass you just created, which also subclasses `usethis._tool.base.Tool`, e.g. for a tool named Xyz, define a class `XyzTool(XyzToolSpec, Tool)`. The only method this usually requires a non-default implementation for is `config_spec` to specify which configuration sections should be set up for the tool (and which sections the tool manages). However, you may find it helpful to provide custom implementations for other methods as well, e.g. `print_how_to_use`. - Mark your `Tool` subclass with `@final` as well, to prevent further subclassing. #### Register your `Tool` and `ToolSpec` subclasses - In `usethis._tool.all_`, add your `Tool` subclass to the `SupportedToolType` union, and to the `ALL_TOOLS` list. - In `usethis._tool.impl.spec.all_`, add your `ToolSpec` subclass to the `ALL_TOOL_SPECS` list. Both lists must stay in alphabetical order and in sync; the `test_in_sync_with_all_tools` test will fail if they differ. #### Create the `use_*` function - Create a function in `usethis._core.tool` named `use_`, e.g. for a tool named Xyz, create a function `use_xyz()`. - The function should follow the pattern of the other functions in the module. It is responsible for invoking methods on your `Tool` subclass to perform the operations necessary to add your tool, remove it, and display a message explaining how the tool is used. #### Create the `typer` command function - Create a function in `usethis._ui.interface.tool` named after your tool in lowercase, e.g. for a tool named Xyz, create a function `xyz()`. - The function should follow the pattern of the other functions in the module. It is responsible for invoking your `use_*` function via the `_run_tool` wrapper. #### Write tests - You should write tests in `tests/usethis/_core/test_core_tool` for the `use_*` function, following the pattern of the other tests in that module for other tools. #### Update tests - Some tests may need updating as a result of new tool registration. In particular: - `TestGetUsageTable` tests in `tests/usethis/_core/test_list.py` — these enumerate all tools and their statuses. Add a `UsageRow` for your new tool. #### Update the README and documentation - Finally, you should update the README and documentation to include your new tool. You can follow the pattern of the existing tools in the documentation. ## License By contributing, you agree that your contributions will be licensed under the MIT license. See the [LICENSE](https://github.com/usethis-python/usethis-python/blob/main/LICENSE) file.