csv-mean-filter 0.1.0__py3-none-any.whl
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.
analise.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Calcula a média de uma coluna numérica de um arquivo CSV."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import csv
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def interpretar_filtro(texto: str) -> tuple[str, str]:
|
|
10
|
+
"""Converte "coluna=valor" em (coluna, valor), dividindo no primeiro "="."""
|
|
11
|
+
if "=" not in texto:
|
|
12
|
+
raise ValueError(f"Filtro sem '=': {texto!r}")
|
|
13
|
+
nome, valor = (parte.strip() for parte in texto.split("=", 1))
|
|
14
|
+
if not nome:
|
|
15
|
+
raise ValueError(f"Filtro sem nome de coluna: {texto!r}")
|
|
16
|
+
return nome, valor
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _filtro_argparse(texto: str) -> tuple[str, str]:
|
|
20
|
+
"""Adapta interpretar_filtro para o type= do argparse."""
|
|
21
|
+
try:
|
|
22
|
+
return interpretar_filtro(texto)
|
|
23
|
+
except ValueError:
|
|
24
|
+
raise argparse.ArgumentTypeError("use o formato coluna=valor") from None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def calcular_media(
|
|
28
|
+
caminho_csv: str, coluna: str, filtro: tuple[str, str] | None = None
|
|
29
|
+
) -> float:
|
|
30
|
+
"""Retorna a média dos valores numéricos da coluna informada.
|
|
31
|
+
|
|
32
|
+
Células vazias são ignoradas. Com filtro (coluna, valor), considera só as
|
|
33
|
+
linhas cuja coluna do filtro é igual ao valor (após strip). Levanta
|
|
34
|
+
FileNotFoundError se o arquivo não existir e ValueError para coluna
|
|
35
|
+
ausente, valor não numérico ou ausência de valores numéricos.
|
|
36
|
+
"""
|
|
37
|
+
if not os.path.isfile(caminho_csv):
|
|
38
|
+
raise FileNotFoundError(f"Arquivo não encontrado: {caminho_csv}")
|
|
39
|
+
|
|
40
|
+
with open(caminho_csv, encoding="utf-8-sig", newline="") as arquivo:
|
|
41
|
+
leitor = csv.DictReader(arquivo)
|
|
42
|
+
cabecalho = leitor.fieldnames or []
|
|
43
|
+
for nome in [coluna] + ([filtro[0]] if filtro else []):
|
|
44
|
+
if nome not in cabecalho:
|
|
45
|
+
raise ValueError(f"Coluna '{nome}' não encontrada no cabeçalho")
|
|
46
|
+
|
|
47
|
+
soma = 0.0
|
|
48
|
+
quantidade = 0
|
|
49
|
+
for linha in leitor:
|
|
50
|
+
if filtro and (linha.get(filtro[0]) or "").strip() != filtro[1].strip():
|
|
51
|
+
continue
|
|
52
|
+
bruto = (linha.get(coluna) or "").strip()
|
|
53
|
+
if not bruto:
|
|
54
|
+
continue
|
|
55
|
+
try:
|
|
56
|
+
soma += float(bruto)
|
|
57
|
+
except ValueError:
|
|
58
|
+
raise ValueError(
|
|
59
|
+
f"Valor não numérico '{bruto}' na linha {leitor.line_num}"
|
|
60
|
+
) from None
|
|
61
|
+
quantidade += 1
|
|
62
|
+
|
|
63
|
+
if quantidade == 0:
|
|
64
|
+
sufixo = f" com o filtro {filtro[0]}={filtro[1]}" if filtro else ""
|
|
65
|
+
raise ValueError(f"Nenhum valor numérico na coluna '{coluna}'{sufixo}")
|
|
66
|
+
return soma / quantidade
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def main(argv=None) -> int:
|
|
70
|
+
"""Ponto de entrada da CLI; retorna o código de saída."""
|
|
71
|
+
parser = argparse.ArgumentParser(description="Média de uma coluna de um CSV.")
|
|
72
|
+
parser.add_argument("arquivo", help="caminho do arquivo CSV")
|
|
73
|
+
parser.add_argument("coluna", help="nome da coluna")
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
"--filtrar-coluna", type=_filtro_argparse, metavar="COLUNA=VALOR",
|
|
76
|
+
help="considera só as linhas em que COLUNA é igual a VALOR",
|
|
77
|
+
)
|
|
78
|
+
args = parser.parse_args(argv)
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
media = calcular_media(args.arquivo, args.coluna, args.filtrar_coluna)
|
|
82
|
+
except (FileNotFoundError, ValueError) as erro:
|
|
83
|
+
print(f"Erro: {erro}", file=sys.stderr)
|
|
84
|
+
return 1
|
|
85
|
+
print(media)
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
sys.exit(main())
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: csv-mean-filter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI em Python para calcular a média de colunas CSV com filtros dinâmicos, zero dependências e 36 testes unitários.
|
|
5
|
+
Author: jjuffagtotal-source
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# analise
|
|
14
|
+
|
|
15
|
+
Script de linha de comando em Python que lê um arquivo CSV e calcula a média de uma coluna numérica, com filtro opcional por valor de outra coluna.
|
|
16
|
+
|
|
17
|
+
## Funcionalidades
|
|
18
|
+
|
|
19
|
+
- Calcula a média de qualquer coluna numérica de um CSV com cabeçalho.
|
|
20
|
+
- Filtro opcional `--filtrar-coluna COLUNA=VALOR` para considerar só as linhas que batem com o valor.
|
|
21
|
+
- Ignora células vazias.
|
|
22
|
+
- Lê CSVs gravados pelo Excel (UTF-8 com BOM).
|
|
23
|
+
- Mensagens de erro claras e códigos de saída previsíveis.
|
|
24
|
+
- Usa apenas a biblioteca padrão: nenhuma dependência externa.
|
|
25
|
+
|
|
26
|
+
## Requisitos
|
|
27
|
+
|
|
28
|
+
- Python 3.10 ou superior.
|
|
29
|
+
|
|
30
|
+
## Instalação
|
|
31
|
+
|
|
32
|
+
Não há pacotes para instalar. Basta clonar o repositório:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone <url-do-repositorio>
|
|
36
|
+
cd <pasta-do-repositorio>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Instalação via pip
|
|
40
|
+
|
|
41
|
+
O projeto também pode ser instalado como pacote, o que disponibiliza o comando `csv-mean` no terminal. Na raiz do repositório:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Futuramente, quando o pacote estiver publicado no PyPI:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install csv-mean-filter
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Depois de instalado, use `csv-mean` no lugar de `python src/analise.py`:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
$ csv-mean dados.csv salario --filtrar-coluna idade=30
|
|
57
|
+
1500.0
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Recomenda-se instalar dentro de um ambiente virtual (`python -m venv .venv`).
|
|
61
|
+
|
|
62
|
+
## Uso
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
python src/analise.py ARQUIVO COLUNA [--filtrar-coluna COLUNA=VALOR]
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Exemplos
|
|
69
|
+
|
|
70
|
+
Dado o arquivo `dados.csv`:
|
|
71
|
+
|
|
72
|
+
```csv
|
|
73
|
+
nome,idade,salario
|
|
74
|
+
Ana,30,1000
|
|
75
|
+
Bia,25,3000
|
|
76
|
+
Caio,30,2000
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Média de uma coluna:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
$ python src/analise.py dados.csv salario
|
|
83
|
+
2000.0
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Média só das linhas em que `idade` é `30`:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
$ python src/analise.py dados.csv salario --filtrar-coluna idade=30
|
|
90
|
+
1500.0
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Regras do filtro
|
|
94
|
+
|
|
95
|
+
- O formato é `coluna=valor`. A divisão acontece no primeiro `=`, então `obs=a=b` filtra a coluna `obs` pelo valor `a=b`.
|
|
96
|
+
- A comparação é exata e diferencia maiúsculas de minúsculas, após remover espaços nas pontas. `30` não bate com `30.0`.
|
|
97
|
+
- `coluna=` (valor vazio) seleciona as linhas em que a coluna está vazia.
|
|
98
|
+
- Valores não numéricos em linhas que não batem com o filtro são ignorados.
|
|
99
|
+
|
|
100
|
+
### Códigos de saída
|
|
101
|
+
|
|
102
|
+
| Código | Situação |
|
|
103
|
+
|--------|----------|
|
|
104
|
+
| `0` | Sucesso: a média é impressa na saída padrão. |
|
|
105
|
+
| `1` | Arquivo inexistente, coluna inexistente, valor não numérico ou nenhum valor numérico (mensagem `Erro: ...` na saída de erro). |
|
|
106
|
+
| `2` | Argumentos inválidos, como um filtro sem `=` (`use o formato coluna=valor`). |
|
|
107
|
+
|
|
108
|
+
### Uso como módulo
|
|
109
|
+
|
|
110
|
+
Com a pasta `src/` no `PYTHONPATH` (ou executando a partir dela):
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from analise import calcular_media, interpretar_filtro
|
|
114
|
+
|
|
115
|
+
calcular_media("dados.csv", "salario") # 2000.0
|
|
116
|
+
calcular_media("dados.csv", "salario", ("idade", "30")) # 1500.0
|
|
117
|
+
calcular_media("dados.csv", "salario", interpretar_filtro("idade=30"))
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Testes
|
|
121
|
+
|
|
122
|
+
Os testes usam apenas o `unittest` da biblioteca padrão. Na raiz do projeto:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
python -m unittest discover -s tests -v
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Estrutura
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
src/analise.py # script e funções
|
|
132
|
+
tests/test_analise.py # testes unitários
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Licença
|
|
136
|
+
|
|
137
|
+
Distribuído sob a licença MIT. Veja o arquivo [LICENSE](LICENSE).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
analise.py,sha256=Huof9wOXzfQEB-N7c-lSUpoiIbPwqq3ximBQZHXzEN8,3271
|
|
2
|
+
csv_mean_filter-0.1.0.dist-info/METADATA,sha256=Uk7pOPoZxJb6bWbh5kjqCPEQxnlJ_KHbLJ-ggIhlCMI,3600
|
|
3
|
+
csv_mean_filter-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
|
|
4
|
+
csv_mean_filter-0.1.0.dist-info/entry_points.txt,sha256=mVVBoJuoCaRX3PLd6G-sMPh-hApDrulU246cNaLpizo,42
|
|
5
|
+
csv_mean_filter-0.1.0.dist-info/licenses/LICENSE,sha256=CKKPTH-S0Hc0AldBPYfyAwkCmSSebN63KtzgWiep_7k,1076
|
|
6
|
+
csv_mean_filter-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jjuffagtotal-source
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|