Coder Social home page Coder Social logo

cdgriffith / box Goto Github PK

View Code? Open in Web Editor NEW
2.4K 2.4K 96.0 2.66 MB

Python dictionaries with advanced dot notation access

Home Page: https://github.com/cdgriffith/Box/wiki

License: MIT License

Python 100.00%
addict box bunch dictionaries helper object pypi python python-box python-library python-types python3

box's People

Contributors

cdgriffith 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  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

box's Issues

Auto attributes creation

>>> b = box.Box()
>>> b.o.x = 42
AttributeError: 'Box' object has no attribute 'o'

IMHO this should work, possibly via a subtype (AutoBox) or __init__ keyword arg (auto_attr=True).

Another variant would be OpenBox combined with a close() method โ€“ i.e. allow path construction initially, but then switch to default mode (AttributeError) after construction.

Can we keep the order of the keys?

Just like OrderedDict, maybe we can have something like OrderedBox.
Then we should be able to have something like this:

from box import OrderedBox
ob = OrderedBox()
ob.c = 1
ob.b = 2
ob.a = 3

assert ob.keys() != ['a', 'b', 'c']
assert ob.keys() == ['c', 'b', 'a']

bug leading to recursive list

The following code should print [['foo']] but instead prints [[...]] showing that a auto-referential list has been created.

from box import Box

def bl():
    b = Box()
    bl = b.setdefault("l", [])
    bl.append(["foo"])
    print(bl)

Issue lies on https://github.com/cdgriffith/Box/blob/master/box.py#L771 (or in the append function at https://github.com/cdgriffith/Box/blob/master/box.py#L802) that detects self referential entities.
The id(iterable) is not robust enought as two object may have the same id if the first object has been garbage collected.
One can see this in the example below

def bl():
    b = Box()
    l1 = []
    bl = b.setdefault("l", l1)
    del l1    # comment this to see that now it works as l1 is not garbage collected
    bl.append(["foo"])
    print(bl)

A fix could be to replace https://github.com/cdgriffith/Box/blob/master/box.py#L771 by

        self.box_org_ref = id(iterable) if iterable else 0

Add multiline JSON support

Some odd people like to put multiple JSON objects into a file, one per line. Possibly make a 'multiline' kwarg for to_json under BoxList to accommodate?

Box(default_box=True) not pickleable

Enabling default_box make a Box undumpable

from pickle import dumps
from box import Box
dumps(Box())  # => OK
dumps(Box(default_box=True)) # => raise exception below
Traceback (most recent call last):
  File "...", line 1, in <module>
    dumps(Box(default_box=True))
TypeError: 'Box' object is not callable

Support for non-string keys

I specifically ran into this issue when calling to_dict on a Box instance with an integer key. A TypeError is raised on this line:

Box/box.py

Line 524 in 37ad926

object.__getattribute__(self, key)

TypeError: attribute name must be string, not 'int'
I saw the issue when using Python 3.5.2.

The following works for an integer key:
self[key] = value
so the change may be as simple as adding TypeError to line 525:

Box/box.py

Line 525 in 37ad926

except (AttributeError, UnicodeEncodeError):

This was not an issue in 2.0 and most likely later versions, although it may not have been intentionally supported:

Box/box.py

Line 71 in 8340399

def __setattr__(self, key, value):

As a result I would argue this is a bug not a feature request.

Box __hash__ does not adhere to the specification

Per Python docs, __hash__ should have this property:

The only required property is that objects which compare equal have the same hash value;

However:

def f():
  return Box(data={'Python': 'Rocks', 'inferior': ['java', 'cobol']}, frozen_box=True)

b1 = f()
b2 = f()
print(b1 == b2)
print(hash(b1), hash(b2))
print(set([b1, b2]))

yields:

True
-7253217714821940563 1337995706200090896
{<Box: {'data': {'Python': 'Rocks', 'inferior': ('java', 'cobol')}}>, <Box: {'data': {'Python': 'Rocks', 'inferior': ('java', 'cobol')}}>}

This also makes is unusable for using in sets, as seen in the above message.

The reason for this looks to be due to __hash__ having a random uuid4 included in the calculation:

                hashing = hash(uuid4().hex)

Is there a reason for this line?

Keep object extended from dict/list intact

Hi @cdgriffith,

I am the one who proposed ordered_box.
I was recently bothered by Box turning some objects that are inherited from dict/list into Box/BoxList.
Consider the following codes:

from box import Box

