Skip to content

v2.1.4: every API method fires its HTTP request twice (duplicate call_api in every method) #100

@bennemann

Description

@bennemann

Summary

In v2.1.4 (current release), every method in every API class contains a literal duplicate self.api_client.call_api(...) invocation, causing every API call to fire its HTTP request to the FIC server twice. The second response overwrites the first one, so the SDK only returns one — but both hit the server.

Source

In fattureincloud_python_sdk/api/issued_documents_api.py at lines 129–137 (create_issued_document method) and at identical patterns throughout every other method:

https://github.com/fattureincloud/fattureincloud-python-sdk/blob/v2.1.4/fattureincloud_python_sdk/api/issued_documents_api.py#L129-L137

response_data = self.api_client.call_api(
    *_param,
    _request_timeout=_request_timeout
)
response_data = self.api_client.call_api(    # ← identical duplicate; same args, same target variable
    *_param,
    _request_timeout=_request_timeout
)
response_data.read()

The pattern repeats throughout the SDK:

  • issued_documents_api.py: 108 call_api( invocations (54 method variants × 2 each)
  • info_api.py: 96 (48 × 2)
  • received_documents_api.py: 96 (48 × 2)
  • settings_api.py: 90 (45 × 2)
  • receipts_api.py: 42 (21 × 2)
  • taxes_api.py: 42 (21 × 2)
  • … same pattern in every api/*.py

Reproduction

import logging
logging.basicConfig(level=logging.DEBUG)

import fattureincloud_python_sdk as fic

configuration = fic.Configuration(access_token="<your token>")
with fic.ApiClient(configuration) as client:
    fic.UserApi(client).list_user_companies()

urllib3.connectionpool debug output shows two GET /user/companies requests for one method call:

DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): api-v2.fattureincloud.it:443
DEBUG:urllib3.connectionpool:https://api-v2.fattureincloud.it:443 "GET /user/companies HTTP/1.1" 200 None
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (2): api-v2.fattureincloud.it:443
DEBUG:urllib3.connectionpool:https://api-v2.fattureincloud.it:443 "GET /user/companies HTTP/1.1" 200 None

Cross-verified against the FIC server-side audit log (Impostazioni → Sviluppatore → app → "Mostra log") — both requests appear there with identical timestamps, confirming this is not a client-side logging artifact.

Impact

Method type Effect of the duplicate call
GET (idempotent) 2× requests; identical 200 responses; wastes quota & bandwidth but no functional bug
POST create_* Creates two resources per script call. Verified for IssuedDocumentsApi.create_issued_document — calling it once produces two issued documents (visible in FIC and via subsequent list) with consecutive doc IDs.
DELETE delete_* First request 200 (deletes the resource); second request 404 (already gone). The SDK returns the 404 to the caller, so user code may log "delete failed" while the resource is actually deleted server-side. Real and confusing failure mode.
PUT modify_* Both succeed; usually idempotent in practice.

The DELETE behavior is especially confusing because the SDK's response actively misleads callers about what happened on the server.

Suggested fix

This looks like a codegen template issue — the SDK files are clearly generated. The fix is one line in whichever Mustache/Jinja template emits the response_data = self.api_client.call_api(...) block: remove the duplicate emission. Then re-generate.

Happy to send a PR if you point me at the template repo (presumably an openapi-generator config or a custom template repo).

Workaround for users hitting this in the meantime

Monkey-patch ApiClient.call_api to dedupe identical back-to-back invocations on the same instance within a small time window (the duplicates in practice fire ~260 ms apart). Apply at import time, before any Api class is instantiated.

import functools, hashlib, time
from fattureincloud_python_sdk.api_client import ApiClient

_orig = ApiClient.call_api

@functools.wraps(_orig)
def _patched(self, method, url, header_params=None, body=None, post_params=None, _request_timeout=None):
    key = (method, url, hashlib.sha1((body or b"") if isinstance(body, (bytes, bytearray)) else str(body or "").encode()).hexdigest())
    now = time.monotonic()
    cache = getattr(self, "__patch_last", None)
    if cache and cache[0] == key and (now - cache[1]) < 2.0:
        return cache[2]  # collapse SDK's duplicate to one wire request
    resp = _orig(self, method, url, header_params, body, post_params, _request_timeout)
    self.__patch_last = (key, now, resp)
    return resp

ApiClient.call_api = _patched

Thanks for maintaining the SDK!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions