{"id":"code-dev-py-type-hints-python-38","audience":"developers","category":"coding","track":"coding","technique":"context","title":"Add type hints that still run on Python 3.8","free":false,"task":"The team wants type hints on users.py, but the batch servers still run Python 3.8, so list[str], dict[str, int] and X | None would crash at import. Use typing.List, Dict and Optional with one import line. Team style: define Row = List[str] once under the import and use List[Row] for users everywhere. Keep every function body unchanged and get find_user's return type right (it can return None). Return the 3 functions plus the import in exactly 1 code block of at most 18 lines, with under 20 words of text.","materialLabel":"The module (attached to your prompt automatically)","material":"# users.py - shared helpers for the nightly billing jobs (Ashgrove Cloud)\n# each line of users.csv is: name,email,team\n\ndef load_users(path):\n    with open(path) as f:\n        return [line.strip().split(\",\") for line in f if line.strip()]\n\ndef find_user(users, email):\n    for row in users:\n        if row[1] == email:\n            return {\"name\": row[0], \"email\": row[1], \"team\": row[2]}\n    return None\n\ndef team_counts(users):\n    counts = {}\n    for row in users:\n        counts[row[2]] = counts.get(row[2], 0) + 1\n    return counts\n\n\n# usage in billing/run.py:\n#   users = load_users(\"/srv/data/users.csv\")\n#   me = find_user(users, \"ola@ashgrove.test\")\n#   if me is None:\n#       log.warning(\"unknown user\")\n#   per_team = team_counts(users)   # {\"infra\": 4, \"web\": 7}\n#\n# Ruben started on hints last month but his branch never got merged.","rules":[{"type":"codeBlocks","count":1},{"type":"maxCodeLines","max":18},{"type":"maxProseWords","max":20}],"mistakes":[{"title":"Modern syntax on an old server","why":"The AI writes list[str] and X | None by default, which crashes Python 3.8 at import.","fix":"Add: \"Python 3.8: use typing.List, Dict, Optional; no list[...] or | None.\"","checks":["r0"]},{"title":"Ignoring the team's alias","why":"The AI writes List[List[str]] everywhere or invents its own alias name.","fix":"Add: \"Define Row = List[str] under the import and use List[Row]; find_user returns Optional[Dict[str, str]].\"","checks":["r1"]},{"title":"Extra edits","why":"The AI adds docstrings, a TypedDict or annotations inside the loops.","fix":"Add: \"Only change the def lines and add one import. Bodies unchanged.\"","checks":["r2"]},{"title":"A long tutorial","why":"The AI explains every hint and suggests mypy settings.","fix":"Add: \"One code block, no text.\"","checks":["codeBlocks","maxCodeLines","maxProseWords"]}],"promptHints":[{"label":"Names the Python version","patterns":["3\\.8","python 3\\.8"]},{"label":"Asks for typing.List style","patterns":["typing","List\\[","Optional"]},{"label":"Gives the Row alias","patterns":["Row =","alias","List\\[Row\\]"]},{"label":"Protects the bodies","patterns":["bod(y|ies) unchanged","only (change )?the (def|signature)","don'?t change"]},{"label":"Limits the reply","patterns":["only the code","no (text|explanation)","one code block"]}],"hint":"Type hint syntax depends on the Python version, and the AI assumes the newest. Tell it which version, your team's alias and that only the def lines change.","rubricLabels":["Uses no Python 3.9+ typing syntax anywhere: no list[...], dict[...] or | None, only typing.List, Dict and Optional, imported with a single from typing import line","Defines Row = List[str] once below the import, and the signatures are load_users(path: str) -> List[Row], find_user(users: List[Row], email: str) -> Optional[Dict[str, str]] and team_counts(users: List[Row]) -> Dict[str, int]","Every function body is unchanged (no added docstrings, comments, annotations inside the body or logic changes)"]}