Coverage for src\pncp\contratacoes.py: 0%

329 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-24 16:58 -0300

1from datetime import datetime 

2from typing import Iterable, Literal 

3 

4import httpx 

5 

6from .tipos import Lista, ModeloBasico 

7 

8type IdStatus = str 

9type IdInstrumentoConvocatorio = str 

10type IdModalidadeDeContratacao = str 

11type IdOrgao = str 

12type IdUnidade = str 

13type IdUnidadeDaFederacao = str 

14type IdMunicipio = str 

15type IdEsfera = str 

16type IdPoder = str 

17 

18 

19class Status(ModeloBasico): 

20 id: IdStatus 

21 nome: str 

22 

23 

24class InstrumentoConvocatorio(ModeloBasico): 

25 id: IdInstrumentoConvocatorio 

26 nome: str 

27 total: int 

28 

29 

30class ModalidadeDeContratacao(ModeloBasico): 

31 id: IdModalidadeDeContratacao 

32 nome: str 

33 total: int 

34 

35 

36class Orgao(ModeloBasico): 

37 id: IdOrgao 

38 cnpj: str 

39 nome: str 

40 total: int 

41 

42 

43class Unidade(ModeloBasico): 

44 id: IdUnidade 

45 codigo: str 

46 nome: str 

47 total: int 

48 codigo_nome: str 

49 

50 

51class UnidadeDaFederacao(ModeloBasico): 

52 id: IdUnidadeDaFederacao 

53 total: int 

54 

55 

56class Municipio(ModeloBasico): 

57 id: IdMunicipio 

58 nome: str 

59 total: int 

60 

61 

62class Esfera(ModeloBasico): 

63 id: IdEsfera 

64 nome: str 

65 total: int 

66 

67 

68class Poder(ModeloBasico): 

69 id: IdPoder 

70 nome: str 

71 total: int 

72 

73 

74class Ano(ModeloBasico): 

75 ano: str 

76 total: int 

77 

78 

79class ParametrosDeBusca(ModeloBasico): 

80 tipos_documento: Literal["edital"] = "edital" 

81 

82 q: str = "" 

83 pagina: int = 1 

84 tam_pagina: int = 10 

85 status: IdStatus = "todos" 

86 tipos: Lista[IdInstrumentoConvocatorio] = Lista() 

87 orgaos: Lista[IdOrgao] = Lista() 

88 unidades: Lista[IdUnidade] = Lista() 

89 esferas: Lista[IdEsfera] = Lista() 

90 poderes: Lista[IdPoder] = Lista() 

91 ufs: Lista[IdUnidadeDaFederacao] = Lista() 

92 municipios: Lista[IdMunicipio] = Lista() 

93 modalidades: Lista[IdModalidadeDeContratacao] = Lista() 

94 anos: Lista[str] = Lista() 

95 

96 

97class Edital(ModeloBasico): 

98 id: str 

99 index: str 

100 doc_type: str 

101 title: str 

102 description: str 

103 item_url: str 

104 document_type: str 

105 createdAt: datetime 

106 numero: str | None 

107 ano: str 

108 numero_sequencial: str 

109 numero_sequencial_compra_ata: str | None 

110 numero_controle_pncp: str 

111 orgao_id: str 

112 orgao_cnpj: str 

113 orgao_nome: str 

114 orgao_subrogado_id: str | None 

115 orgao_subrogado_nome: str | None 

116 unidade_id: str 

117 unidade_codigo: str 

118 unidade_nome: str 

119 esfera_id: str 

120 esfera_nome: str 

121 poder_id: str 

122 poder_nome: str 

123 municipio_id: str 

124 municipio_nome: str 

125 uf: str 

126 modalidade_licitacao_id: str 

127 modalidade_licitacao_nome: str 

128 situacao_id: str 

129 situacao_nome: str 

130 data_publicacao_pncp: datetime 

131 data_atualizacao_pncp: datetime 

132 data_assinatura: datetime | None 

133 data_inicio_vigencia: datetime 

134 data_fim_vigencia: datetime 

135 cancelado: bool 

136 valor_global: float | None 

137 tem_resultado: bool 

138 tipo_id: str 

139 tipo_nome: str 

140 tipo_contrato_nome: str | None 

141 fonte_orcamentaria: str 

142 fonte_orcamentaria_id: str 

