Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/new api integration #9

Merged
merged 41 commits into from
Feb 20, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
42e8e1e
Refactor client.py to use new API authentication and response handling
Feb 6, 2025
84225ef
Update project dependencies and configuration
Feb 6, 2025
780df28
Refactor client.py: Improve code readability and naming conventions
Feb 6, 2025
daeec2d
Update README with installation and usage instructions
Feb 6, 2025
09bc608
Enhance VCR configuration for secure test recordings
Feb 10, 2025
525c354
Add environment template and update client configuration
Feb 10, 2025
5b99a0b
Update search method to allow configurable match score
Feb 10, 2025
5a1bd4b
Refactor Person class to use dataclass and simplify client parsing
Feb 10, 2025
77e3ecd
Extend search method with additional optional search parameters
Feb 10, 2025
793d2df
Refactor SearchResult to use dataclass instead of TypedDict
Feb 10, 2025
092853b
Migrate from requests to httpx for async support
Feb 10, 2025
f82315b
Update README
Feb 10, 2025
bbfdcea
Implement token caching in Client authentication method
Feb 10, 2025
c52ecc4
Add type hints to tests
Feb 10, 2025
23099dc
Migrate Person class from dataclass to Pydantic with field aliases
Feb 12, 2025
56e4851
Simplify VCR configuration for token handling in tests
Feb 12, 2025
c3f6d43
Refactor search URL construction using httpx params
Feb 12, 2025
756e3bb
Refactor client request handling
Feb 12, 2025
386e29f
Enhance search method with type-safe enums and improved parameter han…
Feb 12, 2025
1e21ae8
Improve error handling and search method flexibility
Feb 14, 2025
0d94fff
Refactor VCR configuration
Feb 14, 2025
050f6a8
Update README
Feb 14, 2025
0e38482
Update README example with more detailed search parameters
Feb 14, 2025
8f0f1df
Remove redundant response structure details from README
Feb 14, 2025
7704724
Improve client error handling
Feb 14, 2025
976ccd8
Refactor Person model to support dynamic metadata and extra fields
Feb 17, 2025
854c595
Bump development version to 1.0.0.dev4
Feb 17, 2025
091948a
Update VCR configuration to filter Set-Cookie headers
Feb 18, 2025
ac0e7e7
Change SearchType enum base type to int
Feb 18, 2025
f234432
Fix search list parameter handling in client method
Feb 18, 2025
d60cc8e
Refactor search method to use full_name parameter
Feb 18, 2025
977839e
Refactor error handling in client search method
Feb 19, 2025
af820a9
Update enums and client to use StrEnum and IntEnum
Feb 19, 2025
15653d2
Enhance error handling and testing
Feb 19, 2025
4e82be9
Refactor test mocks to use a generic mock response generator
Feb 19, 2025
d6af95d
Update enums and client to use Enum instead of StrEnum
Feb 19, 2025
364d5f3
Reorder enum imports
Feb 19, 2025
bae7c1e
Update search method to use Enum value for search type
Feb 19, 2025
29cc903
Refactor test mocks to use respx
Feb 20, 2025
df8dcc6
Bump version to 1.0.0
Feb 20, 2025
88a371f
Refactor error handling with pattern matching
Feb 20, 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
24 changes: 24 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: release

on:
release:
types: [published]

jobs:
publish-pypi:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.13
uses: actions/setup-python@v5
with:
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
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
54 changes: 54 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: test

on: push

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.13
- name: Install dependencies
run: make install-test
- name: Lint
run: make lint

pytest:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: make install-test
- name: Run tests
run: pytest

coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
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@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: true
60 changes: 41 additions & 19 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,32 +1,54 @@
SHELL := bash
PATH := ./venv/bin:${PATH}
PYTHON=python3.7
PYTHON = python3.13
PROJECT = quienesquien
isort = isort $(PROJECT) tests setup.py
black = black -S -l 79 --target-version py313 $(PROJECT) tests setup.py


all: test

install-dev:
pip install -q -e .[dev]

venv:
$(PYTHON) -m venv --prompt quienesquien venv
source venv/bin/activate
pip install --quiet --upgrade pip
$(PYTHON) -m venv --prompt $(PROJECT) venv
pip install -qU pip

install:
pip install -qU -r requirements.txt

test: clean install-dev lint
python setup.py test
install-test: install
pip install -qU -r requirements-test.txt

coverage: clean install-dev lint
coverage run --source=quienesquien setup.py test
coverage report -m
test: clean install-test lint
pytest

format:
$(isort)
$(black)

lint:
pycodestyle setup.py quienesquien/
flake8 $(PROJECT) tests setup.py
$(isort) --check-only
$(black) --check
mypy $(PROJECT) tests

clean:
find . -name '*.pyc' -exec rm -f {} +
find . -name '*.pyo' -exec rm -f {} +
find . -name '*~' -exec rm -f {} +
rm -rf build dist quienesquien.egg-info

.PHONY: all coverage lint install-dev release test clean
rm -rf `find . -name __pycache__`
rm -f `find . -type f -name '*.py[co]' `
rm -f `find . -type f -name '*~' `
rm -f `find . -type f -name '.*~' `
rm -rf .cache
rm -rf .pytest_cache
rm -rf .mypy_cache
rm -rf htmlcov
rm -rf *.egg-info
rm -f .coverage
rm -f .coverage.*
rm -rf build
rm -rf dist