class MyList(list): 
    def sq(self):
        return [x*x for x in self]
	
b = Box(a = MyList([1,2,4]))
b.a.sq()
# AttributeError: 'BoxList' object has no attribute 'sq'
# Expect [1,4,16]

I extended list to add some extra methods for convenience. However, when it was changed by Box into a BoxList, then I lost my method sq.
Is it possible to keep the objects of extended dict/list intact, but convert the raw dict/lists?

One simple way is to check the __bases__, if it is (object, ) then it should be a raw dict/list:

def _israwinstance(obj, klass):
    return isinstance(obj, klass) and obj.__class__.__bases__ \
       and obj.__class__.__bases__[0] is object

Thanks,
Panwen

Deafault value in case of key not found

Thank you for your software!

Is there a way to receive a None, or a default value, rather than a BoxKeyError like we could in: get(key, default) if the key is not found in the Box?

Box raises an AttributeError on nonexistent key, should raise a KeyError (or custom error)

When accessing a key which does not exist, an AttributeError will be raised. This sends the wrong message (in my opinion), implying that the user is attempting to access an attribute on the Box object which does not exist. When the true behavior is just syntactic sugar over a dict object, a KeyError seems more appropriate. I get that this is a philosophical opinion, so I could certainly be convinced that AttributeError is appropriate.

Repro in python repl:

Python 2.7.10 (default, Aug 22 2015, 20:33:39)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from box import Box
>>>
>>> b = Box({})
>>> b.attr
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/jiso/projects/jwplayer-python/venv/lib/python2.7/site-packages/box.py", line 385, in __getattr__
    raise err
AttributeError: 'Box' object has no attribute 'attr'

Actual: in above example, AttributeError is raised
Expected: KeyError is raised

Performance metrics

Oversimplifying the whole library would imply JSON -> PyDict with dot notation is what is being done here. As parsing JSON can be very slow, I am very much interested in some perf numbers as to how it scales. Can you please add these metrics in README ?

No Output in Default Sublime Build Window

OS:
Windows 7
Sublime 2.0

Works in terminal.
Low priority.
See gif. When running under default python build env runs without error but not of the links show up.

from box import Box

class Job(object):
	"""Configure the job object via runner."""
	def __init__(self, **kwargs):
		self.job = Box(kwargs)


job = {
	'uuid': None,
	'started' : None,
	'force_stop': None,
	'created_by ': None,
	'created_date': None,
	'status': 'Building',
	'last_saved': None,
	'debug': True,
	'human_name': "Refusa Record Extractor - Restaurant SIC Codes",
	'record_tag': 'refusa-restaurant',
	'error_count': 0,
	'last_error': None,
	'extraction_last_event': None,
	'extraction_average_time': 0,
	'extraction_completion_time': 0, #Seconds
	'extraction_total_records': 0,
	'extraction_duplicate_records': None,
	'search_total_records_found': None,
	'page_current' : None,
	'page_last' : None,
	'driver' : None,
	'pending_tasks':[],
	'task': {
		'inputs': {
			'load_page': { 'url': "https://google.com" },
			'screenshot_as_base64' : { 'file_name' : None, }
		}, 
		'outputs': {
			'generate_refind_key' : None,
			'add_tag_record': None,		
			'save_record': None,
			'refind_record': None,
		}
	},
	'completed_tasks':[],
}

tjob = Job(**job)

tjob.job.status
tjob.job.error_count
tjob.job.task.inputs.load_page.url
tjob.job.human_name
tjob.job.task.inputs.screenshot_as_base64.file_name

no-output

Treat None values as unexisting keys for default_box

Hi,

thanks a lot for your project, it's currently helping me a lot :)
one feature i missed is the ability to treat None values as if there was no key defined.

like in this example.

b=Box({'brol': None}, default_box=True)
b.brol.truc

gives

Traceback (most recent call last):
File "", line 1, in
AttributeError: 'NoneType' object has no attribute 'truc'

but this works as exepected

b=Box({}, default_box=True)
b.brol.truc

many many thanks ๐Ÿ‘

`items()` / `keys()` can return unboxed objects

Test case:

>>> import box
>>> box.__version__
'3.1.0'
>>> b = box.Box()
>>> b.foo = {}
>>> b.values()
dict_values([{}])
>>> b.foo # Force conversion by accessing the key
<Box: {}>
>>> b.values()
dict_values([<Box: {}>])

Box.copy() returns a dictionary

Is this intentional?