143 fonte_orcamentaria_nome: str 

144 

145 def detalhar(self) -> "EditalDetalhado": 

146 url_edital = ( 

147 "https://pncp.gov.br/api/consulta/v1/orgaos/" 

148 f"{self.orgao_cnpj}/compras/{self.ano}/{self.numero_sequencial}" 

149 ) 

150 

151 url_itens = url_edital + "/itens?pagina=1&tamanhoPagina=500" 

152 

153 with httpx.Client(timeout=30) as client: 

154 response = client.get(url_edital) 

155 edital_detalhado = response.json() 

156 response = client.get(url_itens) 

157 itens = response.json() 

158 

159 return { 

160 "edital": edital_detalhado, 

161 "itens": itens, 

162 } 

163 

164 

165class EditalDetalhado(ModeloBasico): 

166 pass 

167 

168 

169class Resultado(ModeloBasico): 

170 items: Lista[Edital] 

171 total: int 

172 

173 

174class Busca: 

175 _url_filtros = "https://pncp.gov.br/api/search/filters?tipos_documento=edital" 

176 _url_busca = "https://pncp.gov.br/api/search" 

177 

178 def __init__(self): 

179 self._filtros = None 

180 self._parametros = ParametrosDeBusca() 

181 self._resultados = None 

182 

183 def _carregar_filtros(self): 

184 if self._filtros is None: 

185 with httpx.Client(timeout=30) as client: 

186 response = client.get(self._url_filtros) 

187 data = response.json() 

188 self._filtros = data["filters"] 

189 

190 def listar_status(self) -> Lista[Status]: 

191 status = [ 

192 Status(id="todos", nome="Todos"), 

193 Status(id="encerradas", nome="Encerradas"), 

194 Status(id="propostas_encerradas", nome="Em Julgamento/Propostas Encerradas"), 

195 Status(id="recebendo_proposta", nome="A Receber/Recebendo Propostas"), 

196 ] 

197 

198 return Lista(status) 

199 

200 def listar_instrumentos_convocatorios(self) -> Lista[InstrumentoConvocatorio]: 

201 self._carregar_filtros() 

202 

203 if not self._filtros or "tipos" not in self._filtros: 

204 return Lista() 

205 

206 return Lista( 

207 InstrumentoConvocatorio(**instrumento_convocatorio) 

208 for instrumento_convocatorio in self._filtros["tipos"] 

209 ) 

210 

211 def listar_modalidades_de_contratacao(self) -> Lista[ModalidadeDeContratacao]: 

212 self._carregar_filtros() 

213 

214 if not self._filtros or "modalidades" not in self._filtros: 

215 return Lista() 

216 

217 return Lista( 

218 ModalidadeDeContratacao(**modalidade_de_contratacao) 

219 for modalidade_de_contratacao in self._filtros["modalidades"] 

220 ) 

221 

222 def listar_orgaos(self) -> Lista[Orgao]: 

223 self._carregar_filtros() 

224 

225 if not self._filtros or "orgaos" not in self._filtros: 

226 return Lista() 

227 

228 return Lista(Orgao(**orgao) for orgao in self._filtros["orgaos"]) 

229 

230 def listar_unidades(self) -> Lista[Unidade]: 

231 self._carregar_filtros() 

232 

233 if not self._filtros or "unidades" not in self._filtros: 

234 return Lista() 

235 

236 return Lista(Unidade(**unidade) for unidade in self._filtros["unidades"]) 

237 

238 def listar_ufs(self) -> Lista[UnidadeDaFederacao]: 

239 self._carregar_filtros() 

240 

241 if not self._filtros or "ufs" not in self._filtros: 

242 return Lista() 

243 

244 return Lista(UnidadeDaFederacao(**uf) for uf in self._filtros["ufs"]) 

245 

246 def listar_municipios(self) -> Lista[Municipio]: 

247 self._carregar_filtros() 

248 

249 if not self._filtros or "municipios" not in self._filtros: 

250 return Lista() 

251 

252 return Lista(Municipio(**municipio) for municipio in self._filtros["municipios"]) 

253 

254 def listar_esferas(self) -> Lista[Esfera]: 

255 self._carregar_filtros() 

256 

257 if not self._filtros or "esferas" not in self._filtros: 

258 return Lista() 

259 

260 return Lista(Esfera(**esfera) for esfera in self._filtros["esferas"]) 

261 

262 def listar_poderes(self) -> Lista[Poder]: 

263 self._carregar_filtros() 

264 

265 if not self._filtros or "poderes" not in self._filtros: 

266 return Lista() 

267 

268 return Lista(Poder(**poder) for poder in self._filtros["poderes"]) 

269 

270 def listar_anos(self) -> Lista[Ano]: 

271 self._carregar_filtros() 

272 

273 if not self._filtros or "anos" not in self._filtros: 

274 return Lista() 

275 

276 return Lista(Ano(**ano) for ano in self._filtros["anos"]) 

277 

278 @property 

279 def texto(self) -> str: 

280 return self._parametros.q 

281 

282 @property 

283 def pagina(self) -> int: 

284 return self._parametros.pagina 

285 

286 @property 

287 def tam_pagina(self) -> int: 

288 return self._parametros.tam_pagina 

289 

290 @property 

291 def status(self) -> Status: 

292 return ( 

293 self.listar_status().filtrar(lambda status: status.id == self._parametros.status).pop() 

294 ) 

295 

296 @property 

297 def instrumentos_convocatorios(self) -> Lista[InstrumentoConvocatorio]: 

298 return Lista( 

299 instrumento_convocatorio 

300 for instrumento_convocatorio in self.listar_instrumentos_convocatorios() 

301 if instrumento_convocatorio.id in self._parametros.tipos 

302 ) 

303 

304 @property 

305 def modalidades_de_contratacao(self) -> Lista[ModalidadeDeContratacao]: 

306 return Lista( 

307 modalidade_de_contratacao 

308 for modalidade_de_contratacao in self.listar_modalidades_de_contratacao() 

309 if modalidade_de_contratacao.id in self._parametros.modalidades 

310 ) 

311 

312 @property 

313 def orgaos(self) -> Lista[Orgao]: 

314 return Lista(orgao for orgao in self.listar_orgaos() if orgao.id in self._parametros.orgaos) 

315 

316 @property 

317 def unidades(self) -> Lista[Unidade]: 

318 return Lista( 

319 unidade for unidade in self.listar_unidades() if unidade.id in self._parametros.unidades 

320 ) 

321 

322 @property 

323 def ufs(self) -> Lista[UnidadeDaFederacao]: 

324 return Lista(uf for uf in self.listar_ufs() if uf.id in self._parametros.ufs) 

325 

326 @property 

327 def municipios(self) -> Lista[Municipio]: 

328 return Lista( 

329 municipio 

330 for municipio in self.listar_municipios() 

331 if municipio.id in self._parametros.municipios 

332 ) 

333 

334 @property 

335 def esferas(self) -> Lista[Esfera]: 

336 return Lista( 

337 esfera for esfera in self.listar_esferas() if esfera.id in self._parametros.esferas 

338 ) 

339 

340 @property 

341 def poderes(self) -> Lista[Poder]: 

342 return Lista( 

343 poder for poder in self.listar_poderes() if poder.id in self._parametros.poderes 

344 ) 

345 

346 @property 

347 def anos(self) -> Lista[Ano]: 

348 return Lista(ano for ano in self.listar_anos() if ano.ano in self._parametros.anos) 

349 

350 @property 

351 def resultados(self) -> Resultado: 

352 if self._resultados is not None: 

353 return self._resultados 

354 with httpx.Client(timeout=30) as client: 

355 response = client.get(self._url_busca, params=self._parametros.model_dump()) 

356 data = response.json() 

357 self._resultados = Resultado( 

358 items=Lista(Edital(**resultado) for resultado in data["items"]), 

359 total=data["total"], 

360 ) 

361 return self._resultados 

362 

363 @texto.setter 

364 def texto(self, texto: str): 

365 if self._resultados is not None: 

366 raise ValueError("Não é possível modificar uma busca já realizada.") 

367 self._parametros.q = texto 

368 

369 @pagina.setter 

370 def pagina(self, pagina: int): 

371 if self._resultados is not None: 

372 raise ValueError("Não é possível modificar uma busca já realizada.") 

373 self._parametros.pagina = pagina 

374 

375 @tam_pagina.setter 

376 def tam_pagina(self, tam_pagina: int): 

377 if self._resultados is not None: 

378 raise ValueError("Não é possível modificar uma busca já realizada.") 

