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
« 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
4import httpx
6from .tipos import Lista, ModeloBasico
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
19class Status(ModeloBasico):
20 id: IdStatus
21 nome: str
24class InstrumentoConvocatorio(ModeloBasico):
25 id: IdInstrumentoConvocatorio
26 nome: str
27 total: int
30class ModalidadeDeContratacao(ModeloBasico):
31 id: IdModalidadeDeContratacao
32 nome: str
33 total: int
36class Orgao(ModeloBasico):
37 id: IdOrgao
38 cnpj: str
39 nome: str
40 total: int
43class Unidade(ModeloBasico):
44 id: IdUnidade
45 codigo: str
46 nome: str
47 total: int
48 codigo_nome: str
51class UnidadeDaFederacao(ModeloBasico):
52 id: IdUnidadeDaFederacao
53 total: int
56class Municipio(ModeloBasico):
57 id: IdMunicipio
58 nome: str
59 total: int
62class Esfera(ModeloBasico):
63 id: IdEsfera
64 nome: str
65 total: int
68class Poder(ModeloBasico):
69 id: IdPoder
70 nome: str
71 total: int
74class Ano(ModeloBasico):
75 ano: str
76 total: int
79class ParametrosDeBusca(ModeloBasico):
80 tipos_documento: Literal["edital"] = "edital"
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()
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
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 )
151 url_itens = url_edital + "/itens?pagina=1&tamanhoPagina=500"
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()
159 return {
160 "edital": edital_detalhado,
161 "itens": itens,
162 }
165class EditalDetalhado(ModeloBasico):
166 pass
169class Resultado(ModeloBasico):
170 items: Lista[Edital]
171 total: int
174class Busca:
175 _url_filtros = "https://pncp.gov.br/api/search/filters?tipos_documento=edital"
176 _url_busca = "https://pncp.gov.br/api/search"
178 def __init__(self):
179 self._filtros = None
180 self._parametros = ParametrosDeBusca()
181 self._resultados = None
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"]
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 ]
198 return Lista(status)
200 def listar_instrumentos_convocatorios(self) -> Lista[InstrumentoConvocatorio]:
201 self._carregar_filtros()
203 if not self._filtros or "tipos" not in self._filtros:
204 return Lista()
206 return Lista(
207 InstrumentoConvocatorio(**instrumento_convocatorio)
208 for instrumento_convocatorio in self._filtros["tipos"]
209 )
211 def listar_modalidades_de_contratacao(self) -> Lista[ModalidadeDeContratacao]:
212 self._carregar_filtros()
214 if not self._filtros or "modalidades" not in self._filtros:
215 return Lista()
217 return Lista(
218 ModalidadeDeContratacao(**modalidade_de_contratacao)
219 for modalidade_de_contratacao in self._filtros["modalidades"]
220 )
222 def listar_orgaos(self) -> Lista[Orgao]:
223 self._carregar_filtros()
225 if not self._filtros or "orgaos" not in self._filtros:
226 return Lista()
228 return Lista(Orgao(**orgao) for orgao in self._filtros["orgaos"])
230 def listar_unidades(self) -> Lista[Unidade]:
231 self._carregar_filtros()
233 if not self._filtros or "unidades" not in self._filtros:
234 return Lista()
236 return Lista(Unidade(**unidade) for unidade in self._filtros["unidades"])
238 def listar_ufs(self) -> Lista[UnidadeDaFederacao]:
239 self._carregar_filtros()
241 if not self._filtros or "ufs" not in self._filtros:
242 return Lista()
244 return Lista(UnidadeDaFederacao(**uf) for uf in self._filtros["ufs"])
246 def listar_municipios(self) -> Lista[Municipio]:
247 self._carregar_filtros()
249 if not self._filtros or "municipios" not in self._filtros:
250 return Lista()
252 return Lista(Municipio(**municipio) for municipio in self._filtros["municipios"])
254 def listar_esferas(self) -> Lista[Esfera]:
255 self._carregar_filtros()
257 if not self._filtros or "esferas" not in self._filtros:
258 return Lista()
260 return Lista(Esfera(**esfera) for esfera in self._filtros["esferas"])
262 def listar_poderes(self) -> Lista[Poder]:
263 self._carregar_filtros()
265 if not self._filtros or "poderes" not in self._filtros:
266 return Lista()
268 return Lista(Poder(**poder) for poder in self._filtros["poderes"])
270 def listar_anos(self) -> Lista[Ano]:
271 self._carregar_filtros()
273 if not self._filtros or "anos" not in self._filtros:
274 return Lista()
276 return Lista(Ano(**ano) for ano in self._filtros["anos"])
278 @property
279 def texto(self) -> str:
280 return self._parametros.q
282 @property
283 def pagina(self) -> int:
284 return self._parametros.pagina
286 @property
287 def tam_pagina(self) -> int:
288 return self._parametros.tam_pagina
290 @property
291 def status(self) -> Status:
292 return (
293 self.listar_status().filtrar(lambda status: status.id == self._parametros.status).pop()
294 )
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 )
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 )
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)
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 )
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)
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 )
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 )
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 )
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)
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
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
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
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
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
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)
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)
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)
407 @unidades.setter
408 def unidades(self, ids: Iterable[IdUnidade]):
409 self._parametros.unidades = Lista(ids)
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)
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)
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)
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)
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)
441 def preencher_texto(self, texto: str):
442 self.texto = texto
444 def preencher_pagina(self, pagina: int):
445 self.pagina = pagina
447 def preencher_tam_pagina(self, tam_pagina: int):
448 self.tam_pagina = tam_pagina
450 def preencher_instrumentos_convocatorios(self, ids: Iterable[IdInstrumentoConvocatorio]):
451 self.tipos = ids
453 def preencher_modalidades_de_contratacao(self, ids: Iterable[IdModalidadeDeContratacao]):
454 self.modalidades = ids
456 def preencher_orgaos(self, ids: Iterable[IdOrgao]):
457 self.orgaos = ids
459 def preencher_unidades(self, ids: Iterable[IdUnidade]):
460 self.unidades = ids
462 def preencher_ufs(self, ids: Iterable[IdUnidadeDaFederacao]):
463 self.ufs = ids
465 def preencher_municipios(self, ids: Iterable[IdMunicipio]):
466 self.municipios = ids
468 def preencher_esferas(self, ids: Iterable[IdEsfera]):
469 self.esferas = ids
471 def preencher_poderes(self, ids: Iterable[IdPoder]):
472 self.poderes = ids
474 def preencher_anos(self, anos: Iterable[str] | Iterable[int]):
475 self.anos = anos
477 def buscar(self):
478 return self.resultados