d = dict.fromkeys([1,2,3,4],0)
d = box.Box(d)
print(type(d))
print(type(d.copy()))
---------------------------
>> <class 'box.Box'>
>> <class 'dict'>

Collection ABCs not used on Python 2.7

On Python 2.7 there is no collections.abc module, instead the collection base types are available directly in the collections module
The current code says:

try:
    from collections.abc import Mapping, Iterable
except ImportError:
    Mapping = dict
    Iterable = (tuple, list)

I recommend putting from collections import Iterable, Mapping as the except case, possibly still including the fallback for even earlier Pythons but possibly not as this totally changes how the library works rather unexpectedly. At the very least, a warning should happen when this fallback mode is used.

Data with 'items' and 'keys' for keys causes an error

When I run this:

import box
import sys

print(sys.version)
print(box.__version__)

data = {
    "fine": ['this', 'that', 'the other'],
    "items": ['this', 'that', 'the other'],
    "keys": ['this', 'that', 'the other'],
}

print("# working ########################################")
a = box.Box(data)
print(a.fine)
print(a.fine[1])

print("# broken ########################################")
print(a.items)
print(a.items[1])

print("# also broken but we don't get this far ########################################")
print(a.keys)
print(a.keys[1])

I get this:

3.7.1 (default, Nov 28 2018, 11:51:54)
[Clang 10.0.0 (clang-1000.11.45.5)]
3.2.3
# working ########################################
['this', 'that', 'the other']
that
# broken ########################################
<bound method Box.items of <Box: {'fine': ['this', 'that', 'the other'], 'items': ['this', 'that', 'the other'], 'keys': ['this', 'that', 'the other']}>>
Traceback (most recent call last):
  File "parse_json_with_box.py", line 20, in <module>
    print(a.items[1])
TypeError: 'method' object is not subscriptable

This seems related to #62 but is still a problem is version 3.2.3.

Look into auto conversion of keys to proper attributes

It would be beneficial to be able to access things that have spaces in them via dot notation, by assuming that spaces are underscores.

b = Box({"my dict": 1})
b.my_dict

This will require changes in both get and dir.

Also consider adding auto snake_case to stuff that is camelCase, and potentially lower case for anything as well.

BoxList(args, frozen_box=True) doesn't make the outer list immutable.

BoxList(args, frozen_box=True) should return a BoxList which will raise BoxError: BoxList is frozen on item modification such as:

box_list[0] = "new_value"
box_list += [1,2]
box_list.append("new")
box_list.extend([1,2,3])
box_list.insert(..)
box_list.pop(..)
box_list.remove(..)
box_list.reverse(..)
box_list.sort(..)

I know that this can be sorted out using tuple(BoxList(args, frozen_box=True)) but would like BoxList behavior to be consistent with Box behavior when frozen_box is True

Feature Request: LayeredBox

When using Box to represent a configuration hierarchy, it would be great to be able to have a LayeredBox virtually merging multiple Boxes in layers. That way, Boxes containing content from say a system wide, user specific and directory specific configuration could be combined while retaining the ability to write back changes using a round-trip yaml parser to preserve formatting and comments in the config provided by the user.

The LayeredBox would probably require some flags defining the way merging is done recursively. Should dictionaries at all levels be combined, or should they be overwritten? Should lists be overwritten or merged by appending? Typically, you'd want dictionaries to be merged and lists to be overwritten I think. It's a semantic decision though.

Cannot pickle

pickle.dump(Box({}), open('/tmp/test.p', 'wb'))
test = pickle.load(open('/tmp/test.p', 'rb'))

caused endless KeyError: '_box_config' and then RecursionError: maximum recursion depth exceeded while calling a Python object

python version: 3.6

py3.7 collections deprecation warning

Box/box.py

Line 450 in ae49ac2

elif isinstance(default_value, collections.Callable):

DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
    elif isinstance(default_value, collections.Callable):

camel_case_killer conflicts with conversion_box and default_box

Hey!
I've recently started using your awesome package and it has been a delight. Unfortunately, I encountered couple of issues with the camel_killer_box option:
image

I've tested it on Python 2 and 3.
Copy-pasteable reproduction code:

from box import Box

b = Box({
    'SomeKey': 999
}, camel_killer_box=True, conversion_box=True)

assert b.some_key == 999 # Cool, works as expected
assert b.SomeKey == 999  # Should not work because of conversion_box=True!