379 self._parametros.tam_pagina = tam_pagina 

380 

381 @status.setter 

382 def status(self, id_status: IdStatus): 

383 if self._resultados is not None: 

384 raise ValueError("Não é possível modificar uma busca já realizada.") 

385 if id_status not in [status.id for status in self.listar_status()]: 

386 raise ValueError(f"Status inválido: {id_status}") 

387 self._parametros.status = id_status 

388 

389 @instrumentos_convocatorios.setter 

390 def instrumentos_convocatorios(self, ids: Iterable[IdInstrumentoConvocatorio]): 

391 if self._resultados is not None: 

392 raise ValueError("Não é possível modificar uma busca já realizada.") 

393 self._parametros.tipos = Lista(ids) 

394 

395 @modalidades_de_contratacao.setter 

396 def modalidades_de_contratacao(self, ids: Iterable[IdModalidadeDeContratacao]): 

397 if self._resultados is not None: 

398 raise ValueError("Não é possível modificar uma busca já realizada.") 

399 self._parametros.modalidades = Lista(ids) 

400 

401 @orgaos.setter 

402 def orgaos(self, ids: Iterable[IdOrgao]): 

403 if self._resultados is not None: 

404 raise ValueError("Não é possível modificar uma busca já realizada.") 

405 self._parametros.orgaos = Lista(ids) 

406 

407 @unidades.setter 

408 def unidades(self, ids: Iterable[IdUnidade]): 

409 self._parametros.unidades = Lista(ids) 

410 

411 @ufs.setter 

412 def ufs(self, ids: Iterable[IdUnidadeDaFederacao]): 

413 if self._resultados is not None: 

414 raise ValueError("Não é possível modificar uma busca já realizada.") 

415 self._parametros.ufs = Lista(ids) 

416 

417 @municipios.setter 

418 def municipios(self, ids: Iterable[IdMunicipio]): 

419 if self._resultados is not None: 

420 raise ValueError("Não é possível modificar uma busca já realizada.") 

421 self._parametros.municipios = Lista(ids) 

422 

423 @esferas.setter 

424 def esferas(self, ids: Iterable[IdEsfera]): 

425 if self._resultados is not None: 

426 raise ValueError("Não é possível modificar uma busca já realizada.") 

427 self._parametros.esferas = Lista(ids) 

428 

429 @poderes.setter 

430 def poderes(self, ids: Iterable[IdPoder]): 

431 if self._resultados is not None: 

432 raise ValueError("Não é possível modificar uma busca já realizada.") 

433 self._parametros.poderes = Lista(ids) 

434 

435 @anos.setter 

436 def anos(self, anos: Iterable[str] | Iterable[int]): 

437 if self._resultados is not None: 

438 raise ValueError("Não é possível modificar uma busca já realizada.") 

439 self._parametros.anos = Lista(str(ano) for ano in anos) 

440 

441 def preencher_texto(self, texto: str): 

442 self.texto = texto 

443 

444 def preencher_pagina(self, pagina: int): 

445 self.pagina = pagina 

446 

447 def preencher_tam_pagina(self, tam_pagina: int): 

448 self.tam_pagina = tam_pagina 

449 

450 def preencher_instrumentos_convocatorios(self, ids: Iterable[IdInstrumentoConvocatorio]): 

451 self.tipos = ids 

452 

453 def preencher_modalidades_de_contratacao(self, ids: Iterable[IdModalidadeDeContratacao]): 

454 self.modalidades = ids 

455 

456 def preencher_orgaos(self, ids: Iterable[IdOrgao]): 

457 self.orgaos = ids 

458 

459 def preencher_unidades(self, ids: Iterable[IdUnidade]): 

460 self.unidades = ids 

461 

462 def preencher_ufs(self, ids: Iterable[IdUnidadeDaFederacao]): 

463 self.ufs = ids 

464 

465 def preencher_municipios(self, ids: Iterable[IdMunicipio]): 

466 self.municipios = ids 

467 

468 def preencher_esferas(self, ids: Iterable[IdEsfera]): 

469 self.esferas = ids 

470 

471 def preencher_poderes(self, ids: Iterable[IdPoder]): 

472 self.poderes = ids 

473 

474 def preencher_anos(self, anos: Iterable[str] | Iterable[int]): 

475 self.anos = anos 

476 

477 def buscar(self): 

478 return self.resultados