indigo153
indigo153

Reputation: 1193

How to safely unpack dict in python?

I have a wrapper class - it's an abstraction that I return from backend to frontend.

from typing import NamedTuple

class NewsItem(NamedTuple):
    id: str
    title: str
    content: str
    service: str
    published_at: datetime

    @classmethod
    def from_payload(cls, payload) -> 'NewsItem':
        return cls(**payload)

For example, when I get data from elastic I convert it to NewsItem:

return [NewsItem.from_payload(hit['_source'])
        for hit in result['hits']['hits']]

The problem is I don't want to fail because of unknown fields that can come from elastic. How to ignore them (or put into a separate dedicated attribute list NewsItem.extra)?

Upvotes: 3

Views: 1463

Answers (3)

Benjamin Toueg
Benjamin Toueg

Reputation: 10867

You can use **kwargs to let your __init__ take an arbitrary number of keyword arguments ("kwargs" means "keyword arguments") and discard unnecessary arguments:

class NewsItem(NamedTuple):
    id: str
    title: str
    content: str
    service: str
    published_at: datetime

    @classmethod
    def from_payload(cls, id=None, title=None, content=None, service=None, published_at=None, **kwargs) -> 'NewsItem':
        return cls(id, title, content, service, published_at)

Alternative solution with introspection NamedTuple class attributes (see @MOROZILnic answer + comment)

Upvotes: 1

Andrew Morozko
Andrew Morozko

Reputation: 2816

I think the most elegant way is to use ._fields of NewsItem:

@classmethod
def from_payload(cls, payload) -> 'NewsItem':
    return cls(*(payload[field] for field in cls._fields))

If you want to keep extras, you would need to do some work (field extra declared as extra: dict = {}):

@classmethod
def from_payload(cls, payload) -> 'NewsItem':
    fields_no_extra = set(cls._fields) - {'extra'}
    extra_fields = payload.keys() - fields_no_extra
    extras = {field: payload[field] for field in extra_fields}
    data = {field: payload[field] for field in fields_no_extra}
    data['extra'] = extras
    return cls(**data)

You can optimize this further, too much computation with sets;)

Of course my solutions do not handle case where payload doesn't contain all of the fields of the NewsItem

Upvotes: 2

rawwar
rawwar

Reputation: 4992

Since your problem is with the unknown key's you can use get method of the dictionary to safely ignore unknown keys.

For get method, first argument is the key you are looking for and the second argument is the Default value which will be returned when the key is not found.

so, do the following

return [NewsItem.from_payload(hit['_source'])
        for hit in result.get('hits',{}).get('hits',"NOT FOUND"):

The above is just a example. do modify what you want to get when the hit does not have the key you want.

Upvotes: 0

Related Questions