Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

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

## [2.2.0] - 2024-09-12

Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ False
- [generate_voter_id](#generate_voter_id)
- [IBGE](#ibge)
- [convert_code_to_uf](#convert_code_to_uf)
- [get\_municipality\_by\_code](#get_municipality_by_code)

## CPF

Expand Down Expand Up @@ -1087,6 +1088,7 @@ Exemplo:
```

## IBGE

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

Expand All @@ -1109,6 +1111,24 @@ Exemplo:
>>>
```

### get_municipality_by_code

Retorna o nome do município e a UF para um código do IBGE.

Args:
* code (str): O código do IBGE para o município.

Returns:
* tuple: Retorna uma Tupla formatado como ("Município", "UF").
* None: Retorna None se o código for inválido.

Example:

```python
>>> from brutils import get_municipality_by_code
>>> get_municipality_by_code(3550308)
("São Paulo", "SP")
```

# Novos Utilitários e Reportar Bugs

Expand Down
22 changes: 21 additions & 1 deletion README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ False
- [generate_voter_id](#generate_voter_id)
- [IBGE](#ibge)
- [convert_code_to_uf](#convert_code_to_uf)
- [get\_municipality\_by\_code](#get_municipality_by_code)

## CPF

Expand Down Expand Up @@ -1088,8 +1089,8 @@ Example:
>>> generate_voter_id(federative_union ="MG")
'950125640248'
```

## IBGE

### convert_code_to_uf
Converts a given IBGE code (2-digit string) to its corresponding UF (state abbreviation).

Expand All @@ -1112,6 +1113,25 @@ Exemplo:
>>>
```

### get_municipality_by_code

Returns the municipality name and UF for a given IBGE code.

Args:
* code (str): The IBGE code of the municipality.

Returns:
* tuple: Returns a tuple formatted as ("Município", "UF").
* None: Returns None if the code is not valid.

Example:

```python
>>> from brutils import get_municipality_by_code
>>> get_municipality_by_code(3550308)
("São Paulo", "SP")
```

# Feature Request and Bug Report

If you want to suggest new features or report bugs, simply create
Expand Down
4 changes: 4 additions & 0 deletions brutils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@

# Email Import
from brutils.email import is_valid as is_valid_email
from brutils.ibge.municipality import (
get_municipality_by_code,
)

# IBGE Imports
from brutils.ibge.uf import (
Expand Down Expand Up @@ -172,4 +175,5 @@
"is_valid_voter_id",
# IBGE
"convert_code_to_uf",
"get_municipality_by_code",
]
81 changes: 81 additions & 0 deletions brutils/ibge/municipality.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import gzip
import io
import json
from urllib.error import HTTPError
from urllib.request import urlopen


def get_municipality_by_code(code): # type: (str) -> Tuple[str, str] | None
"""
Returns the municipality name and UF for a given IBGE code.

This function takes a string representing an IBGE municipality code
and returns a tuple with the municipality's name and its corresponding UF.

Args:
code (str): The IBGE code of the municipality.

Returns:
tuple: A tuple formatted as ("Município", "UF").
- Returns None if the code is not valid.

Example:
>>> get_municipality_by_code("3550308")
("São Paulo", "SP")
"""
baseUrl = (
f"https://servicodados.ibge.gov.br/api/v1/localidades/municipios/{code}"
)
try:
with urlopen(baseUrl) as f:
compressed_data = f.read()
if f.info().get("Content-Encoding") == "gzip":
try:
with gzip.GzipFile(
fileobj=io.BytesIO(compressed_data)
) as gzip_file:
decompressed_data = gzip_file.read()
except OSError as e:
print(f"Erro ao descomprimir os dados: {e}")
return None
except Exception as e:
print(f"Erro desconhecido ao descomprimir os dados: {e}")
return None
else:
decompressed_data = compressed_data

if _is_empty(decompressed_data):
print(f"{code} é um código inválido")
return None

except HTTPError as e:
if e.code == 404:
print(f"{code} é um código inválido")
return None
else:
print(f"Erro HTTP ao buscar o código {code}: {e}")
return None

except Exception as e:
print(f"Erro desconhecido ao buscar o código {code}: {e}")
return None

try:
json_data = json.loads(decompressed_data)
return _get_values(json_data)
except json.JSONDecodeError as e:
print(f"Erro ao decodificar os dados JSON: {e}")
return None
except KeyError as e:
print(f"Erro ao acessar os dados do município: {e}")
return None


def _get_values(data):
municipio = data["nome"]
estado = data["microrregiao"]["mesorregiao"]["UF"]["sigla"]
return (municipio, estado)


def _is_empty(zip):
return zip == b"[]" or len(zip) == 0
90 changes: 90 additions & 0 deletions tests/ibge/test_municipality.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import gzip
from json import JSONDecodeError
from unittest import TestCase, main
from unittest.mock import MagicMock, patch
from urllib.error import HTTPError

from brutils.ibge.municipality import get_municipality_by_code


class TestIBGE(TestCase):
def test_get_municipality_by_code(self):
self.assertEqual(
get_municipality_by_code("3550308"), ("São Paulo", "SP")
)
self.assertEqual(
get_municipality_by_code("3304557"), ("Rio de Janeiro", "RJ")
)
self.assertEqual(get_municipality_by_code("5208707"), ("Goiânia", "GO"))
self.assertIsNone(get_municipality_by_code("1234567"))

@patch("brutils.ibge.municipality.urlopen")
def test_get_municipality_http_error(self, mock):
mock.side_effect = HTTPError(
"http://fakeurl.com", 404, "Not Found", None, None
)
result = get_municipality_by_code("342432")
self.assertIsNone(result)

@patch("brutils.ibge.municipality.urlopen")
def test_get_municipality_http_error_1(self, mock):
mock.side_effect = HTTPError(
"http://fakeurl.com", 401, "Denied", None, None
)
result = get_municipality_by_code("342432")
self.assertIsNone(result)

@patch("brutils.ibge.municipality.urlopen")
def test_get_municipality_excpetion(self, mock):
mock.side_effect = Exception("Erro desconhecido")
result = get_municipality_by_code("342432")
self.assertIsNone(result)

@patch("brutils.ibge.municipality.urlopen")
def test_successfull_decompression(self, mock_urlopen):
valid_json = '{"nome":"São Paulo","microrregiao":{"mesorregiao":{"UF":{"sigla":"SP"}}}}'
compressed_data = gzip.compress(valid_json.encode("utf-8"))
mock_response = MagicMock()
mock_response.read.return_value = compressed_data
mock_response.info.return_value.get.return_value = "gzip"
mock_urlopen.return_value.__enter__.return_value = mock_response

result = get_municipality_by_code("3550308")
self.assertEqual(result, ("São Paulo", "SP"))

@patch("brutils.ibge.municipality.urlopen")
def test_successful_json_without_compression(self, mock_urlopen):
valid_json = '{"nome":"São Paulo","microrregiao":{"mesorregiao":{"UF":{"sigla":"SP"}}}}'
mock_response = MagicMock()
mock_response.read.return_value = valid_json
mock_urlopen.return_value.__enter__.return_value = mock_response

result = get_municipality_by_code("3550308")
self.assertEqual(result, ("São Paulo", "SP"))

@patch("gzip.GzipFile.read", side_effect=OSError("Erro na descompressão"))
def test_error_decompression(self, mock_gzip_read):
result = get_municipality_by_code("3550308")
self.assertIsNone(result)

@patch(
"gzip.GzipFile.read",
side_effect=Exception("Erro desconhecido na descompressão"),
)
def test_error_decompression_generic_exception(self, mock_gzip_read):
result = get_municipality_by_code("3550308")
self.assertIsNone(result)

@patch("json.loads", side_effect=JSONDecodeError("error", "city.json", 1))
def test_error_json_load(self, mock_json_loads):
result = get_municipality_by_code("3550308")
self.assertIsNone(result)

@patch("json.loads", side_effect=KeyError)
def test_error_json_key_error(self, mock_json_loads):
result = get_municipality_by_code("3550308")
self.assertIsNone(result)


if __name__ == "__main__":
main()