feat: add support for simples and ccc apis - #41
Conversation
This adds the new type parameter that allows queries to the CCC and Simples APIs. Those APIs query different source websites and return different data. We are also adding new parsers and output files for each type. This also adds a Dockerfile for easier execution without pip installation if desired. Signed-off-by: Vinicius K. Ruoso <vinicius@leads2b.com>
There was a problem hiding this comment.
Pull request overview
Adds a new --type/api_type parameter to support querying ReceitaWS “simples” and “ccc” endpoints (in addition to the existing “cnpj” flow), and updates the build pipeline to emit API-type-specific CSV outputs. Also includes Docker support for running the CLI without local installation.
Changes:
- Add
api_typeplumbing across CLI → Get → Runner → Client, with URL construction forcnpj|simples|ccc - Add new
Buildvisitors and CSV outputs forsimplesandccc, and update input file naming to include the API type - Add tests/resources + new tests, plus Dockerfile and dockerignore/gitignore updates
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/test_client.py | Adds tests for URL routing and auth header behavior per API type |
| tests/test_build.py | Adds tests for CSV generation for simples and ccc plus default-type behavior |
| tests/resources/simples_03420926004979.json | Adds fixture JSON for Simples API responses |
| tests/resources/ccc_03420926004979.json | Adds fixture JSON for CCC API responses |
| tests/conftest.py | Refactors CNPJ batch fixture into a shared constant |
| receita/tools/runner.py | Passes api_type through to Client calls |
| receita/tools/get.py | Adds api_type, validates required args for commercial APIs, and changes output filenames to <type>_<cnpj>.json |
| receita/tools/client.py | Builds endpoint paths by api_type and adds auth header when using commercial mode (days+token) |
| receita/tools/build.py | Introduces per-API visitor sets and new CSV generators; reads only <type>_*.json inputs |
| receita/cli.py | Adds --type option to get and build subcommands |
| README.rst | Documents --type, type-specific build outputs, and Docker usage (PT) |
| README.en.rst | Documents --type, type-specific build outputs, and Docker usage (EN) |
| Dockerfile | Adds containerized execution entrypoint for receita |
| .gitignore | Expands ignore patterns (venvs, caches, build artifacts, CSV outputs, etc.) |
| .dockerignore | Adds a comprehensive dockerignore to keep images small/clean |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Finish the simples/ccc work and ready the project for the 3.0.0 release. Features: - add --base-url to the get command to query an API base URL other than the default one - fail fast on an unknown api_type in Client and Build Packaging: - migrate to a PEP 621 pyproject.toml; remove setup.py - drop the legacy SSL shims (pyOpenSSL/ndg-httpsclient/pyasn1) and use progressbar2 - read the version from receita/__init__.py; bump to 3.0.0 - add PUBLISHING.md with the release runbook CI and Docker: - ci.yml runs lint, tests on 3.10-3.12 and a docker build smoke test - release.yml publishes to PyPI via Trusted Publishing and pushes the image to Docker Hub on v* tags - clean up the Dockerfile (no test deps, non-root user, OCI labels) - remove the redundant black workflow and the stale travis config Fixes: - clarify the -d wording in the CLI help and the error message - make the test response fixture tolerant of extra URL segments - drop unused test imports and fix an invalid \D escape sequence BREAKING CHANGE: get now writes <type>_<cnpj>.json (was <cnpj>.json) and build only reads files for the requested --type. Data downloaded by 2.x must be re-downloaded before running build with 3.0.0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
There are confirmed correctness/operational issues around worker-thread error handling and unclosed CSV file handles that can lead to hangs and resource leaks.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
receita/tools/runner.py:24
Runnernow acceptsapi_type/base_url, butwork()does not handleClient.get()raising (it can raiseValueErrorfor an invalidapi_type). In a worker thread this would terminate the thread and the main iterator can block forever on_results.get(). Validateapi_typeinRunner.__init__(fail fast) so worker threads cannot die silently and hang the run.
def __init__(self, cnpjs, days=None, token=None, api_type="cnpj", base_url=None):
self._returned = 0
self._stop = False
self._list = cnpjs
self._todo = queue.Queue()
receita/tools/client.py:39
Client.get()uses a bareexcept:around the HTTP call, which will also swallowKeyboardInterrupt/SystemExitand make CLI aborts unreliable (it can also mask other programming errors). Narrow the exception to request/JSON-related failures and keep the existingNone-on-error behavior.
try:
response = requests.get(url, headers=headers, timeout=70)
except:
return None
if response.status_code != 200:
- Files reviewed: 25/27 changed files
- Comments generated: 1
- Review effort level: Lite
- pin the runtime and test dependency sets in requirements.txt and requirements-test.txt so builds and test runs are reproducible; the Dockerfile and CI install from those files - keep bounded ranges in pyproject.toml so the published package stays installable alongside other projects - raise requires-python to >=3.10 to match the pinned dependency set and the CI matrix, and advertise the supported versions as classifiers - present the Docker usage before the pip installation in both READMEs, and pass RWS_TOKEN in the first example - use code-block directives so the commands are highlighted and copyable - fix the 3.0.0 release date in the changelog Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
A verified bug in receita/tools/client.py breaks token-authenticated requests (and tests), and the runner should guard against invalid api_type causing a hang.
Review details
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
receita/tools/runner.py:22
Client.get()raisesValueErrorfor an invalidapi_type. In a worker thread this would crash the thread and can cause the iterator to block forever waiting on_results.get(). Validatingapi_typeup-front avoids a hard-to-debug hang for programmatic callers.
receita/tools/client.py:34- The Authorization header formatting is incorrect here: the string literal has no placeholder, so using the
%operator will raise aTypeErrorwheneverdays+tokenare provided, and the request will never be sent. This also breaks tests that assert aBearer <token>header.
receita/tools/runner.py:72 Client(...)is called with multiple positional arguments, which is easy to mis-order now thatapi_typeandbase_urlwere added. Using keyword arguments makes the call site robust to future signature changes and improves readability.
- Files reviewed: 25/27 changed files
- Comments generated: 0 new
- Review effort level: Lite
- close the CSV visitor file handles when build finishes, including when it fails partway through, so output is flushed instead of waiting for the garbage collector; covered by a regression test - remove the contents directive from both READMEs: it wrapped every section title in a table-of-contents backlink, so the headings rendered as links on GitHub Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The new authenticated request path has a broken Authorization header construction in Client.get(), which will cause authenticated calls to fail and breaks the added test coverage.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
receita/tools/runner.py:76
- On transient failures (
Client.get()returningNone), the worker immediately re-queues the same CNPJ with no delay. With multiple threads this can become a tight retry loop that hammers the remote API (and can quickly hit rate limits) while also wasting CPU.
Add a small backoff before re-queuing failed items (or implement exponential backoff / max retries).
).get()
if data:
self._results.put((cnpj, data))
else:
self._todo.put(cnpj)
receita/tools/client.py:39
except:here catchesBaseException(includingKeyboardInterrupt/SystemExit) and can hide unexpected failures. Also,json.loads(response.content)can raiseValueError/JSONDecodeError, which would kill the worker thread.
Prefer catching requests.exceptions.RequestException, and treat JSON decode errors as a None result.
try:
response = requests.get(url, headers=headers, timeout=70)
except:
return None
if response.status_code != 200:
- Files reviewed: 25/27 changed files
- Comments generated: 1
- Review effort level: Lite
- correct "Laguages" and "Comercial API" in the English README, adjusting the section underline to match the new heading length - link the language names instead of the flag images: GitHub underlines any link wrapping an image, which drew a blue line under the flags Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GitHub wraps every inline image in a link to the proxied image, and that link is underlined, so a flag image always drew a blue underline. Use flag emoji instead: they are text, so only the language names are links. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The runner can retry failing CNPJs indefinitely with no cap/backoff, which can cause the get command to hang forever on permanent failures (e.g., invalid token or persistent non-200 responses).
Review details
Suppressed comments (5)
Previously missed (4) — in code that hasn't changed since the last review.
README.en.rst:126
- Grammar/terminology: “two extra informations” is incorrect (information is uncountable) and “data deprecation value” is inconsistent with the CLI wording (“maximum allowed data age”).
``RWS_TOKEN`` environment variable. The deprecation value must be provided
using the ``-d`` option.
receita/tools/client.py:41
Client.get()uses a bareexcept:which also catchesKeyboardInterrupt/SystemExitand can hide non-network programming errors. It’s safer to catchrequests.exceptions.RequestException(and optionally JSON parse errors) and let unexpected exceptions surface.
try:
response = requests.get(url, headers=headers, timeout=70)
except:
return None
if response.status_code != 200:
return None
return json.loads(response.content)
PUBLISHING.md:59
- This claim is inaccurate:
setuptoolsdynamicversion = { attr = "receita.__version__" }is obtained by importing the module to read the attribute. Consider rewording to avoid implying a no-import mechanism.
README.rst:100 - Typo in the Portuguese README: “oção” should be “opção”.
receita/tools/runner.py:76
Runner.work()re-queues a CNPJ wheneverClient.get()returns a falsy value, with no retry limit or backoff. If the request fails permanently (e.g., invalid token / consistent non-200 responses), the same CNPJ will be retried forever and the iterator will never complete, causinggetto hang indefinitely.
if data:
self._results.put((cnpj, data))
else:
self._todo.put(cnpj)
- Files reviewed: 25/27 changed files
- Comments generated: 0 new
- Review effort level: Lite
There was a problem hiding this comment.
🟡 Changes recommended
Client.get() now raises on invalid api_type, but Runner does not validate/catch this, which can crash worker threads and cause the iterator to block indefinitely in programmatic usage.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 25/27 changed files
- Comments generated: 1
- Review effort level: Lite
Client.get() raising ValueError for an unknown api type killed every worker thread with the CNPJ already taken off the queue, so the iterator waited on results that never arrived and the process could not exit. Validate in Runner.__init__ instead, before any thread is started, so the error reaches the caller. The check moved to a shared validate_api_type helper used by both Client and Runner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Packaging/docs issues remain (notably pyproject.toml license metadata format and an English README wording inconsistency) that should be corrected before release automation is relied upon.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
pyproject.toml:10
- In PEP 621
[project],licenseis expected to be a table (e.g.{ text = ... }or{ file = ... }). Using a bare string can break stricter build/metadata tooling even if it works with some backends.
README.en.rst:121 - README wording here is grammatically incorrect ("informations") and still uses the old "data deprecation" terminology, while the CLI/help text was updated to "maximum allowed data age". Updating this keeps the English docs consistent with the CLI and avoids confusion.
- Files reviewed: 26/28 changed files
- Comments generated: 0 new
- Review effort level: Lite
This adds the new type parameter that allows queries to the CCC and Simples APIs. Those APIs query different source websites and return different data. We are also adding new parsers and output files for each type.
This also adds a Dockerfile for easier execution without pip installation if desired.