Papel
.query(Workflow.user_id, func.count(Log.id))
adiciona tanto
Workflow e Log à sua consulta. O primeiro modelo é marcado como tabela primária e os outros são marcados como secundários. Se não houver chamadas para .join() depois, as tabelas primárias e secundárias serão adicionadas ao FROM cláusula. Se houver chamadas para .join() ele moverá a tabela que receber para o JOIN cláusula. O importante aqui é que .join() pode ser aplicado apenas à tabela secundária. O problema é que a sua chamada para
.join(Workflow, Workflow.id == Log.workflow_id)
tenta marcar a tabela primária como unida. Para corrigir o problema, você precisa ingressar na tabela secundária:
.join(Log, Workflow.id == Log.workflow_id)
Você pode adicionar
echo=True
para ver o SQL gerado pelo SQLAlchemy. É realmente conveniente depurar suas consultas. Ou você pode compilar
consulta única para ver o SQL gerado.