========
AttrDict
========
.. image:: https://travis-ci.org/bcj/AttrDict.svg?branch=master
:target: https://travis-ci.org/bcj/AttrDict?branch=master
.. image:: https://coveralls.io/repos/bcj/AttrDict/badge.png?branch=master
:target: https://coveralls.io/r/bcj/AttrDict?branch=master
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'})
> a.foo
'bar'
> a['foo']
'bar'
Attribute access makes it easy to create convenient, hierarchical settings
objects::
with open('settings.yaml') as fileobj:
settings = AttrDict(yaml.safe_load(fileobj))
cursor = connect(**settings.db.credentials).cursor()
cursor.execute("SELECT column FROM table;")
Installation
============
AttrDict is in PyPI, so it can be installed directly using::
$ pip install attrdict
Or from Github::
$ git clone https://github.com/bcj/AttrDict
$ cd AttrDict
$ python setup.py install
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'
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::
> attr = AttrDict({'foo': [{'bar': 'baz'}, {'bar': 'qux'}]})
> for sub_attr in attr.foo:
> print(sub_attr.foo)
'baz'
'qux'
To get this recursive functionality for keys that cannot be used as attributes,
you can replicate the behavior by calling the Attr object::
> attr = AttrDict({1: {'two': 3}})
> attr(1).two
3
Classes
-------
AttrDict comes with three different objects, `AttrMap`, `AttrDict`, and
`AttrDefault`.
AttrMap
^^^^^^^
The most basic implementation. Use this if you want to limit the number of
invalid keys, or otherwise cannot use `AttrDict`
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.
The `copy` method (which returns a shallow copy of the mapping) returns a
`dict` instead of an `AttrDict`.
Recursive attribute access results in a shallow copy, so recursive assignment
will fail (as you will be writing to a copy of that dictionary)::
> attr = AttrDict('foo': {})
> attr.foo.bar = 'baz'
> attr.foo
{}
Assignment as keys will still work::
> attr = AttrDict('foo': {})
> attr['foo']['bar'] = 'baz'
> attr.foo
{'bar': 'baz'}
If either of these caveats are deal-breakers, or you don't need your object to
be a `dict`, consider using `AttrMap` instead.
AttrDefault
^^^^^^^^^^^
At Attr object that behaves like a `defaultdict`. This allows on-the-fly,
automatic key creation::
> attr = AttrDefault(int, {})
> attr.foo += 1
> attr.foo
1
AttrDefault also has a `pass_key` option that passes the supplied key to the
`default_factory`::
> attr = AttrDefault(sorted, {}, pass_key=True)
> attr.banana
['a', 'a', 'a', 'b', 'n', 'n']
Merging
-------
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::
> a = {'foo': 'bar', 'alpha': {'beta': 'a', 'a': 'a'}}
> b = {'lorem': 'ipsum', 'alpha': {'bravo': 'b', 'a': 'b'}}
> AttrDict(a) + b
{'foo': 'bar', 'lorem': 'ipsum', 'alpha': {'beta': 'a', 'bravo': 'b', 'a': 'b'}}
NOTE: AttrDict's add is not commutative, ``a + b != b + a``::
> a = {'foo': 'bar', 'alpha': {'beta': 'b', 'a': 0}}
> b = {'lorem': 'ipsum', 'alpha': {'bravo': 'b', 'a': 1}}
> b + AttrDict(a)
{'foo': 'bar', 'lorem': 'ipsum', 'alpha': {'beta': 'a', 'bravo': 'b', 'a': }}
Sequences
---------
By default, items in non-string Sequences (e.g. lists, tuples) will be
converted to AttrDicts::
> adict = AttrDict({'list': [{'value': 1}, {'value': 2}]})
> for element in adict.list:
> element.value
1
2
This will not occur if you access the AttrDict as a dictionary::
> adict = AttrDict({'list': [{'value': 1}, {'value': 2}]})
> for element in adict['list']:
> isinstance(element, AttrDict)
False
False
To disable this behavior globally, pass the attribute ``recursive=False`` to
the constructor::
> adict = AttrDict({'list': [{'value': 1}, {'value': 2}]}, recursive=False)
> for element in adict.list:
> isinstance(element, AttrDict)
False
False
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``.
License
=======
AttrDict is released under a MIT license.
Raw data
{
"_id": null,
"home_page": "https://github.com/bcj/AttrDict",
"name": "attrdict",
"maintainer": "",
"docs_url": null,
"requires_python": "",
"maintainer_email": "",
"keywords": "",
"author": "Brendan Curran-Johnson",
"author_email": "brendan@bcjbcj.ca",
"download_url": "https://files.pythonhosted.org/packages/3f/72/614aae677d28e81a5bf830fadcf580803876ef76e0306902d3ca5790cd9a/attrdict-2.0.1.tar.gz",
"platform": "",
"description": "========\nAttrDict\n========\n.. image:: https://travis-ci.org/bcj/AttrDict.svg?branch=master\n :target: https://travis-ci.org/bcj/AttrDict?branch=master\n.. image:: https://coveralls.io/repos/bcj/AttrDict/badge.png?branch=master\n :target: https://coveralls.io/r/bcj/AttrDict?branch=master\n\nAttrDict is an MIT-licensed library that provides mapping objects that allow\ntheir elements to be accessed both as keys and as attributes::\n\n > from attrdict import AttrDict\n > a = AttrDict({'foo': 'bar'})\n > a.foo\n 'bar'\n > a['foo']\n 'bar'\n\nAttribute access makes it easy to create convenient, hierarchical settings\nobjects::\n\n with open('settings.yaml') as fileobj:\n settings = AttrDict(yaml.safe_load(fileobj))\n\n cursor = connect(**settings.db.credentials).cursor()\n\n cursor.execute(\"SELECT column FROM table;\")\n\nInstallation\n============\nAttrDict is in PyPI, so it can be installed directly using::\n\n $ pip install attrdict\n\nOr from Github::\n\n $ git clone https://github.com/bcj/AttrDict\n $ cd AttrDict\n $ python setup.py install\n\nBasic Usage\n===========\nAttrDict comes with three different classes, `AttrMap`, `AttrDict`, and\n`AttrDefault`. They are all fairly similar, as they all are MutableMappings (\nread: dictionaries) that allow creating, accessing, and deleting key-value\npairs as attributes.\n\nValid Names\n-----------\nAny key can be used as an attribute as long as:\n\n#. The key represents a valid attribute (i.e., it is a string comprised only of\n alphanumeric characters and underscores that doesn't start with a number)\n#. The key represents a public attribute (i.e., it doesn't start with an\n underscore). This is done (in part) so that implementation changes between\n minor and micro versions don't force major version changes.\n#. The key does not shadow a class attribute (e.g., get).\n\nAttributes vs. Keys\n-------------------\nThere is a minor difference between accessing a value as an attribute vs.\naccessing it as a key, is that when a dict is accessed as an attribute, it will\nautomatically be converted to an Attr object. This allows you to recursively\naccess keys::\n\n > attr = AttrDict({'foo': {'bar': 'baz'}})\n > attr.foo.bar\n 'baz'\n\nRelatedly, by default, sequence types that aren't `bytes`, `str`, or `unicode`\n(e.g., lists, tuples) will automatically be converted to tuples, with any\nmappings converted to Attrs::\n\n > attr = AttrDict({'foo': [{'bar': 'baz'}, {'bar': 'qux'}]})\n > for sub_attr in attr.foo:\n > print(sub_attr.foo)\n 'baz'\n 'qux'\n\nTo get this recursive functionality for keys that cannot be used as attributes,\nyou can replicate the behavior by calling the Attr object::\n\n > attr = AttrDict({1: {'two': 3}})\n > attr(1).two\n 3\n\nClasses\n-------\nAttrDict comes with three different objects, `AttrMap`, `AttrDict`, and\n`AttrDefault`.\n\nAttrMap\n^^^^^^^\nThe most basic implementation. Use this if you want to limit the number of\ninvalid keys, or otherwise cannot use `AttrDict`\n\nAttrDict\n^^^^^^^^\nAn Attr object that subclasses `dict`. You should be able to use this\nabsolutely anywhere you can use a `dict`. While this is probably the class you\nwant to use, there are a few caveats that follow from this being a `dict` under\nthe hood.\n\nThe `copy` method (which returns a shallow copy of the mapping) returns a\n`dict` instead of an `AttrDict`.\n\nRecursive attribute access results in a shallow copy, so recursive assignment\nwill fail (as you will be writing to a copy of that dictionary)::\n\n > attr = AttrDict('foo': {})\n > attr.foo.bar = 'baz'\n > attr.foo\n {}\n\nAssignment as keys will still work::\n\n > attr = AttrDict('foo': {})\n > attr['foo']['bar'] = 'baz'\n > attr.foo\n {'bar': 'baz'}\n\nIf either of these caveats are deal-breakers, or you don't need your object to\nbe a `dict`, consider using `AttrMap` instead.\n\nAttrDefault\n^^^^^^^^^^^\nAt Attr object that behaves like a `defaultdict`. This allows on-the-fly,\nautomatic key creation::\n\n > attr = AttrDefault(int, {})\n > attr.foo += 1\n > attr.foo\n 1\n\nAttrDefault also has a `pass_key` option that passes the supplied key to the\n`default_factory`::\n\n > attr = AttrDefault(sorted, {}, pass_key=True)\n > attr.banana\n ['a', 'a', 'a', 'b', 'n', 'n']\n\nMerging\n-------\nAll three Attr classes can be merged with eachother or other Mappings using the\n``+`` operator. For conflicting keys, the right dict's value will be\npreferred, but in the case of two dictionary values, they will be\nrecursively merged::\n\n > a = {'foo': 'bar', 'alpha': {'beta': 'a', 'a': 'a'}}\n > b = {'lorem': 'ipsum', 'alpha': {'bravo': 'b', 'a': 'b'}}\n > AttrDict(a) + b\n {'foo': 'bar', 'lorem': 'ipsum', 'alpha': {'beta': 'a', 'bravo': 'b', 'a': 'b'}}\n\nNOTE: AttrDict's add is not commutative, ``a + b != b + a``::\n\n > a = {'foo': 'bar', 'alpha': {'beta': 'b', 'a': 0}}\n > b = {'lorem': 'ipsum', 'alpha': {'bravo': 'b', 'a': 1}}\n > b + AttrDict(a)\n {'foo': 'bar', 'lorem': 'ipsum', 'alpha': {'beta': 'a', 'bravo': 'b', 'a': }}\n\nSequences\n---------\nBy default, items in non-string Sequences (e.g. lists, tuples) will be\nconverted to AttrDicts::\n\n > adict = AttrDict({'list': [{'value': 1}, {'value': 2}]})\n > for element in adict.list:\n > element.value\n 1\n 2\n\nThis will not occur if you access the AttrDict as a dictionary::\n\n > adict = AttrDict({'list': [{'value': 1}, {'value': 2}]})\n > for element in adict['list']:\n > isinstance(element, AttrDict)\n False\n False\n\nTo disable this behavior globally, pass the attribute ``recursive=False`` to\nthe constructor::\n\n > adict = AttrDict({'list': [{'value': 1}, {'value': 2}]}, recursive=False)\n > for element in adict.list:\n > isinstance(element, AttrDict)\n False\n False\n\nWhen merging an AttrDict with another mapping, this behavior will be disabled\nif at least one of the merged items is an AttrDict that has set ``recursive``\nto ``False``.\n\nLicense\n=======\nAttrDict is released under a MIT license.\n\n\n",
"bugtrack_url": null,
"license": "MIT License",
"summary": "A dict with attribute-style access",
"version": "2.0.1",
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"md5": "b10e44e149116ba79ab6c04ec9c892e7",
"sha256": "9432e3498c74ff7e1b20b3d93b45d766b71cbffa90923496f82c4ae38b92be34"
},
"downloads": -1,
"filename": "attrdict-2.0.1-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "b10e44e149116ba79ab6c04ec9c892e7",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 9946,
"upload_time": "2019-02-01T22:33:56",
"upload_time_iso_8601": "2019-02-01T22:33:56.508208Z",
"url": "https://files.pythonhosted.org/packages/ef/97/28fe7e68bc7adfce67d4339756e85e9fcf3c6fd7f0c0781695352b70472c/attrdict-2.0.1-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"md5": "6d541c82592568dc0dfcecf57978b326",
"sha256": "35c90698b55c683946091177177a9e9c0713a0860f0e049febd72649ccd77b70"
},
"downloads": -1,
"filename": "attrdict-2.0.1.tar.gz",
"has_sig": false,
"md5_digest": "6d541c82592568dc0dfcecf57978b326",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 9593,
"upload_time": "2019-02-01T22:33:58",
"upload_time_iso_8601": "2019-02-01T22:33:58.493807Z",
"url": "https://files.pythonhosted.org/packages/3f/72/614aae677d28e81a5bf830fadcf580803876ef76e0306902d3ca5790cd9a/attrdict-2.0.1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2019-02-01 22:33:58",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "bcj",
"github_project": "AttrDict",
"travis_ci": true,
"coveralls": false,
"github_actions": false,
"tox": true,
"lcname": "attrdict"
}