66.. image :: https://coveralls.io/repos/bcj/AttrDict/badge.png?branch=master
77 :target: https://coveralls.io/r/bcj/AttrDict?branch=master
88
9-
10- AttrDict is a 2.6, 2.7, 3-compatible dictionary that allows its elements
11- to be accessed both as keys and as attributes::
9+ AttrDict is an MIT-licensed library that provides mapping objects that allow
10+ their elements to be accessed both as keys and as attributes::
1211
1312 > from attrdict import AttrDict
1413 > a = AttrDict({'foo': 'bar'})
@@ -17,18 +16,15 @@ to be accessed both as keys and as attributes::
1716 > a['foo']
1817 'bar'
1918
20- With this, you can easily create convenient, hierarchical settings
21- objects.
22-
23- ::
19+ Attribute access makes it easy to create convenient, hierarchical settings
20+ objects::
2421
25- with open('settings.yaml', 'r' ) as fileobj:
22+ with open('settings.yaml') as fileobj:
2623 settings = AttrDict(yaml.safe_load(fileobj))
2724
2825 cursor = connect(**settings.db.credentials).cursor()
2926
30- cursor.execute("SELECT column FROM table");
31-
27+ cursor.execute("SELECT column FROM table;")
3228
3329Installation
3430============
@@ -42,80 +38,110 @@ Or from Github::
4238 $ cd AttrDict
4339 $ python setup.py install
4440
45- Documentation
46- =============
47-
48- Documentation is available at https://github.com/bcj/AttrDict
49-
50- Usage
51- =====
52- Creation
53- --------
54- An empty AttrDict can be created with::
41+ Basic Usage
42+ ===========
43+ AttrDict comes with three different classes, `AttrMap `, `AttrDict `, and
44+ `AttrDefault `. They are all fairly similar, as they all are MutableMappings (
45+ read: dictionaries) that allow creating, accessing, and deleting key-value
46+ pairs as attributes.
47+
48+ Valid Names
49+ -----------
50+ Any key can be used as an attribute as long as:
51+
52+ #. The key represents a valid attribute (i.e., it is a string comprised only of
53+ alphanumeric characters and underscores that doesn't start with a number)
54+ #. The key represents a public attribute (i.e., it doesn't start with an
55+ underscore). This is done (in part) so that implementation changes between
56+ minor and micro versions don't force major version changes.
57+ #. The key does not shadow a class attribute (e.g., get).
58+
59+ Attributes vs. Keys
60+ -------------------
61+ There is a minor difference between accessing a value as an attribute vs.
62+ accessing it as a key, is that when a dict is accessed as an attribute, it will
63+ automatically be converted to an Attr object. This allows you to recursively
64+ access keys::
65+
66+ > attr = AttrDict({'foo': {'bar': 'baz'}})
67+ > attr.foo.bar
68+ 'baz'
5569
56- a = AttrDict()
70+ Relatedly, by default, sequence types that aren't `bytes `, `str `, or `unicode `
71+ (e.g., lists, tuples) will automatically be converted to tuples, with any
72+ mappings converted to Attrs::
5773
58- Or, you can pass an existing ``dict `` (or other type of ``Mapping `` object)::
74+ > attr = AttrDict({'foo': [{'bar': 'baz'}, {'bar': 'qux'}]})
75+ > for sub_attr in attr.foo:
76+ > print(subattr.foo)
77+ 'baz'
78+ 'qux'
5979
60- a = AttrDict({'foo': 'bar'})
80+ To get this recursive functionality for keys that cannot be used as attributes,
81+ you can replicate the behavior by calling the Attr object::
6182
62- NOTE: Unlike ``dict ``, AttrDict will not clone on creation. AttrDict's
63- internal dictionary will be the same instance as the dict passed in.
83+ > attr = AttrDict({1: {'two': 3}})
84+ > attr(1).two
85+ 3
6486
65- Access
66- ------
67- AttrDict can be used *exactly * like a normal dict::
87+ Classes
88+ -------
89+ AttrDict comes with three different objects, `AttrMap `, `AttrDict `, and
90+ `AttrDefault `.
6891
69- > a = AttrDict()
70- > a['foo'] = 'bar'
71- > a['foo']
72- 'bar'
73- > '{foo}'.format(**a)
74- 'bar'
75- > del a['foo']
76- > a.get('foo', 'default')
77- 'default'
92+ AttrMap
93+ ^^^^^^^
94+ The most basic implementation. Use this if you want to limit the number of
95+ invalid keys, or otherwise cannot use `AttrDict `
7896
79- AttrDict can also have it's keys manipulated as attributes to the object::
97+ AttrDict
98+ ^^^^^^^^
99+ An Attr object that subclasses `dict `. You should be able to use this
100+ absolutely anywhere you can use a `dict `. While this is probably the class you
101+ want to use, there are a few caveats that follow from this being a `dict ` under
102+ the hood.
80103
81- > a = AttrDict()
82- > a.foo = 'bar'
83- > a.foo
84- 'bar'
85- > del a.foo
104+ The `copy ` method (which returns a shallow copy of the mapping) returns a
105+ `dict ` instead of an `AttrDict `.
86106
87- Both methods operate on the same underlying object, so operations are
88- interchangeable. The only difference between the two methods is that
89- where dict-style access would return a dict, attribute-style access will
90- return an AttrDict. This allows recursive attribute-style access::
107+ Recursive attribute access results in a shallow copy, so recursive assignment
108+ will fail (as you will be writing to a copy of that dictionary)::
91109
92- > a = AttrDict({'foo': {'bar': 'baz'}})
93- > a.foo.bar
94- 'baz'
95- > a['foo'].bar
96- AttributeError: 'dict' object has no attribute 'bar'
110+ > attr = AttrDict('foo': {})
111+ > attr.foo.bar = 'baz'
112+ > attr.foo
113+ {}
97114
98- There are some valid keys that cannot be accessed as attributes. To be
99- accessed as an attribute, a key must:
115+ Assignment as keys will still work::
100116
101- * be a string
117+ > attr = AttrDict('foo': {})
118+ > attr['foo']['bar'] = 'baz'
119+ > attr.foo
120+ {'bar': 'baz'}
102121
103- * start with an alphabetic character
122+ If either of these caveats are deal-breakers, or you don't need your object to
123+ be a `dict `, consider using `AttrMap ` instead.
104124
105- * be comprised solely of alphanumeric characters and underscores
125+ AttrDefault
126+ ^^^^^^^^^^^
127+ At Attr object that behaves like a `defaultdict `. This allows on-the-fly,
128+ automatic key creation::
106129
107- * not map to an existing attribute name (e.g., get, items)
130+ > attr = AttrDefault(int, {})
131+ > attr.foo += 1
132+ > attr.foo
133+ 1
108134
109- To access these attributes while retaining an AttrDict wrapper (or to
110- dynamically access any key as an attribute) ::
135+ AttrDefault also has a ` pass_key ` option that passes the supplied key to the
136+ ` default_factory ` ::
111137
112- > a = AttrDict({'_foo': {'bar': 'baz'}} )
113- > a('_foo').bar
114- 'baz'
138+ > attr = AttrDefault(sorted, {}, pass_key=True )
139+ > attr.banana
140+ ['a', 'a', 'a', 'b', 'n', 'n']
115141
116142Merging
117143-------
118- AttrDicts can be merged with each other or other dict objects using the
144+ All three Attr classes can be merged with eachother or other Mappings using the
119145``+ `` operator. For conflicting keys, the right dict's value will be
120146preferred, but in the case of two dictionary values, they will be
121147recursively merged::
@@ -164,54 +190,6 @@ When merging an AttrDict with another mapping, this behavior will be disabled
164190if at least one of the merged items is an AttrDict that has set ``recursive ``
165191to ``False ``.
166192
167- DefaultDict
168- ===========
169-
170- AttrDict supports defaultdict-style automatic creation of attributes::
171-
172- > adict = AttrDict(default_factory=list)
173- > adict.foo
174- []
175-
176- Furthermore, if ``pass_key=True ``, then the key will be passed to the function
177- used when creating the value::
178-
179- > adict = AttrDict(default_factory=lambda value: value.upper(), pass_key=True)
180- > adict.foo
181- 'FOO'
182-
183- load
184- ====
185- A common usage for AttrDict is to use it in combination with settings files to
186- create hierarchical settings. attrdict comes with a load function to make this
187- easier::
188-
189- from attrdict import load
190-
191- settings = load('settings.json')
192-
193- By default, ``load `` uses ``json.load `` to load the settings file, but this can
194- be overridden by passing ``load_function=YOUR_LOAD_FUNCTION ``.
195-
196- ``load `` supports loading from multiple files at once. This allows for
197- overriding of default settings, e.g.::
198-
199- from attrdict import load
200- from yaml import safe_load
201-
202- # config.yaml =
203- # emergency:
204- 205- # message: Something went wrong
206- #
207- # user.yaml =
208- # emergency:
209- 210- settings = load('config.yaml', 'user.yaml', load_function=safe_load)
211-
212- assert settings.email == '[email protected] ' 213- assert settings.message == 'Something went wrong'
214-
215193License
216194=======
217195AttrDict is released under a MIT license.
0 commit comments