Coder Social home page Coder Social logo

attrdict's Introduction

AttrDict

https://travis-ci.org/bcj/AttrDict.svg?branch=master https://coveralls.io/repos/bcj/AttrDict/badge.png?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:

  1. 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)
  2. 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.
  3. 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.

attrdict's People

Contributors

bcj avatar digenis avatar donsignore avatar eukaryote avatar jtratner avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

attrdict's Issues

Override __dir__ to allow for tab completion in ipython and easier inspection

The common thing to do if you override __getattr__ is to also override __dir__ like so:

def __dir__(self):
    # you may also want to filter the keys to only include the strings
    return sorted(set(super(AttrDict, self).__dir__()) + set(self.keys()))

This would make it much easier to use attrdict in IPython and other IDEs as you would gain auto-completion.

Use apache or mit license

I'd like to try to use your project but the licensing has me worried. Is it possible to change to a MIT or apache 2.0 license instead of your personal license?

AttrDict.get() does not return AttrDict but dict on nested AttrDicts

Hello everyone,

test = AttrDict({"a": {"b": "c"}})
type(test.a)
<class 'attrdict.AttrDict'>
type(test.get("a"))
<type 'dict'>

Is this behavior wanted or do you think as well that it is something worth changing?

As a workaround I am using: a = test.a if "a" in test else None

Best regards and thank you for this module,
Tim

AttrDict is not a dict

The project description ready:

A dictionary that allows attribute-style access.

However, I discovered:

> from attrdict import AttrDict
> a = AttrDict()
> isinstance(a, dict)
False

Is there a reason AttrDict is not extending dict? It also makes it impossible to use AttrDict as a "drop in" replacement in existing APIs.
Consider an API that used to return (or receive) a dict and could be switched to an AttrDict.

Dict access does not return a dict

This example is shown:

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

This is not the case. a['foo'] is still an AttrDict and a['foo'].bar will happily return 'baz'.

Bugs in doc examples

Hi!

There seem to be two bugs in the examples.

First, this example right off the docs

> attr = AttrDict({'foo': [{'bar': 'baz'}, {'bar': 'qux'}]})
> for sub_attr in attr.foo:
>     print(subattr.foo)
'baz'
'qux'

gets me NameError: name 'subattr' is not defined because of the missing underscore in subattr but it's also foo a second time, while it would need to be bar, i.e. making print(sub_attr.bar) for the body of the loop.

Further down the docs read:

> adict = AttrDict({'list': [{'value': 1}, {'value': 2}]}, recursive=False)
> for element in adict.list:
>     isinstance(element, AttrDict)
False
False

But in ipython (with Python 2.7.13), I get this, instead:

In [29]: adict = AttrDict({'list': [{'value': 1}, {'value': 2}]}, recursive=False)

In [31]: [isinstance(element, AttrDict) for element in adict.list]
Out[31]: [True, True]  # ???

I am unsure if recursive=False is not working as expected or if just the example is broken. Please enlighten me. Thanks!

PS: The second example seems to also miss use of print(...) for the examples to be consistent to each other.

Nested AttrDicts don't work

In [17]: a = AttrDict()

In [18]: a.b = AttrDict()

In [19]: a.b.c = "Hi there"

In [20]: a
Out[20]: AttrDict({'b': AttrDict({})})

In [21]: a.b
Out[21]: AttrDict({})

In [22]: a.b.c
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-22-d34de4f2be62> in <module>()
----> 1 a.b.c

/usr/local/lib/python2.7/dist-packages/attrdict/mixins.pyc in __getattr__(self, key)
     80             raise AttributeError(
     81                 "'{cls}' instance has no attribute '{name}'".format(
---> 82                     cls=self.__class__.__name__, name=key
     83                 )
     84             )

AttributeError: 'AttrDict' instance has no attribute 'c'

recursive converts all Sequences to lists

If Recursive mode is set, all Sequences will be converted into a list by build

e.g.:

