Skip to content

Fails to parse api.tidsbanken.net API response with "ValueError: dictionary update sequence element #0 has length 1; 2 is required" #1370

@Talkless

Description

@Talkless

Describe the bug

I tired to use openapi-python-client to generate API wrapper for some Norwegian time tracking system (api.tidsbanken.net/developer.tidsbanken.net).

Some api call https://api.tidsbanken.net/ansatt/ansatt?%24select=Id&%24top=3 (ansatt = worker) returns:

b'{"@odata.context":"https://api.tidsbanken.net/ansatt/$metadata#Ansatt(Id)","value":[{"Id":1},{"Id":4},{"Id":5}]}'

But that results in error:

Traceback (most recent call last):
  File "/home/vincas/code/python/tidsbanken_test/tidsbanken_test.py", line 82, in <module>
    main()
  File "/home/vincas/code/python/tidsbanken_test/tidsbanken_test.py", line 77, in main
    emp = get_all_employees(client)  
          ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vincas/code/python/tidsbanken_test/tidsbanken_test.py", line 21, in get_all_employees
    return getansatt.sync(
           ^^^^^^^^^^^^^^^
  File "/home/vincas/code/python/tidsbanken_test/ansatt-client/ansatt_client/api/ansatt/getansatt.py", line 184, in sync
    return sync_detailed(
           ^^^^^^^^^^^^^^
  File "/home/vincas/code/python/tidsbanken_test/ansatt-client/ansatt_client/api/ansatt/getansatt.py", line 141, in sync_detailed
    return _build_response(client=client, response=response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vincas/code/python/tidsbanken_test/ansatt-client/ansatt_client/api/ansatt/getansatt.py", line 82, in _build_response
    parsed=_parse_response(client=client, response=response),
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vincas/code/python/tidsbanken_test/ansatt-client/ansatt_client/api/ansatt/getansatt.py", line 61, in _parse_response
    componentsschemas_get_ansatt_model_item = GetAnsattModelItem.from_dict(
                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/vincas/code/python/tidsbanken_test/ansatt-client/ansatt_client/models/get_ansatt_model_item.py", line 295, in from_dict
    d = dict(src_dict)
        ^^^^^^^^^^^^^^
ValueError: dictionary update sequence element #0 has length 1; 2 is required

OpenAPI Spec File

https://gist.github.com/Talkless/6199fbb4b0b4f62d0b2da6d4c78ec344

Desktop (please complete the following information):

  • OS: Debian 12 amd64
  • Python Version: 3.11
  • openapi-python-client version 0.27.1

Additional context

ChatGPT suggested to patch def _parse_response in ansatt-client/ansatt_client/api/ansatt/getansatt.py into:

def _parse_response(
    *, client: AuthenticatedClient | Client, response: httpx.Response
) -> list[GetAnsattModelItem] | None:
    if response.status_code == 200:
        data = response.json()

        # OData always wraps results inside "value"
        if isinstance(data, dict) and "value" in data:
            items = data["value"]
        else:
            items = data

        result = []

        for raw in items:
            # raw is a dict like {"Id": 1}
            result.append(GetAnsattModelItem.from_dict(raw))

        return result

    if client.raise_on_unexpected_status:
        raise errors.UnexpectedStatus(response.status_code, response.content)

    return None

And then it starts working.

Now I get list of GetAnsattModelItem (with just Id as requested):

[GetAnsattModelItem(id=1, fornavn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, etternavn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, adresse=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, postnummer=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, poststed=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, tlf_privat=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, mobil=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, epost=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, fodt=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, tittel=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, lonnskonto=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ansatt_dato=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, personnummer=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, sluttet=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, sluttet_dato=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, avdeling_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, aktiv=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, identifikator=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, antall_ferie_dager=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, notat=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, prosjekt_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, stillingsprosent=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, element_1_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, element_2_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, kjonn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, kommune=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ansatt_gruppe_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, innleie=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, fastlonn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ice_navn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ice_nr=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, personalia_bekreftet=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, antall_halve_ferie_dager=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, utelat_rapporter=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, utelat_status_rapporter=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, utelat_fra_lonn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, filnavn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ekstern_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, timelonn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, sist_endret_dato=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, sist_endret_av_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, opprettet_dato=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, opprettet_av_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, additional_properties={}), GetAnsattModelItem(id=4, fornavn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, etternavn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, adresse=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, postnummer=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, poststed=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, tlf_privat=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, mobil=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, epost=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, fodt=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, tittel=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, lonnskonto=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ansatt_dato=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, personnummer=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, sluttet=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, sluttet_dato=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, avdeling_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, aktiv=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, identifikator=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, antall_ferie_dager=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, notat=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, prosjekt_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, stillingsprosent=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, element_1_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, element_2_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, kjonn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, kommune=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ansatt_gruppe_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, innleie=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, fastlonn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ice_navn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ice_nr=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, personalia_bekreftet=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, antall_halve_ferie_dager=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, utelat_rapporter=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, utelat_status_rapporter=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, utelat_fra_lonn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, filnavn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ekstern_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, timelonn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, sist_endret_dato=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, sist_endret_av_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, opprettet_dato=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, opprettet_av_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, additional_properties={}), GetAnsattModelItem(id=5, fornavn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, etternavn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, adresse=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, postnummer=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, poststed=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, tlf_privat=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, mobil=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, epost=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, fodt=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, tittel=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, lonnskonto=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ansatt_dato=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, personnummer=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, sluttet=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, sluttet_dato=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, avdeling_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, aktiv=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, identifikator=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, antall_ferie_dager=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, notat=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, prosjekt_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, stillingsprosent=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, element_1_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, element_2_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, kjonn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, kommune=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ansatt_gruppe_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, innleie=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, fastlonn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ice_navn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ice_nr=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, personalia_bekreftet=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, antall_halve_ferie_dager=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, utelat_rapporter=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, utelat_status_rapporter=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, utelat_fra_lonn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, filnavn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, ekstern_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, timelonn=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, sist_endret_dato=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, sist_endret_av_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, opprettet_dato=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, opprettet_av_id=<ansatt_client.types.Unset object at 0x7f33deffe0d0>, additional_properties={})]

So in the end I am not sure what to make of it:

  • Does tidsbanken api returns result in non-conforming format?
  • Is .yaml specification wrong so that openapi-python-client generates sub-optimal code?
  • Or it's just some bug in openapi-python-client?

Thanks!

My example application:

from pathlib import Path
from typeguard import typechecked
import httpx

import sys

# Add the folder that contains ansatt_client/
sys.path.append(str(Path(__file__).parent / "ansatt-client"))

from ansatt_client import Client
from ansatt_client.api.ansatt import getansatt

class Consts:
    TB_KEY = "redacted" # "API key"
    SUBSCRIPTION_KEY = "redacted"

@typechecked
def get_all_employees(client: Client):
    return getansatt.sync(
    client=client,
    select="Id",
    top=3,
    tb_key = Consts.TB_KEY)
    
    
def log_request(request):
    print(f"Request event hook: {request.method} {request.url} - Waiting for response")

def log_response(response: httpx.Response):
    request = response.request

    print(f"Response event hook: {request.method} {request.url} - Status {response.status_code}")

    print("---- HEADERS ----")
    for k, v in response.headers.items():
        print(f"{k}: {v}")
    print("---- END HEADERS ----")

    try:
        response.read()
    except Exception as e:
        print("ERROR during response.read():", e)

    print("---- RAW RESPONSE BODY ----")
    try:
        # Show raw for debugging JSON truncation
        print(response.content)
    except Exception as e:
        print("ERROR reading .content:", e)
    print("---- END RAW RESPONSE BODY ----")


def main():
    client = Client(
    base_url="https://api.tidsbanken.net/ansatt",
    timeout=30,
    headers={
        "tb-key": Consts.TB_KEY,
        "Ocp-Apim-Subscription-Key": Consts.SUBSCRIPTION_KEY,
        "x-api-version": "3.0",
        "Cache-Control": "no-cache",
    },
    httpx_args={
        "event_hooks": {
            "request": [log_request],
            "response": [log_response]
        }
    })
    
    emp = get_all_employees(client)  
    print(emp)
    
    
if __name__ == "__main__":
    main()


Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions