Box 4.0 Features and Changes
============================

Box 4.0 has brought a lot of great new features, but also some breaking changes. They are documented here to help you upgrade.

To install the latest 4.x you will need at least Python 3.6 (or current supported python 3.x version)

..code:: bash

    pip install --upgrade python-box>=4



If your application is no longer working, and need a quick fix:

..code:: bash

    pip install --upgrade python-box<4


Additions
---------

Dot notation access by keys!
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Enabled with `box_dots=True`.

.. code:: python

    from box import Box
    my_box = Box(a={'b': {'c': {'d': 'my_value'}}}, box_dots=True)
    print(my_box['a.b.c.d'])
    # 'my_value'
    my_box['a.b.c.d'] = 'test'
    # <Box: {'a': {'b': {'c': {'d': 'test'}}}}>
    del my_box['a.b.c.d']
    # <Box: {'a': {'b': {'c': {}}}}>

This only works with keys that are string to begin with, as we don't do any automatic conversion behind the scene.

4.1 Update: This now also supports list traversal, like `my_box['my_key[0][0]']`


Support for adding two Boxes together
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. code:: python

    from box import Box
    Box(a=4) + Box(a='overwritten', b=5)
    # <Box: {'a': 'overwritten', 'b': 5}>


Additional additions
~~~~~~~~~~~~~~~~~~~~

* Added toml conversion support
* Added CSV conversion support
* Added box_from_file helper function

Changes
-------

Adding merge_update as its own function, update now works like the default dictionary update
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Traditional update is destructive to nested dictionaries.

.. code:: python

    from box import Box
    box_one = Box(inside_dict={'data': 5})
    box_two = Box(inside_dict={'folly': True})
    box_one.update(box_two)
    repr(box_one)
    # <Box: {'inside_dict': {'folly': True}}>


Merge update takes existing sub dictionaries into consideration

.. code:: python

    from box import Box
    box_one = Box(inside_dict={'data': 5})
    box_two = Box(inside_dict={'folly': True})
    box_one.merge_update(box_two)
    repr(box_one)
    "<Box: {'inside_dict': {'data': 5, 'folly': True}}>"

Camel Killer Box now changes keys on insertion
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

There was a bug in the 4.0 code that meant camel killer was not working at all under normal conditions due
to the change of how the box is instantiated.

.. code:: python

        from box import Box

        my_box = Box({'CamelCase': 'Item'}, camel_killer_box=True)
        assert my_box.camel_case == 'Item'
        print(my_box.to_dict())
        # {'camel_case': 'Item'}


Conversion keys are now a bit smarter with how they are handled
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Keys with safety underscores used to be treated internally as if the underscores didn't always exist, i.e.

.. code:: python

    from box import Box
    b = Box(_out = 'preserved')
    b.update({'out': 'updated'})
    # expected:
    # {'_out': 'preserved', 'out': 'updated'}
    # observed:
    # {'_out': 'updated'}


Those issues have been (hopefully) overcome and now will have the expected  `<Box: {'_out': 'preserved', 'out': 'updated'}>`

YAML 1.2 default instead of 1.1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

ruamel.yaml is now an install requirement and new default instead of PyYAML.
By design ruamel.yaml uses the newer YAML v1.2 (which PyYAML does not yet support as of Jan 2020).

To use the older version of 1.1, make sure to specify the version while using the from_yaml methods.

.. code:: python

    from box import Box
    Box.from_yaml("fire_ze_missiles: no")
    <Box: {'fire_ze_missiles': 'no'}>

    Box.from_yaml("fire_ze_missiles: no", version='1.1')
    <Box: {'fire_ze_missiles': False}>

You can read more about the differences `here <https://yaml.readthedocs.io/en/latest/pyyaml.html#differences-with-pyyaml>`_

To use PyYAML instead of ruamel.yaml you must install box without dependencies (such as `--no-deps` with `pip`)

If you do chose to stick with PyYaML, you can suppress the warning on just box's import:

.. code:: python

    import warnings
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        from box import Box


Additional changes
~~~~~~~~~~~~~~~~~~

* Default Box will also work on `None` placeholders

Removed
-------

No more Python 2 support
~~~~~~~~~~~~~~~~~~~~~~~~

Python 2 is soon officially EOL and Box 4 won't support it in anyway. Box 3 will not be updated, other than will consider PRs for bugs or security issues.

Removing Ordered Box
~~~~~~~~~~~~~~~~~~~~

As dictionaries are ordered by default in Python 3.6+ there is no point to continue writing and testing code outside of that.

Removing `BoxObject`
~~~~~~~~~~~~~~~~~~~~

As BoxObject was not cross platform compatible and had some `issues <https://github.com/GrahamDumpleton/wrapt/issues/132>`_ it has been removed.

Removing `box_it_up`
~~~~~~~~~~~~~~~~~~~~

Everything is converted on creation again, as the speed was seldom worth the extra headaches associated with such a design.
