pypncp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pypncp-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,236 @@
1
+ Metadata-Version: 2.4
2
+ Name: pypncp
3
+ Version: 0.1.0
4
+ Summary: pypncp — Cliente Python assíncrono para a API de Consulta do PNCP
5
+ License: MIT
6
+ Requires-Python: >=3.12
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: httpx>=0.28.1
9
+ Requires-Dist: pydantic>=2.13.4
10
+ Requires-Dist: tenacity>=9.0.0
11
+ Provides-Extra: dev
12
+ Requires-Dist: pytest>=8.0.0; extra == "dev"
13
+ Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
14
+ Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
15
+ Requires-Dist: pytest-httpx>=0.35.0; extra == "dev"
16
+ Requires-Dist: ruff>=0.11.0; extra == "dev"
17
+ Requires-Dist: mypy>=1.15.0; extra == "dev"
18
+
19
+ # pypncp
20
+
21
+ ![PyPI](https://img.shields.io/pypi/v/pypncp) ![Python Version](https://img.shields.io/pypi/pyversions/pypncp) ![License](https://img.shields.io/pypi/l/pypncp)
22
+
23
+ **Cliente Python assíncrono para a API de Consulta do PNCP** — Portal Nacional de Contratações Públicas.
24
+
25
+ ---
26
+
27
+ ## Como usar
28
+
29
+ ### Adicionar ao projeto
30
+
31
+ Com `uv` (recomendado):
32
+
33
+ ```bash
34
+ uv add pypncp
35
+ ```
36
+
37
+ Com `pip`:
38
+
39
+ ```bash
40
+ pip install pypncp
41
+ ```
42
+
43
+ Ou direto no `pyproject.toml`:
44
+
45
+ ```toml
46
+ [project]
47
+ dependencies = [
48
+ "pypncp>=0.1",
49
+ ]
50
+ ```
51
+
52
+ ### Criar o cliente
53
+
54
+ O cliente é a entrada única para a API. Use `async with` pra gerenciar o ciclo de vida:
55
+
56
+ ```python
57
+ from pypncp import PNCPClient
58
+
59
+
60
+ async def buscar_dados():
61
+ async with PNCPClient() as client:
62
+ # client está pronto — usa os resources abaixo
63
+ ...
64
+
65
+ # Fora de async def, use asyncio.run():
66
+ # asyncio.run(buscar_dados())
67
+ ```
68
+
69
+ ### Exemplos por recurso
70
+
71
+ **Contratos** — busque contratos por período de publicação:
72
+
73
+ ```python
74
+ async with PNCPClient() as client:
75
+ # Uma página
76
+ page = await client.contratos.list(
77
+ data_inicial="2025-01-01",
78
+ data_final="2025-03-31",
79
+ )
80
+ for contrato in page.data:
81
+ print(contrato.numero_contrato_empenho, contrato.valor_global)
82
+
83
+ # Todas as páginas (paginação automática)
84
+ async for contrato in client.contratos.list_all(
85
+ data_inicial="2025-01-01",
86
+ data_final="2025-03-31",
87
+ ):
88
+ print(contrato.objeto_contrato)
89
+ ```
90
+
91
+ **Contratações (licitações)** — filtre por modalidade (código 1 = pregão):
92
+
93
+ ```python
94
+ async with PNCPClient() as client:
95
+ async for compra in client.contratacoes.list_all_publicacao(
96
+ data_inicial="2025-01-01",
97
+ data_final="2025-03-31",
98
+ codigo_modalidade=1, # 1 = Pregão
99
+ ):
100
+ print(compra.objeto_compra, compra.orgao_nome)
101
+
102
+ # Com propostas abertas (apenas data_final)
103
+ async for compra in client.contratacoes.list_all_com_proposta(
104
+ data_final="2025-12-31",
105
+ ):
106
+ print(compra.objeto_compra, compra.data_abertura_proposta)
107
+ ```
108
+
109
+ **Atas de registro de preço:**
110
+
111
+ ```python
112
+ async with PNCPClient() as client:
113
+ async for ata in client.atas.list_all(
114
+ data_inicial="2025-01-01",
115
+ data_final="2025-12-31",
116
+ ):
117
+ print(ata.objeto_contratacao, ata.orgao_nome)
118
+ ```
119
+
120
+ ### Paginação
121
+
122
+ ```python
123
+ # Automática — use list_all*() (async generator)
124
+ async for contrato in client.contratos.list_all(
125
+ data_inicial="2025-01-01",
126
+ data_final="2025-12-31",
127
+ ):
128
+ ...
129
+
130
+
131
+ # Manual — use list() e controle a página
132
+ page = await client.contratos.list(
133
+ data_inicial="2025-01-01",
134
+ data_final="2025-12-31",
135
+ pagina=1,
136
+ )
137
+ print(f"Página {page.numero_pagina} de {page.total_paginas}")
138
+ print(f"Itens nesta página: {len(page.data)}")
139
+
140
+ # Propriedades úteis de Page[T]:
141
+ # page.data → list[T]
142
+ # page.numero_pagina
143
+ # page.total_paginas
144
+ # page.has_more → True se há mais páginas
145
+ ```
146
+
147
+ ### Tratamento de erros
148
+
149
+ Todas as exceções herdam de `PNCPError` — nunca vazam exceções de transporte:
150
+
151
+ ```python
152
+ from pypncp import PNCPError, NotFoundError, RateLimitError
153
+
154
+ try:
155
+ page = await client.contratos.list(
156
+ data_inicial="2025-01-01",
157
+ data_final="2025-12-31",
158
+ )
159
+ except NotFoundError:
160
+ print("Recurso não encontrado (HTTP 404)")
161
+ except RateLimitError:
162
+ print("Muitas requisições (HTTP 429)")
163
+ except PNCPError as e:
164
+ print(f"Erro na API: {e}")
165
+ ```
166
+
167
+ ### Exemplo completo com FastAPI
168
+
169
+ ```python
170
+ from fastapi import FastAPI, HTTPException
171
+ from pypncp import PNCPClient, NotFoundError
172
+
173
+ app = FastAPI()
174
+
175
+
176
+ @app.get("/contratos")
177
+ async def listar_contratos(
178
+ data_inicial: str,
179
+ data_final: str,
180
+ pagina: int = 1,
181
+ ):
182
+ async with PNCPClient() as client:
183
+ page = await client.contratos.list(
184
+ data_inicial=data_inicial,
185
+ data_final=data_final,
186
+ pagina=pagina,
187
+ )
188
+ return {
189
+ "contratos": [c.model_dump() for c in page.data],
190
+ "pagina": page.numero_pagina,
191
+ "total_paginas": page.total_paginas,
192
+ "total_registros": page.total_registros,
193
+ }
194
+
195
+
196
+ @app.get("/contratos/{orgao_cnpj}/{ano}/{sequencial}")
197
+ async def get_contrato(orgao_cnpj: str, ano: int, sequencial: int):
198
+ async with PNCPClient() as client:
199
+ try:
200
+ contrato = await client.contratos.get(
201
+ orgao_cnpj=orgao_cnpj,
202
+ ano=ano,
203
+ sequencial=sequencial,
204
+ )
205
+ return contrato.model_dump()
206
+ except NotFoundError:
207
+ raise HTTPException(status_code=404, detail="Contrato não encontrado")
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Para contribuir
213
+
214
+ ### Requisitos
215
+
216
+ - Python **3.12+**
217
+ - [uv](https://docs.astral.sh/uv/) — gerenciador de projetos e pacotes
218
+
219
+ ```bash
220
+ uv sync
221
+ ```
222
+
223
+ ### Rodar verificações
224
+
225
+ ```bash
226
+ git clone https://github.com/gabrielgz0/pypncp
227
+ cd pypncp
228
+ uv sync # instala tudo — dev deps inclusas
229
+ uv run pytest -v # tests
230
+ uv run ruff check src/ tests/
231
+ uv run mypy src/
232
+ ```
233
+
234
+ ### Licença
235
+
236
+ MIT
pypncp-0.1.0/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # pypncp
2
+
3
+ ![PyPI](https://img.shields.io/pypi/v/pypncp) ![Python Version](https://img.shields.io/pypi/pyversions/pypncp) ![License](https://img.shields.io/pypi/l/pypncp)
4
+
5
+ **Cliente Python assíncrono para a API de Consulta do PNCP** — Portal Nacional de Contratações Públicas.
6
+
7
+ ---
8
+
9
+ ## Como usar
10
+
11
+ ### Adicionar ao projeto
12
+
13
+ Com `uv` (recomendado):
14
+
15
+ ```bash
16
+ uv add pypncp
17
+ ```
18
+
19
+ Com `pip`:
20
+
21
+ ```bash
22
+ pip install pypncp
23
+ ```
24
+
25
+ Ou direto no `pyproject.toml`:
26
+
27
+ ```toml
28
+ [project]
29
+ dependencies = [
30
+ "pypncp>=0.1",
31
+ ]
32
+ ```
33
+
34
+ ### Criar o cliente
35
+
36
+ O cliente é a entrada única para a API. Use `async with` pra gerenciar o ciclo de vida:
37
+
38
+ ```python
39
+ from pypncp import PNCPClient
40
+
41
+
42
+ async def buscar_dados():
43
+ async with PNCPClient() as client:
44
+ # client está pronto — usa os resources abaixo
45
+ ...
46
+
47
+ # Fora de async def, use asyncio.run():
48
+ # asyncio.run(buscar_dados())
49
+ ```
50
+
51
+ ### Exemplos por recurso
52
+
53
+ **Contratos** — busque contratos por período de publicação:
54
+
55
+ ```python
56
+ async with PNCPClient() as client:
57
+ # Uma página
58
+ page = await client.contratos.list(
59
+ data_inicial="2025-01-01",
60
+ data_final="2025-03-31",
61
+ )
62
+ for contrato in page.data:
63
+ print(contrato.numero_contrato_empenho, contrato.valor_global)
64
+
65
+ # Todas as páginas (paginação automática)
66
+ async for contrato in client.contratos.list_all(
67
+ data_inicial="2025-01-01",
68
+ data_final="2025-03-31",
69
+ ):
70
+ print(contrato.objeto_contrato)
71
+ ```
72
+
73
+ **Contratações (licitações)** — filtre por modalidade (código 1 = pregão):
74
+
75
+ ```python
76
+ async with PNCPClient() as client:
77
+ async for compra in client.contratacoes.list_all_publicacao(
78
+ data_inicial="2025-01-01",
79
+ data_final="2025-03-31",
80
+ codigo_modalidade=1, # 1 = Pregão
81
+ ):
82
+ print(compra.objeto_compra, compra.orgao_nome)
83
+
84
+ # Com propostas abertas (apenas data_final)
85
+ async for compra in client.contratacoes.list_all_com_proposta(
86
+ data_final="2025-12-31",
87
+ ):
88
+ print(compra.objeto_compra, compra.data_abertura_proposta)
89
+ ```
90
+
91
+ **Atas de registro de preço:**
92
+
93
+ ```python
94
+ async with PNCPClient() as client:
95
+ async for ata in client.atas.list_all(
96
+ data_inicial="2025-01-01",
97
+ data_final="2025-12-31",
98
+ ):
99
+ print(ata.objeto_contratacao, ata.orgao_nome)
100
+ ```
101
+
102
+ ### Paginação
103
+
104
+ ```python
105
+ # Automática — use list_all*() (async generator)
106
+ async for contrato in client.contratos.list_all(
107
+ data_inicial="2025-01-01",
108
+ data_final="2025-12-31",
109
+ ):
110
+ ...
111
+
112
+
113
+ # Manual — use list() e controle a página
114
+ page = await client.contratos.list(
115
+ data_inicial="2025-01-01",
116
+ data_final="2025-12-31",
117
+ pagina=1,
118
+ )
119
+ print(f"Página {page.numero_pagina} de {page.total_paginas}")
120
+ print(f"Itens nesta página: {len(page.data)}")
121
+
122
+ # Propriedades úteis de Page[T]:
123
+ # page.data → list[T]
124
+ # page.numero_pagina
125
+ # page.total_paginas
126
+ # page.has_more → True se há mais páginas
127
+ ```
128
+
129
+ ### Tratamento de erros
130
+
131
+ Todas as exceções herdam de `PNCPError` — nunca vazam exceções de transporte:
132
+
133
+ ```python
134
+ from pypncp import PNCPError, NotFoundError, RateLimitError
135
+
136
+ try:
137
+ page = await client.contratos.list(
138
+ data_inicial="2025-01-01",
139
+ data_final="2025-12-31",
140
+ )
141
+ except NotFoundError:
142
+ print("Recurso não encontrado (HTTP 404)")
143
+ except RateLimitError:
144
+ print("Muitas requisições (HTTP 429)")
145
+ except PNCPError as e:
146
+ print(f"Erro na API: {e}")
147
+ ```
148
+
149
+ ### Exemplo completo com FastAPI
150
+
151
+ ```python
152
+ from fastapi import FastAPI, HTTPException
153
+ from pypncp import PNCPClient, NotFoundError
154
+
155
+ app = FastAPI()
156
+
157
+
158
+ @app.get("/contratos")
159
+ async def listar_contratos(
160
+ data_inicial: str,
161
+ data_final: str,
162
+ pagina: int = 1,
163
+ ):
164
+ async with PNCPClient() as client:
165
+ page = await client.contratos.list(
166
+ data_inicial=data_inicial,
167
+ data_final=data_final,
168
+ pagina=pagina,
169
+ )
170
+ return {
171
+ "contratos": [c.model_dump() for c in page.data],
172
+ "pagina": page.numero_pagina,
173
+ "total_paginas": page.total_paginas,
174
+ "total_registros": page.total_registros,
175
+ }
176
+
177
+
178
+ @app.get("/contratos/{orgao_cnpj}/{ano}/{sequencial}")
179
+ async def get_contrato(orgao_cnpj: str, ano: int, sequencial: int):
180
+ async with PNCPClient() as client:
181
+ try:
182
+ contrato = await client.contratos.get(
183
+ orgao_cnpj=orgao_cnpj,
184
+ ano=ano,
185
+ sequencial=sequencial,
186
+ )
187
+ return contrato.model_dump()
188
+ except NotFoundError:
189
+ raise HTTPException(status_code=404, detail="Contrato não encontrado")
190
+ ```
191
+
192
+ ---
193
+
194
+ ## Para contribuir
195
+
196
+ ### Requisitos
197
+
198
+ - Python **3.12+**
199
+ - [uv](https://docs.astral.sh/uv/) — gerenciador de projetos e pacotes
200
+
201
+ ```bash
202
+ uv sync
203
+ ```
204
+
205
+ ### Rodar verificações
206
+
207
+ ```bash
208
+ git clone https://github.com/gabrielgz0/pypncp
209
+ cd pypncp
210
+ uv sync # instala tudo — dev deps inclusas
211
+ uv run pytest -v # tests
212
+ uv run ruff check src/ tests/
213
+ uv run mypy src/
214
+ ```
215
+
216
+ ### Licença
217
+
218
+ MIT
@@ -0,0 +1,94 @@
1
+ [build-system]
2
+ requires = ["setuptools>=70.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pypncp"
7
+ version = "0.1.0"
8
+ description = "pypncp — Cliente Python assíncrono para a API de Consulta do PNCP"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = { text = "MIT" }
12
+ dependencies = [
13
+ "httpx>=0.28.1",
14
+ "pydantic>=2.13.4",
15
+ "tenacity>=9.0.0",
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ dev = [
20
+ "pytest>=8.0.0",
21
+ "pytest-asyncio>=0.24.0",
22
+ "pytest-cov>=6.0.0",
23
+ "pytest-httpx>=0.35.0",
24
+ "ruff>=0.11.0",
25
+ "mypy>=1.15.0",
26
+ ]
27
+
28
+ [tool.setuptools.packages.find]
29
+ where = ["src"]
30
+
31
+ [tool.setuptools.package-data]
32
+ pypncp = ["py.typed"]
33
+
34
+ # --------------------------------------------------------------------------- #
35
+ # Ruff — linter + formatador
36
+ # --------------------------------------------------------------------------- #
37
+
38
+ [tool.ruff]
39
+ line-length = 88
40
+ target-version = "py312"
41
+
42
+ [tool.ruff.lint]
43
+ select = [
44
+ "E", # pycodestyle errors
45
+ "W", # pycodestyle warnings
46
+ "F", # pyflakes
47
+ "I", # isort
48
+ "UP", # pyupgrade
49
+ "B", # flake8-bugbear
50
+ "SIM", # flake8-simplify
51
+ ]
52
+ ignore = [
53
+ "B905", # zip() sem strict= — comum em kwargs dinâmicos
54
+ ]
55
+
56
+ [tool.ruff.format]
57
+ quote-style = "double"
58
+ indent-style = "space"
59
+ line-ending = "lf"
60
+
61
+ # --------------------------------------------------------------------------- #
62
+ # mypy — type checker
63
+ # --------------------------------------------------------------------------- #
64
+
65
+ [tool.mypy]
66
+ strict = true
67
+ python_version = "3.12"
68
+ exclude = [
69
+ "tests/",
70
+ ".venv/",
71
+ ]
72
+ [[tool.mypy.overrides]]
73
+ module = [
74
+ "httpx.*",
75
+ "pydantic.*",
76
+ "tenacity.*",
77
+ ]
78
+ ignore_missing_imports = true
79
+
80
+ # --------------------------------------------------------------------------- #
81
+ # pytest
82
+ # --------------------------------------------------------------------------- #
83
+
84
+ [tool.pytest.ini_options]
85
+ asyncio_mode = "auto"
86
+ testpaths = ["tests"]
87
+
88
+ # --------------------------------------------------------------------------- #
89
+ # Cobertura
90
+ # --------------------------------------------------------------------------- #
91
+
92
+ [tool.coverage.report]
93
+ fail_under = 80
94
+ show_missing = true
pypncp-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,34 @@
1
+ """pypncp — Cliente Python assíncrono para a API de Consulta do PNCP.
2
+
3
+ API pública — apenas o que está em __all__ é suportado por semver.
4
+
5
+ Uso básico:
6
+ from pypncp import PNCPClient
7
+
8
+ async with PNCPClient() as client:
9
+ async for contrato in client.contratos.list_all(
10
+ data_inicial="2024-01-01",
11
+ data_final="2024-12-31",
12
+ ):
13
+ print(contrato.objeto_contrato)
14
+ """
15
+
16
+ from .client import PNCPClient
17
+ from .exceptions import (
18
+ AuthError,
19
+ NotFoundError,
20
+ PNCPError,
21
+ RateLimitError,
22
+ ServerError,
23
+ ValidationError,
24
+ )
25
+
26
+ __all__ = [
27
+ "PNCPClient",
28
+ "PNCPError",
29
+ "NotFoundError",
30
+ "AuthError",
31
+ "RateLimitError",
32
+ "ServerError",
33
+ "ValidationError",
34
+ ]
@@ -0,0 +1,5 @@
1
+ """Módulo interno — não faz parte da API pública.
2
+
3
+ Qualquer mudança aqui não segue semver. Prefira usar a API pública
4
+ exposta em `pncp/__init__.py`.
5
+ """