Skip to content
This repository was archived by the owner on Feb 2, 2019. It is now read-only.

Commit 9f67299

Browse files
committed
Merge pull request #32 from bcj/v2.0
V2.0
2 parents ac01cda + 39a1e5d commit 9f67299

23 files changed

+1965
-1389
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ MANIFEST
44
build/*
55
dist/*
66
.tox
7+
attrdict.egg-info/*

.travis.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ language: python
22
python:
33
- "3.4"
44
- "3.3"
5+
- "3.2"
56
- "2.7"
67
- "2.6"
78
- "pypy"
9+
- "pypy3"
810
install:
911
- "pip install -r requirements-tests.txt"
1012
- "python setup.py install"
11-
script: nosetests --with-coverage --cover-package attrdict -v
13+
script: "python setup.py nosetests && flake8 attrdict tests"
1214
after_success:
1315
- coveralls

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ v0.5.1, 2014/07/14 -- tox for local testing, README fix, 0.5.0 no longer from th
1010
v1.0.0, 2014/08/18 -- Development Status :: 5 - Production/Stable
1111
v1.1.0, 2014/10/29 -- has_key support to match python2 dicts (by Nikolaos-Digenis Karagiannis @Digenis)
1212
v1.2.0, 2014/11/26 -- Happy U.S. Thanksgiving, now you can pickle AttrDict! (by @jtratner), bugfix: default_factory will no longer be erroneously called when accessing private attributes.
13+
v2.0, 2015/04/09 -- Happy PyCon. An almost-complete rewrite. Hopefully in a good way

README.rst

Lines changed: 90 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ AttrDict
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

3329
Installation
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

116142
Merging
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
120146
preferred, but in the case of two dictionary values, they will be
121147
recursively merged::
@@ -164,54 +190,6 @@ When merging an AttrDict with another mapping, this behavior will be disabled
164190
if at least one of the merged items is an AttrDict that has set ``recursive``
165191
to ``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-
215193
License
216194
=======
217195
AttrDict is released under a MIT license.

0 commit comments

Comments
 (0)