Skip to content

feat: add support for simples and ccc apis - #41

Merged
vkruoso merged 7 commits into
leads2b:masterfrom
vkruoso:feature/new-apis
Sep 3, 2026
Merged

feat: add support for simples and ccc apis#41
vkruoso merged 7 commits into
leads2b:masterfrom
vkruoso:feature/new-apis

Conversation

@vkruoso

@vkruoso vkruoso commented Mar 20, 2026

Copy link
Copy Markdown
Member

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.

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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_type plumbing across CLI → Get → Runner → Client, with URL construction for cnpj|simples|ccc
  • Add new Build visitors and CSV outputs for simples and ccc, 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.

Comment thread tests/test_client.py
Comment thread receita/tools/client.py Outdated
Comment thread receita/tools/get.py
Comment thread receita/tools/build.py Outdated
Comment thread receita/tools/get.py Outdated
Comment thread receita/tools/build.py Outdated
Comment thread tests/test_build.py Outdated
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>
Copilot AI review requested due to automatic review settings September 3, 2026 18:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

  • Runner now accepts api_type/base_url, but work() does not handle Client.get() raising (it can raise ValueError for an invalid api_type). In a worker thread this would terminate the thread and the main iterator can block forever on _results.get(). Validate api_type in Runner.__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 bare except: around the HTTP call, which will also swallow KeyboardInterrupt/SystemExit and make CLI aborts unreliable (it can also mask other programming errors). Narrow the exception to request/JSON-related failures and keep the existing None-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

Comment thread receita/tools/build.py Outdated
- 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>
Copilot AI review requested due to automatic review settings September 3, 2026 19:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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() raises ValueError for an invalid api_type. In a worker thread this would crash the thread and can cause the iterator to block forever waiting on _results.get(). Validating api_type up-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 a TypeError whenever days+token are provided, and the request will never be sent. This also breaks tests that assert a Bearer <token> header.
    receita/tools/runner.py:72
  • Client(...) is called with multiple positional arguments, which is easy to mis-order now that api_type and base_url were 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>
Copilot AI review requested due to automatic review settings September 3, 2026 19:20

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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() returning None), 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 catches BaseException (including KeyboardInterrupt/SystemExit) and can hide unexpected failures. Also, json.loads(response.content) can raise ValueError/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

Comment thread receita/tools/client.py
- 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>
Copilot AI review requested due to automatic review settings September 3, 2026 19:33
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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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 bare except: which also catches KeyboardInterrupt/SystemExit and can hide non-network programming errors. It’s safer to catch requests.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: setuptools dynamic version = { 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 whenever Client.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, causing get to 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

Copilot AI review requested due to automatic review settings September 3, 2026 19:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

Comment thread receita/tools/runner.py
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>
Copilot AI review requested due to automatic review settings September 3, 2026 19:57
@vkruoso
vkruoso merged commit 94d296f into leads2b:master Sep 3, 2026
5 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 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], license is 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants