[tool.ruff] # Linting Python (équivalent d'ESLint côté crawler/geocoder). line-length = 110 target-version = "py312" exclude = [".git", "node_modules", "__pycache__"] [tool.ruff.lint] select = [ "E", # pycodestyle "F", # pyflakes — imports/variables inutilisés, noms indéfinis "I", # isort — ordre des imports "B", # bugbear — pièges classiques (mutable default, boucle+except…) "UP", # pyupgrade "S", # bandit — analyse de sécurité (SQLi par f-string, subprocess, secrets…) "C4", # comprehensions ] ignore = [ "E501", # longueur gérée par le formateur "S101", # `assert` : légitime dans les tests ] [tool.ruff.lint.per-file-ignores] # Les requêtes SQL sont construites avec des placeholders psycopg2 (%s) ; # les rares f-strings ne portent que des noms de colonnes issus d'allowlists. "apps/crawler/src/db.py" = ["S608"] "apps/geocoder/src/*.py" = ["S608"] "**/tests/*.py" = ["S105", "S106"] # `random` sert au jitter de politesse entre deux requêtes HTTP, pas à générer # des secrets : S311 (générateur non cryptographique) ne s'applique pas. "apps/crawler/src/polite.py" = ["S311"] "apps/geocoder/src/worker.py" = ["S311", "S608"] # md5 sert d'empreinte de contenu pour détecter les pages inchangées, # jamais de primitive de sécurité. Le test compare explicitement à md5 pour # verrouiller ce choix (changer d'algorithme invaliderait tout le cache). "apps/crawler/src/scraper_band.py" = ["S324"] "apps/crawler/tests/test_scraper_band.py" = ["S324"] [tool.pytest.ini_options] # Chaque app est testée depuis sa propre racine (`rootdir`), d'où le chemin relatif # `src.` dans les imports de test. Voir le script `test:py` du package.json. testpaths = ["tests"] python_files = ["test_*.py"] addopts = "-q --strict-markers"