|
| 1 | +# Coding Agent Instructions |
| 2 | + |
| 3 | +Guidance on how to navigate and modify this codebase. |
| 4 | + |
| 5 | +## What this library does |
| 6 | +It is an async Python wrapper to interact with the Overkiz API, used by vendors and platforms like Somfy TaHoma and Atlantic CozyTouch. |
| 7 | + |
| 8 | +## Python Version |
| 9 | + |
| 10 | +Write for Python 3.10-3.13. Do NOT write code to support earlier versions of Python. |
| 11 | +Always use modern Python practices appropriate for Python 3.10-3.13. |
| 12 | + |
| 13 | +Always use full type annotations, generics, and other modern practices. |
| 14 | + |
| 15 | +## Project Setup and Developer Workflows |
| 16 | + |
| 17 | +- Important: BE SURE you read and understand the project setup by reading the |
| 18 | + pyproject.toml file and the Makefile. |
| 19 | + |
| 20 | +- ALWAYS use uv for running all code and managing dependencies. |
| 21 | + Never use direct `pip` or `python` commands. |
| 22 | + |
| 23 | +- Use modern uv commands: `uv sync`, `uv run ...`, etc. |
| 24 | + Prefer `uv add` over `uv pip install`. |
| 25 | + |
| 26 | +- You may use the following shortcuts |
| 27 | + ```shell |
| 28 | + |
| 29 | + # Install all dependencies: |
| 30 | + uv sync |
| 31 | + |
| 32 | + # Run linting (with ruff), pre-commit checks and type checking (with mypy). |
| 33 | + # Note when you run this, ruff will auto-format and sort imports, resolving any |
| 34 | + # linter warnings about import ordering: |
| 35 | + uv run pre-commit run --all-files |
| 36 | + |
| 37 | + # Run tests: |
| 38 | + uv run pytest |
| 39 | + ``` |
| 40 | + |
| 41 | +- Run individual tests and see output with `uv run pytest -s some/file.py`. |
| 42 | + |
| 43 | +- You must verify there are zero linter warnings/errors or test failures before considering any task complete. |
| 44 | + |
| 45 | +## General Development Practices |
| 46 | + |
| 47 | +- Be sure to resolve the mypy type checker errors as you develop and make |
| 48 | + changes. |
| 49 | + |
| 50 | +- If type checker errors are hard to resolve, you may add a comment `# type: ignore` |
| 51 | + to disable mypy warnings or errors but ONLY if you know they are not a real problem |
| 52 | + and are difficult to fix. |
| 53 | + |
| 54 | +- In special cases you may consider disabling it globally it in pyproject.toml but YOU |
| 55 | + MUST ASK FOR CONFIRMATION from the user before globally disabling lint or type checker |
| 56 | + rules. |
| 57 | + |
| 58 | +- Never change an existing comment, pydoc, or a log statement, unless it is directly |
| 59 | + fixing the issue you are changing, or the user has asked you to clean up the code. |
| 60 | + Do not drop existing comments when editing code! |
| 61 | + And do not delete or change logging statements. |
| 62 | + |
| 63 | +## Coding Conventions and Imports |
| 64 | + |
| 65 | +- Always use full, absolute imports for paths. |
| 66 | + do NOT use `from .module1.module2 import ...`. Such relative paths make it hard to |
| 67 | + refactor. Use `from toplevel_pkg.module1.modlule2 import ...` instead. |
| 68 | + |
| 69 | +- Be sure to import things like `Callable` and other types from the right modules, |
| 70 | + remembering that many are now in `collections.abc` or `typing_extensions`. For |
| 71 | + example: `from collections.abc import Callable, Coroutine` |
| 72 | + |
| 73 | +- Use `typing_extensions` for things like `@override` (you need to use this, and not |
| 74 | + `typing` since we want to support Python 3.11). |
| 75 | + |
| 76 | +- Add `from __future__ import annotations` on files with types whenever applicable. |
| 77 | + |
| 78 | +- Use pathlib `Path` instead of strings. |
| 79 | + Use `Path(filename).read_text()` instead of two-line `with open(...)` blocks. |
| 80 | + |
| 81 | +- Use strif’s `atomic_output_file` context manager when writing files to ensure output |
| 82 | + files are written atomically. |
| 83 | + |
| 84 | +## Use Modern Python Practices |
| 85 | + |
| 86 | +- ALWAYS use `@override` decorators to override methods from base classes. |
| 87 | + This is a modern Python practice and helps avoid bugs. |
| 88 | + |
| 89 | +## Testing |
| 90 | + |
| 91 | +- For longer tests put them in a file like `tests/test_somename.py` in the `tests/` |
| 92 | + directory (or `tests/module_name/test_somename.py` file for a submodule). |
| 93 | + |
| 94 | +- For simple tests, prefer inline functions in the original code file below a `## Tests` |
| 95 | + comment. This keeps the tests easy to maintain and close to the code. |
| 96 | + Inline tests should NOT import pytest or pytest fixtures as we do not want runtime |
| 97 | + dependency on pytest. |
| 98 | + |
| 99 | +- DO NOT write one-off test code in extra files that are throwaway. |
| 100 | + |
| 101 | +- DO NOT put `if __name__ == "__main__":` just for quick testing. |
| 102 | + Instead use the inline function tests and run them with `uv run pytest`. |
| 103 | + |
| 104 | +- You can run such individual tests with `uv run pytest -s src/.../path/to/test` |
| 105 | + |
| 106 | +- Don’t add docs to assertions unless it’s not obvious what they’re checking - the |
| 107 | + assertion appears in the stack trace. |
| 108 | + Do NOT write `assert x == 5, "x should be 5"`. Do NOT write `assert x == 5 # Check if |
| 109 | + x is 5`. That is redundant. |
| 110 | + Just write `assert x == 5`. |
| 111 | + |
| 112 | +- DO NOT write trivial or obvious tests that are evident directly from code, such as |
| 113 | + assertions that confirm the value of a constant setting. |
| 114 | + |
| 115 | +- NEVER write `assert False`. If a test reaches an unexpected branch and must fail |
| 116 | + explicitly, `raise AssertionError("Some explanation")` instead. |
| 117 | + This is best typical best practice in Python since assertions can be removed with |
| 118 | + optimization. |
| 119 | + |
| 120 | +- DO NOT use pytest fixtures like parameterized tests or expected exception decorators |
| 121 | + unless absolutely necessary in more complex tests. |
| 122 | + It is typically simpler to use simple assertions and put the checks inside the test. |
| 123 | + This is also preferable because then simple tests have no explicit pytest dependencies |
| 124 | + and can be placed in code anywhere. |
| 125 | + |
| 126 | +- DO NOT write trivial tests that test something we know already works, like |
| 127 | + instantiating a Pydantic object. |
| 128 | + |
| 129 | + ```python |
| 130 | + class Link(BaseModel): |
| 131 | + url: str |
| 132 | + title: str = None |
| 133 | + |
| 134 | + # DO NOT write tests like this. They are trivial and only create clutter! |
| 135 | + def test_link_model(): |
| 136 | + link = Link(url="https://example.com", title="Example") |
| 137 | + assert link.url == "https://example.com" |
| 138 | + assert link.title == "Example" |
| 139 | + ``` |
| 140 | + |
| 141 | +## Types and Type Annotations |
| 142 | + |
| 143 | +- Use modern union syntax: `str | None` instead of `Optional[str]`, `dict[str]` instead |
| 144 | + of `Dict[str]`, `list[str]` instead of `List[str]`, etc. |
| 145 | + |
| 146 | +- Never use/import `Optional` for new code. |
| 147 | + |
| 148 | +- Use modern enums like `StrEnum` if appropriate. |
| 149 | + |
| 150 | +## Guidelines for Comments |
| 151 | + |
| 152 | +- Comments should be EXPLANATORY: Explain *WHY* something is done a certain way and not |
| 153 | + just *what* is done. |
| 154 | + |
| 155 | +- Comments should be CONCISE: Remove all extraneous words. |
| 156 | + |
| 157 | +- DO NOT use comments to state obvious things or repeat what is evident from the code. |
| 158 | + Here is an example of a comment that SHOULD BE REMOVED because it simply repeats the |
| 159 | + code, which is distracting and adds no value: |
| 160 | + ```python |
| 161 | + if self.failed == 0: |
| 162 | + # All successful |
| 163 | + return "All tasks finished successfully" |
| 164 | + ``` |
| 165 | + |
| 166 | +## Guidelines for Docstrings |
| 167 | + |
| 168 | +- Here is an example of the correct style for docstrings: |
| 169 | + ```python |
| 170 | + def check_if_url( |
| 171 | + text: UnresolvedLocator, only_schemes: list[str] | None = None |
| 172 | + ) -> ParseResult | None: |
| 173 | + """ |
| 174 | + Convenience function to check if a string or Path is a URL and if so return |
| 175 | + the `urlparse.ParseResult`. |
| 176 | +
|
| 177 | + Also returns false for Paths, so that it's easy to use local paths and URLs |
| 178 | + (`Locator`s) interchangeably. Can provide `HTTP_ONLY` or `HTTP_OR_FILE` to |
| 179 | + restrict to only certain schemes. |
| 180 | + """ |
| 181 | + # Function body |
| 182 | + |
| 183 | + def is_url(text: UnresolvedLocator, only_schemes: list[str] | None = None) -> bool: |
| 184 | + """ |
| 185 | + Check if a string is a URL. For convenience, also returns false for |
| 186 | + Paths, so that it's easy to use local paths and URLs interchangeably. |
| 187 | + """ |
| 188 | + return check_if_url(text, only_schemes) is not None |
| 189 | + ``` |
| 190 | + |
| 191 | +- Use concise pydoc strings with triple quotes on their own lines. |
| 192 | + |
| 193 | +- Use `backticks` around variable names and inline code excerpts. |
| 194 | + |
| 195 | +- Use plain fences (```) around code blocks inside of pydocs. |
| 196 | + |
| 197 | +- For classes with many methods, use a concise docstring on the class that explains all |
| 198 | + the common information, and avoid repeating the same information on every method. |
| 199 | + |
| 200 | +- Docstrings should provide context or as concisely as possible explain “why”, not |
| 201 | + obvious details evident from the class names, function names, parameter names, and |
| 202 | + type annotations. |
| 203 | + |
| 204 | +- Docstrings *should* mention any key rationale or pitfalls when using the class or |
| 205 | + function. |
| 206 | + |
| 207 | +- Avoid obvious or repetitive docstrings. |
| 208 | + Do NOT add pydocs that just repeat in English facts that are obvious from the function |
| 209 | + name, variable name, or types. |
| 210 | + That is silly and obvious and makes the code longer for no reason. |
| 211 | + |
| 212 | +- Do NOT list args and return values if they’re obvious. |
| 213 | + In the above examples, you do not need and `Arguments:` or `Returns:` section, since |
| 214 | + sections as it is obvious from context. |
| 215 | + do list these if there are many arguments and their meaning isn’t clear. |
| 216 | + If it returns a less obvious type like a tuple, do explain in the pydoc. |
| 217 | + |
| 218 | +- Exported/public variables, functions, or methods SHOULD have concise docstrings. |
| 219 | + Internal/local variables, functions, and methods DO NOT need docstrings unless their |
| 220 | + purpose is not obvious. |
| 221 | + |
| 222 | +## General Clean Coding Practices |
| 223 | + |
| 224 | +- Avoid writing trivial wrapper functions. |
| 225 | + For example, when writing a class DO NOT blindly make delegation methods around public |
| 226 | + member variables. DO NOT write methods like this: |
| 227 | + ```python |
| 228 | + def reassemble(self) -> str: |
| 229 | + """Call the original reassemble method.""" |
| 230 | + return self.paragraph.reassemble() |
| 231 | + ``` |
| 232 | + In general, the user can just call the enclosed objects methods, reducing code bloat. |
| 233 | + |
| 234 | +- If a function does not use a parameter, but it should still be present, you can use `# type: ignore` in a comment to suppress the type checker warning. |
| 235 | + |
| 236 | +## Guidelines for Backward Compatibility |
| 237 | + |
| 238 | +- When changing code in a library or general function, if a change to an API or library |
| 239 | + will break backward compatibility, MENTION THIS to the user. |
| 240 | + |
| 241 | +- DO NOT implement additional code for backward compatiblity (such as extra methods or |
| 242 | + variable aliases or comments about backward compatibility) UNLESS the user has |
| 243 | + confirmed that it is necessary. |
0 commit comments