Skip to content

Commit d7b645d

Browse files
authored
Utilitário que retorna Município e UF a partir do Código IBGE (#412)
1 parent 4af1cdc commit d7b645d

6 files changed

Lines changed: 217 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010

1111
- Utilitário `convert_code_to_uf` [#397](https://github.com/brazilian-utils/brutils-python/pull/410)
12+
- Utilitário `get_municipality_by_code` [412](https://github.com/brazilian-utils/brutils-python/pull/412)
1213

1314
## [2.2.0] - 2024-09-12
1415

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ False
8989
- [generate_voter_id](#generate_voter_id)
9090
- [IBGE](#ibge)
9191
- [convert_code_to_uf](#convert_code_to_uf)
92+
- [get\_municipality\_by\_code](#get_municipality_by_code)
9293

9394
## CPF
9495

@@ -1087,6 +1088,7 @@ Exemplo:
10871088
```
10881089

10891090
## IBGE
1091+
10901092
### convert_code_to_uf
10911093
Converte um determinado código do IBGE (string de 2 dígitos) para sua UF (abreviatura estadual) correspondente.
10921094

@@ -1109,6 +1111,24 @@ Exemplo:
11091111
>>>
11101112
```
11111113

1114+
### get_municipality_by_code
1115+
1116+
Retorna o nome do município e a UF para um código do IBGE.
1117+
1118+
Args:
1119+
* code (str): O código do IBGE para o município.
1120+
1121+
Returns:
1122+
* tuple: Retorna uma Tupla formatado como ("Município", "UF").
1123+
* None: Retorna None se o código for inválido.
1124+
1125+
Example:
1126+
1127+
```python
1128+
>>> from brutils import get_municipality_by_code
1129+
>>> get_municipality_by_code(3550308)
1130+
("São Paulo", "SP")
1131+
```
11121132

11131133
# Novos Utilitários e Reportar Bugs
11141134

README_EN.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ False
8989
- [generate_voter_id](#generate_voter_id)
9090
- [IBGE](#ibge)
9191
- [convert_code_to_uf](#convert_code_to_uf)
92+
- [get\_municipality\_by\_code](#get_municipality_by_code)
9293

9394
## CPF
9495

@@ -1088,8 +1089,8 @@ Example:
10881089
>>> generate_voter_id(federative_union ="MG")
10891090
'950125640248'
10901091
```
1091-
10921092
## IBGE
1093+
10931094
### convert_code_to_uf
10941095
Converts a given IBGE code (2-digit string) to its corresponding UF (state abbreviation).
10951096

@@ -1112,6 +1113,25 @@ Exemplo:
11121113
>>>
11131114
```
11141115

1116+
### get_municipality_by_code
1117+
1118+
Returns the municipality name and UF for a given IBGE code.
1119+
1120+
Args:
1121+
* code (str): The IBGE code of the municipality.
1122+
1123+
Returns:
1124+
* tuple: Returns a tuple formatted as ("Município", "UF").
1125+
* None: Returns None if the code is not valid.
1126+
1127+
Example:
1128+
1129+
```python
1130+
>>> from brutils import get_municipality_by_code
1131+
>>> get_municipality_by_code(3550308)
1132+
("São Paulo", "SP")
1133+
```
1134+
11151135
# Feature Request and Bug Report
11161136

11171137
If you want to suggest new features or report bugs, simply create

brutils/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444

4545
# Email Import
4646
from brutils.email import is_valid as is_valid_email
47+
from brutils.ibge.municipality import (
48+
get_municipality_by_code,
49+
)
4750

4851
# IBGE Imports
4952
from brutils.ibge.uf import (
@@ -172,4 +175,5 @@
172175
"is_valid_voter_id",
173176
# IBGE
174177
"convert_code_to_uf",
178+
"get_municipality_by_code",
175179
]

brutils/ibge/municipality.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import gzip
2+
import io
3+
import json
4+
from urllib.error import HTTPError
5+
from urllib.request import urlopen
6+
7+
8+
def get_municipality_by_code(code): # type: (str) -> Tuple[str, str] | None
9+
"""
10+
Returns the municipality name and UF for a given IBGE code.
11+
12+
This function takes a string representing an IBGE municipality code
13+
and returns a tuple with the municipality's name and its corresponding UF.
14+
15+
Args:
16+
code (str): The IBGE code of the municipality.
17+
18+
Returns:
19+
tuple: A tuple formatted as ("Município", "UF").
20+
- Returns None if the code is not valid.
21+
22+
Example:
23+
>>> get_municipality_by_code("3550308")
24+
("São Paulo", "SP")
25+
"""
26+
baseUrl = (
27+
f"https://servicodados.ibge.gov.br/api/v1/localidades/municipios/{code}"
28+
)
29+
try:
30+
with urlopen(baseUrl) as f:
31+
compressed_data = f.read()
32+
if f.info().get("Content-Encoding") == "gzip":
33+
try:
34+
with gzip.GzipFile(
35+
fileobj=io.BytesIO(compressed_data)
36+
) as gzip_file:
37+
decompressed_data = gzip_file.read()
38+
except OSError as e:
39+
print(f"Erro ao descomprimir os dados: {e}")
40+
return None
41+
except Exception as e:
42+
print(f"Erro desconhecido ao descomprimir os dados: {e}")
43+
return None
44+
else:
45+
decompressed_data = compressed_data
46+
47+
if _is_empty(decompressed_data):
48+
print(f"{code} é um código inválido")
49+
return None
50+
51+
except HTTPError as e:
52+
if e.code == 404:
53+
print(f"{code} é um código inválido")
54+
return None
55+
else:
56+
print(f"Erro HTTP ao buscar o código {code}: {e}")
57+
return None
58+
59+
except Exception as e:
60+
print(f"Erro desconhecido ao buscar o código {code}: {e}")
61+
return None
62+
63+
try:
64+
json_data = json.loads(decompressed_data)
65+
return _get_values(json_data)
66+
except json.JSONDecodeError as e:
67+
print(f"Erro ao decodificar os dados JSON: {e}")
68+
return None
69+
except KeyError as e:
70+
print(f"Erro ao acessar os dados do município: {e}")
71+
return None
72+
73+
74+
def _get_values(data):
75+
municipio = data["nome"]
76+
estado = data["microrregiao"]["mesorregiao"]["UF"]["sigla"]
77+
return (municipio, estado)
78+
79+
80+
def _is_empty(zip):
81+
return zip == b"[]" or len(zip) == 0

tests/ibge/test_municipality.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import gzip
2+
from json import JSONDecodeError
3+
from unittest import TestCase, main
4+
from unittest.mock import MagicMock, patch
5+
from urllib.error import HTTPError
6+
7+
from brutils.ibge.municipality import get_municipality_by_code
8+
9+
10+
class TestIBGE(TestCase):
11+
def test_get_municipality_by_code(self):
12+
self.assertEqual(
13+
get_municipality_by_code("3550308"), ("São Paulo", "SP")
14+
)
15+
self.assertEqual(
16+
get_municipality_by_code("3304557"), ("Rio de Janeiro", "RJ")
17+
)
18+
self.assertEqual(get_municipality_by_code("5208707"), ("Goiânia", "GO"))
19+
self.assertIsNone(get_municipality_by_code("1234567"))
20+
21+
@patch("brutils.ibge.municipality.urlopen")
22+
def test_get_municipality_http_error(self, mock):
23+
mock.side_effect = HTTPError(
24+
"http://fakeurl.com", 404, "Not Found", None, None
25+
)
26+
result = get_municipality_by_code("342432")
27+
self.assertIsNone(result)
28+
29+
@patch("brutils.ibge.municipality.urlopen")
30+
def test_get_municipality_http_error_1(self, mock):
31+
mock.side_effect = HTTPError(
32+
"http://fakeurl.com", 401, "Denied", None, None
33+
)
34+
result = get_municipality_by_code("342432")
35+
self.assertIsNone(result)
36+
37+
@patch("brutils.ibge.municipality.urlopen")
38+
def test_get_municipality_excpetion(self, mock):
39+
mock.side_effect = Exception("Erro desconhecido")
40+
result = get_municipality_by_code("342432")
41+
self.assertIsNone(result)
42+
43+
@patch("brutils.ibge.municipality.urlopen")
44+
def test_successfull_decompression(self, mock_urlopen):
45+
valid_json = '{"nome":"São Paulo","microrregiao":{"mesorregiao":{"UF":{"sigla":"SP"}}}}'
46+
compressed_data = gzip.compress(valid_json.encode("utf-8"))
47+
mock_response = MagicMock()
48+
mock_response.read.return_value = compressed_data
49+
mock_response.info.return_value.get.return_value = "gzip"
50+
mock_urlopen.return_value.__enter__.return_value = mock_response
51+
52+
result = get_municipality_by_code("3550308")
53+
self.assertEqual(result, ("São Paulo", "SP"))
54+
55+
@patch("brutils.ibge.municipality.urlopen")
56+
def test_successful_json_without_compression(self, mock_urlopen):
57+
valid_json = '{"nome":"São Paulo","microrregiao":{"mesorregiao":{"UF":{"sigla":"SP"}}}}'
58+
mock_response = MagicMock()
59+
mock_response.read.return_value = valid_json
60+
mock_urlopen.return_value.__enter__.return_value = mock_response
61+
62+
result = get_municipality_by_code("3550308")
63+
self.assertEqual(result, ("São Paulo", "SP"))
64+
65+
@patch("gzip.GzipFile.read", side_effect=OSError("Erro na descompressão"))
66+
def test_error_decompression(self, mock_gzip_read):
67+
result = get_municipality_by_code("3550308")
68+
self.assertIsNone(result)
69+
70+
@patch(
71+
"gzip.GzipFile.read",
72+
side_effect=Exception("Erro desconhecido na descompressão"),
73+
)
74+
def test_error_decompression_generic_exception(self, mock_gzip_read):
75+
result = get_municipality_by_code("3550308")
76+
self.assertIsNone(result)
77+
78+
@patch("json.loads", side_effect=JSONDecodeError("error", "city.json", 1))
79+
def test_error_json_load(self, mock_json_loads):
80+
result = get_municipality_by_code("3550308")
81+
self.assertIsNone(result)
82+
83+
@patch("json.loads", side_effect=KeyError)
84+
def test_error_json_key_error(self, mock_json_loads):
85+
result = get_municipality_by_code("3550308")
86+
self.assertIsNone(result)
87+
88+
89+
if __name__ == "__main__":
90+
main()

0 commit comments

Comments
 (0)