Skip to content

Commit 1e1c6b7

Browse files
committed
Se añadió Makefile y la carpeta ejemplos. Se añadió más contenido al README.rst. Se corrigieron la base de la API, boletas.py, y tests. Se eliminó la carpeta docs. Se debe volver a generar automáticamente con Sphinx. Se corrigieron múltiples errores de texto.
1 parent d09443c commit 1e1c6b7

21 files changed

Lines changed: 509 additions & 310 deletions

Makefile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
all: dist
2+
3+
dist:
4+
python3 setup.py sdist
5+
6+
upload: dist
7+
twine upload dist/*
8+
9+
install-dev:
10+
python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt
11+
12+
tests: install-dev
13+
python tests/run.py
14+
15+
docs:
16+
sphinx-apidoc -o docs bhexpress && sphinx-build -b html docs docs/_build/html
17+
18+
clean:
19+
rm -rf dist bhexpress.egg-info bhexpress/__pycache__ bhexpress/*.pyc

README.rst

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,39 +45,78 @@ el cual será reconocida automáticamente por el cliente:
4545
4646
client = Boleta()
4747
48-
boletas = self.client.listar()
48+
boletas = client.listar()
4949
print(boletas)
5050
5151
Lo que hizo el ejemplo anterior es listar boletas emitidas en un resultado e imprimir dicho resultado en consola.
5252

5353
Ejemplos
5454
--------
5555

56-
Estos ejemplos provienen de la versión PHP. Para crear la versión Python de BHExpress,
57-
se tomó en cuenta dichos ejemplos.
56+
Para crear la versión Python de BHExpress, se tomaron en cuenta ejemplos para 5 casos de uso que
57+
involucran boletas, y se llevaron a Python.
5858
Los ejemplos cubren los siguientes casos:
5959

60-
- `001-boletas_listado.php`: obtener las boletas de un período.
61-
- `002-boleta_emitir.php`: emisitir una BHE.
62-
- `003-boleta_pdf.php`: descargar el PDF de una BHE.
63-
- `004-boleta_email.php`: enviar por email una BHE.
64-
- `005-boleta_anular.php`: anular una BHE.
60+
- `e01-boletas_listado.py`: obtener las boletas de un período.
61+
- `e02-boleta_emitir.py`: emitir una BHE.
62+
- `e03-boleta_pdf.py`: descargar el PDF de una BHE.
63+
- `e04-boleta_email.py`: enviar por email una BHE.
64+
- `e05-boleta_anular.py`: anular una BHE.
6565

6666
Los ejemplos, por defecto, hacen uso de variables de entornos, si quieres usar
6767
esto debes tenerlas creadas, por ejemplo, en Windows 10, con:
6868

6969
.. code:: shell
70+
7071
set BHEXPRESS_API_URL="https://bhexpress.cl"
7172
set BHEXPRESS_API_TOKEN="" # aquí el token obtenido en https://bhexpress.cl/usuarios/perfil#token
7273
set BHEXPRESS_EMISOR_RUT="" # aquí el RUT del emisor de las BHE
7374
74-
Ejemplo en la consola de Linux:
75+
Ejemplo de definición de variables de entorno en la consola de Linux:
7576

7677
.. code:: shell
78+
7779
export BHEXPRESS_API_URL="https://bhexpress.cl"
7880
export BHEXPRESS_API_TOKEN="" # aquí el token obtenido en https://bhexpress.cl/usuarios/perfil#token
7981
export BHEXPRESS_EMISOR_RUT="" # aquí el RUT del emisor de las BHE
8082
83+
Pruebas
84+
-------
85+
86+
Las pruebas utilizan un archivo llamado `test.env`, que sirve para definir todas las variables de entorno
87+
necesarias para ejecutar estas pruebas. Las pruebas se crearon para probar los ejemplos vistos previamente
88+
en el capítulo `Ejemplos`.
89+
90+
Estas pruebas utilizan `unittest`, se ejecutan con el archivo `run.py`, y dependiendo de cómo se configure
91+
`test.env`, se pueden omitir ciertas pruebas. Asegúrate de definir `BHEXPRESS_API_URL`, `BHEXPRESS_API_TOKEN`
92+
y `BHEXPRESS_EMISOR_RUT` en `test.env`, o no podrás efectuar las pruebas.
93+
94+
A continuación se pondrán instrucciones de cómo probar el cliente de API de Python:
95+
96+
* `test1_listar()`:
97+
- Prueba que permite obtener un listado de todas las boletas emitidas a través de BHExpress usando algunos filtros.
98+
- Variables necesarias: `TEST_LISTAR_PERIODO`, `TEST_LISTAR_CODIGORECEPTOR`
99+
- Variable de ejecución: `Ninguna`
100+
* `test2_emitir()`:
101+
- Prueba que permite emitir una BHE a un receptor.
102+
- Variables necesarias: `TEST_EMITIR_FECHA_EMIS`, `TEST_EMITIR_EMISOR`, `TEST_EMITIR_RECEPTOR`, `TEST_EMITIR_RZNSOC_REC`, `TEST_EMITIR_DIR_REC`, `TEST_EMITIR_COM_REC`
103+
- Variable de ejecución: `TEST_EMITIR_EMISOR`
104+
* `test3_pdf()`:
105+
- Prueba que permite obtener una BHE y convertirla a un PDF.
106+
- Variables necesarias: `Ninguna`
107+
- Variable de ejecución: `TEST_PDF_PROBAR`
108+
* `test4_email()`:
109+
- Prueba que permite enviar un email a un destinatario con una BHE específica.
110+
- Variables necesarias: `TEST_EMAIL_NUMEROBHE`, `TEST_EMAIL_CORREO`
111+
- Variable de ejecución: `TEST_EMAIL_NUMEROBHE` y `TEST_EMAIL_CORREO`
112+
* `test5_anular()`:
113+
- Prueba que permite anular una BHE existente.
114+
- Variables necesarias: `Ninguna`
115+
- Variables de ejecución: `TEST_ANULAR_PROBAR`
116+
117+
Las `variables necesarias` son aquellas variables que se necesitan para ejecutar las pruebas.
118+
Las `variables de ejecución` son aquellas variables que permitirán ejecutar u omitir las pruebas a las que pertenecen.
119+
Si las variables de ejecución tienen un valor específico o son texto en blanco, entonces la prueba será omitida, pero no fallida.
81120

82121
Licencia
83122
--------

bhexpress/api_client/__init__.py

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,13 @@ class ApiClient:
3636
__DEFAULT_URL = 'https://bhexpress.cl'
3737
__DEFAULT_VERSION = 'v1'
3838

39-
def __init__(self, token=None, url=None, version=None, raise_for_status=True):
39+
def __init__(self, token = None, url = None, version = None, raise_for_status = True):
40+
'''
41+
Constructor para inicializar el Cliente de la API de BHExpress.
42+
'''
4043
self.token = self.__validate_token(token)
4144
self.url = self.__validate_url(url)
45+
self.rut = self.__validate_rut()
4246
self.headers = self.__generate_headers()
4347
self.version = version or self.__DEFAULT_VERSION
4448
self.raise_for_status = raise_for_status
@@ -68,6 +72,20 @@ def __validate_url(self, url):
6872
'''
6973
return str(url).strip() if url else getenv('BHEXPRESS_API_URL', self.__DEFAULT_URL).strip()
7074

75+
def __validate_rut(self):
76+
'''
77+
Valida y retorna el RUT del Emisor de BHEs a utilizar.
78+
79+
:param str rut: RUT a validar.
80+
:return: RUT validado.
81+
:rtype: str
82+
:raises ApiException: Si el RUT no es válido o está ausente.
83+
'''
84+
rut = getenv('BHEXPRESS_EMISOR_RUT', '')
85+
if rut == '':
86+
raise ApiException('Se debe configurar la variable de entorno: BHEXPRESS_EMISOR_RUT.')
87+
return str(rut).strip()
88+
7189
def __generate_headers(self):
7290
'''
7391
Genera y retorna las cabeceras por defecto para las solicitudes.
@@ -78,11 +96,12 @@ def __generate_headers(self):
7896
return {
7997
'User-Agent': 'BHExpress: Cliente de API en Python.',
8098
'Accept': 'application/json',
81-
'Content-Type': 'application/json', # Faltaba ESTA línea de código...
82-
'Authorization': 'Token ' + self.token
99+
'Content-Type': 'application/json',
100+
'Authorization': 'Token %(token)s' % {'token': self.token},
101+
'X-Bhexpress-Emisor': self.rut
83102
}
84103

85-
def get(self, resource, headers=None):
104+
def get(self, resource, headers = None):
86105
'''
87106
Realiza una solicitud GET a la API.
88107
@@ -91,9 +110,9 @@ def get(self, resource, headers=None):
91110
:return: Respuesta de la solicitud.
92111
:rtype: requests.Response
93112
'''
94-
return self.__request('GET', resource, headers=headers)
113+
return self.__request('GET', resource, headers = headers)
95114

96-
def delete(self, resource, headers=None):
115+
def delete(self, resource, headers = None):
97116
'''
98117
Realiza una solicitud DELETE a la API.
99118
@@ -102,9 +121,9 @@ def delete(self, resource, headers=None):
102121
:return: Respuesta de la solicitud.
103122
:rtype: requests.Response
104123
'''
105-
return self.__request('DELETE', resource, headers=headers)
124+
return self.__request('DELETE', resource, headers = headers)
106125

107-
def post(self, resource, data=None, headers=None):
126+
def post(self, resource, data = None, headers = None):
108127
'''
109128
Realiza una solicitud POST a la API.
110129
@@ -114,9 +133,9 @@ def post(self, resource, data=None, headers=None):
114133
:return: Respuesta de la solicitud.
115134
:rtype: requests.Response
116135
'''
117-
return self.__request('POST', resource, data, headers)
136+
return self.__request('POST', resource, data = data, headers = headers)
118137

119-
def put(self, resource, data=None, headers=None):
138+
def put(self, resource, data = None, headers = None):
120139
'''
121140
Realiza una solicitud PUT a la API.
122141
@@ -126,9 +145,9 @@ def put(self, resource, data=None, headers=None):
126145
:return: Respuesta de la solicitud.
127146
:rtype: requests.Response
128147
'''
129-
return self.__request('PUT', resource, data, headers)
148+
return self.__request('PUT', resource, data = data, headers = headers)
130149

131-
def __request(self, method, resource, data=None, headers=None):
150+
def __request(self, method, resource, data = None, headers = None):
132151
'''
133152
Método privado para realizar solicitudes HTTP.
134153
@@ -140,21 +159,21 @@ def __request(self, method, resource, data=None, headers=None):
140159
:rtype: requests.Response
141160
:raises ApiException: Si el método HTTP no es soportado o si hay un error de conexión.
142161
'''
143-
api_path = f'/api/{self.version}{resource}'
162+
api_path = '/api/%(version)s%(resource)s' % {'version': self.version, 'resource': resource}
144163
full_url = urllib.parse.urljoin(self.url + '/', api_path.lstrip('/'))
145164
headers = headers or {}
146165
headers = {**self.headers, **headers}
147166
if data and not isinstance(data, str):
148167
data = json.dumps(data)
149168
try:
150-
response = requests.request(method, full_url, data=data, headers=headers)
169+
response = requests.request(method, full_url, data = data, headers = headers)
151170
return self.__check_and_return_response(response)
152171
except requests.exceptions.ConnectionError as error:
153-
raise ApiException(f'Error de conexión: {error}')
172+
raise ApiException('Error de conexión: %(error)s' % {'error': error})
154173
except requests.exceptions.Timeout as error:
155-
raise ApiException(f'Error de timeout: {error}')
174+
raise ApiException('Error de timeout: %(error)s' % {'error': error})
156175
except requests.exceptions.RequestException as error:
157-
raise ApiException(f'Error en la solicitud: {error}')
176+
raise ApiException('Error en la solicitud: %(error)s' % {'error': error})
158177

159178
def __check_and_return_response(self, response):
160179
'''
@@ -173,20 +192,23 @@ def __check_and_return_response(self, response):
173192
error = response.json()
174193
message = error.get('message', '') or error.get('exception', '') or 'Error desconocido.'
175194
except json.decoder.JSONDecodeError:
176-
message = f'Error al decodificar los datos en JSON: {response.text}'
177-
raise ApiException(f'Error HTTP: {message}')
195+
message = 'Error al decodificar los datos en JSON: %(response)s' % {'response': response.text}
196+
raise ApiException('Error HTTP: %(message)s' % {'message': message})
178197
return response
179198

180199
class ApiException(Exception):
181200
'''
182201
Excepción personalizada para errores en el cliente de la API.
183-
184-
:param str message: Mensaje de error.
185-
:param int code: Código de error (opcional).
186-
:param dict params: Parámetros adicionales del error (opcional).
187202
'''
188203

189-
def __init__(self, message, code=None, params=None):
204+
def __init__(self, message, code = None, params = None):
205+
'''
206+
Constructor para la creación de manejo de errores.
207+
208+
:param str message: Mensaje de error.
209+
:param int code: Código de error (opcional).
210+
:param dict params: Parámetros adicionales del error (opcional).
211+
'''
190212
self.message = message
191213
self.code = code
192214
self.params = params
@@ -207,9 +229,9 @@ def __str__(self):
207229
:return: Una cadena que representa el error de una manera clara y concisa.
208230
'''
209231
if self.code is not None:
210-
return f'[BHExpress] Error {self.code}: {self.message}'
232+
return '[BHExpress] Error %(code)s: %(message)s' % {'code': self.code, 'message': self.message}
211233
else:
212-
return f'[BHExpress] {self.message}'
234+
return '[BHExpress] %(message)s' % {'message': self.message}
213235

214236
class ApiBase(ABC):
215237
'''
@@ -221,6 +243,6 @@ class ApiBase(ABC):
221243
:param bool api_raise_for_status: Si se debe lanzar una excepción automáticamente para respuestas de error HTTP. Por defecto es True.
222244
'''
223245

224-
def __init__(self, api_token=None, api_url=None, api_version=None, api_raise_for_status=True):
246+
def __init__(self, api_token = None, api_url = None, api_version = None, api_raise_for_status = True):
225247
self.client = ApiClient(api_token, api_url, api_version, api_raise_for_status)
226248

0 commit comments

Comments
 (0)