Imported from cupang-afk/the-downloader (
AGENTS.md). Install upstream withnpx skills add cupang-afk/the-downloader. Copyright stays with the author.
AGENTS.md
Scope
This file defines Python code style and docstring rules for this repository.
Focus on readable, predictable declarations. Keep examples minimal. Follow these rules for new code and for touched code when practical.
Python Style Priorities
- Correctness first.
- Clear type hints for public APIs and non-trivial internals.
- Google style docstrings for modules, classes, functions, methods, properties, and important attributes.
- Stable declaration ordering.
- Simple, explicit Python over clever compact code.
Naming
Use standard Python naming unless an existing API requires otherwise.
- Constants:
UPPER_SNAKE_CASE. - Variables:
snake_case. - Functions and methods:
snake_case. - Classes and exceptions:
PascalCase. - Private names: single leading underscore, e.g.
_parse_url. - Protected/internal-reserved names: double leading underscore, e.g.
__build_headers. - Dunder names: double leading and trailing underscores, e.g.
__init__.
Global Naming Order
When a category can contain private, protected, and public names, order by naming tier first:
_private__protectedpublic
Inside each tier, sort names A-Z, case-insensitive.
Sort A-Z unless doing so conflicts with a runtime, inheritance, or dependency requirement. When a dependency requirement exists, keep the required declaration before its dependents.
Dunder names do not use this naming order.
Module Declaration Order
Order top-level declarations by category:
- Constants
- Variables
- Functions
- Classes
if __name__ == "__main__":
Within each category, apply global naming order, then A-Z case-insensitive order unless a runtime, inheritance, or dependency requirement needs a different order.
_PRIVATE_TIMEOUT = 5
__PROTECTED_RETRIES = 3
PUBLIC_CHUNK_SIZE = 8192
_private_cache = {}
__protected_session = None
public_default_headers = {}
def _build_path() -> str: ...
def __normalize_url() -> str: ...
def download() -> None: ...
class DownloadTask: ...
if __name__ == "__main__":
download()
Class Declaration Order
Order declarations inside classes by category:
- Constants
- Variables
- Dunder methods
- Methods, static methods, and class methods
- Properties
Within constants, variables, methods, static methods, class methods, and properties:
- Apply global naming order when applicable.
- Sort A-Z case-insensitive inside each naming tier unless a runtime, inheritance, or dependency requirement needs a different order.
Dunder methods are ordered separately:
__init__first.- Builtin/override dunders A-Z, case-insensitive.
- Custom dunders A-Z, case-insensitive.
class DownloadTask:
_PRIVATE_LIMIT = 1
PUBLIC_LIMIT = 10
_private_state: str
public_url: str
def __init__(self, url: str) -> None:
self.public_url = url
self._private_state = "pending"
def __repr__(self) -> str:
return f"DownloadTask(url={self.public_url!r})"
def __custom_hook__(self) -> None: ...
def _private_method(self) -> None: ...
@classmethod
def from_url(cls, url: str) -> "DownloadTask":
return cls(url)
@property
def status(self) -> str:
return self._private_state
Docstring Style
Use Google style docstrings compatible with sphinx.ext.napoleon and Ruff
pydocstyle convention google.
General rules:
- Start with a one-line imperative or descriptive summary.
- Add a blank line before sections.
- Use sections only when needed:
Args,Returns,Yields,Raises,Attributes,Examples,Note. - Do not document
selforclsinArgs. - If PEP 484 annotations already show types clearly, omit repeated types in docstrings.
- Document exceptions that are part of the interface.
- Document properties in the getter docstring.
- For
__init__, document initialization either in the class docstring or in__init__, not both.
def fetch(url: str, timeout: float) -> bytes:
"""Fetch bytes from a URL.
Args:
url: URL to fetch.
timeout: Request timeout in seconds.
Returns:
Response body bytes.
Raises:
TimeoutError: If the request exceeds `timeout`.
"""
class DownloadError(Exception):
"""Raised when a download fails.
Args:
message: Human-readable failure message.
url: URL that failed.
Attributes:
message: Human-readable failure message.
url: URL that failed.
"""
@property
def progress(self) -> float:
"""Download progress from 0.0 to 1.0."""
Type Hints
- Annotate all public functions, methods, and class attributes.
- Annotate non-trivial private helpers.
- Prefer built-in generics:
list[str],dict[str, int],tuple[str, ...]. - Use
| Noneinstead ofOptional. - Avoid
Anyunless the value is intentionally unconstrained. - Keep docstrings focused on behavior, not duplicated type info.
Agentic Workflow
Before running any check workflow, first ensure the code style rules above are applied to the relevant source files. Use the helper snippets below when declaration order, docstrings, or other style requirements are hard to inspect manually. After code style is applied, continue with the BasedPyright, Ruff, and Pytest check workflows.
Use BasedPyright for type checking. Use Ruff for linting, fixing, and formatting. Use Pytest for tests.
BasedPyright, Ruff, and Pytest are development dependencies in
pyproject.toml. They are expected to be installed in the active development
environment. If all command forms for a tool fail, stop work, treat the check
as failed, and ask the user to install development tools with uv sync --dev
or another environment-specific method.
Project source code lives under src/. Run BasedPyright and Ruff against
src/ only unless the user asks to check another path.
BasedPyright Workflow
BasedPyright command priority:
uv run basedpyright.python -m basedpyrightifuv run basedpyrightfails.basedpyrightifpython -m basedpyrightfails.- If BasedPyright is unavailable through all options, stop work, treat the
check as failed, and ask the user to install development tools with
uv sync --devor another environment-specific method.
Type checking workflow:
- Run
uv run basedpyright src/and record the initial errors. - If errors exist, fix the troubled code manually.
- Never suppress BasedPyright errors with comments, ignore directives, or rule configuration changes unless the user explicitly approves that exact suppression.
- Never modify BasedPyright rules to make errors disappear.
- Run BasedPyright again.
- If the check still fails, repeat manual fixes and checks, up to 5 attempts.
- After 5 failed manual attempts, stop and ask the user for guidance. Include the remaining error output, affected code context, what was tried, and suggested fixes.
When using fallback commands, keep the same check intent:
uv run basedpyright src/
python -m basedpyright src/
basedpyright src/
Pytest Workflow
Pytest command priority:
uv run pytest.python -m pytestifuv run pytestfails.pytestifpython -m pytestfails.- If Pytest is unavailable through all options, stop work, treat the check as
failed, and ask the user to install development tools with
uv sync --devor another environment-specific method.
Testing workflow:
- Run
uv run pytestand record the initial failures. - If failures exist, fix the troubled code manually.
- Run Pytest again.
- If the check still fails, repeat manual fixes and checks, up to 5 attempts.
- After 5 failed manual attempts, stop and ask the user for guidance. Include the remaining failure output, affected code context, what was tried, and suggested fixes.
When using fallback commands, keep the same check intent:
uv run pytest
python -m pytest
pytest
Ruff Workflow
Ruff command priority:
uv run ruff.python -m ruffifuv run rufffails.ruffifpython -m rufffails.- If Ruff is unavailable through all options, stop work, treat the check as
failed, and ask the user to install development tools with
uv sync --devor another environment-specific method.
Code checking workflow:
- Run
uv run ruff check src/and record the initial errors. - Run
uv run ruff check src/ --fix. - Run
uv run ruff format src/. - Run
uv run ruff check src/again and compare remaining errors with the initial errors. - If errors remain, fix the troubled code manually.
- Never suppress Ruff errors with
# noqa, disable comments, or rule configuration changes unless the user explicitly approves that exact suppression. - Never modify Ruff rules to make errors disappear.
- Run a final
ruff check src/. - If the final check still fails, repeat manual fixes and final checks, up to 5 attempts.
- After 5 failed manual attempts, stop and ask the user for guidance. Include the remaining error output, affected code context, what was tried, and suggested fixes.
When using fallback commands, keep the same arguments:
uv run ruff check src/
uv run ruff check src/ --fix
uv run ruff format src/
python -m ruff check src/
python -m ruff check src/ --fix
python -m ruff format src/
ruff check src/
ruff check src/ --fix
ruff format src/
AST Helper Snippets
Use ast for quick checks when declaration order is hard to inspect manually.
These snippets are written exclusively for the current project Python version:
Python 3.14.5.
List top-level declarations:
import ast
from pathlib import Path
TARGET = Path("src/the_downloader/task.py")
module = ast.parse(TARGET.read_text(encoding="utf-8"), filename=str(TARGET))
for node in module.body:
match node:
case ast.Assign():
print("variable", node.lineno)
case ast.AnnAssign(target=ast.Name(id=name)):
print("variable", name, node.lineno)
case ast.AsyncFunctionDef(name=name) | ast.FunctionDef(name=name):
print("function", name, node.lineno)
case ast.ClassDef(name=name):
print("class", name, node.lineno)
List class declarations:
import ast
from pathlib import Path
TARGET = Path("src/the_downloader/task.py")
module = ast.parse(TARGET.read_text(encoding="utf-8"), filename=str(TARGET))
for node in module.body:
if isinstance(node, ast.ClassDef):
print(node.name)
for item in node.body:
match item:
case ast.Assign():
print(" variable", item.lineno)
case ast.AnnAssign(target=ast.Name(id=name)):
print(" variable", name, item.lineno)
case (
ast.AsyncFunctionDef(name=name)
| ast.FunctionDef(name=name)
):
print(" method", name, item.lineno)
Find missing docstrings:
import ast
from pathlib import Path
TARGET = Path("src/the_downloader/task.py")
module = ast.parse(TARGET.read_text(encoding="utf-8"), filename=str(TARGET))
if ast.get_docstring(module) is None:
print("missing docstring", "module", 1)
for node in ast.walk(module):
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef):
if ast.get_docstring(node) is None:
print("missing docstring", node.name, node.lineno)
Print existing docstring summaries:
import ast
from pathlib import Path
TARGET = Path("src/the_downloader/task.py")
module = ast.parse(TARGET.read_text(encoding="utf-8"), filename=str(TARGET))
module_docstring = ast.get_docstring(module)
if module_docstring:
print("module", 1, module_docstring.splitlines()[0])
for node in ast.walk(module):
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef):
docstring = ast.get_docstring(node)
if docstring:
print(node.name, node.lineno, docstring.splitlines()[0])
Find Google-style section headers:
import ast
from pathlib import Path
SECTION_HEADERS = {
"Args:",
"Returns:",
"Yields:",
"Raises:",
"Attributes:",
"Examples:",
"Note:",
}
TARGET = Path("src/the_downloader/task.py")
module = ast.parse(TARGET.read_text(encoding="utf-8"), filename=str(TARGET))
for node in ast.walk(module):
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef):
docstring = ast.get_docstring(node) or ""
headers = [
line.strip()
for line in docstring.splitlines()
if line.strip() in SECTION_HEADERS
]
if headers:
print(node.name, node.lineno, headers)
Sketch a sorting helper:
def naming_tier(name: str) -> int:
if name.startswith("__") and name.endswith("__"):
return 99
if name.startswith("__"):
return 1
if name.startswith("_"):
return 0
return 2
def name_key(name: str) -> tuple[int, str]:
return naming_tier(name), name.casefold()
Sketch dunder ordering:
BUILTIN_DUNDERS = {
"__aenter__",
"__aexit__",
"__bool__",
"__enter__",
"__eq__",
"__exit__",
"__hash__",
"__iter__",
"__len__",
"__repr__",
"__str__",
}
def dunder_key(name: str) -> tuple[int, str]:
if name == "__init__":
return 0, name
if name in BUILTIN_DUNDERS:
return 1, name.casefold()
return 2, name.casefold()