DotDict¶
New in version 1.0dev15: DotDict
added.
Warning
The following pertains to features added in v1.0dev15 related to MWS requests.
These features are disabled by default. To use these features, set flag _use_feature_mwsresponse
to True
on an API class instance before making any requests:
api_class = Orders(...)
api_class._use_feature_mwsresponse = True
If the flag is False
, all requests will return either DictWrapper
or DataWrapper
objects (deprecated);
and parsed XML contents will be returned as an instance of ObjectDict
(deprecated).
New features using MWSResponse
and DotDict
will become the default in v1.0.
The DotDict
class is a subclass of a standard Python dict that provides
access to its keys as attributes. This object is used mainly for parsed XML content returned by
MWSResponse.parsed
and
MWSResponse.metadata
, but DotDict
can also be used as a
general-purpose dict
replacement (with some caveats, as shown below).
Keys as attributes¶
While keys of a DotDict
can be accessed the same as keys in a standard dict
, they can also be accessed
as attributes:
from mws.utils.collections import DotDict
foo = DotDict({'spam': 'ham'})
print(foo['spam'])
# 'ham'
print(foo.spam)
# 'ham'
print(foo.get('spam'))
# 'ham'
This is useful for traversing the nested structures created by parsing XML documents, where several keys are required in order to access a leaf node.
Consider the following (truncated and edited) example response from the MWS operation ListMatchingProducts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <?xml version="1.0"?> <ListMatchingProductsResponse xmlns="http://mws.amazonservices.com/schema/Products/2011-10-01"> <ListMatchingProductsResult> <Products> <Product> <Identifiers> <MarketplaceASIN> <MarketplaceId>ACBDEFGH</MarketplaceId> <ASIN>B0987654</ASIN> </MarketplaceASIN> </Identifiers> </Product> </Products> </ListMatchingProductsResult> </ListMatchingProductsResponse> |
When this document is parsed, accessing the <ASIN>
on line 9 using dict keys looks like the following:
# assuming `response` is an instance of `MWSResponse`
asin = response.parsed['Products']['Product']['Identifiers']['MarketplaceASIN']['ASIN']
Using attribute access, the above call turns into:
asin = response.parsed.Products.Product.Identifiers.MarketplaceASIN.ASIN
And, of course, a mix of the different methods is possible:
asin = response.parsed['Products'].get('Product').Identifiers['MarketplaceASIN'].get('ASIN')
Tip
Accessing specific data in an MWS response will often produce lengthy code lines, as the above samples show. We recommend following best practices for Python programs in general, breaking up these longer lines by assigning chunks of data to intermediary variables:
product = response.parsed.Products.Product
asin = product.Identifiers.MarketplaceASIN.ASIN
Native iteration¶
XML represents sequences of similar objects by having sibling tags with the same tag name. Consider the following
toy example with three <Product>
tags:
<Response>
<Products>
<Product>
<Name>spam</Name>
</Product>
<Product>
<Name>ham</Name>
</Product>
<Product>
<Name>eggs</Name>
</Product>
</Products>
</Response>
When parsed, these are collected into a list of DotDict
instances:
DotDict({
'Products': DotDict({
'Product': [
DotDict({'Name': 'spam'}),
DotDict({'Name': 'ham'}),
DotDict({'Name': 'eggs'}),
]
})
})
Note
The list of objects will always be found under the same key name as the duplicate tags, i.e. Product
;
not under their parent key, Products
. This may seem counterintuitive, but the parser is simply preserving all
tag names present in the XML document.
Further, if a tag attribute is present on the parent <Products>
tag, you would be able to access it as a
separate key at the same level as Product
. This would not be possible if Products
returned a list.
To gather the names of all products in this response, we can simply iterate over this list:
names = []
for product in response.parsed.Products.Product:
names.append(product.Name)
print(names)
# ['spam', 'ham', 'eggs']
If the same request returns only one <Product>
tag, the Product
key in the parsed response will return only
a single DotDict
, similar to any other node in the XML tree. Trying to access the Product
node in this case
as though it were a list - such as using indices (.Product[0]
) - will result in errors.
However, when a DotDict
is iterated, it will wrap itself in a list in order to provide the same interface as before.
So, for an XML response like so:
<Response>
<Products>
<Product>
<Name>foo</Name>
</Product>
</Products>
</Response>
…the same Python code can be used to access “all” Product
keys:
names = []
for product in response.parsed.Products.Product:
names.append(product.Name)
print(names)
# ['foo']
Note
While DotDict
is a subclass of dict
, this behavior is different from that of the standard dict
,
where iterating directly on the dict
object is equivalent to iterating on dict.keys()
. We have chosen to
implement the above behavior to more closely match most users’ intended usage when working with parsed XML,
even though DotDict
can be used much like a standard dict
for (most) general purposes.
Recursive conversion of dict objects¶
DotDict
instances expect to hold nested data, as seen in the examples throughout this document. As such, any
dict
assigned as a value to a DotDict
is automatically converted to a DotDict
, as well. The values
of the assigned dict
are then recursively built the same way, such that every dict
(or other mapping type)
instance in the structure is also converted to DotDict
.
This holds true in a variety of scenarios:
Wrapping a nested
dict
inDotDict
:example1 = DotDict({'spam': {'ham': {'eggs': 'juice'}}}) print(example1) # DotDict({'spam': DotDict({'ham': DotDict({'eggs': 'juice'})})})
Using kwargs to build
DotDict
, with adict
as one of the values:example2 = DotDict(spam={'muffin': {'cereal': 'milk'}}) print(example2) DotDict({'spam': DotDict({'muffin': DotDict({'cereal': 'milk'})})})
Assigning a
dict
to a key of an existingDotDict
, including creating new keys:example3 = DotDict() example3.pancakes = {'maple': 'syrup'} print(example3) # DotDict({'pancakes': DotDict({'maple': 'syrup'})}) example3.pancakes.toast = {'strawberry': 'jam'} print(example3) # DotDict({'pancakes': DotDict({'maple': 'syrup', 'toast': DotDict({'strawberry': 'jam'})})})
Using
DotDict.update
in a similar manner asdict.update
:example4 = DotDict() example4.update({'chicken': {'waffles': 'honey'}}) print(example4) # DotDict({'chicken': DotDict({'waffles': 'honey'})}) # Including a mix of a plain dict and kwargs example5 = DotDict() example5.update({'running': {'out': 'of'}}, food='examples', to={'use': 'here'}) print(example5) # DotDict({'running': DotDict({'out': 'of'}), 'food': 'examples', 'to': DotDict({'use': 'here'})})
Working with XML tag attributes¶
DotDict
is used in python-amazon-mws primarily for parsed XML content. As such, some features of the class are
specialized for working with that content.
XML tags can contain attributes with additional data points. When parsed, these attributes are assigned to their own
dict keys starting with @
, differentiating them from normal tag names.
Further, tags that contain an attribute and text content will store the text on a special key, #text
.
For example, with the following XML document:
<Response>
<Products>
<Product Name="spam">
<SomethingElse>ham</SomethingElse>
<WhatHaveYou anotherAttr="foo">eggs</WhatHaveYou>
</Product>
</Products>
</Response>
The parsed response would look like:
DotDict({
'Products': DotDict({
'Product': DotDict({
'@Name': 'spam',
'SomethingElse': 'ham',
'WhatHaveYou': DotDict({
'@anotherAttr': 'foo',
'#text': 'eggs'
})
})
})
})
These @
and #text
keys cannot be accessed directly as attributes due to Python syntax, which reserves the
@
and #
characters. You can still use standard dict keys to access this content:
print(dotdict.Products.Product['@Name'])
# 'spam'
print(dotdict.Products.Product.WhatHaveYou['#text'])
# 'eggs'
DotDict
also allows accessing these keys using a fallback method. Simply provide the key name without
@
or #
in front, and it will attempt to find a matching key:
print(dotdict.Products.Product.Name)
# 'spam'
print(dotdict.Products.Product.WhatHaveYou.text)
# 'eggs'
Note
In case of a conflicting key name, a key matching the attribute will be returned first:
dotdict = DotDict({'foo': 'spam', '@foo': 'ham'})
print(dotdict.foo)
# 'spam'
print(dotdict['@foo'])
# 'ham'
This conflict is a rare occurrence for most XML documents, however, as they are not likely to return a tag attribute with the same name as an immediate child tag.
DotDict API¶
-
class
mws.
DotDict
(*args, **kwargs)[source]¶ Read-only dict-like object class that wraps a mapping object.
New in version 1.0dev15.
-
update
(*args, **kwargs)[source]¶ Recursively builds values in any nested objects, such that any mapping object in the nested structure is converted to a
DotDict
.Each nested mapping object will be converted to
DotDict
.Each non-string, non-dict iterable will have elements built, as well.
All other objects in the data are left unchanged.
-