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

Commit

Permalink
Merge pull request #32 from bcj/v2.0
Browse files Browse the repository at this point in the history
V2.0
  • Loading branch information
bcj committed Apr 10, 2015
2 parents ac01cda + 39a1e5d commit 9f67299
Show file tree
Hide file tree
Showing 23 changed files with 1,965 additions and 1,389 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ MANIFEST
build/*
dist/*
.tox
attrdict.egg-info/*
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ language: python
python:
- "3.4"
- "3.3"
- "3.2"
- "2.7"
- "2.6"
- "pypy"
- "pypy3"
install:
- "pip install -r requirements-tests.txt"
- "python setup.py install"
script: nosetests --with-coverage --cover-package attrdict -v
script: "python setup.py nosetests && flake8 attrdict tests"
after_success:
- coveralls
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ v0.5.1, 2014/07/14 -- tox for local testing, README fix, 0.5.0 no longer from th
v1.0.0, 2014/08/18 -- Development Status :: 5 - Production/Stable
v1.1.0, 2014/10/29 -- has_key support to match python2 dicts (by Nikolaos-Digenis Karagiannis @Digenis)
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.
v2.0, 2015/04/09 -- Happy PyCon. An almost-complete rewrite. Hopefully in a good way
202 changes: 90 additions & 112 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ AttrDict
.. image:: https://coveralls.io/repos/bcj/AttrDict/badge.png?branch=master
:target: https://coveralls.io/r/bcj/AttrDict?branch=master


AttrDict is a 2.6, 2.7, 3-compatible dictionary that allows its elements
to be accessed both as keys and as attributes::
AttrDict is an MIT-licensed library that provides mapping objects that allow
their elements to be accessed both as keys and as attributes::

> from attrdict import AttrDict
> a = AttrDict({'foo': 'bar'})
Expand All @@ -17,18 +16,15 @@ to be accessed both as keys and as attributes::
> a['foo']
'bar'

With this, you can easily create convenient, hierarchical settings
objects.

::
Attribute access makes it easy to create convenient, hierarchical settings
objects::

with open('settings.yaml', 'r') as fileobj:
with open('settings.yaml') as fileobj:
settings = AttrDict(yaml.safe_load(fileobj))

cursor = connect(**settings.db.credentials).cursor()

cursor.execute("SELECT column FROM table");

cursor.execute("SELECT column FROM table;")

Installation
============
Expand All @@ -42,80 +38,110 @@ Or from Github::
$ cd AttrDict
$ python setup.py install

Documentation
=============

Documentation is available at https://github.com/bcj/AttrDict

Usage
=====
Creation
--------
An empty AttrDict can be created with::
Basic Usage
===========
AttrDict comes with three different classes, `AttrMap`, `AttrDict`, and
`AttrDefault`. They are all fairly similar, as they all are MutableMappings (
read: dictionaries) that allow creating, accessing, and deleting key-value
pairs as attributes.

Valid Names
-----------
Any key can be used as an attribute as long as:

#. The key represents a valid attribute (i.e., it is a string comprised only of
alphanumeric characters and underscores that doesn't start with a number)
#. The key represents a public attribute (i.e., it doesn't start with an
underscore). This is done (in part) so that implementation changes between
minor and micro versions don't force major version changes.
#. The key does not shadow a class attribute (e.g., get).

Attributes vs. Keys
-------------------
There is a minor difference between accessing a value as an attribute vs.
accessing it as a key, is that when a dict is accessed as an attribute, it will
automatically be converted to an Attr object. This allows you to recursively
access keys::

> attr = AttrDict({'foo': {'bar': 'baz'}})
> attr.foo.bar
'baz'

a = AttrDict()
Relatedly, by default, sequence types that aren't `bytes`, `str`, or `unicode`
(e.g., lists, tuples) will automatically be converted to tuples, with any
mappings converted to Attrs::

Or, you can pass an existing ``dict`` (or other type of ``Mapping`` object)::
> attr = AttrDict({'foo': [{'bar': 'baz'}, {'bar': 'qux'}]})
> for sub_attr in attr.foo:
> print(subattr.foo)
'baz'
'qux'

a = AttrDict({'foo': 'bar'})
To get this recursive functionality for keys that cannot be used as attributes,
you can replicate the behavior by calling the Attr object::

NOTE: Unlike ``dict``, AttrDict will not clone on creation. AttrDict's
internal dictionary will be the same instance as the dict passed in.
> attr = AttrDict({1: {'two': 3}})
> attr(1).two
3

Access
------
AttrDict can be used *exactly* like a normal dict::
Classes
-------
AttrDict comes with three different objects, `AttrMap`, `AttrDict`, and
`AttrDefault`.

> a = AttrDict()
> a['foo'] = 'bar'
> a['foo']
'bar'
> '{foo}'.format(**a)
'bar'
> del a['foo']
> a.get('foo', 'default')
'default'
AttrMap
^^^^^^^
The most basic implementation. Use this if you want to limit the number of
invalid keys, or otherwise cannot use `AttrDict`

AttrDict can also have it's keys manipulated as attributes to the object::
AttrDict
^^^^^^^^
An Attr object that subclasses `dict`. You should be able to use this
absolutely anywhere you can use a `dict`. While this is probably the class you
want to use, there are a few caveats that follow from this being a `dict` under
the hood.

> a = AttrDict()
> a.foo = 'bar'
> a.foo
'bar'
> del a.foo
The `copy` method (which returns a shallow copy of the mapping) returns a
`dict` instead of an `AttrDict`.

Both methods operate on the same underlying object, so operations are
interchangeable. The only difference between the two methods is that
where dict-style access would return a dict, attribute-style access will
return an AttrDict. This allows recursive attribute-style access::
Recursive attribute access results in a shallow copy, so recursive assignment
will fail (as you will be writing to a copy of that dictionary)::

> a = AttrDict({'foo': {'bar': 'baz'}})
> a.foo.bar
'baz'
> a['foo'].bar
AttributeError: 'dict' object has no attribute 'bar'
> attr = AttrDict('foo': {})
> attr.foo.bar = 'baz'
> attr.foo
{}

There are some valid keys that cannot be accessed as attributes. To be
accessed as an attribute, a key must:
Assignment as keys will still work::

* be a string
> attr = AttrDict('foo': {})
> attr['foo']['bar'] = 'baz'
> attr.foo
{'bar': 'baz'}

* start with an alphabetic character
If either of these caveats are deal-breakers, or you don't need your object to
be a `dict`, consider using `AttrMap` instead.

* be comprised solely of alphanumeric characters and underscores
AttrDefault
^^^^^^^^^^^
At Attr object that behaves like a `defaultdict`. This allows on-the-fly,
automatic key creation::

* not map to an existing attribute name (e.g., get, items)
> attr = AttrDefault(int, {})
> attr.foo += 1
> attr.foo
1

To access these attributes while retaining an AttrDict wrapper (or to
dynamically access any key as an attribute)::
AttrDefault also has a `pass_key` option that passes the supplied key to the
`default_factory`::

> a = AttrDict({'_foo': {'bar': 'baz'}})
> a('_foo').bar
'baz'
> attr = AttrDefault(sorted, {}, pass_key=True)
> attr.banana
['a', 'a', 'a', 'b', 'n', 'n']

Merging
-------
AttrDicts can be merged with each other or other dict objects using the
All three Attr classes can be merged with eachother or other Mappings using the
``+`` operator. For conflicting keys, the right dict's value will be
preferred, but in the case of two dictionary values, they will be
recursively merged::
Expand Down Expand Up @@ -164,54 +190,6 @@ When merging an AttrDict with another mapping, this behavior will be disabled
if at least one of the merged items is an AttrDict that has set ``recursive``
to ``False``.

DefaultDict
===========

AttrDict supports defaultdict-style automatic creation of attributes::

> adict = AttrDict(default_factory=list)
> adict.foo
[]

Furthermore, if ``pass_key=True``, then the key will be passed to the function
used when creating the value::

> adict = AttrDict(default_factory=lambda value: value.upper(), pass_key=True)
> adict.foo
'FOO'

load
====
A common usage for AttrDict is to use it in combination with settings files to
create hierarchical settings. attrdict comes with a load function to make this
easier::

from attrdict import load

settings = load('settings.json')

By default, ``load`` uses ``json.load`` to load the settings file, but this can
be overridden by passing ``load_function=YOUR_LOAD_FUNCTION``.

``load`` supports loading from multiple files at once. This allows for
overriding of default settings, e.g.::

from attrdict import load
from yaml import safe_load

# config.yaml =
# emergency:
# email: [email protected]
# message: Something went wrong
#
# user.yaml =
# emergency:
# email: [email protected]
settings = load('config.yaml', 'user.yaml', load_function=safe_load)

assert settings.email == '[email protected]'
assert settings.message == 'Something went wrong'

License
=======
AttrDict is released under a MIT license.
Loading

0 comments on commit 9f67299

Please sign in to comment.