release: test clean
python setup.py sdist bdist_wheel
twine upload dist/*


.PHONY: all install-test test format lint clean release
32 changes: 28 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
# quienesquien-python
# quienesquien

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

**Requerimientos**
Python v3 o superior
Cliente para el servicio de listas de Quienesquien (https://app.q-detect.com/)

## Instalación

```bash
pip install quienesquien
```

## Uso

```python
from quienesquien import Client

client = Client(URL, USER, CLIENT_ID, SECRET_ID)

try:
response = client.search('Juan', 'Lopez', 'Perez')

if response.get('resumen', {}).get('success'):
persons = [vars(person) for person in response.get('persons', [])]
print(persons)
except Exception as e:
print(f"Error during search: {e}")
```
6 changes: 5 additions & 1 deletion quienesquien/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
name = "quienesquien"
__all__ = ['__version__', 'Client', 'Person']

from .client import Client
from .person import Person
from .version import __version__
102 changes: 58 additions & 44 deletions quienesquien/client.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,65 @@
from dataclasses import dataclass
from typing import TypedDict

import requests
import xml.etree.ElementTree as ET

from .person import Person, PERSON_FIELDNAMES
from .person import PERSON_FIELDNAMES, Person


class SearchResult(TypedDict):
resumen: dict[str, int | bool]
persons: list[Person]


@dataclass
class Client:
base_url: str
username: str
client_id: str
secret_key: str
percent: int = 80

client_user = None
client_password = None
client_url = None

def __init__(self, url: str, user: str, password: str):
"""
Configura el endpoint y las credenciales para realizar busquedas
:param url: https://peps.mx/datos/qws
:param user: usuario
:param password: password
:return:
"""
self.client_user = user
self.client_password = password
self.client_url = url

def search(self, nombre, paterno, materno):
login_url = f'{self.client_url}/access?var1={self.client_user}'\
f'&var2={self.client_password}'
login_response = requests.get(login_url)
url = f'{self.client_url}/pepsp?nombre={nombre}' \
f'&paterno={paterno}&materno={materno}'
response = requests.get(url, cookies=login_response.cookies)
root = ET.fromstring(response.content)
persons = []
for person in root.iter('persona'):
if len(person) > 0:
kwargs = dict()
for field in PERSON_FIELDNAMES:
if person.find(field) is not None:
field_value = person.find(field).text
kwargs[field] = field_value
persons.append(Person(kwargs))
xml_resumen = root.find('resumen')
resumen = dict(
id_resultado=xml_resumen.find('id_resultado').text,
num_registros=xml_resumen.find('num_registros').text,
num_conex_dia=xml_resumen.find('num_conex_dia').text
)
return dict(
resumen=resumen,
persons=persons
def _fetch_auth_token(self) -> str:
"""Retrieve authentication token from the API."""
auth_url = f'{self.base_url}/api/token?client_id={self.client_id}'
headers = {'Authorization': f'Bearer {self.secret_key}'}

response = requests.get(auth_url, headers=headers)
response.raise_for_status()

return response.text

def search(self, nombre: str, paterno: str, materno: str) -> SearchResult:
"""Perform a search request and return the results."""
token = self._fetch_auth_token()

search_url = (
f'{self.base_url}/api/find?client_id={self.client_id}'
f'&username={self.username}&percent={self.percent}'
f'&name={nombre} {paterno} {materno}'
)

headers = {'Authorization': f'Bearer {token}'}
response = requests.get(search_url, headers=headers)
response.raise_for_status()

response_data = response.json()
matched_persons = []

for person_data in response_data.get('data', []):
person_attributes = {
field: person_data.get(field.upper())
for field in PERSON_FIELDNAMES
if person_data.get(field.upper()) is not None
}
matched_persons.append(Person(person_attributes))

result: SearchResult = {
'resumen': {
'success': response_data.get('success', False),
'num_registros': len(matched_persons),
},
'persons': matched_persons,
}

return result
4 changes: 1 addition & 3 deletions quienesquien/person.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
PERSON_FIELDNAMES = """
id_persona
peso1
peso2
coincidencia
nombre
paterno
materno
Expand All @@ -25,7 +24,6 @@
nombrecomp
apellidos
entidad
curp_ok
periodo
expediente
fecha_resolucion
Expand Down
1 change: 1 addition & 0 deletions quienesquien/version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = '1.0.0'
8 changes: 8 additions & 0 deletions requirements-test.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
pytest==8.*
black==25.*
pytest-cov==6.*
pytest-vcr==1.*
isort==6.*
flake8==7.*
mypy==1.*
types-requests==2.*
23 changes: 1 addition & 22 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,22 +1 @@
atomicwrites==1.3.0
attrs==19.1.0
certifi==2019.3.9
chardet==3.0.4
coverage==4.5.3
cssselect==1.0.3
fake-useragent==0.1.11
idna==2.8
lxml==4.9.1
more-itertools==7.0.0
parse==1.12.0
pluggy==0.9.0
py==1.10.0
pycodestyle==2.5.0
pyquery==1.4.0
pytest==4.4.0
requests==2.21.0
requests-xml==0.2.3
six==1.12.0
urllib3>=1.24.2
w3lib==1.20.0
xmljson==0.2.0
requests==2.32.3
20 changes: 19 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,22 @@
test=pytest

[tool:pytest]
addopts = --verbose
addopts = -p no:warnings -v --cov-report term-missing --cov=quienesquien

[flake8]
inline-quotes = '
multiline-quotes = """

[isort]
multi_line_output=3
include_trailing_comma=True
force_grid_wrap=0
combine_as_imports=True

[mypy-pytest]
ignore_missing_imports = True

[coverage:report]
precision = 2
exclude_lines =
if TYPE_CHECKING:
Loading
Loading