magic-dot 0.2.0
Library that allows deep extraction of layered data structures (like JSON).
License: BSD license
Requires Python: >=3.5
Keywords: magic_dot

Magic Dot
*********


.. image:: https://img.shields.io/pypi/v/magic_dot.svg
        :target: https://pypi.python.org/pypi/magic_dot

.. image:: https://img.shields.io/pypi/dm/magic_dot.svg
        :target: https://pypi.python.org/pypi/magic_dot

.. image:: https://github.com/bonafideduck/magic_dot/workflows/Sanity/badge.svg
        :target: https://github.com/bonafideduck/magic_dot/actions?query=branch%3Amaster+workflow%3A%22Sanity%22

.. image:: https://readthedocs.org/projects/magic-dot/badge/?version=latest
        :target: https://magic-dot.readthedocs.io/en/latest/?badge=latest
        :alt: Documentation Status




Library that allows deep extraction of layered data structures (like JSON).


* Free software: BSD license
* Documentation: https://magic-dot.readthedocs.io.


Introduction
============

Magic Dot encapsulates data to allow the versatile extraction of its contents.
It is easier to use than ``setdefault`` or ``try:`` ``except`` that typical
extraction from a structured like JSON.  Consider the following simplified JSON 
snipppet curl https://api.github.com/events: ::

  import json
  data = json.loads("""
    [
      {
        "type": "PushEvent",
        "payload": {
          "commits": [
            {
              "author": {
                "name": "Bubba"
    }}]}}]
  """)

``magic_dot`` can retrieve the first name of the first commit, with a default of "nobody" if any
part of that chain is missing with the the following: ::

  from magic_dot import MagicDot, NOT_FOUND
  name = MagicDot(data)[0].payload.commits[0].author.name.get("nobody")

Since the incoming JSON can't be trusted, without magic_dot, you have to verify that 
each layer is there.  This can be done with a ``try:`` ``except``, nearly as
efficiently, but it is more verbose. ::

  try:
    name = data[0]['payload']['commits'][0]['author']['name']
  except (IndexError, KeyError):
    name = "nobody"

Other features, like pluck, selective exceptions,
attribute support, and iteration can lead to cleaner code.

Features
========

Forgiving NOT_FOUND Handling
----------------------------

Manipulations of the MagicDot structure will raise no exceptions
when one of the attributes or keys are not found.  Instead it delays
this until the ``get()`` call that extracts the data at the end.
When the ``get()`` is called, there are three ways of handling
missing data:

**Default is to return magic_dot.NOT_FOUND** ::

  >>> md.nonexistent.get()
  magic_dot.NOT_FOUND

**You can request a default value for magic_dot.NOT_FOUND** ::

  >>> md.nonexistent.get('bubba')
  'bubba'

**Or you can enable exceptions when referencing the nonexistent data** ::

    >>> md.exception().nonexistent
    ---------------------------------------------------------------------------
    NotFound                                  Traceback (most recent call last)

Exceptions are not enabled by default.  They can be enabled during creation
I.E ``MagicDot(data, exception=True)`` and switched on and off with the 
``MagicDot::exception(exception=False)`` method.

Dict and List Item Handling
---------------------------

When a `md[item]` is encountered, data will be extracted as follows:

1. If ``md.__data[item]`` exists, that is used.
2. If ``md.__data.item`` attribute exists it is used.
3. If ``.exception()`` is enabled, a NotFound exception is raised.
4. Otherwise ``md.NOT_FOUND`` is assigned to the resulting encapsulated data.

Attribute Handling
------------------

When a ``md.key`` is supplied data will be extracted as follows:

1. If ``md.__data.key`` attribute exists it is used.
2. If ``md.__data[key]`` item exists, it is used.
3. If ``.exception()`` is enabled, a NotFound exception is raised.
4. Otherwise ``md.NOT_FOUND`` is assigned to the resulting ``md.__data``.


Iteration Support
-----------------

If the currently encapsulated data is an iterable, MagicDot supports iterating
over the contained data with the resulting iteration being a MagicDot wrapper
around the iterated data.

  >>> from collections import namedtuple
  >>> data = [1, {'x': 2}, namedtuple('x', 'x')(3)]
  >>> for md in MagicDot(data):
  ...   print(md.get())
  1
  {'x': 2}
  x(x=3)

By default, if an attempt is made to iterate over ``NOT_FOUND`` data, a ``TypeError``
will be raised.  The iteration code can be changed to instead return an empty list. :::

  >> md = MagicDot(1, iter_nf_as_empty=True)
  >> for x in md.nonexistent:
  ..   print(md.get())
  (prints nothing)


Other Operators
---------------

Currently, there is one additional operator, ``MagicDot::pluck()``, which if
the encapsulated data is a list, it will attempt to extract a named attribute
or key from the entire list.  The returned value is a MagicDot with the new plucked list.


Future Enhancement
==================

Future enhancements will be to support many of the `Underscore js`_ array and collection capabilities
like ``compact``, ``reject``, and ``count``.

.. _`Underscore js`: https://underscorejs.org/#arrays


Credits
=======

This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.

.. _Cookiecutter: https://github.com/audreyr/cookiecutter
.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage


History
*******

0.2.0 (2020-04-30)
==================
* This contains some modifications that are not backwards compatible.
* Remove .lists() support.
* Add .pluck() support.
* Change exception() to be raised when NOT_FOUND is generated instead of at the .get()
* Make documentation changes to reflect the above.

0.1.1 (2020-03-19)
==================

* No significant chagnes.  Testing github release automations.

0.1.0 (2020-03-19)
==================

* First release on PyPI.