csf-scraper-python 0.1.1__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- csf_scraper_python-0.2.0/.github/workflows/publish.yml +103 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/PKG-INFO +119 -8
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/README.md +114 -7
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/docs/migration-map.md +43 -40
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/pyproject.toml +6 -1
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/__init__.py +7 -0
- csf_scraper_python-0.2.0/src/csf_scraper/image_readers/adapters/cpu/__init__.py +18 -0
- csf_scraper_python-0.2.0/src/csf_scraper/image_readers/adapters/cpu/open_cv/__init__.py +37 -0
- csf_scraper_python-0.2.0/src/csf_scraper/image_readers/adapters/cpu/open_cv/config.py +84 -0
- csf_scraper_python-0.2.0/src/csf_scraper/image_readers/adapters/cpu/open_cv/detection.py +269 -0
- csf_scraper_python-0.2.0/src/csf_scraper/image_readers/adapters/cpu/open_cv/enhance.py +103 -0
- csf_scraper_python-0.2.0/src/csf_scraper/image_readers/adapters/cpu/open_cv/errors.py +18 -0
- csf_scraper_python-0.2.0/src/csf_scraper/image_readers/adapters/cpu/open_cv/io.py +31 -0
- csf_scraper_python-0.2.0/src/csf_scraper/image_readers/adapters/cpu/open_cv/pipeline.py +89 -0
- csf_scraper_python-0.2.0/src/csf_scraper/image_readers/adapters/cpu/open_cv/qr.py +108 -0
- csf_scraper_python-0.2.0/src/csf_scraper/image_readers/adapters/cpu/open_cv/transform.py +97 -0
- csf_scraper_python-0.2.0/src/csf_scraper/image_readers/adapters/cpu/open_cv_image_reader.py +56 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/scraper.py +14 -3
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/.gitignore +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/LICENSE +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/NOTICE.md +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/docs/differences.md +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/data_extractor.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/date_parser.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/exceptions/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/exceptions/image_reader.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/exceptions/pdf_reader.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/exceptions/value_objects.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/adapters/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/adapters/llms/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/adapters/llms/gemini_image_reader.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/adapters/llms/prompts/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/adapters/llms/prompts/llm_image_reader_prompt.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/csf_image_extractor.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/ports/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/ports/image_reader_port.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/schemas/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/schemas/rfc_cif_schema.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/services/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/image_readers/services/llm_image_reader_service.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/pdf_reader/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/pdf_reader/csf_pdf_extractor.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/pdf_reader/pdf_csf_text_parser.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/pdf_reader/pdf_to_text.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/persona.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/persona_fisica.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/persona_moral.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/ports/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/ports/rfc_cif_extractor_port.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/py.typed +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/regimen.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/rfc_cif_data.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/value_objects/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/value_objects/csf_image.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/value_objects/id_cif.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/src/csf_scraper/value_objects/rfc.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/_files/csf-bad-content.txt +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/_files/csf-content.txt +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/_files/csf-correct-but-invalid.pdf +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/_files/csf-without-cif.pdf +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/_files/csf-without-rfc.pdf +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/_files/error.html +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/_files/scrap_fisica.html +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/_files/scrap_moral.html +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/_files/scrap_moral_without_colonia.html +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/_files/scrap_regimenes.html +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/helpers.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/integration/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/integration/helpers.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/integration/test_scrap_csf_from_rfc_and_cif.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/integration/test_scrap_from_csf_file.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/exceptions/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/exceptions/pdf_reader/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/exceptions/pdf_reader/test_cif_from_pdf_not_found_exception.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/exceptions/pdf_reader/test_empty_pdf_content_exception.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/exceptions/pdf_reader/test_pdf_to_text_convert_exception.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/exceptions/pdf_reader/test_rfc_from_pdf_not_found_exception.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/exceptions/test_cif_download_exception.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/exceptions/test_cif_not_found_exception.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/image_readers/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/image_readers/test_csf_image_extractor.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/image_readers/test_llm_image_reader.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/pdf_reader/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/pdf_reader/pdf_csf_text_extractor/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/pdf_reader/pdf_csf_text_extractor/test_extract.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/pdf_reader/pdf_csf_text_extractor/test_get_cif_id.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/pdf_reader/pdf_csf_text_extractor/test_get_rfc.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/pdf_reader/pdf_to_text/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/pdf_reader/pdf_to_text/test_pdf_to_text_constructor.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/persona/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/persona/test_common_persona_properties.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/persona/test_persona_fisica_properties.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/persona/test_persona_json_serialize.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/persona/test_persona_moral_properties.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/persona/traits/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/persona/traits/persona_trait.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/regimen/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/regimen/test_regimen_json_serialize.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/regimen/test_regimenes_json_serialize.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/regimen/test_set_regimen.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/test_regimenes.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/test_scraper.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/value_objects/__init__.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/value_objects/test_csf_image.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/value_objects/test_id_cif.py +0 -0
- {csf_scraper_python-0.1.1 → csf_scraper_python-0.2.0}/tests/unit/value_objects/test_rfc.py +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: publish-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
|
+
- uses: actions/setup-python@v7
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.14.8"
|
|
23
|
+
cache: pip
|
|
24
|
+
cache-dependency-path: pyproject.toml
|
|
25
|
+
- name: Install poppler-utils
|
|
26
|
+
run: sudo apt-get update && sudo apt-get install -y poppler-utils
|
|
27
|
+
- name: Install development dependencies
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade pip
|
|
30
|
+
python -m pip install -e ".[dev,opencv,llm-gemini]"
|
|
31
|
+
- name: Ruff
|
|
32
|
+
run: ruff check src tests
|
|
33
|
+
- name: Mypy
|
|
34
|
+
run: mypy
|
|
35
|
+
- name: Pytest
|
|
36
|
+
run: pytest
|
|
37
|
+
- name: Verify OpenCV extra
|
|
38
|
+
run: |
|
|
39
|
+
python -c "import cv2, numpy; print('OpenCV', cv2.__version__, 'NumPy', numpy.__version__)"
|
|
40
|
+
python -c "from csf_scraper import OpenCvImageReader; print(OpenCvImageReader.__name__)"
|
|
41
|
+
|
|
42
|
+
build:
|
|
43
|
+
needs: test
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v7
|
|
47
|
+
- uses: actions/setup-python@v7
|
|
48
|
+
with:
|
|
49
|
+
python-version: "3.14.8"
|
|
50
|
+
- name: Install build tools
|
|
51
|
+
run: python -m pip install --upgrade build twine
|
|
52
|
+
- name: Verify version matches tag
|
|
53
|
+
run: |
|
|
54
|
+
set -euo pipefail
|
|
55
|
+
VERSION=$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')
|
|
56
|
+
echo "pyproject.toml version: v${VERSION}"
|
|
57
|
+
echo "Received tag: ${GITHUB_REF_NAME}"
|
|
58
|
+
test "v${VERSION}" = "${GITHUB_REF_NAME}"
|
|
59
|
+
- name: Build distributions
|
|
60
|
+
run: python -m build
|
|
61
|
+
- name: Check distributions
|
|
62
|
+
run: python -m twine check dist/*
|
|
63
|
+
- name: Upload distributions
|
|
64
|
+
uses: actions/upload-artifact@v7
|
|
65
|
+
with:
|
|
66
|
+
name: dist
|
|
67
|
+
path: dist/
|
|
68
|
+
|
|
69
|
+
publish:
|
|
70
|
+
needs: build
|
|
71
|
+
runs-on: ubuntu-latest
|
|
72
|
+
environment: pypi
|
|
73
|
+
permissions:
|
|
74
|
+
contents: read
|
|
75
|
+
id-token: write
|
|
76
|
+
steps:
|
|
77
|
+
- name: Download distributions
|
|
78
|
+
uses: actions/download-artifact@v8
|
|
79
|
+
with:
|
|
80
|
+
name: dist
|
|
81
|
+
path: dist/
|
|
82
|
+
- name: Publish to PyPI
|
|
83
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
84
|
+
|
|
85
|
+
release:
|
|
86
|
+
needs: publish
|
|
87
|
+
runs-on: ubuntu-latest
|
|
88
|
+
permissions:
|
|
89
|
+
contents: write
|
|
90
|
+
steps:
|
|
91
|
+
- name: Download distributions
|
|
92
|
+
uses: actions/download-artifact@v8
|
|
93
|
+
with:
|
|
94
|
+
name: dist
|
|
95
|
+
path: dist/
|
|
96
|
+
- name: Create GitHub Release
|
|
97
|
+
env:
|
|
98
|
+
GH_TOKEN: ${{ github.token }}
|
|
99
|
+
run: |
|
|
100
|
+
gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1 || \
|
|
101
|
+
gh release create "$GITHUB_REF_NAME" dist/* \
|
|
102
|
+
--title "$GITHUB_REF_NAME" \
|
|
103
|
+
--generate-notes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: csf-scraper-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Obtiene los datos fiscales actuales de una persona moral o física dado su RFC y su idCIF (port de phpcfdi/csf-scraper)
|
|
5
5
|
Project-URL: Homepage, https://github.com/PJMP4826/csf-scraper-python
|
|
6
6
|
Project-URL: Issues, https://github.com/PJMP4826/csf-scraper-python/issues
|
|
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
|
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.11
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
23
|
Classifier: Typing :: Typed
|
|
23
24
|
Requires-Python: >=3.11
|
|
24
25
|
Requires-Dist: beautifulsoup4>=4.12
|
|
@@ -35,9 +36,12 @@ Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
|
35
36
|
Provides-Extra: llm-gemini
|
|
36
37
|
Requires-Dist: google-genai>=1.0; extra == 'llm-gemini'
|
|
37
38
|
Requires-Dist: llama-index-llms-google-genai<0.12,>=0.11; extra == 'llm-gemini'
|
|
39
|
+
Provides-Extra: opencv
|
|
40
|
+
Requires-Dist: numpy>=2; extra == 'opencv'
|
|
41
|
+
Requires-Dist: opencv-python-headless>=5.0; extra == 'opencv'
|
|
38
42
|
Description-Content-Type: text/markdown
|
|
39
43
|
|
|
40
|
-
# csf-scraper
|
|
44
|
+
# csf-scraper-python
|
|
41
45
|
|
|
42
46
|
Port a Python de [phpcfdi/csf-scraper](https://github.com/phpcfdi/csf-scraper):
|
|
43
47
|
obtiene los datos fiscales actuales de una persona moral o física dado su RFC y
|
|
@@ -50,7 +54,8 @@ su idCIF (SAT, Constancia de Situación Fiscal).
|
|
|
50
54
|
`poppler-utils`), necesario **solo** para leer la CSF desde un archivo PDF
|
|
51
55
|
(`obtain_from_pdf_path`).
|
|
52
56
|
- Dependencias: `httpx`, `beautifulsoup4`, `lxml`, `pydantic`, `llama-index-core`.
|
|
53
|
-
Los clientes de cada proveedor LLM son extras opcionales (`llm-gemini`).
|
|
57
|
+
Los clientes de cada proveedor LLM son extras opcionales (`llm-gemini`). El
|
|
58
|
+
lector de imágenes basado en OpenCV requiere el extra `opencv`.
|
|
54
59
|
|
|
55
60
|
## Instalación
|
|
56
61
|
|
|
@@ -66,6 +71,12 @@ Para leer CSF escaneadas (JPEG/PNG) con Gemini, agrega el extra:
|
|
|
66
71
|
pip install "csf-scraper-python[llm-gemini]"
|
|
67
72
|
```
|
|
68
73
|
|
|
74
|
+
Para leerlas usando el lector basado en OpenCV:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install "csf-scraper-python[opencv]"
|
|
78
|
+
```
|
|
79
|
+
|
|
69
80
|
Desde el código fuente:
|
|
70
81
|
|
|
71
82
|
```bash
|
|
@@ -77,6 +88,10 @@ pip install .
|
|
|
77
88
|
Todo el flujo es asíncrono: `Scraper` envuelve un `httpx.AsyncClient` y todos
|
|
78
89
|
los métodos de lectura son corrutinas, así que se consumen con `await`.
|
|
79
90
|
|
|
91
|
+
`Scraper.create()` crea y devuelve la instancia directamente; **no** es
|
|
92
|
+
obligatorio usarla con `async with`. Basta con cerrar el cliente al terminar,
|
|
93
|
+
ya sea llamando a `aclose()` o usando el scraper como _async context manager_:
|
|
94
|
+
|
|
80
95
|
```python
|
|
81
96
|
import asyncio
|
|
82
97
|
|
|
@@ -84,14 +99,17 @@ from csf_scraper import Scraper
|
|
|
84
99
|
|
|
85
100
|
|
|
86
101
|
async def main() -> None:
|
|
87
|
-
|
|
88
|
-
|
|
102
|
+
scraper = Scraper.create()
|
|
103
|
+
|
|
104
|
+
try:
|
|
89
105
|
# RFC + idCIF directamente (se validan con los value objects Rfc e IdCif)
|
|
90
106
|
person = await scraper.obtain_from_rfc_and_cif("DIM8701081LA", "1904014102123")
|
|
91
107
|
|
|
92
108
|
# También puedes obtener los datos directamente de un archivo local PDF
|
|
93
109
|
# (requiere pdftotext / poppler-utils instalado)
|
|
94
110
|
person = await scraper.obtain_from_pdf_path("ruta/al/csf.pdf")
|
|
111
|
+
finally:
|
|
112
|
+
await scraper.aclose()
|
|
95
113
|
|
|
96
114
|
# Acceso a los datos de la persona (moral o física) por atributos
|
|
97
115
|
if person.is_fisica():
|
|
@@ -115,6 +133,16 @@ if __name__ == "__main__":
|
|
|
115
133
|
asyncio.run(main())
|
|
116
134
|
```
|
|
117
135
|
|
|
136
|
+
Si prefieres no gestionar el cierre manualmente, el scraper también funciona
|
|
137
|
+
como _async context manager_ (cierra el `AsyncClient` al salir del bloque):
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
async def main() -> None:
|
|
141
|
+
async with Scraper.create() as scraper:
|
|
142
|
+
person = await scraper.obtain_from_rfc_and_cif("DIM8701081LA", "1904014102123")
|
|
143
|
+
print(person.to_dict())
|
|
144
|
+
```
|
|
145
|
+
|
|
118
146
|
Para leer una CSF escaneada (JPEG/PNG) se inyecta el motor de lectura de imágenes.
|
|
119
147
|
Requiere el extra `llm-gemini`:
|
|
120
148
|
|
|
@@ -126,8 +154,33 @@ from csf_scraper import CsfImageExtractor, GeminiImageReader, Scraper
|
|
|
126
154
|
|
|
127
155
|
async def main() -> None:
|
|
128
156
|
reader = GeminiImageReader(api_key="…", model_name="…")
|
|
129
|
-
|
|
157
|
+
scraper = Scraper.create(image_extractor=CsfImageExtractor(reader))
|
|
158
|
+
try:
|
|
159
|
+
person = await scraper.obtain_from_image_path("ruta/al/csf.png")
|
|
160
|
+
finally:
|
|
161
|
+
await scraper.aclose()
|
|
162
|
+
print(person.to_dict())
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
asyncio.run(main())
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Para usar el lector basado en OpenCV no se necesita un proveedor LLM. Requiere
|
|
170
|
+
el extra `opencv`:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
import asyncio
|
|
174
|
+
|
|
175
|
+
from csf_scraper import CsfImageExtractor, OpenCvImageReader, Scraper
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
async def main() -> None:
|
|
179
|
+
scraper = Scraper.create(image_extractor=CsfImageExtractor(OpenCvImageReader()))
|
|
180
|
+
try:
|
|
130
181
|
person = await scraper.obtain_from_image_path("ruta/al/csf.png")
|
|
182
|
+
finally:
|
|
183
|
+
await scraper.aclose()
|
|
131
184
|
print(person.to_dict())
|
|
132
185
|
|
|
133
186
|
|
|
@@ -135,14 +188,31 @@ if __name__ == "__main__":
|
|
|
135
188
|
asyncio.run(main())
|
|
136
189
|
```
|
|
137
190
|
|
|
138
|
-
|
|
191
|
+
`to_dict()` devuelve el conjunto completo de propiedades del modelo que se haya
|
|
192
|
+
obtenido: los campos comunes de `Persona` más los propios de `PersonaMoral` o de
|
|
193
|
+
`PersonaFisica`. Las fechas se devuelven como `datetime` (o `None`) y los value
|
|
194
|
+
objects `Rfc`/`IdCif` como cadenas.
|
|
195
|
+
|
|
196
|
+
Salida de `person.to_dict()` para una **persona moral**:
|
|
139
197
|
|
|
140
198
|
```python
|
|
141
199
|
{
|
|
200
|
+
# Compartidos con PersonaFisica
|
|
142
201
|
"rfc": "DIM8701081LA",
|
|
143
202
|
"id_cif": "12345678",
|
|
144
203
|
"fecha_inicio_operaciones": datetime(2019, 2, 21),
|
|
145
|
-
|
|
204
|
+
"situacion_contribuyente": "ACTIVO",
|
|
205
|
+
"fecha_ultimo_cambio_situacion": datetime(2019, 2, 21),
|
|
206
|
+
"entidad_federativa": "CIUDAD DE MEXICO",
|
|
207
|
+
"municipio_delegacion": "CUAUHTEMOC",
|
|
208
|
+
"colonia": "CENTRO",
|
|
209
|
+
"tipo_vialidad": "CALLE",
|
|
210
|
+
"nombre_vialidad": "AVENIDA REFORMA",
|
|
211
|
+
"numero_exterior": "123",
|
|
212
|
+
"numero_interior": "A",
|
|
213
|
+
"codigo_postal": "06000",
|
|
214
|
+
"correo_electronico": "contacto@ejemplo.mx",
|
|
215
|
+
"al": "CIUDAD DE MEXICO",
|
|
146
216
|
"regimenes": [
|
|
147
217
|
{
|
|
148
218
|
"regimen": "Régimen General de Ley Personas Morales",
|
|
@@ -151,12 +221,53 @@ Salida de `person.to_dict()` para una persona moral (fechas como `datetime`):
|
|
|
151
221
|
},
|
|
152
222
|
],
|
|
153
223
|
"extra_data": {"localidad": ""},
|
|
224
|
+
# Propios de PersonaMoral
|
|
154
225
|
"razon_social": "Mi razón social",
|
|
155
226
|
"regimen_de_capital": "SA DE CV",
|
|
156
227
|
"fecha_constitucion": datetime(2019, 2, 21),
|
|
157
228
|
}
|
|
158
229
|
```
|
|
159
230
|
|
|
231
|
+
Salida de `person.to_dict()` para una **persona física**:
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
{
|
|
235
|
+
# Compartidos con PersonaMoral
|
|
236
|
+
"rfc": "GODE561231GR8",
|
|
237
|
+
"id_cif": "12345678",
|
|
238
|
+
"fecha_inicio_operaciones": datetime(2019, 2, 21),
|
|
239
|
+
"situacion_contribuyente": "ACTIVO",
|
|
240
|
+
"fecha_ultimo_cambio_situacion": datetime(2019, 2, 21),
|
|
241
|
+
"entidad_federativa": "CIUDAD DE MEXICO",
|
|
242
|
+
"municipio_delegacion": "CUAUHTEMOC",
|
|
243
|
+
"colonia": "CENTRO",
|
|
244
|
+
"tipo_vialidad": "CALLE",
|
|
245
|
+
"nombre_vialidad": "AVENIDA REFORMA",
|
|
246
|
+
"numero_exterior": "123",
|
|
247
|
+
"numero_interior": "A",
|
|
248
|
+
"codigo_postal": "06000",
|
|
249
|
+
"correo_electronico": "contacto@ejemplo.mx",
|
|
250
|
+
"al": "CIUDAD DE MEXICO",
|
|
251
|
+
"regimenes": [
|
|
252
|
+
{
|
|
253
|
+
"regimen": "Régimen de Sueldos y Salarios e Ingresos Asimilados a Salarios",
|
|
254
|
+
"fecha_alta": datetime(2019, 2, 21),
|
|
255
|
+
"regimen_id": "605",
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
"extra_data": {"localidad": ""},
|
|
259
|
+
# Propios de PersonaFisica
|
|
260
|
+
"curp": "GODE561231HDFRRN09",
|
|
261
|
+
"nombre": "MI NOMBRE",
|
|
262
|
+
"apellido_paterno": "MI APELLIDO PATERNO",
|
|
263
|
+
"apellido_materno": "MI APELLIDO MATERNO",
|
|
264
|
+
"fecha_nacimiento": datetime(1956, 12, 31),
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Si necesitas las fechas en ISO-8601, usa `person.model_dump_json()`, que
|
|
269
|
+
serializa exactamente las mismas propiedades.
|
|
270
|
+
|
|
160
271
|
## Modelos
|
|
161
272
|
|
|
162
273
|
- `Persona` es la base de `PersonaFisica` y `PersonaMoral`; sus campos se
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# csf-scraper
|
|
1
|
+
# csf-scraper-python
|
|
2
2
|
|
|
3
3
|
Port a Python de [phpcfdi/csf-scraper](https://github.com/phpcfdi/csf-scraper):
|
|
4
4
|
obtiene los datos fiscales actuales de una persona moral o física dado su RFC y
|
|
@@ -11,7 +11,8 @@ su idCIF (SAT, Constancia de Situación Fiscal).
|
|
|
11
11
|
`poppler-utils`), necesario **solo** para leer la CSF desde un archivo PDF
|
|
12
12
|
(`obtain_from_pdf_path`).
|
|
13
13
|
- Dependencias: `httpx`, `beautifulsoup4`, `lxml`, `pydantic`, `llama-index-core`.
|
|
14
|
-
Los clientes de cada proveedor LLM son extras opcionales (`llm-gemini`).
|
|
14
|
+
Los clientes de cada proveedor LLM son extras opcionales (`llm-gemini`). El
|
|
15
|
+
lector de imágenes basado en OpenCV requiere el extra `opencv`.
|
|
15
16
|
|
|
16
17
|
## Instalación
|
|
17
18
|
|
|
@@ -27,6 +28,12 @@ Para leer CSF escaneadas (JPEG/PNG) con Gemini, agrega el extra:
|
|
|
27
28
|
pip install "csf-scraper-python[llm-gemini]"
|
|
28
29
|
```
|
|
29
30
|
|
|
31
|
+
Para leerlas usando el lector basado en OpenCV:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install "csf-scraper-python[opencv]"
|
|
35
|
+
```
|
|
36
|
+
|
|
30
37
|
Desde el código fuente:
|
|
31
38
|
|
|
32
39
|
```bash
|
|
@@ -38,6 +45,10 @@ pip install .
|
|
|
38
45
|
Todo el flujo es asíncrono: `Scraper` envuelve un `httpx.AsyncClient` y todos
|
|
39
46
|
los métodos de lectura son corrutinas, así que se consumen con `await`.
|
|
40
47
|
|
|
48
|
+
`Scraper.create()` crea y devuelve la instancia directamente; **no** es
|
|
49
|
+
obligatorio usarla con `async with`. Basta con cerrar el cliente al terminar,
|
|
50
|
+
ya sea llamando a `aclose()` o usando el scraper como _async context manager_:
|
|
51
|
+
|
|
41
52
|
```python
|
|
42
53
|
import asyncio
|
|
43
54
|
|
|
@@ -45,14 +56,17 @@ from csf_scraper import Scraper
|
|
|
45
56
|
|
|
46
57
|
|
|
47
58
|
async def main() -> None:
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
scraper = Scraper.create()
|
|
60
|
+
|
|
61
|
+
try:
|
|
50
62
|
# RFC + idCIF directamente (se validan con los value objects Rfc e IdCif)
|
|
51
63
|
person = await scraper.obtain_from_rfc_and_cif("DIM8701081LA", "1904014102123")
|
|
52
64
|
|
|
53
65
|
# También puedes obtener los datos directamente de un archivo local PDF
|
|
54
66
|
# (requiere pdftotext / poppler-utils instalado)
|
|
55
67
|
person = await scraper.obtain_from_pdf_path("ruta/al/csf.pdf")
|
|
68
|
+
finally:
|
|
69
|
+
await scraper.aclose()
|
|
56
70
|
|
|
57
71
|
# Acceso a los datos de la persona (moral o física) por atributos
|
|
58
72
|
if person.is_fisica():
|
|
@@ -76,6 +90,16 @@ if __name__ == "__main__":
|
|
|
76
90
|
asyncio.run(main())
|
|
77
91
|
```
|
|
78
92
|
|
|
93
|
+
Si prefieres no gestionar el cierre manualmente, el scraper también funciona
|
|
94
|
+
como _async context manager_ (cierra el `AsyncClient` al salir del bloque):
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
async def main() -> None:
|
|
98
|
+
async with Scraper.create() as scraper:
|
|
99
|
+
person = await scraper.obtain_from_rfc_and_cif("DIM8701081LA", "1904014102123")
|
|
100
|
+
print(person.to_dict())
|
|
101
|
+
```
|
|
102
|
+
|
|
79
103
|
Para leer una CSF escaneada (JPEG/PNG) se inyecta el motor de lectura de imágenes.
|
|
80
104
|
Requiere el extra `llm-gemini`:
|
|
81
105
|
|
|
@@ -87,8 +111,33 @@ from csf_scraper import CsfImageExtractor, GeminiImageReader, Scraper
|
|
|
87
111
|
|
|
88
112
|
async def main() -> None:
|
|
89
113
|
reader = GeminiImageReader(api_key="…", model_name="…")
|
|
90
|
-
|
|
114
|
+
scraper = Scraper.create(image_extractor=CsfImageExtractor(reader))
|
|
115
|
+
try:
|
|
116
|
+
person = await scraper.obtain_from_image_path("ruta/al/csf.png")
|
|
117
|
+
finally:
|
|
118
|
+
await scraper.aclose()
|
|
119
|
+
print(person.to_dict())
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
asyncio.run(main())
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Para usar el lector basado en OpenCV no se necesita un proveedor LLM. Requiere
|
|
127
|
+
el extra `opencv`:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
import asyncio
|
|
131
|
+
|
|
132
|
+
from csf_scraper import CsfImageExtractor, OpenCvImageReader, Scraper
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
async def main() -> None:
|
|
136
|
+
scraper = Scraper.create(image_extractor=CsfImageExtractor(OpenCvImageReader()))
|
|
137
|
+
try:
|
|
91
138
|
person = await scraper.obtain_from_image_path("ruta/al/csf.png")
|
|
139
|
+
finally:
|
|
140
|
+
await scraper.aclose()
|
|
92
141
|
print(person.to_dict())
|
|
93
142
|
|
|
94
143
|
|
|
@@ -96,14 +145,31 @@ if __name__ == "__main__":
|
|
|
96
145
|
asyncio.run(main())
|
|
97
146
|
```
|
|
98
147
|
|
|
99
|
-
|
|
148
|
+
`to_dict()` devuelve el conjunto completo de propiedades del modelo que se haya
|
|
149
|
+
obtenido: los campos comunes de `Persona` más los propios de `PersonaMoral` o de
|
|
150
|
+
`PersonaFisica`. Las fechas se devuelven como `datetime` (o `None`) y los value
|
|
151
|
+
objects `Rfc`/`IdCif` como cadenas.
|
|
152
|
+
|
|
153
|
+
Salida de `person.to_dict()` para una **persona moral**:
|
|
100
154
|
|
|
101
155
|
```python
|
|
102
156
|
{
|
|
157
|
+
# Compartidos con PersonaFisica
|
|
103
158
|
"rfc": "DIM8701081LA",
|
|
104
159
|
"id_cif": "12345678",
|
|
105
160
|
"fecha_inicio_operaciones": datetime(2019, 2, 21),
|
|
106
|
-
|
|
161
|
+
"situacion_contribuyente": "ACTIVO",
|
|
162
|
+
"fecha_ultimo_cambio_situacion": datetime(2019, 2, 21),
|
|
163
|
+
"entidad_federativa": "CIUDAD DE MEXICO",
|
|
164
|
+
"municipio_delegacion": "CUAUHTEMOC",
|
|
165
|
+
"colonia": "CENTRO",
|
|
166
|
+
"tipo_vialidad": "CALLE",
|
|
167
|
+
"nombre_vialidad": "AVENIDA REFORMA",
|
|
168
|
+
"numero_exterior": "123",
|
|
169
|
+
"numero_interior": "A",
|
|
170
|
+
"codigo_postal": "06000",
|
|
171
|
+
"correo_electronico": "contacto@ejemplo.mx",
|
|
172
|
+
"al": "CIUDAD DE MEXICO",
|
|
107
173
|
"regimenes": [
|
|
108
174
|
{
|
|
109
175
|
"regimen": "Régimen General de Ley Personas Morales",
|
|
@@ -112,12 +178,53 @@ Salida de `person.to_dict()` para una persona moral (fechas como `datetime`):
|
|
|
112
178
|
},
|
|
113
179
|
],
|
|
114
180
|
"extra_data": {"localidad": ""},
|
|
181
|
+
# Propios de PersonaMoral
|
|
115
182
|
"razon_social": "Mi razón social",
|
|
116
183
|
"regimen_de_capital": "SA DE CV",
|
|
117
184
|
"fecha_constitucion": datetime(2019, 2, 21),
|
|
118
185
|
}
|
|
119
186
|
```
|
|
120
187
|
|
|
188
|
+
Salida de `person.to_dict()` para una **persona física**:
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
{
|
|
192
|
+
# Compartidos con PersonaMoral
|
|
193
|
+
"rfc": "GODE561231GR8",
|
|
194
|
+
"id_cif": "12345678",
|
|
195
|
+
"fecha_inicio_operaciones": datetime(2019, 2, 21),
|
|
196
|
+
"situacion_contribuyente": "ACTIVO",
|
|
197
|
+
"fecha_ultimo_cambio_situacion": datetime(2019, 2, 21),
|
|
198
|
+
"entidad_federativa": "CIUDAD DE MEXICO",
|
|
199
|
+
"municipio_delegacion": "CUAUHTEMOC",
|
|
200
|
+
"colonia": "CENTRO",
|
|
201
|
+
"tipo_vialidad": "CALLE",
|
|
202
|
+
"nombre_vialidad": "AVENIDA REFORMA",
|
|
203
|
+
"numero_exterior": "123",
|
|
204
|
+
"numero_interior": "A",
|
|
205
|
+
"codigo_postal": "06000",
|
|
206
|
+
"correo_electronico": "contacto@ejemplo.mx",
|
|
207
|
+
"al": "CIUDAD DE MEXICO",
|
|
208
|
+
"regimenes": [
|
|
209
|
+
{
|
|
210
|
+
"regimen": "Régimen de Sueldos y Salarios e Ingresos Asimilados a Salarios",
|
|
211
|
+
"fecha_alta": datetime(2019, 2, 21),
|
|
212
|
+
"regimen_id": "605",
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
"extra_data": {"localidad": ""},
|
|
216
|
+
# Propios de PersonaFisica
|
|
217
|
+
"curp": "GODE561231HDFRRN09",
|
|
218
|
+
"nombre": "MI NOMBRE",
|
|
219
|
+
"apellido_paterno": "MI APELLIDO PATERNO",
|
|
220
|
+
"apellido_materno": "MI APELLIDO MATERNO",
|
|
221
|
+
"fecha_nacimiento": datetime(1956, 12, 31),
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Si necesitas las fechas en ISO-8601, usa `person.model_dump_json()`, que
|
|
226
|
+
serializa exactamente las mismas propiedades.
|
|
227
|
+
|
|
121
228
|
## Modelos
|
|
122
229
|
|
|
123
230
|
- `Persona` es la base de `PersonaFisica` y `PersonaMoral`; sus campos se
|