attrbox


Nameattrbox JSON
Version 0.1.5 PyPI version JSON
download
home_page
SummaryAttribute-based data structures.
upload_time2023-06-05 11:37:16
maintainer
docs_urlNone
author
requires_python>=3.8
licenseMIT
keywords attr attributes dict list
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # attrbox

_Attribute-based data structures._

[![Build Status](https://img.shields.io/github/actions/workflow/status/metaist/attrbox/.github/workflows/ci.yaml?branch=main&style=for-the-badge)](https://github.com/metaist/attrbox/actions)
[![attrbox on PyPI](https://img.shields.io/pypi/v/attrbox.svg?color=blue&style=for-the-badge)](https://pypi.org/project/attrbox)
[![Supported Python versions](https://img.shields.io/pypi/pyversions/attrbox?style=for-the-badge)](https://pypi.org/project/attrbox)

[Changelog] - [Issues] - [Documentation]

[changelog]: https://github.com/metaist/attrbox/blob/main/CHANGELOG.md
[issues]: https://github.com/metaist/attrbox/issues
[documentation]: https://metaist.github.io/attrbox/

## Why?

I have common use cases where I want to improve python's `dict` and `list`:

- [`AttrDict`](#attrdict): attribute-based `dict` with better merge and deep value access
- [`AttrList`](#attrlist): `list` that broadcasts operations to its members
- [Environment](#environment): reading environment files
- [Configuration](#configuration): loading command-line arguments and configuration files
- [`JSend`](#jsend): sending JSON responses

## Install

```bash
python -m pip install attrbox
```

## AttrDict

`AttrDict` features:

- **Attribute Syntax** for `dict` similar to [accessing properties in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_objects#accessing_properties): `thing.prop` means `thing["prop"]` for get / set / delete.

- **No `KeyError`**: if a key is missing, just return `None` (like `dict.get()`).

- **Deep Indexing**: use a list of keys and `int` to get and set deeply nested values. This is similar to [lodash.get](https://lodash.com/docs/#get) except that only the array-like syntax is supported and you must use actual `int` to index across `list` objects.

- **Deep Merge**: combine two `dict` objects by extending deeply-nested keys where possible. This is different than the new `dict` union operator ([PEP 584](https://peps.python.org/pep-0584/)).

```python
from attrbox import AttrDict

items = AttrDict(a=1, b=[{"c": {"d": 5}}], e={"f": {"g": 7}})
items.a
# => 1
items.x is None
# => True
items.x = 10
items['x']
# => 10
items.get(["b", 0, "c", "d"])
# => 5
items <<= {"e": {"f": {"g": 20, "h": [30, 40]}}}
items.e.f.g
# => 20
items[['e', 'f', 'h', 1]]
# => 40
```

[Read more about `AttrDict`](https://metaist.github.io/attrbox/attrdict.html#attrbox.attrdict.AttrDict)

## AttrList

`AttrList` provides **member broadcast**: performing operations on the list performs the operation on all the items in the list. I typically use this to achieve the [Composite design pattern](https://en.wikipedia.org/wiki/Composite_pattern).

```python
from attrbox import AttrDict, AttrList

numbers = AttrList([complex(1, 2), complex(3, 4), complex(5, 6)])
numbers.real
# => [1.0, 3.0, 5.0]

words = AttrList(["Apple", "Bat", "Cat"])
words.lower()
# => ['apple', 'bat', 'cat']

items = AttrList([AttrDict(a=1, b=2), AttrDict(a=5)])
items.a
# => [1, 5]
items.b
# => [2, None]
```

[Read more about `AttrList`](https://metaist.github.io/attrbox/attrlist.html#attrbox.attrlist.AttrList)

## Environment

`attrbox.env` is similar to [python-dotenv](https://github.com/theskumar/python-dotenv), but uses the `AttrDict` ability to do deep indexing to allow for things like dotted variable names. Typically, you'll use it by calling `attrbox.load_env()` which will find the nearest <code>.env</code> file and load it into `os.environ`.

[Read more about `attrbox.env`](https://metaist.github.io/attrbox/env.html)

## Configuration

`attrbox` supports loading configuration files from `.json`, `.toml`, and `.env` files. By default, `load_config()` looks for a key `imports` and will recursively import those files (relative to the current file) before loading the rest of the current file (data is merged using `AttrDict`). This allows you to create templates or smaller configurations that build up to a larger configuration.

For CLI applications, `attrbox.parse_docopt()` let's you use the power of [`docopt`](https://github.com/docopt/docopt) with the flexibility of `AttrDict`. By default, `--config` and `<config>` arguments will load the file using the `load_config()`

```python
"""Usage: prog.py [--help] [--version] [-c CONFIG] --file FILE

Options:
  --help                show this message and exit
  --version             show the version number and exit
  -c, --config CONFIG   load this configuration file (supported: .toml, .json, .env)
  --file FILE           the file to process
"""

def main():
    args = parse_docopt(__doc__, version=__version__)
    args.file # has the value of --file

if __name__ == "__main__":
    main()
```

Building on top of `docopt` we strip off leading dashes and convert them to underscores so that we can access the arguments as `AttrDict` attributes.

[Read more about `attrbox.config`](https://metaist.github.io/attrbox/config.html)

## JSend

`JSend` is an approximate implementation of the [`JSend` specification](https://labs.omniti.com/labs/jsend) that makes it easy to create standard JSON responses. The main difference is that I added an `ok` attribute to make it easy to tell if there was a problem (`fail` or `error`).

```python
from attrbox import JSend

def some_function(arg1):
    result = JSend() # default is "success"

    if not is_good(arg1):
        # fail = controlled problem
        return result.fail(message="You gone messed up.")

    try:
        result.success(data=process(arg1))
    except Exception:
        # error = uncontrolled problem
        return result.error(message="We have a problem.")

    return result
```

Because the `JSend` object is an `AttrDict`, it acts like a `dict` in every other respect (e.g., it is JSON-serializable).

[Read more about `JSend`](https://metaist.github.io/attrbox/jsend.html#attrbox.jsend.JSend)

## License

[MIT License](https://github.com/metaist/attrbox/blob/main/LICENSE.md)

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "attrbox",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "",
    "keywords": "attr,attributes,dict,list",
    "author": "",
    "author_email": "Metaist LLC <metaist@metaist.com>",
    "download_url": "https://files.pythonhosted.org/packages/87/f6/fe8d5f804a5fef520fa9e2f2e018827dcc0b267aa8449ca3322e4d27bbaa/attrbox-0.1.5.tar.gz",
    "platform": null,
    "description": "# attrbox\n\n_Attribute-based data structures._\n\n[![Build Status](https://img.shields.io/github/actions/workflow/status/metaist/attrbox/.github/workflows/ci.yaml?branch=main&style=for-the-badge)](https://github.com/metaist/attrbox/actions)\n[![attrbox on PyPI](https://img.shields.io/pypi/v/attrbox.svg?color=blue&style=for-the-badge)](https://pypi.org/project/attrbox)\n[![Supported Python versions](https://img.shields.io/pypi/pyversions/attrbox?style=for-the-badge)](https://pypi.org/project/attrbox)\n\n[Changelog] - [Issues] - [Documentation]\n\n[changelog]: https://github.com/metaist/attrbox/blob/main/CHANGELOG.md\n[issues]: https://github.com/metaist/attrbox/issues\n[documentation]: https://metaist.github.io/attrbox/\n\n## Why?\n\nI have common use cases where I want to improve python's `dict` and `list`:\n\n- [`AttrDict`](#attrdict): attribute-based `dict` with better merge and deep value access\n- [`AttrList`](#attrlist): `list` that broadcasts operations to its members\n- [Environment](#environment): reading environment files\n- [Configuration](#configuration): loading command-line arguments and configuration files\n- [`JSend`](#jsend): sending JSON responses\n\n## Install\n\n```bash\npython -m pip install attrbox\n```\n\n## AttrDict\n\n`AttrDict` features:\n\n- **Attribute Syntax** for `dict` similar to [accessing properties in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_objects#accessing_properties): `thing.prop` means `thing[\"prop\"]` for get / set / delete.\n\n- **No `KeyError`**: if a key is missing, just return `None` (like `dict.get()`).\n\n- **Deep Indexing**: use a list of keys and `int` to get and set deeply nested values. This is similar to [lodash.get](https://lodash.com/docs/#get) except that only the array-like syntax is supported and you must use actual `int` to index across `list` objects.\n\n- **Deep Merge**: combine two `dict` objects by extending deeply-nested keys where possible. This is different than the new `dict` union operator ([PEP 584](https://peps.python.org/pep-0584/)).\n\n```python\nfrom attrbox import AttrDict\n\nitems = AttrDict(a=1, b=[{\"c\": {\"d\": 5}}], e={\"f\": {\"g\": 7}})\nitems.a\n# => 1\nitems.x is None\n# => True\nitems.x = 10\nitems['x']\n# => 10\nitems.get([\"b\", 0, \"c\", \"d\"])\n# => 5\nitems <<= {\"e\": {\"f\": {\"g\": 20, \"h\": [30, 40]}}}\nitems.e.f.g\n# => 20\nitems[['e', 'f', 'h', 1]]\n# => 40\n```\n\n[Read more about `AttrDict`](https://metaist.github.io/attrbox/attrdict.html#attrbox.attrdict.AttrDict)\n\n## AttrList\n\n`AttrList` provides **member broadcast**: performing operations on the list performs the operation on all the items in the list. I typically use this to achieve the [Composite design pattern](https://en.wikipedia.org/wiki/Composite_pattern).\n\n```python\nfrom attrbox import AttrDict, AttrList\n\nnumbers = AttrList([complex(1, 2), complex(3, 4), complex(5, 6)])\nnumbers.real\n# => [1.0, 3.0, 5.0]\n\nwords = AttrList([\"Apple\", \"Bat\", \"Cat\"])\nwords.lower()\n# => ['apple', 'bat', 'cat']\n\nitems = AttrList([AttrDict(a=1, b=2), AttrDict(a=5)])\nitems.a\n# => [1, 5]\nitems.b\n# => [2, None]\n```\n\n[Read more about `AttrList`](https://metaist.github.io/attrbox/attrlist.html#attrbox.attrlist.AttrList)\n\n## Environment\n\n`attrbox.env` is similar to [python-dotenv](https://github.com/theskumar/python-dotenv), but uses the `AttrDict` ability to do deep indexing to allow for things like dotted variable names. Typically, you'll use it by calling `attrbox.load_env()` which will find the nearest <code>.env</code> file and load it into `os.environ`.\n\n[Read more about `attrbox.env`](https://metaist.github.io/attrbox/env.html)\n\n## Configuration\n\n`attrbox` supports loading configuration files from `.json`, `.toml`, and `.env` files. By default, `load_config()` looks for a key `imports` and will recursively import those files (relative to the current file) before loading the rest of the current file (data is merged using `AttrDict`). This allows you to create templates or smaller configurations that build up to a larger configuration.\n\nFor CLI applications, `attrbox.parse_docopt()` let's you use the power of [`docopt`](https://github.com/docopt/docopt) with the flexibility of `AttrDict`. By default, `--config` and `<config>` arguments will load the file using the `load_config()`\n\n```python\n\"\"\"Usage: prog.py [--help] [--version] [-c CONFIG] --file FILE\n\nOptions:\n  --help                show this message and exit\n  --version             show the version number and exit\n  -c, --config CONFIG   load this configuration file (supported: .toml, .json, .env)\n  --file FILE           the file to process\n\"\"\"\n\ndef main():\n    args = parse_docopt(__doc__, version=__version__)\n    args.file # has the value of --file\n\nif __name__ == \"__main__\":\n    main()\n```\n\nBuilding on top of `docopt` we strip off leading dashes and convert them to underscores so that we can access the arguments as `AttrDict` attributes.\n\n[Read more about `attrbox.config`](https://metaist.github.io/attrbox/config.html)\n\n## JSend\n\n`JSend` is an approximate implementation of the [`JSend` specification](https://labs.omniti.com/labs/jsend) that makes it easy to create standard JSON responses. The main difference is that I added an `ok` attribute to make it easy to tell if there was a problem (`fail` or `error`).\n\n```python\nfrom attrbox import JSend\n\ndef some_function(arg1):\n    result = JSend() # default is \"success\"\n\n    if not is_good(arg1):\n        # fail = controlled problem\n        return result.fail(message=\"You gone messed up.\")\n\n    try:\n        result.success(data=process(arg1))\n    except Exception:\n        # error = uncontrolled problem\n        return result.error(message=\"We have a problem.\")\n\n    return result\n```\n\nBecause the `JSend` object is an `AttrDict`, it acts like a `dict` in every other respect (e.g., it is JSON-serializable).\n\n[Read more about `JSend`](https://metaist.github.io/attrbox/jsend.html#attrbox.jsend.JSend)\n\n## License\n\n[MIT License](https://github.com/metaist/attrbox/blob/main/LICENSE.md)\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Attribute-based data structures.",
    "version": "0.1.5",
    "project_urls": {
        "Changelog": "https://github.com/metaist/attrbox/blob/main/CHANGELOG.md",
        "Documentation": "https://metaist.github.io/attrbox/",
        "Homepage": "https://github.com/metaist/attrbox",
        "Repository": "https://github.com/metaist/attrbox.git"
    },
    "split_keywords": [
        "attr",
        "attributes",
        "dict",
        "list"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c3334520765f25f1f4f9707a8b3244226b9cc63d2724054b22234f5f26aaf6bd",
                "md5": "1c688948b0247d11c5cde01063c6c543",
                "sha256": "ffea00298ecc7b4d109fc0fe71d9490ecd89b698abff152dc643a820ed8c5755"
            },
            "downloads": -1,
            "filename": "attrbox-0.1.5-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "1c688948b0247d11c5cde01063c6c543",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 19161,
            "upload_time": "2023-06-05T11:37:14",
            "upload_time_iso_8601": "2023-06-05T11:37:14.587855Z",
            "url": "https://files.pythonhosted.org/packages/c3/33/4520765f25f1f4f9707a8b3244226b9cc63d2724054b22234f5f26aaf6bd/attrbox-0.1.5-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "87f6fe8d5f804a5fef520fa9e2f2e018827dcc0b267aa8449ca3322e4d27bbaa",
                "md5": "8de1a5ec074ca059f489e72e4e50af59",
                "sha256": "7c5a898e176ad43bc7369caa1d6c737b2542e6cddb15fef38c032f44794b3d91"
            },
            "downloads": -1,
            "filename": "attrbox-0.1.5.tar.gz",
            "has_sig": false,
            "md5_digest": "8de1a5ec074ca059f489e72e4e50af59",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 20715,
            "upload_time": "2023-06-05T11:37:16",
            "upload_time_iso_8601": "2023-06-05T11:37:16.349692Z",
            "url": "https://files.pythonhosted.org/packages/87/f6/fe8d5f804a5fef520fa9e2f2e018827dcc0b267aa8449ca3322e4d27bbaa/attrbox-0.1.5.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-06-05 11:37:16",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "metaist",
    "github_project": "attrbox",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "attrbox"
}
        
Elapsed time: 0.07140s