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

1 from datetime import datetime

2 from typing import Iterable, Literal

3

4 import httpx

5

6 from .tipos import Lista, ModeloBasico

7

8 type IdStatus = str

9 type IdInstrumentoConvocatorio = str

10 type IdModalidadeDeContratacao = str

11 type IdOrgao = str

12 type IdUnidade = str

13 type IdUnidadeDaFederacao = str

14 type IdMunicipio = str

15 type IdEsfera = str

16 type IdPoder = str

17

18

19 class Status(ModeloBasico):

20 id: IdStatus

21 nome: str

22

23

24 class InstrumentoConvocatorio(ModeloBasico):

25 id: IdInstrumentoConvocatorio

26 nome: str

27 total: int

28

29

30 class ModalidadeDeContratacao(ModeloBasico):

31 id: IdModalidadeDeContratacao

32 nome: str

33 total: int

34

35

36 class Orgao(ModeloBasico):

37 id: IdOrgao

38 cnpj: str

39 nome: str

40 total: int

41

42

43 class Unidade(ModeloBasico):

44 id: IdUnidade

45 codigo: str

46 nome: str

47 total: int

48 codigo_nome: str

49

50

51 class UnidadeDaFederacao(ModeloBasico):

52 id: IdUnidadeDaFederacao

53 total: int

54

55

56 class Municipio(ModeloBasico):

57 id: IdMunicipio

58 nome: str

59 total: int

60

61

62 class Esfera(ModeloBasico):

63 id: IdEsfera

64 nome: str

65 total: int

66

67

68 class Poder(ModeloBasico):

69 id: IdPoder

70 nome: str

71 total: int

72

73

74 class Ano(ModeloBasico):

75 ano: str

76 total: int

77

78

79 class 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

97 class 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

165 class EditalDetalhado(ModeloBasico):

166 pass

167

168

169 class Resultado(ModeloBasico):

170 items: Lista[Edital]

171 total: int

172

173

174 class 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