# When using default_box=True, things get even worse:
b = Box({
    'SomeKey': 999
}, camel_killer_box=True, conversion_box=True, default_box=True)

assert b.SomeKey == 999   # Same issue
assert b.some_key == Box({})  # Oh man, the default_box is prioritized over camel_killer_box...

I'm working on a fix for this, but I'll be happy for input.

AttributeError if update from a dict with protected key name

In [1]: a=box.Box()
In [2]: a.update({'items': 0})
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-7-ccd724442f09> in <module>()
----> 1 a.update({'items': 0})

~/.pyenv/versions/3.6.6/bin/box.py in update(self, item, **kwargs)
    669                 v = BoxList(v)
    670             try:
--> 671                 self.__setattr__(k, v)
    672             except TypeError:
    673                 self.__setitem__(k, v)

~/.pyenv/versions/3.6.6/bin/box.py in __setattr__(self, key, value)
    540             raise BoxError('Box is frozen')
    541         if key in self._protected_keys:
--> 542             raise AttributeError("Key name '{0}' is protected".format(key))
    543         if key == '_box_config':
    544             return object.__setattr__(self, key, value)

AttributeError: Key name 'items' is protected

this breaks the original method of dict, and box can have such protected keys, just without dot accessing

because i use defaultbox only so i temporarily hotfix this line:

Box/box.py

Line 671 in ae49ac2

self.__setattr__(k, v)

to:

self[k] = v

Ability to recast the type of parameter(s)

I'd look at this as almost a way to subclass Box and specify fields to recast. For example:

class Example(Box):
    _recast_params = {
        'id': int,
        'settings.group.id': int
    }

Add `to_csv` capability

Thought up by @gaojiuli

Going to have to think if it should be list of lists format, aka would be a property of BoxList, or rather a dictionary of keys would be the column names and lists of the data in the column. (or heck, support both methods, depending on which it is invoked on).

Versions of ruamel.yaml since 0.15 generate warning about Loader for Box.from_yaml()

Box 3.2.0, Python 3.6.2, Windows 7

For versions of ruamel.yaml since 0.15, the following files:

== 1.yaml ==

greeting: Hello!
date: 2018-01-01

== 1.py ==

import box

info = box.Box.from_yaml(filename='1.yaml')
print(info)

Generates the following output (paths edited):

<path>\box.py:112: UnsafeLoaderWarning
The default 'Loader' for 'load(stream)' without further arguments can be unsafe.
Use 'load(stream, Loader=ruamel.yaml.Loader)' explicitly if that is OK.
Alternatively include the following in your code:

  import warnings
  warnings.simplefilter('ignore', ruamel.yaml.error.UnsafeLoaderWarning)

In most other cases you should consider using 'safe_load(stream)'
  data = yaml.load(f, **kwargs)
{'greeting': 'Hello!', 'date': datetime.date(2018, 1, 1)}

Add a way to view the last made request to the box (and sub box) objects.

from box import Box

class test(object):
	def __init__(self, **kwargs):
		d = Box({
		'item':'value', 
		'item2': self.ok
		})
		self.d = d 

	def ok(self, **kwargs):
		print "test"
		print kwargs.keys()
		for key in kwargs.keys():
			print kwargs[key]
		return 

data={
	'test':'value',
	'test1':'value1'
}

t = test()
print t.d.item2(**data)

From t.d.item2 how do I get that path while in the 'ok' function.

Thanks!

hierarchical path as string

Would you consider to support dotted string as a access key to sub boxes:

b=Box(dotted=True)

b.c.d.e = 10
b['c.d.e'] == b.c.d.e

That is, to have an equivalence between those two notations.
If positive, then also to support some corresponding functions for traversing (for example):

b = Box(dotted=True)
b.c = dict(e=1, f=2)
b.a = dict(b=3, c=4)
[*Box.keys(dotted=True)]
# ['c.d.e', 'c.d.f', 'a.b', 'a.c']

Thanks!

Feature: Default box that doesn't raise key errors

Hello,

Box is a nice little Dictionary Helper hich is working just fine most of the time. Currently i'm seeing a problem with the pop-method... When popping a non-exitent key with default_box=True i get a KeyError:

File "/mnt/d/projekte/python/envs/depmgmt/bin/box.py", line 592, in pop
del self[key]
File "/mnt/d/projekte/python/envs/depmgmt/bin/box.py", line 560, in delitem
super(Box, self).delitem(key)

Is this working as intended ? I would expect to get the given default value when the key is not existant ?

