Skip to content

Commit cbe9519

Browse files
committed
Muitas alterações, documentação do módulo e mais testes.
1 parent 49fa6d3 commit cbe9519

File tree

4 files changed

+418
-57
lines changed

4 files changed

+418
-57
lines changed

postmon.py

Lines changed: 225 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,266 @@
11
# coding: utf-8
2+
"""
3+
O postmon-python é um wrapper da API do Postmon.
4+
5+
As chamadas devem ser feitas para as funções do módulo, que fazem as chamadas
6+
para o Postmon e retornam objetos com os resultados. Em caso de falha, todas as
7+
funções retornam `None`.
8+
"""
9+
from decimal import Decimal
10+
import logging
11+
212
import requests
313

4-
BASE_URL = 'http://api.postmon.com.br/v1'
14+
logger = logging.getLogger(__name__)
15+
16+
17+
class PostmonModel(object):
18+
"""Objeto base para os modelos do Postmon."""
19+
20+
base_url = 'http://api.postmon.com.br/v1'
21+
22+
def buscar(self):
23+
"""Faz a busca das informações do objeto no Postmon.
24+
25+
Retorna um ``bool`` indicando se a busca foi bem sucedida.
26+
"""
27+
try:
28+
self._response = requests.get(self.url)
29+
except requests.RequestException:
30+
logger.exception("%s.buscar() falhou: GET %s" %
31+
(self.__class__.__name__, self.url))
32+
return False
33+
34+
if self._response.ok:
35+
self.atualizar(**self._response.json())
36+
return self._response.ok
37+
38+
@property
39+
def url(self):
40+
"""Retorna a URL chamada pelo objeto.
41+
42+
>>> e = Endereco('11111111')
43+
>>> e.url
44+
'http://api.postmon.com.br/v1/cep/11111111'
45+
"""
46+
return self.base_url + (self.endpoint % self._params)
47+
48+
@property
49+
def status(self):
50+
"""Status da resposta recebida do Postmon.
551
52+
Os status previstos pelo Postmon são:
653
7-
class Cidade(object):
54+
* ``200 OK``
55+
* ``404 CEP NAO ENCONTRADO``
56+
* ``503 SERVICO INDISPONIVEL``
857
9-
def __init__(self, nome, area_km2=None, codigo_ibge=None):
58+
Além dos status listados, outros status HTTP podem ocorrer, como
59+
em qualquer chamada HTTP.
60+
61+
O único caso de sucesso é o ``200 OK``, caso em que o resultado no
62+
objeto é válido e pode ser utilizado.
63+
"""
64+
try:
65+
r = self._response
66+
except AttributeError:
67+
return None
68+
else:
69+
return r.status_code, r.reason
70+
71+
@property
72+
def _ok(self):
73+
"""Retorna ``True`` ou ``False``, indicando se a busca funcionou
74+
corretamente.
75+
76+
Retorna ``None`` caso o ``buscar`` ainda não tenha sido chamado.
77+
"""
78+
try:
79+
r = self._response
80+
except AttributeError:
81+
return None
82+
else:
83+
return r.ok
84+
85+
86+
class Cidade(PostmonModel):
87+
"""
88+
Objeto que representa uma cidade do Postmon.
89+
"""
90+
endpoint = '/cidade/%s/%s'
91+
92+
def __init__(self, uf, nome, area_km2=None, codigo_ibge=None, **kwargs):
93+
self.uf = uf
1094
self.nome = nome
95+
self._params = (uf, nome)
96+
self.atualizar(area_km2, codigo_ibge, **kwargs)
97+
98+
def atualizar(self, area_km2, codigo_ibge, **kwargs):
1199
self.area_km2 = area_km2
12100
self.codigo_ibge = codigo_ibge
13101

102+
@property
103+
def area_km2(self):
104+
return self._area_km2
105+
106+
@area_km2.setter
107+
def area_km2(self, value):
108+
self._area_km2 = _parse_area_km2(value)
109+
14110
def __repr__(self):
15-
return '<postmon.Cidade %r>' % self.nome
111+
return '<%s %r>' % (self.__class__.__name__, self.nome)
16112

17113
def __str__(self):
18-
return self.nome
114+
return '%s - %s' % (self.nome, self.uf)
19115

20116

21-
class Estado(object):
117+
class Estado(PostmonModel):
118+
"""
119+
Objeto que representa um estado do Postmon.
120+
"""
121+
endpoint = '/uf/%s'
22122

23-
def __init__(self, uf, nome=None, area_km2=None, codigo_ibge=None):
123+
def __init__(self, uf, nome=None, area_km2=None, codigo_ibge=None,
124+
**kwargs):
24125
self.uf = uf
126+
self._params = uf
127+
self.atualizar(nome, area_km2, codigo_ibge, **kwargs)
128+
129+
def atualizar(self, nome, area_km2, codigo_ibge, **kwargs):
25130
self.nome = nome
26131
self.area_km2 = area_km2
27132
self.codigo_ibge = codigo_ibge
28133

134+
@property
135+
def area_km2(self):
136+
return self._area_km2
137+
138+
@area_km2.setter
139+
def area_km2(self, value):
140+
self._area_km2 = _parse_area_km2(value)
141+
29142
def __repr__(self):
30-
return '<postmon.Estado %r>' % self.uf
143+
return '<%s %r>' % (self.__class__.__name__, self.uf)
31144

32145
def __str__(self):
33146
return self.uf
34147

35148

