Getitem-objects «skin» for attribute-like access
Project description
[![travis](https://img.shields.io/travis/pohmelie/skin.svg)](https://travis-ci.org/pohmelie/skin)
[![coveralls](https://img.shields.io/coveralls/pohmelie/skin.svg)](https://coveralls.io/github/pohmelie/skin)
[![pypi](https://img.shields.io/pypi/v/skin.svg)](https://pypi-hypernode.com/pypi/skin)
# Skin
Getitem-objects «skin» for attribute-like access.
## Reason
[addict](https://github.com/mewwts/addict), [python-box](https://github.com/cdgriffith/Box) and [tri.struct](https://github.com/TriOptima/tri.struct) do not respect `dict` reference transparency.
### addict
``` python
>>> from addict import Dict
>>> original = {"foo": [1, 2, 3]}
>>> d = Dict(original)
>>> d.foo
[1, 2, 3]
>>> type(d.foo)
<class 'list'>
>>> d.foo.append(4)
>>> original
{'foo': [1, 2, 3]}
>>> d.foo
[1, 2, 3, 4]
>>>
```
### python-box
``` python
>>> from box import Box
>>> original = {"foo": [1, 2, 3]}
>>> b = Box(original)
>>> b.foo
<BoxList: [1, 2, 3]>
>>> type(b.foo)
<class 'box.BoxList'>
>>> b.foo.append(4)
>>> original
{'foo': [1, 2, 3]}
>>> b.foo
<BoxList: [1, 2, 3, 4]>
>>>
```
### tri.struct
``` python
>>> from tri.struct import Struct
>>> o = {"foo": [1, 2, {"bar": "baz"}]}
>>> s = Struct(o)
>>> s.foo[-1].bar
Traceback (most recent call last):
File "<input>", line 1, in <module>
s.foo[-1].bar
AttributeError: 'dict' object has no attribute 'bar'
>>> s.new = "new"
>>> o
{'foo': [1, 2, {'bar': 'baz'}]}
>>> s
Struct(foo=[1, 2, {'bar': 'baz'}], new='new')
>>>
```
### skin
``` python
>>> from skin import Skin
>>> original = {"foo": [1, 2, 3]}
>>> s = Skin(original)
>>> s.foo
Skin([1, 2, 3])
>>> type(s.foo)
<class 'skin.Skin'>
>>> type(s.foo.value)
<class 'list'>
>>> s.foo.value is original["foo"]
True
>>> s.foo.append(4)
>>> original
{'foo': [1, 2, 3, 4]}
>>>
```
# Documentation
``` python
Skin(value=DEFAULT_VALUE, *, allowed=ANY, forbidden=FORBIDDEN)
```
* value — any object with `__getitem__` method (default: `dict`).
* allowed — tuple of allowed types to wrap or `skin.ANY` for all types allowed (default: `skin.ANY`)
* forbidden — tuple of forbidden types to wrap (default: `(str, bytes, bytearray, memoryview, range)`)
What is `allowed` and `forbidden`?
Since skin target is not to recreate containers there should be a rule to determine is object container or endpoint-node. Some objects (listed above as `forbidden`) have `__getitem__` method, but wont act like containers.
Example:
You have original dictionary `{"foo": "bar"}`, and you expect from skin that `Skin({"foo": "bar"}).foo` is `"bar"` string, not skin wrapper. But, `str`, `bytes`, etc. have `__getitiem__` method. That is why there is `allowed` and `forbidden` tuples. I hope defaults are enough for 99% usecases.
In general: if `value` have no `__getitem__` or not allowed or forbidden you will get `SkinValueError` exception, which skin catches to determine if object can be wrapped.
**Skin class have only one accessible attribute: `value` — original object, which skin wraps** :tada:
Skin supports both "item" and "attribute" notations:
``` python
>>> s = Skin({"foo": "bar"})
>>> s.foo is s["foo"]
True
>>>
```
But, in case of nested containers:
``` python
>>> s = Skin({"foo": {"bar": "baz"}})
>>> s.foo is s["foo"]
False
>>> s.foo.value is s["foo"].value
True
>>>
```
Both objects `s.foo` and `s["foo"]` is instances of `Skin`, but since they are created dynamicaly they are not the same object.
Skin use strict order to find "items":
* in case of attribute access:
* skin attribute
* value attribute
* value item
* orphan item
* in case of item access:
* value item
* orphan item
Orphan item is just naming for item, which is not yet set. Example:
``` python
>>> s = Skin()
>>> s.foo.bar
Skin({})
>>> s
Skin({})
>>>
```
As you can see there is no "foo" or "bar" items. But in case of setting:
``` python
>>> s = Skin()
>>> s.foo.bar = "baz"
>>> s
Skin({'foo': {'bar': 'baz'}})
>>>
```
Since skin is just wrapper, which do not recreate container you can use any object with `__getitem__`:
``` python
>>> import collections
>>> s = Skin(collections.defaultdict(list))
>>> s.foo.append(1)
>>> s
Skin(defaultdict(<class 'list'>, {'foo': [1]}))
>>>
```
# Benchmark (v0.0.5)
``` text
Create instance:
Box 0.7227337849326432
Dict 0.8247780610108748
Skin 0.14907896996010095
tri.struct 0.014445346896536648
Access exist:
dict 0.005448702024295926
Box 0.32549735193606466
Dict 0.21359142300207168
Skin 1.5485703510930762
Access non-exist:
Dict 0.2847607780713588
Skin 1.007843557978049
```
`Skin` do not wrap objects recursively, so it have constant creation time. In case of access, `Skin` create wrappers every time. That is why it is 3x-8x slower, than `Dict` and `Box`.
[![coveralls](https://img.shields.io/coveralls/pohmelie/skin.svg)](https://coveralls.io/github/pohmelie/skin)
[![pypi](https://img.shields.io/pypi/v/skin.svg)](https://pypi-hypernode.com/pypi/skin)
# Skin
Getitem-objects «skin» for attribute-like access.
## Reason
[addict](https://github.com/mewwts/addict), [python-box](https://github.com/cdgriffith/Box) and [tri.struct](https://github.com/TriOptima/tri.struct) do not respect `dict` reference transparency.
### addict
``` python
>>> from addict import Dict
>>> original = {"foo": [1, 2, 3]}
>>> d = Dict(original)
>>> d.foo
[1, 2, 3]
>>> type(d.foo)
<class 'list'>
>>> d.foo.append(4)
>>> original
{'foo': [1, 2, 3]}
>>> d.foo
[1, 2, 3, 4]
>>>
```
### python-box
``` python
>>> from box import Box
>>> original = {"foo": [1, 2, 3]}
>>> b = Box(original)
>>> b.foo
<BoxList: [1, 2, 3]>
>>> type(b.foo)
<class 'box.BoxList'>
>>> b.foo.append(4)
>>> original
{'foo': [1, 2, 3]}
>>> b.foo
<BoxList: [1, 2, 3, 4]>
>>>
```
### tri.struct
``` python
>>> from tri.struct import Struct
>>> o = {"foo": [1, 2, {"bar": "baz"}]}
>>> s = Struct(o)
>>> s.foo[-1].bar
Traceback (most recent call last):
File "<input>", line 1, in <module>
s.foo[-1].bar
AttributeError: 'dict' object has no attribute 'bar'
>>> s.new = "new"
>>> o
{'foo': [1, 2, {'bar': 'baz'}]}
>>> s
Struct(foo=[1, 2, {'bar': 'baz'}], new='new')
>>>
```
### skin
``` python
>>> from skin import Skin
>>> original = {"foo": [1, 2, 3]}
>>> s = Skin(original)
>>> s.foo
Skin([1, 2, 3])
>>> type(s.foo)
<class 'skin.Skin'>
>>> type(s.foo.value)
<class 'list'>
>>> s.foo.value is original["foo"]
True
>>> s.foo.append(4)
>>> original
{'foo': [1, 2, 3, 4]}
>>>
```
# Documentation
``` python
Skin(value=DEFAULT_VALUE, *, allowed=ANY, forbidden=FORBIDDEN)
```
* value — any object with `__getitem__` method (default: `dict`).
* allowed — tuple of allowed types to wrap or `skin.ANY` for all types allowed (default: `skin.ANY`)
* forbidden — tuple of forbidden types to wrap (default: `(str, bytes, bytearray, memoryview, range)`)
What is `allowed` and `forbidden`?
Since skin target is not to recreate containers there should be a rule to determine is object container or endpoint-node. Some objects (listed above as `forbidden`) have `__getitem__` method, but wont act like containers.
Example:
You have original dictionary `{"foo": "bar"}`, and you expect from skin that `Skin({"foo": "bar"}).foo` is `"bar"` string, not skin wrapper. But, `str`, `bytes`, etc. have `__getitiem__` method. That is why there is `allowed` and `forbidden` tuples. I hope defaults are enough for 99% usecases.
In general: if `value` have no `__getitem__` or not allowed or forbidden you will get `SkinValueError` exception, which skin catches to determine if object can be wrapped.
**Skin class have only one accessible attribute: `value` — original object, which skin wraps** :tada:
Skin supports both "item" and "attribute" notations:
``` python
>>> s = Skin({"foo": "bar"})
>>> s.foo is s["foo"]
True
>>>
```
But, in case of nested containers:
``` python
>>> s = Skin({"foo": {"bar": "baz"}})
>>> s.foo is s["foo"]
False
>>> s.foo.value is s["foo"].value
True
>>>
```
Both objects `s.foo` and `s["foo"]` is instances of `Skin`, but since they are created dynamicaly they are not the same object.
Skin use strict order to find "items":
* in case of attribute access:
* skin attribute
* value attribute
* value item
* orphan item
* in case of item access:
* value item
* orphan item
Orphan item is just naming for item, which is not yet set. Example:
``` python
>>> s = Skin()
>>> s.foo.bar
Skin({})
>>> s
Skin({})
>>>
```
As you can see there is no "foo" or "bar" items. But in case of setting:
``` python
>>> s = Skin()
>>> s.foo.bar = "baz"
>>> s
Skin({'foo': {'bar': 'baz'}})
>>>
```
Since skin is just wrapper, which do not recreate container you can use any object with `__getitem__`:
``` python
>>> import collections
>>> s = Skin(collections.defaultdict(list))
>>> s.foo.append(1)
>>> s
Skin(defaultdict(<class 'list'>, {'foo': [1]}))
>>>
```
# Benchmark (v0.0.5)
``` text
Create instance:
Box 0.7227337849326432
Dict 0.8247780610108748
Skin 0.14907896996010095
tri.struct 0.014445346896536648
Access exist:
dict 0.005448702024295926
Box 0.32549735193606466
Dict 0.21359142300207168
Skin 1.5485703510930762
Access non-exist:
Dict 0.2847607780713588
Skin 1.007843557978049
```
`Skin` do not wrap objects recursively, so it have constant creation time. In case of access, `Skin` create wrappers every time. That is why it is 3x-8x slower, than `Dict` and `Box`.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
skin-0.0.5.tar.gz
(4.3 kB
view details)
File details
Details for the file skin-0.0.5.tar.gz
.
File metadata
- Download URL: skin-0.0.5.tar.gz
- Upload date:
- Size: 4.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 913eb5e4cf7f47b410606658e6920335ecb42d8121f10fd76ae25c409b9eaf94 |
|
MD5 | 26ad72a1dd1e6ea427685f115b084f5c |
|
BLAKE2b-256 | f20283b563430dedea7b4bbf904088a54e6ad4dd1efb1ca1f12a39896270de8d |