Thank you...

Consider using solid class name instead of self.__class__

Hi @cdgriffith,

Really appreciated that you acted so quick to add box_intact_types.
Now I have another issue when I was trying to extend Box class with some more features, personally:

from box import Box

class Box2(Box):
    # My other methods go here ...

b = Box2(a=1)
b # <Box: {'a': 1}>

Everything worked fine, however, when I tried to copy the box:

c = b.copy()

I have got an exception:

---------------------------------------------------------------------------
RecursionError                            Traceback (most recent call last)
<ipython-input-4-f0d8dd24f807> in <module>
      5 
      6 b = Box2(a=1)
----> 7 b.copy()

/.../lib/python3.7/site-packages/box.py in copy(self)
    402 
    403     def copy(self):
--> 404         return self.__class__(super(self.__class__, self).copy())
    405 
    406     def __copy__(self):

... last 1 frames repeated, from the frame below ...

/.../lib/python3.7/site-packages/box.py in copy(self)
    402 
    403     def copy(self):
--> 404         return self.__class__(super(self.__class__, self).copy())
    405 
    406     def __copy__(self):

RecursionError: maximum recursion depth exceeded while calling a Python object

Then I modified the source code of copy:

    def copy(self):
        return Box(super(Box, self).copy())

every thing worked fine.

Maybe consider using a solid class name instead of self.__class__?
Or I have to copy the copy method to each subclass of Box.

box blows recursion stack

"box it up" doesn't handle recursion gracefully

>>> from box import Box
>>> d = {}
>>> d['d'] = d
>>> b = Box(d)
>>> b.box_it_up()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/wglenn/.virtualenvs/scratch/lib/python3.6/site-packages/box.py", line 251, in box_it_up
    self[k].box_it_up()
  File "/home/wglenn/.virtualenvs/scratch/lib/python3.6/site-packages/box.py", line 251, in box_it_up
    self[k].box_it_up()
  File "/home/wglenn/.virtualenvs/scratch/lib/python3.6/site-packages/box.py", line 251, in box_it_up
    self[k].box_it_up()
  [Previous line repeated 978 more times]
  File "/home/wglenn/.virtualenvs/scratch/lib/python3.6/site-packages/box.py", line 250, in box_it_up
    if hasattr(self[k], 'box_it_up'):
  File "/home/wglenn/.virtualenvs/scratch/lib/python3.6/site-packages/box.py", line 317, in __getitem__
    return self.__convert_and_store(item, value)
  File "/home/wglenn/.virtualenvs/scratch/lib/python3.6/site-packages/box.py", line 332, in __convert_and_store
    **self.__box_config())
  File "/home/wglenn/.virtualenvs/scratch/lib/python3.6/site-packages/box.py", line 218, in __init__
    if isinstance(args[0], Mapping):
  File "/home/wglenn/.virtualenvs/scratch/lib64/python3.6/abc.py", line 182, in __instancecheck__
    if subclass in cls._abc_cache:
  File "/home/wglenn/.virtualenvs/scratch/lib64/python3.6/_weakrefset.py", line 75, in __contains__
    return wr in self.data
RecursionError: maximum recursion depth exceeded in comparison

Now b.to_dict() is broken.

It also mangles the string representation afterwards :

>>> print(b)
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d'
: {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': 
{'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'
d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {'d': {...}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}


Figure out best way to deal with circular references

As discovered with #20

Issue can be duplicated with:

troll_dict = {}
troll_dict ['e'] = {'g': troll_dict}

bx = Box(troll_dict)
bx.e.g
<Box: {'e': {'g': {'e': {'g': {'e': {'g': {'e': {'g': {'e': {'g': {'e': ..................

Also would include way to detect and raise custom BoxError instead of related recursive built in error for functions like box_it_up.

Other libraries such as addict and DotMap cannot handle even first level circular references, so may not be an easy or worthwhile thing to address.

Only current thought on how to address it would be to keep list of references to original objects that were converted, and upon creation of sub-boxes check all other previously generated ones to see if any have that link already and re-reference the newly created sub-box. The issue is that this would be very memory and process heavy for a rare issue check.

Use property instead of to_json(),to_yaml(),etc.

Use

box.json
box.yaml
box.csv


@property
def json(self):
    return {}

but not

box.to_json()
box.to_yaml()
box.to_csv()

which is more pythonic. And the arguments "filename" of to_yaml() could be instead by a method "save()". What do you think?

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.