36-
class Endereco(object):
149+
class Endereco(PostmonModel):
150+
"""
151+
Objeto que representa um endereço do Postmon.
152+
153+
O ``Endereco`` pode ser criado apenas com o CEP para posteriormente
154+
ser buscado.
155+
156+
>>> import postmon
157+
>>> e = postmon.Endereco('30110-012')
158+
>>> if e.buscar():
159+
... print("Bairro: %s" % e.bairro)
160+
... else:
161+
... print("Busca falhou: %s" % e.status)
162+
Bairro: Floresta
163+
"""
164+
endpoint = '/cep/%s'
165+
166+
def __init__(self, cep, logradouro=None, bairro=None, cidade=None,
167+
estado=None, cidade_info=None, estado_info=None, **kwargs):
168+
self.cep = cep
169+
self._params = cep
170+
self.atualizar(logradouro=logradouro, bairro=bairro, cidade=cidade,
171+
estado=estado, cidade_info=cidade_info,
172+
estado_info=estado_info, **kwargs)
37173

38-
def __init__(self, **kwargs):
39-
self.cep = kwargs['cep']
40-
self.logradouro = kwargs.get('logradouro')
41-
self.bairro = kwargs.get('bairro')
174+
def atualizar(self, logradouro=None, bairro=None, cidade=None,
175+
estado=None, cidade_info=None, estado_info=None, **kwargs):
176+
self.logradouro = logradouro
177+
self.bairro = bairro
42178

43-
estado_info = kwargs.get('estado_info', {})
44-
self.estado = Estado(kwargs['estado'],
45-
estado_info.get('nome'),
46-
estado_info.get('area_km2'),
47-
estado_info.get('codigo_ibge'))
179+
if estado:
180+
if not estado_info:
181+
estado_info = {}
182+
self.estado = Estado(estado,
183+
estado_info.get('nome'),
184+
estado_info.get('area_km2'),
185+
estado_info.get('codigo_ibge'))
48186

49-
cidade_info = kwargs.get('cidade_info', {})
50-
self.cidade = Cidade(kwargs['cidade'],
51-
cidade_info.get('area_km2'),
52-
cidade_info.get('codigo_ibge'))
187+
if cidade:
188+
if not cidade_info:
189+
cidade_info = {}
190+
self.cidade = Cidade(estado, cidade,
191+
cidade_info.get('area_km2'),
192+
cidade_info.get('codigo_ibge'))
53193

54194
def __repr__(self):
55-
return '<postmon.Endereco %r>' % self.cep
195+
return '<%s %r>' % (self.__class__.__name__, self.cep)
56196

57197
def __str__(self):
58198
return '%s, %s - %s, %s - CEP: %s' % (self.logradouro, self.bairro,
59199
self.cidade, self.estado,
60200
self.cep)
61201

62202

63-
def buscar_cep(cep):
64-
response = _GET('/cep/%s' % cep)
65-
response.raise_for_status()
66-
return Endereco(**response.json())
203+
def cidade(uf, nome):
204+
"""Busca a cidade no Postmon e retorna um objeto ``Cidade``.
205+
206+
Retorna ``None`` caso a cidade não exista ou caso ocorra algum erro de
207+
comunicação.
208+
209+
>>> import postmon
210+
>>> postmon.cidade('MG', 'Belo Horizonte')
211+
<Cidade 'Belo Horizonte'>
212+
"""
213+
return _make_object(Cidade, uf, nome)
214+
215+
216+
def estado(uf):
217+
"""Busca o estado no Postmon e retorna um objeto ``Estado``.
218+
219+
Retorna ``None`` caso o estado não exista ou caso ocorra algum erro de
220+
comunicação.
221+
222+
>>> import postmon
223+
>>> postmon.estado('MG')
224+
<Estado 'MG'>
225+
"""
226+
return _make_object(Estado, uf)
227+
228+
229+
def endereco(cep):
230+
"""Busca o CEP no Postmon e retorna um objeto ``Endereco``.
231+
232+
Retorna ``None`` caso o CEP não exista ou caso ocorra algum erro de
233+
comunicação.
234+
235+
>>> import postmon
236+
>>> postmon.endereco('01001-000')
237+
<Endereco '01001-000'>
238+
"""
239+
return _make_object(Endereco, cep)
240+
241+
242+
def _make_object(cls, *args):
243+
obj = cls(*args)
244+
return obj if obj.buscar() else None
245+
246+
247+
def _parse_area_km2(valor):
248+
"""O campo ``area_km2`` é uma string com um número em formato pt-br, com
249+
casas decimais que representam m2.
250+
251+
Exemplos: "331,401", "248.222,801"
252+
"""
253+
if valor is None:
254+
return None
255+
elif isinstance(valor, Decimal):
256+
return valor
257+
try:
258+
int_, dec = valor.split(',', 1)
259+
except ValueError:
260+
# valor não tem separador decimal
261+
int_, dec = valor, '000'
67262

263+
# remove os separadores de milhar
264+
int_ = int_.replace('.', '')
68265

69-
def _GET(endpoint):
70-
url = '%s%s' % (BASE_URL, endpoint)
71-
response = requests.get(url)
72-
return response
266+
return Decimal('%s.%s' % (int_, dec))

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
setup(
1010
name='postmon',
11-
version='0.1.1',
11+
version='0.2',
1212
description='Postmon service wrapper',
1313
url='http://github.com/PostmonAPI/postmon-python',
1414

0 commit comments

Comments
 (0)