Repository navigation
Conversation
Workflow novo em .github/workflows/ci.yml, disparado em pull_request e em push para a main. O job de testes roda a suíte em Python 3.11 e 3.14 com o addopts do projeto, inclusive o gate de cobertura. O job de lint roda pre-commit run --all-files com a .venv na raiz, onde os hooks locais de mypy e pyright a procuram. Actions fixadas por SHA, permissão só de leitura e nenhum segredo.
…arquivos No checkout do CI o índice coincide com o HEAD e não há merge em curso. Nesse estado, check-added-large-files só mede arquivos adicionados no índice e check-merge-conflict retorna 0 sem olhar o conteúdo, então os dois apareciam como Passed com um arquivo de 600 KB ou com marcadores de conflito commitados. --enforce-all e --assume-in-merge fazem os dois examinarem os arquivos recebidos. A árvore atual passa, porque o maior arquivo versionado tem 299 KB e nenhum tem marcador de conflito. tests/test_pre_commit_config.py reprova a configuração quando um hook que decide pelo estado do git entra sem o argumento que o obriga a olhar os arquivos, ou quando entra um hook que não tem esse argumento. O pyyaml, que o teste importa, passa a constar no extra dev, em vez de chegar só como dependência do pre-commit.
EvidênciaCaso concreto na base ( Na base não há workflow, então nenhum PR roda suíte nem hooks no GitHub. Localmente, suíte e hooks já passam, e por isso este PR não precisa mexer em código para o CI ficar verde. O mesmo caso no ramo ( O Hooks que não examinavam nada no CI (achado da revisão, corrigido em
Testes. Base ( Árvore mesclada contra Risco: baixo para usuário, porque o PR acrescenta SHA revisado: e58055b |
O que o CI roda
O workflow novo
.github/workflows/ci.ymldispara em todopull_requeste empushpara amain. Ele tem dois jobs.O job
testesroda em Python 3.11, piso dorequires-python, e em Python 3.14, a versão mais nova em que todas as dependências, inclusive as dos extrasdevebrowser, têm wheel. A 3.15 ficou fora porque opyyaml, que opre-commitpuxa, ainda não publica wheel para ela. O job cria a.venvcomuv, instala-e ".[dev,browser]"e chamapytestsem argumentos, de modo que vale oaddoptsdopyproject.toml: os testes de integração ficam de fora e a suíte reprova abaixo dofail_under = 80.O job
lintrodapre-commit run --all-files --show-diff-on-failureem Python 3.11, a mesma versão dopythonVersiondo mypy e do pyright. A.venvfica na raiz do checkout porque os hooks locais chamam.venv/bin/mypye.venv/bin/pyright, e o pyright resolve os imports pelovenv = ".venv"dopyproject.toml. O extrabrowserinstala o pacoteplaywrightpara o pyright achar os imports dos scrapers de navegador; nenhum navegador é baixado. O cache do~/.cache/pre-commitusa como chave o sistema, a versão do Python e o hash do.pre-commit-config.yaml.O workflow declara
permissions: contents: read, faz ocheckoutcompersist-credentials: false, não usa segredo e fixa as três actions (actions/checkoutv7.0.1,astral-sh/setup-uvv10.3.0,actions/cachev6.1.0) por SHA completo, com a versão em comentário. Escolhi a v10.3.0 dosetup-uve não a v10.3.1 porque esta saiu no mesmo dia deste PR e só corrige um aviso de cache. A rede fica nos passos de instalação: o job de lint monta os ambientes dos hooks compre-commit install-hooksantes de rodá-los, ePYRIGHT_PYTHON_IGNORE_WARNINGS=1desliga a consulta ao PyPI que o pacotepyrightfaz a cada execução para avisar de versão nova. Oconcurrencyagrupa por workflow e ref e cancela o run anterior só em PR; namaincada push termina o seu run.O que mudou além do workflow
No checkout do runner, o índice coincide com o HEAD e não há merge em curso. Nesse estado, dois hooks do
pre-commit-hookspassavam sem examinar nada: ocheck-added-large-filesmede só os arquivos adicionados no índice (git diff --staged --diff-filter=A), e ocheck-merge-conflictretorna 0 quando não achaMERGE_HEADnem rebase em curso. Um PR com um arquivo de 600 KB ou com marcadores de conflito commitados passava no lint. O.pre-commit-config.yamlpassa agora--enforce-allao primeiro e--assume-in-mergeao segundo, e os dois examinam todos os arquivos que recebem. A árvore atual passa nos dois: o maior arquivo versionado tem 299 KB e nenhum tem marcador de conflito.No commit local, os dois argumentos também valem: um arquivo modificado acima de 500 KB passa a ser barrado, e não só um arquivo novo, e uma linha formada por exatamente sete sinais de igual, como o sublinhado de um título em Markdown, reprova em qualquer commit, e não só durante um merge.
O teste novo
tests/test_pre_commit_config.pyreprova a configuração quando um desses dois hooks perde o argumento, ou quando entra oforbid-new-submodules, que lê só o índice e não tem argumento equivalente (oforbid-submodulesserve no lugar). Opyyaml, que o teste importa, passa a constar no extradev; antes chegava só como dependência dopre-commit.Fora isso, nada mudou: na base a suíte já passava com 200 testes e 84,47% de cobertura e os outros hooks já passavam, então o PR não toca código do pacote. O
CHANGELOG.mdfica como está, porque a mudança é interna.Como foi validado
actionlint1.7.12, com shellcheck ativo, sem achado.zizmor1.31.1 em modo offline, sem achado.pre-commit run --all-filescom o cache do pre-commit vazio, para reproduzir a primeira execução no CI: os 11 hooks passam.unshare -rn), depois de instalados: passam, o que mostra que os passos que rodam hooks e testes não dependem de rede.pre-commit run --all-files --show-diff-on-failure, antes e depois da correção dos dois hooks:e548e9f)e58055b)Passedcheck-added-large-filesacusatests/grande.bin (600 KB) exceeds 500 KBCHANGELOG.mdPassedcheck-merge-conflictacusa as três linhas--enforce-all, tirar--assume-in-merge, acrescentarforbid-new-submodulese tirar ocheck-merge-conflict.e548e9fe eme58055b.Quanto falta para a tipagem e o lint ficarem estritos
Medi sem aplicar nada. O mypy e o pyright rodaram com mypy 2.4.0 e pyright 1.1.414, da
.venvdo extradev.mypy.
mypy --strict srccom a configuração do projeto dá 92 erros em 16 dos 21 arquivos: 69no-untyped-def, 21no-untyped-calle 2type-arg. Sem os relaxamentos dopyproject.toml(oignore_missing_importsglobal e o override dos três módulos de navegador) e comwarn_unreachable, o strict puro dá 143. Os 51 a mais são 34 erros que o override esconde emplaywright_scraper.py(23),datalegis.py(7) esaudelegis.py(4), sendo 26attr-defined, 5unreachable, 2no-any-returne 1union-attr, e 17import-untyped: 14 do pandas, 2 do tqdm e 1 doplaywright_stealth. Compandas-stubsetypes-tqdminstalados, o strict puro cai para 128, e a configuração atual do projeto passa a acusar um único erro novo, uma inferência de tipo do dicionário de agregação emsrc/raspe/utils.py:122. Dos códigos opcionais do mypy, nove têm zero ocorrência na configuração atual (ignore-without-code,redundant-expr,truthy-bool,truthy-iterable,redundant-self,unused-awaitable,unimported-reveal,exhaustive-match,deprecated),possibly-undefinedtem 1,mutable-overridetem 6 eexplicit-overridetem 71. Cinco módulos já passam no strict puro:exceptions.py,html_scraper.py,scrapers/__init__.py,scrapers/ans.pyescrapers/anvisa.py.O
possibly-undefinedaponta um defeito real: emBaseScraper._request_with_retry, commax_retries = 0o laço não roda e oreturn rlevantaUnboundLocalError. Reproduzi sem rede.pyright. No modo
basicdo projeto, o pyright dá 0 erros e 24 warnings (23reportOptionalMemberAccesse 1reportAttributeAccessIssue), porque opyproject.tomlrebaixa três regras para warning. Com uma configuração temporária fora do repositório, sem esses rebaixamentos, o modostandarddá 31 erros e o modostrictdá 570. Desses, 499 vêm de parâmetro sem anotação ou tipo desconhecido (220reportUnknownMemberType, 115reportUnknownVariableType, 58reportUnknownArgumentType, 55reportMissingParameterType, 50reportUnknownParameterTypee 1reportUnknownLambdaType), 15 de stub ausente (14 do pandas e 1 doplaywright_stealth), 23 de acesso a atributo opcional, 6 dereportIncompatibleVariableOverride, 6 deisinstancedesnecessário emcamara.py, 2 dereportMissingTypeArgumente 19 de outras regras. Treze desses 19 vêm de_import_playwright(), que devolve umdictcom valores de tipos diferentes: o pyright infere a união e acusa, por exemplo, queStealthnão aceitanavigator_languages_override, o que é falso positivo (conferi a assinatura noplaywright-stealth2.0.3). Com os stubs do pandas e do tqdm, o strict cai para 557. Os mesmos cinco módulos do mypy já passam no strict do pyright.pylint. Das 42 regras desligadas em
[tool.pylint.messages_control], estas são de correção e não de estilo, com as ocorrências medidas pelo pylint 4.1.3 da.venv:import-errorno-memberbroad-except(hojebroad-exception-caught)except Exceptionque pode engolir errounused-argumentScraperIpea.__init__aceitadownload_pathe o ignora; o**kwargsdecamara._prep_scrapeé exigência do overrideattribute-defined-outside-initredefined-outer-namereimportedf-string-without-interpolationdiffering-param-docemissing-param-docraspe/__init__.pyque documentam parâmetros que a assinatura não tem, ou omitem o**kwargsAs demais são de estilo, documentação ou desenho (
missing-*-docstring,too-many-*,invalid-name,logging-fstring-interpolation,duplicate-code,import-outside-toplevel, que é intencional para os extras opcionais, e outras) e ficam a critério do projeto.Fora do pylint, o
flake8-bugbear, que o projeto não usa, acusa 7 itens emsrc: 5B030, osexcept pw['PlaywrightTimeout']do mesmo padrão dedict, e 2B042emRateLimitErroreAPIError;pickleecopy.copydessas exceções funcionam hoje, conferi. Nos testes, oF401ignorado esconde 1 import sem uso (reemtests/test_utils.py).Proposta de catraca
Cada passo é um PR próprio que só aperta a régua, com o CI deste PR como portão.
no-member,attribute-defined-outside-init,redefined-outer-name,reimportedef-string-without-interpolation, todas com 0 ocorrência tanto no pylint da.venvquanto no do hook.pycqa/pylintv3.0.0 por hook local.venv/bin/pylint, no mesmo padrão do mypy e do pyright, e religarimport-error. No mesmo PR,pre-commit autoupdatedos hooks remotos; opre-commit-hooksv4.4.0 já emite aviso de nome de stage obsoleto.pandas-stubsetypes-tqdmno extradev, trocar oignore_missing_importsglobal por um override só paraplaywright_stealthe anotar o dicionário desrc/raspe/utils.py:122.dictde_import_playwright()por um retorno tipado e estreitar_page,_browsere afins com um acesso que levanta erro claro quando o navegador não foi iniciado. Isso resolve os 23 acessos opcionais, os 13 erros do pyright derivados dodicte os 5B030, permite apagar o override do mypy dos três módulos e devolver as três regras rebaixadas do pyright para erro. O pyright passa astandard.rpossivelmente indefinido de_request_with_retry, odownload_pathignorado da IPEA, a redeclaração de_typee_api_methodcomLiteralmais estreito emcapes.py,ipea.pyepresidencia.py(6 erros em cada verificador) e as docstrings deraspe/__init__.py. Depois, ligarpossibly-undefinedemutable-overrideno mypy e religarunused-argument,differing-param-docemissing-param-docno pylint.strict = truepor override do mypy e a listastrict = [...]do pyright pelos cinco módulos já limpos, e acrescentar módulos a cada PR. O grosso está em__init__.py,playwright_scraper.py,base_scraper.py,camara.pyecapes.py. No fim,strict = trueglobal no mypy etypeCheckingMode = "strict"no pyright.except Exception, estreitar o que dá e manter o resto com desligamento local comentado; então religar a regra.flake8-bugbear(depois do passo 4 sobram os 2B042) eexplicit-override(71 ocorrências; em Python 3.11 o@overrideexigetyping_extensionsdeclarado como dependência).