WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
31b5cc9
Update dependencies and Python version support
Jan 30, 2025
6ceb003
Handle incomplete CEP responses and improve error handling
Jan 30, 2025
cb08a9f
Update GitHub Actions workflows to latest versions
Jan 30, 2025
8396bbf
Update PyPI badge URL to point to cepmex project
Jan 30, 2025
5d42bbc
Fix PyPI badge URL to point to correct project
Jan 30, 2025
f52e44e
Bump version to 1.0.0.dev1
Feb 7, 2025
cbf9c60
Release version 1.0.0
Feb 7, 2025
a21e39d
Update Python version to 3.13 in Makefile
Feb 7, 2025
f417ad5
Refactor CEP client and transfer validation logic
Feb 11, 2025
dc8e973
Add Config class and update base URL handling
Feb 13, 2025
dbd3ae0
Add NotFoundError
Feb 13, 2025
bd2f210
Update Transferencia class to remove unnecessary validation checks
Feb 13, 2025
a6a6cca
Bump version to 1.0.0.dev2
Feb 13, 2025
430cb6b
Improve transfer validation error handling
Feb 13, 2025
bfa17d3
Update README
Feb 13, 2025
ce92abc
Update README
Feb 13, 2025
d69d13c
Bump version to 1.0.0
Feb 13, 2025
4bb1e4b
Update README formatting
Feb 13, 2025
d60dbc4
Add CepNotAvailableError
Feb 14, 2025
9cb1a46
Rename NotFoundError to TransferNotFoundError
Feb 14, 2025
cd419d8
Simplify fecha_abono parsing
Feb 14, 2025
438799c
Optimize response decoding in transfer validation
Feb 14, 2025
08ad08e
Refactor configuration and base URL handling
Feb 14, 2025
9a67dd5
Update README
Feb 14, 2025
7be5f69
Update README example to use TransferNotFoundError
Feb 14, 2025
dc2f057
Change transfer amount handling to use cents as integer
Feb 18, 2025
9c7541d
Bumb version to 1.0.0.dev3
Feb 18, 2025
d62e4c7
Bumb version to 1.0.0
Feb 18, 2025
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
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ jobs:
publish-pypi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Set up Python 3.8
uses: actions/setup-python@v4.1.0
- uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: 3.13
- name: Install dependencies
run: pip install -qU setuptools wheel twine
- name: Generating distribution archives
run: python setup.py sdist bdist_wheel
- name: Publish distribution 📦 to PyPI
if: startsWith(github.event.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@master
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.pypi_password }}
21 changes: 11 additions & 10 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v4.1.0
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: 3.13
- name: Install dependencies
run: make install-test
- name: Lint
Expand All @@ -20,11 +20,11 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.7, 3.8]
python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4.1.0
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand All @@ -35,18 +35,19 @@ jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v4.1.0
uses: actions/setup-python@v5
with:
python-version: 3.8
python-version: 3.13
- name: Install dependencies
run: make install-test
- name: Generate coverage report
run: pytest --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2.1.0
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
SHELL := bash
PATH := ./venv/bin:${PATH}
PYTHON=python3.7
PYTHON=python3.11
PROJECT=cep
isort = isort $(PROJECT) tests setup.py
black = black -S -l 79 --target-version py37 $(PROJECT) tests setup.py
black = black -S -l 79 --target-version py311 $(PROJECT) tests setup.py

.PHONY: all
all: testt
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![test](https://github.com/cuenca-mx/cep-python/workflows/test/badge.svg)](https://github.com/cuenca-mx/cep-python/actions?query=workflow%3Atest)
[![codecov](https://codecov.io/gh/cuenca-mx/cep-python/branch/master/graph/badge.svg)](https://codecov.io/gh/cuenca-mx/cep-python)
[![PyPI](https://img.shields.io/pypi/v/cuenca.svg)](https://pypi.org/project/cuenca/)
[![PyPI](https://img.shields.io/pypi/v/cepmex.svg)](https://pypi.org/project/cepmex/)

Python client library for CEP (http://www.banxico.org.mx/cep/)

Expand Down
11 changes: 6 additions & 5 deletions cep/cuenta.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
from dataclasses import dataclass
from typing import Optional

from lxml import etree


@dataclass
class Cuenta:
nombre: str
tipo: str
banco: str
numero: str
rfc: str
nombre: Optional[str] = None
tipo: Optional[str] = None
banco: Optional[str] = None
numero: Optional[str] = None
rfc: Optional[str] = None

@classmethod
def from_etree(cls, element: etree._Element):
Expand Down
7 changes: 7 additions & 0 deletions cep/exc.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ class MaxRequestError(CepError):
Máximo número de peticiones alcanzadas para
obtener el CEP de una transferencia
"""


class IncompleteResponseError(CepError):
"""
Respuesta incompleta del sitio web
https://www.banxico.org.mx/cep/
"""
30 changes: 21 additions & 9 deletions cep/transferencia.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from .client import Client
from .cuenta import Cuenta
from .exc import CepError, MaxRequestError
from .exc import CepError, IncompleteResponseError, MaxRequestError

MAX_REQUEST_ERROR_MESSAGE = (
b'Lo sentimos, pero ha excedido el número máximo '
Expand Down Expand Up @@ -54,12 +54,19 @@ def validar(

resp = etree.fromstring(xml)

ordenante = Cuenta.from_etree(resp.find('Ordenante'))
beneficiario = Cuenta.from_etree(resp.find('Beneficiario'))
concepto = resp.find('Beneficiario').get('Concepto')
fecha_operacion = datetime.datetime.fromisoformat(
str(fecha) + ' ' + resp.get('Hora')
)
ordenante_element = resp.find('Ordenante')
beneficiario_element = resp.find('Beneficiario')

if ordenante_element is None or beneficiario_element is None:
raise IncompleteResponseError

ordenante = Cuenta.from_etree(ordenante_element)
beneficiario = Cuenta.from_etree(beneficiario_element)

concepto = beneficiario_element.get('Concepto') or ''
hora = resp.get('Hora') or '00:00:00'
fecha_operacion = datetime.datetime.fromisoformat(f'{fecha} {hora}')

transferencia = cls(
fecha_operacion=fecha_operacion,
ordenante=ordenante,
Expand All @@ -69,7 +76,7 @@ def validar(
clave_rastreo=clave_rastreo,
emisor=emisor,
receptor=receptor,
sello=resp.get('sello'),
sello=resp.get('sello') or '',
)
setattr(transferencia, '__client', client)
return transferencia
Expand All @@ -78,14 +85,19 @@ def descargar(self, formato: str = 'PDF') -> bytes:
"""formato puede ser PDF, XML o ZIP"""
client = getattr(self, '__client', None)
if not client:
numero_cuenta = ''
if self.beneficiario is not None:
numero_cuenta = self.beneficiario.numero or ''
client = self._validar(
self.fecha_operacion.date(),
self.clave_rastreo,
self.emisor,
self.receptor,
self.beneficiario.numero,
numero_cuenta,
self.monto,
)
if not client:
raise CepError
return self._descargar(client, formato)

def to_dict(self) -> dict:
Expand Down
16 changes: 9 additions & 7 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
pytest==6.2.5
pytest-vcr==1.0.2
pytest-cov==3.0.0
black==22.3.0
flake8==4.0.1
isort[pipfile]==5.10.1
mypy==0.790
pytest==8.*
pytest-vcr==1.*
pytest-cov==6.*
black==24.*
flake8==7.*
isort==5.*
mypy==1.*
types-lxml==2024.*
types-requests==2.*
7 changes: 3 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
requests==2.31.0
clabe==1.2.4
lxml==4.9.1
dataclasses>=0.6;python_version<"3.7"
requests==2.32.3
clabe==2.0.0
lxml==5.3.0
14 changes: 8 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,17 @@
packages=find_packages(),
include_package_data=True,
package_data=dict(cep=['py.typed']),
python_requires='>=3.7',
python_requires='>=3.10',
install_requires=[
'requests>=2.25,<2.32',
'clabe>=1.2.4,<1.3',
'lxml>=4.6.2,<4.10',
'dataclasses>=0.6;python_version<"3.7"',
'requests>=2.32.0,<3.0.0',
'clabe>=2.0.0,<3.0.0',
'lxml>=5.3.0,<6.0.0',
],
classifiers=[
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Programming Language :: Python :: 3.13',
'License :: OSI Approved :: MIT License',
'Operating System :: OS Independent',
],
Expand Down
41 changes: 40 additions & 1 deletion tests/test_transferencia.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from requests import HTTPError

from cep import Transferencia
from cep.exc import CepError, MaxRequestError
from cep.client import Client
from cep.exc import CepError, IncompleteResponseError, MaxRequestError


@pytest.mark.vcr
Expand Down Expand Up @@ -89,3 +90,41 @@ def test_maximo_numero_de_requests():
cuenta='012180000',
monto=0.01,
)


def test_incomplete_response(monkeypatch):
mock_post_response = b'Respuesta exitosa'
mock_get_response = (
b'<?xml version="1.0" encoding="UTF-8"?>'
b'<SPEI_Tercero FechaOperacion="2019-04-12" Hora="13:31:44">'
b'</SPEI_Tercero>'
)

def mock_post(self, path, data):
return mock_post_response

def mock_get(self, path):
return mock_get_response

monkeypatch.setattr(Client, 'post', mock_post)
monkeypatch.setattr(Client, 'get', mock_get)

with pytest.raises(IncompleteResponseError):
Transferencia.validar(
fecha=dt.date(2022, 4, 19),
clave_rastreo='CUENCA927820173168',
emisor='90646', # STP
receptor='40012', # BBVA
cuenta='012180000',
monto=0.01,
)


def test_descarga_sin_client_validacion_fallida(monkeypatch, transferencia):
def mock_validar(*args, **kwargs):
return None

monkeypatch.setattr(Transferencia, '_validar', mock_validar)

with pytest.raises(CepError):
transferencia.descargar()
Loading