pythonpython-typingmypyzeep

How can I type-hint a nested object in Python?


I'm currently doing a integration with WSDL, and such decided to go with Python using the Zeep library.

I'm trying to model the response with mypy, so that it works with VSCode's Intellisense, as well as some giving me hints when I'm doing careless assignments or modifications. But I hit a roadblock when the WSDL responses is in a nested object, and I can't figure a way to type-hint it.

Sample response from WSDL:

{
    'result': {
        'code': '1',
        'description': 'Success',
        'errorUUID': None
    },
    'accounts': {
        'accounts': [
            {
                'accountId': 1,
                'accountName': 'Ming',
                'availableCredit': 1
            }
        ]
    }
}

I'm using the following snippet to type-hint:

class MethodResultType:
    code: str
    description: str
    errorUUID: str

class AccountType:
    accountId: int
    accountName: str
    availableCredit: float

class getAccounts:
    result: MethodResultType
    accounts: List[AccountType] # Attempt 1
    accounts = TypedDict("accounts", {"accounts": List[AccountType]}) # Attempt 2

client = Client(os.getenv("API_URL"), wsse=user_name_token)
accountsResponse: getAccounts = client.service.getAccounts()
accounts = accountsResponse.accounts.accounts


# Attempt 1: "List[AccountType]" has no attribute "accounts"; maybe "count"?
# Attempt 2: "Type[accounts]" has no attribute "accounts"

For Attempt 1, the reason is obvious. But after trying Attempt 2, I don't know how to proceed anymore. What am I missing here?

Update: Following @Avi Kaminetzky's answer, I tried with following (playground):

from typing import List, TypedDict, Optional, Dict

class MethodResultType(TypedDict):
    code: str
    description: str
    errorUUID: Optional[str]

class AccountType(TypedDict):
    accountId: int
    accountName: str
    availableCredit: float

class getAccounts(TypedDict):
    result: MethodResultType
    accounts: Dict[str, List[AccountType]]

result: getAccounts = {
    'result': {
        'code': '1',
        'description': 'Success',
        'errorUUID': None
    },
    'accounts': {
        'accounts': [
            {
                'accountId': 1,
                'accountName': 'Ming',
                'availableCredit': 1
            }
        ]
    }
}

print(result.result)
print(result.accounts)

But I'm getting error message from mypy:

"getAccounts" has no attribute "result"
"getAccounts" has no attribute "accounts"

Solution

  • Using this answer as reference, the following works for my case (Intellisense in VSCode, not working if directly assign into a variable):

    Update: Using another answer as reference, I have updated my code to be able to work both ways. (Intellisense in VSCode, directly assign into a variable)

    class MethodResultType:
        code: str
        description: str
        errorUUID: str
    
    class AccountType:
        accountId: int
        accountName: str
        availableCredit: float
    
    class accounts:
        accounts: List[AccountType]
    
    class getAccounts:
        def __init__(self):
            self.accounts = accounts()
        result: MethodResultType
        @property
        def accounts(self):
            return self.accounts
    
    
    client = Client(os.getenv("API_URL"), wsse=user_name_token)
    
    
    # Getting real response from WSDL
    accountsResponse: getAccounts = client.service.getAccounts()
    
    
    # For testing using sample response
    sampleResponse: getAccounts = getAccounts({
        'result': {
            'code': '1',
            'description': 'Success',
            'errorUUID': None
        },
        'accounts': {
            'accounts': [
                {
                    'accountId': 1,
                    'accountName': 'Ming',
                    'availableCredit': 1
                }
            ]
        }
    })