|
| 1 | +# Agent Instructions |
| 2 | + |
| 3 | +You should always follow the best practices outlined in this document. If there is a valid reason why you cannot follow one of these practices, you should inform the user and document the reasons. |
| 4 | + |
| 5 | +Before beginning any task, make sure you review the documentation (`docs/dev/` and `README.md`), the existing tests to understand the project, and the task runner (Makefile) to understand what dev tools are available and how to use them. You should review code related to your request to understand preferred style: for example, you should review other tests before writing a new test suite, or review existing routers before creating a new one. |
| 6 | + |
| 7 | +## Best Practices |
| 8 | + |
| 9 | +### General |
| 10 | + |
| 11 | +* Assume the minimum version of Python is 3.10. |
| 12 | +* Prefer async libraries and functions over synchronous ones. |
| 13 | +* Always define dependencies and tool settings in `pyproject.toml`: never use `setup.py` or `setup.cfg` files. |
| 14 | +* Prefer existing dependencies over adding new ones when possible. |
| 15 | +* For complex code, always consider using third-party libraries instead of writing new code that has to be maintained. |
| 16 | +* Use keyword arguments instead of positional arguments when calling functions and methods. |
| 17 | +* Do not put `import` statements inside functions unless necessary to prevent circular imports. Imports should be at the top of the file. |
| 18 | + |
| 19 | +### Security |
| 20 | + |
| 21 | +* Always write secure code. |
| 22 | +* Never hardcode sensitive data. |
| 23 | +* Do not log sensitive data. |
| 24 | +* All user input should be validated. |
| 25 | + |
| 26 | +### Production Ready |
| 27 | + |
| 28 | +* All generated code should be production ready. |
| 29 | +* There should be no stubs "for production". |
| 30 | +* There should not be any non-production logic branches in the main code package itself. |
| 31 | +* Any code or package differences between Development and Production should be avoided unless absolutely necessary. |
| 32 | + |
| 33 | +### Logging |
| 34 | + |
| 35 | +* Do not use `print` for logging or debugging: use the `getLogger` logger instead. |
| 36 | +* Each file should get its own logger using the `__name__` variable for a name. |
| 37 | +* Use logging levels to allow developers to enable richer logging while testing than in production. |
| 38 | +* Most caught exceptions should be logged with `logger.exception`. |
| 39 | + |
| 40 | +### Commenting |
| 41 | + |
| 42 | +* Comments should improve code readability and understandability. |
| 43 | +* Comments should not simply exist for the sake of existing. |
| 44 | +* Examples of good comments include unclear function names/parameters, decisions about settings or function choices, logic descriptions, variable definitions, security risks, edge cases, and advice for developers refactoring or expanding code. |
| 45 | +* Comments should be concise, accurate, and add value to the codebase. |
| 46 | + |
| 47 | +### Error Handling |
| 48 | + |
| 49 | +* Do not suppress exceptions unless expected, and handle them properly when suppressing. |
| 50 | +* When suppressing exceptions, log them using `logger.exception`. |
| 51 | + |
| 52 | +### Typing |
| 53 | + |
| 54 | +* Everything should be typed: function signatures (including return values), variables, and anything else. |
| 55 | +* Use the union operator for multiple allowed types. |
| 56 | +* Do not use `Optional`: use a union with `None` (i.e., `str | None`). |
| 57 | +* Use typing library metaclasses instead of native types for objects and lists (i.e., `Dict[str, str]` and `List[str]` instead of `dict` or `list`). |
| 58 | +* Avoid using `Any` unless absolutely necessary. |
| 59 | +* If the schema is defined, use a `dataclass` with properly typed parameters instead of a `dict`. |
| 60 | + |
| 61 | +### Settings |
| 62 | + |
| 63 | +* Manage application settings with the `pydantic-settings` library. |
| 64 | +* Sensitive configuration data should always use Pydantic `SecretStr` or `SecretBytes` types. |
| 65 | +* Settings that are allowed to be unset should default to `None` instead of empty strings. |
| 66 | +* Define settings with the Pydantic `Field` function and include descriptions for users. |
| 67 | + |
| 68 | +{%- if cookiecutter.include_fastapi == "y" %} |
| 69 | + |
| 70 | +### FastAPI |
| 71 | + |
| 72 | +* APIs should adhere as closely as possible to REST principles, including appropriate use of GET/PUT/POST/DELETE HTTP verbs. |
| 73 | +* All routes should use Pydantic models for input and output. |
| 74 | +* Use different Pydantic models for inputs and outputs (i.e., creating a `Post` should require a `PostCreate` and return a `PostRead` model, not reuse the same model). |
| 75 | +* Parameters in Pydantic models for user input should use the Field function with validation and descriptions. |
| 76 | +{%- endif %} |
| 77 | + |
| 78 | +{%- if cookiecutter.include_sqlalchemy == "y" %} |
| 79 | + |
| 80 | +### SQLAlchemy |
| 81 | + |
| 82 | +* Always use async SQLAlchemy APIs with SQLAlchemy 2.0 syntax. |
| 83 | +* Represent database tables with the declarative class system. |
| 84 | +* Use Alembic to define migrations. |
| 85 | +* Migrations should be compatible with both SQLite and PostgreSQL. |
| 86 | +* When creating queries, do not use implicit `and`: instead use the `and_` function (instead of `where(Model.parameter_a == A, Model.parameter_b == B)` do `where(and_(Model.parameter_a == A, Model.parameter_b == B))`). |
| 87 | +{%- endif %} |
| 88 | + |
| 89 | +{%- if cookiecutter.include_cli == "y" %} |
| 90 | + |
| 91 | +### Typer |
| 92 | + |
| 93 | +* Any CLI command or script that should be accessible to users should be exposed via the Typer library. |
| 94 | +* The main CLI entrypoint should be `PACKAGE_NAME/cli.py`. |
| 95 | +{%- endif %} |
| 96 | + |
| 97 | +### Testing |
| 98 | + |
| 99 | +* Do not wrap test functions in classes unless there is a specific technical reason: instead prefer single functions. |
| 100 | +* All fixtures should be defined or imported in `conftest.py` so they are available to all tests. |
| 101 | +* Do not use mocks to replace simple dataclasses or Pydantic models unless absolutely necessary: instead create an instance of the appropriate class with desired parameters. |
| 102 | +* Use the FastAPI Test Client (preferably with a fixture) rather than calling FastAPI router classes directly. |
| 103 | +* Use a test database fixture with memory-backed SQLite for tests requiring a database. Including a dependency override for this test database as part of the FastAPI App fixture is extremely useful. |
| 104 | +* When adding new code, you should also add appropriate tests to cover that new code. |
| 105 | +* The test suite file structure should mirror the main code file structure. |
| 106 | + |
| 107 | +### Files |
| 108 | + |
| 109 | +* Filenames should always be lowercase for better compatibility with case-insensitive filesystems. |
| 110 | +* This includes documentation files, except standard files (like `README.md`, `LICENSE`, etc.). |
| 111 | +* Developer documentation should live in `docs/dev`. |
| 112 | +* New developer documents should be added to the table of contents in `docs/dev/README.md`. |
| 113 | +* Files only meant for building containers should live in the `docker/` folder. |
| 114 | +* Database models should live in `PACKAGE_NAME/models/`. |
| 115 | +* The primary settings file should live in `PACKAGE_NAME/conf/settings.py`. |
| 116 | + |
| 117 | +### Developer Environments |
| 118 | + |
| 119 | +* Common developer tasks should be defined in the `makefile` to easy reuse. |
| 120 | +* Developers should always be able to start a fully functional developer instance with `docker compose up`. |
| 121 | +* Developer environments should be initialized with fake data for easy use. |
| 122 | +* Developer settings should live in the `.env` file, which should be in `.gitignore`. |
| 123 | +* A `.env.example` file should exist as a template for new developers to create their `.env` file and learn what variables to set. |
| 124 | +* Python projects should always use virtual environments at `.venv` in the project root. This should be activated before running tests. |
0 commit comments