>>> adict = Attrdict({'foo': ({'bar': 'baz'}, {'lorem': 'ipsum'})
>>> isinstance(adict.foo, list)
True

The Sequence's type should be grabbed from the object.

Property validation in __init__ method makes conclision

Hello bcj,
When i pass {'get': 'example'} to __init__ method, instance created, but i cant access dict key by getattr since __init__ method only add no-conflict key as instance property.

But when i user attrdict_instance.property = "example", it raise a key error.

The question is : this two place acts not the same way.(__init__ and setattr)

Dict has key get but i can't access it(if i haven't read source code , i wouldn't know how can i access it correctly.)

Would __init__ method raises a KeyError to tell user do not use reserved key-name when collision being caught be a better way?

Best wishes:)

AttrDict doesn't implement copy()

I'm using AttrDict as the dict type for ConfigParser and found that it doesn't implement the copy() method, which ConfigParser uses when calling get() to retrieve a config field.

In my use case I extended AttrDict to implement this method. It would be nice to have this in upstream though.

class AttrDict(attrdict.AttrDict):
    def copy(self):
        return self._mapping.copy()

Or to patch it only if it doesn't exist:

if not hasattr(AttrDict, "copy"):
    setattr(AttrDict, "copy", lambda self: self._mapping.copy())

Use variable values in dotted notation?

Is there a way to use variables in the middle of a dotted phrase?

name = 'foo'
indict = {name: "name's value"}
adict = AttrDict(indict)

print adict.foo # works
print adict.name # fails, with AttributeError, because it's looking for `name` and not `foo`

Provide both attrdict.AttrMapping & attrdict.AttrDict?

Given that there seems to be continued interest in a closer matching to dict, I may be convinced that attrdict should provide both an AttrMapping which reflects current AttrDict and an AttrDict that subclasses dict. Would this be a satisfactory solution for those wishing AttrDict more-closely reflected dict?

This would have to be a major-version change since it could easily effect people using this in prod.

AttrDict does not provide simple constructor

This somewhat relates to #18, but I would like to raise this issue independently.

> dict(a=23) == dict(dict(a=23))
True

> AttrDict(a=23) == AttrDict(dict(a=23))
...
TypeError: __init__() got an unexpected keyword argument 'a'

The **kwargs based constructor of dict is incredibly helpful.

Recusion problem

Really like this module.
I have code that uses the old UserDict which has a .data attribute. My code uses that a lot.
My old code uses stuff like:
theImportantData = UserDict()
.. then initialize stuff and then...
theItem = theImportantData.data["NAME"]

(Don't ask me why I liked it, I just did... )

But when I tried to subclass AttrDict like this

class newAttrDict(AttrDict):
    def __init__(self):
        self.data = self

to be able to get access to the theImportantData.data attribute,
I get this:

 File "/usr/local/lib/python3.4/site-packages/attrdict/__init__.py", line 115, in __getattr__
    if self._default_factory is None or key.startswith('_'):
  File "/usr/local/lib/python3.4/site-packages/attrdict/__init__.py", line 115, in __getattr__
    if self._default_factory is None or key.startswith('_'):
  File "/usr/local/lib/python3.4/site-packages/attrdict/__init__.py", line 115, in __getattr__
    if self._default_factory is None or key.startswith('_'):
  File "/usr/local/lib/python3.4/site-packages/attrdict/__init__.py", line 115, in __getattr__
    if self._default_factory is None or key.startswith('_'):
  File "/usr/local/lib/python3.4/site-packages/attrdict/__init__.py", line 115, in __getattr__
    if self._default_factory is None or key.startswith('_'):
  File "/usr/local/lib/python3.4/site-packages/attrdict/__init__.py", line 115, in __getattr__
    if self._default_factory is None or key.startswith('_'):
  File "/usr/local/lib/python3.4/site-packages/attrdict/__init__.py", line 115, in __getattr__
    if self._default_factory is None or key.startswith('_'):
  File "/usr/local/lib/python3.4/site-packages/attrdict/__init__.py", line 115, in __getattr__
    if self._default_factory is None or key.startswith('_'):
  File "/usr/local/lib/python3.4/site-packages/attrdict/__init__.py", line 115, in __getattr__
    if self._default_factory is None or key.startswith('_'):
RuntimeError: maximum recursion depth exceeded while calling a Python object

Anyway thanks for a nice addition to the Dict class.
It might be nice just to have a .data attribute like the old UserDict, instead of playing around with _mapping .

Thanks,

Mike

100% Test Coverage

Issue 4 was the result of not enough testing. Get 100% coverage (plus the badge)

Using the "recursive" param shouldn't show "recursive" as an attribute

The recursive parameter shouldn't modify the contents of the object.

>>> attrdict.AttrDict({'list': [{'value': 1}, {'value': 2}]}, recursive=False)
AttrDict({'list': [{'value': 1}, {'value': 2}], 'recursive': False})

Let's say I have an AttrDict object for which my keys should be all numbers. Recursive should not be added to the keys.

>>> ad=attrdict.AttrDict({1: 'one', 2: 'two'}, recursive=False)
>>> ad.keys()
dict_keys([1, 2, 'recursive'])

immutable AttrDict instances

I find I need an immutable mapping fairly often. Would it be reasonable to add an attrdict.FrozenAttrDict class that supports __hash__?

feature request: copy() to get a new copy (as dict?)

Please support deep-copy for creating an exact non-shared copy of the object (or a branch of it).
Preferably as a method of AttrDict, as the dict object.

e.g.

def copy(self, item=None):
    if item:
        obj = self.get(item, None)
        if obj is None:
            return None
        if type(obj) is list:
            return list(obj)
        if type(obj) is dict:
            return AttrDict2(dict(obj))
        return obj
    else:
        return AttrDict2(dict(self))

list are not handled properly

List are not handled properly
Example:

d = AttrDict({"a": 1, "b": 2, "c": {"d": 3, "e": [4, 5, {"f": 6, "g": 7}]}})
d.c.e[2].f  # AttributeError: 'dict' object has no attribute 'f'

_build method can be changed like this:

  if isinstance(obj, Mapping):
     return obj
  if isinstance(obj, list):
     return [cls._build() for o in obj]
  return obj

Empty list becomes a tuple in AttrDict

In Python 3.5 with attrdict 2.0.0

first_dict = AttrDict()
first_dict.second_list = list()
for i in range(10):
    first_dict.second_list.append(i)
print("Debug dict. Attr dict01: '{}'".format(first_dict))

Gives

AttributeError: 'tuple' object has no attribute 'append'

It looks like AttrDict converts empty lists into tuples.

Pulling a list of dicts from AttrDict results in just a list of dicts.

As the title states, just a list of regular dicts instead of AttrDicts. Here is a workaround.

def deep_attrdict(d: dict) -> AttrDict:
    """Uses recursion to dive deep into the dict
    and convert all dict types to type AttrDict. This will 
    eliminate the bug where pulling a list of dicts 
    out of an AttrDict results in just a list of dicts.
    """
    d = AttrDict(d)
    for k, v in d.items():
        if isinstance(v, dict):
            d[k] = deep_attrdict(v)
        elif isinstance(v, list):
            for i, item in enumerate(v):
                if isinstance(item, dict):
                    v[i] = deep_attrdict(item)
    return d

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.