namedtuple, dataclass そして datajuggler

# はじめに
Pythonで辞書を使うとき地味に面倒なので、`[KEYNAME]`での参照です。辞書をdataclass や namedtuple のようにドット表記でアトリビュート参照するように値にアクセスできるようにしたライブラリが [datajuggler](https://pypi.org/project/datajuggler/) です。

## 先人たちの功績のおかげ
といっても別段真新しいものではなくて、以下の素晴らしいプロジェクトをリファクタリングやラッピングして統合させたただけのものです。

 - [python-benedict](https://github.com/fabiocaccamo/python-benedict)
 - [munch](https://github.com/Infinidat/munch)
 - [adict](https://github.com/mewwts/addict).
 - [serialize](https://github.com/hgrecco/serialize)


# dataclass や namedtuple があるのになぜ？
dataclass や namedtuple は値を保持するという目的では十分に機能するのですが、
[dataset](https://dataset.readthedocs.io/en/latest/) を使ってデータベースにアクセスしているような場合に、面倒な場合があることがモチベーションになりました。

```python
In [1]: from datajuggler import serializer as io

In [2]: io.read_contents('sqlite:///users.sqlite#users')
Out[2]:
[{'id': 1, 'name': 'David Coverdale', 'age': 71, 'belongs': 'Whitesnake'},
 {'id': 2, 'name': 'Neal Schon ', 'age': 68, 'belongs': 'Journey'},
 {'id': 3, 'name': 'Tom Scholz', 'age': 75, 'belongs': 'Boston'},
 {'id': 4, 'name': 'David Gilmour', 'age': 75, 'belongs': 'Pink Floyd'},
 {'id': 5, 'name': 'Ann Wilson', 'age': 71, 'belongs': 'Heart'},
 {'id': 6, 'name': 'Nacy Wilson', 'age': 67, 'belongs': 'Heart'}]

In [3]:
```

ここにある、`io.read_contents()` はdatasetのラッパーです。

datasset はデータベースを読み込んで辞書のリストを返してくれます。
`dataset.connect()` に `row_type` で型を与えるともっと理解しやすい結果になります。そこで、次のようなクラスを作ることになります。


```python
In [8]: # %load users_class.py
   ...: class User:
   ...:
   ...:     def __init__(self, *nargs, **kwargs):
   ...:         self.data = dict(*nargs, **kwargs)
   ...:         self.id: int = self.data['id']
   ...:         self.name: str = self.data['name']
   ...:         self.age: int = self.data['age']
   ...:         self.belongs: str = self.data['belongs']
   ...:
   ...:     def __repr__(self):
   ...:         return(f'User(id={self.id}, '
   ...:                f'name="{self.name}", '
   ...:                f'age={self.age}, '
   ...:                f'belongs="{self.belongs}")' )
   ...:
   ...:

In [9]: users = io.read_contents('sqlite:///users.sqlite#users',row_type=User)

In [10]: users
Out[10]:
[User(id=1, name="David Coverdale", age=71, belongs="Whitesnake"),
 User(id=2, name="Neal Schon ", age=68, belongs="Journey"),
 User(id=3, name="Tom Scholz", age=75, belongs="Boston"),
 User(id=4, name="David Gilmour", age=75, belongs="Pink Floyd"),
 User(id=5, name="Ann Wilson", age=71, belongs="Heart"),
 User(id=6, name="Nacy Wilson", age=67, belongs="Heart")]

In [11]: users[0].name
Out[11]: 'David Coverdale'

In [12]:
```

この`User`クラスを使うことでもドット表記でデータにはアクセスできます。
ここで、`__repr__()` を省略してもこのクラスは定義することはできますが、次のような結果になるため実用的ではなくなります。

```python
In [4]:  class User:
   ...:
   ...:     def __init__(self, *nargs, **kwargs):
   ...:         self.data = dict(*nargs, **kwargs)
   ...:         self.id: int = self.data['id']
   ...:         self.name: str = self.data['name']
   ...:         self.age: int = self.data['age']
   ...:         self.belongs: str = self.data['belongs']
   ...:

In [5]: io.read_contents('sqlite:///users.sqlite#users',row_type=User)
Out[5]:
[<__main__.User at 0x1092c0f40>,
 <__main__.User at 0x1092c3430>,
 <__main__.User at 0x1092c0cd0>,
 <__main__.User at 0x1092c1d20>,
 <__main__.User at 0x1092c26e0>,
 <__main__.User at 0x1092c1660>]

In [6]:
```

結果として、都度`__init__()` や `__repr__()` の定義することになり、地味に面倒です。

そうしたことから namedtuple が生まれているわけです。

## namedtuple の残念なところ
namedtuple でも良さそうなのですが、この例の場合は、初期化時に辞書が渡されるため、安直に `__init__()` を再定義しようとしてもエラーになり定義できません。

```python
In [18]: from typing import NamedTuple
    ...:
    ...: try:
    ...:     class User(NamedTuple):
    ...:         id: int
    ...:         name: str
    ...:         age: int
    ...:         belongs: str
    ...:
    ...:         def __init__(self, profile):
    ...:             d = dict(profile)
    ...:             self.id = d['id']
    ...:             self.name = d['name']
    ...:             self.age = d['age']
    ...:             self.belongs = d['belongs']
    ...:
    ...: except AttributeError as e:
    ...:     print(e)
    ...:
Cannot overwrite NamedTuple attribute __init__

In [19]:
```

`__init__()` だけでなく `__new__()` の再定義も許されません。


この問題を回避するためには、次のようにクラスを階層化します。

```python
In [20]: from typing import NamedTuple
    ...:
    ...: class UserBase(NamedTuple):
    ...:     id: int
    ...:     name: str
    ...:     age: int
    ...:     belongs: str
    ...:
    ...: class User(UserBase):
    ...:     def __new__(cls, profile):
    ...:         return super().__new__(cls, **dict(profile))
    ...:

In [21]:
```

これでも良いとは言えるのですが、`__init__()`の引数について辻褄をあわせるためだけにクラスを定義するのは、すこし残念です。


## dataclass の残念なところ

namedtuple と同じような目的で、dataclass を使って実装することもできます。
この場合は、`__init__()`の引数を辻褄を合わせるためには次のようにします。

```python
In [1]: from datajuggler import serializer as io

In [2]: from dataclasses import dataclass, field
   ...:
   ...: @dataclass
   ...: class User:
   ...:     id: int = field(init=False)
   ...:     name: str = field(init=False)
   ...:     age: int = field(init=False)
   ...:     belongs: str = field(init=False)
   ...:
   ...:     def __init__(self, *nargs, **kwargs):
   ...:         self.data = dict(*nargs, **kwargs)
   ...:         self.id = self.data['id']
   ...:         self.name = self.data['name']
   ...:         self.age = int(self.data['age'])
   ...:         self.belongs = self.data['belongs']
   ...:
   ...:

In [3]:
```

datatclass の利点がほとんどなくなるうえに、フィールドごとに `field(init=False)`が必要になるわけです。これも地味に面倒です。


## datajuggler の aDict を使うと簡潔に記述できる

datajuggler の aDict は dataclass や namedtuple のようにドット表記で値にアクセスできる辞書です。

```python
In [1]: from datajuggler import serializer as io

In [2]: from datajuggler import aDict

In [3]: class User(aDict):
   ...:     pass
   ...:

In [4]: users = io.read_contents('sqlite:///users.sqlite#users',row_type=User)

In [5]: users
Out[5]:
[User({'id': 1, 'name': 'David Coverdale', 'age': 71, 'belongs': 'Whitesnake'}),
 User({'id': 2, 'name': 'Neal Schon ', 'age': 68, 'belongs': 'Journey'}),
 User({'id': 3, 'name': 'Tom Scholz', 'age': 75, 'belongs': 'Boston'}),
 User({'id': 4, 'name': 'David Gilmour', 'age': 75, 'belongs': 'Pink Floyd'}),
 User({'id': 5, 'name': 'Ann Wilson', 'age': 71, 'belongs': 'Heart'}),
 User({'id': 6, 'name': 'Nacy Wilson', 'age': 67, 'belongs': 'Heart'})]

In [6]: users[0].name
Out[6]: 'David Coverdale'

In [7]:
```

## aDict はイミュータブルにもできる
namedtuple は名前通りにイミュータブルなオブジェクトです。dataclass でも`frozen=True`を設定することで、フィールド単位やクラス単位でイミュータブルな
オブジェクトにすることができます。aDict では、`freeze()`メソッドを呼び出すと、以後`unfreeze()`が呼び出されるまではイミュータブルなオブジェクトとなります。

```python
In [8]: users[0].freeze()

In [9]: try:
   ...:     users[0].age=20
   ...: except AttributeError as e:
   ...:     print(e)
   ...:
User frozen object cannot be modified.

In [10]:
```

また、aDict の frozen オブジェクトはハッシュすることができるため、
辞書のキーとして使用することもできます。

```python
In [25]: _ = [x.freeze() for x in users ]

In [26]: artist = {x: x.belongs for x in users}

In [27]: artist
Out[27]:
{User({'id': 1, 'name': 'David Coverdale', 'age': 71, 'belongs': 'Whitesnake'}): 'Whitesnake',
 User({'id': 2, 'name': 'Neal Schon ', 'age': 68, 'belongs': 'Journey'}): 'Journey',
 User({'id': 3, 'name': 'Tom Scholz', 'age': 75, 'belongs': 'Boston'}): 'Boston',
 User({'id': 4, 'name': 'David Gilmour', 'age': 75, 'belongs': 'Pink Floyd'}): 'Pink Floyd',
 User({'id': 5, 'name': 'Ann Wilson', 'age': 71, 'belongs': 'Heart'}): 'Heart',
 User({'id': 6, 'name': 'Nacy Wilson', 'age': 67, 'belongs': 'Heart'}): 'Heart'}

In [28]:
```

辞書データとしてそのままキーにできるため、何かと便利です。

```python
In [28]: gilmour = users[3]

In [29]: gilmour
Out[29]: User({'id': 4, 'name': 'David Gilmour', 'age': 75, 'belongs': 'Pink Floyd'})

In [30]: gilmour in artist
Out[30]: True

In [31]:
```

## iListについて

aDict はイミュータブルを制御でき、ハッシュ可能なオブジェクトを作れるので、これを利用したリストが iList です。

通常、リストオブジェクトはアトリビュートを追加できません。

```python
In [31]: l1 = list([1, 2, 3])

In [32]: l1.python=’osaka'
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In [32], line 1
----> 1 l1.python='osaka'

AttributeError: 'list' object has no attribute 'python'

In [33]:
```

これが、iList では可能になります。

```python
In [38]: i1 = iList([1, 2, 3])

In [39]: i1.python='Osaka'

In [40]: i1
Out[40]: iList([1, 2, 3])

In [41]: i1.python
Out[41]: 'Osaka'

In [42]:
```

これは内部に aDict を保持しているためです。

```python
In [42]: i1._attrs
Out[42]: aDict({'python': 'Osaka'})

In [43]:
```

## listとの互換性
iList 自体は list と互換性を保っているため、
通常のリスト操作についても問題なく、かつ、辞書を内包しているオブジェクトとなります。

```python
In [1]: from datajuggler import iList

In [2]: l1 = iList([1,2,3,4,5])
   ...: l2 = iList([1,2,3,4,5])
   ...: assert l1 == l2

In [3]: l1 = iList([1,2,3,4,5])
   ...: l2 = list([1,2,3,4,5])
   ...: assert l1 == l2

In [4]: l1 = iList([5,4,3,2,1])
   ...: l2 = iList([1,2,3,4,5])
   ...: assert l1 != l2

In [5]: l1 = iList([5,4,3,2,1])
   ...: l2 = list([1,2,3,4,5])
   ...: assert l1 != l2

In [6]: l1 = iList([1,2,3])
   ...: l2 = list([4,5,6])
   ...: assert l1 + l2 == [1,2,3,4,5,6]

In [7]: l1 = iList([1,2,3])
   ...: l2 = iList([4,5,6])
   ...: assert l1 + l2 == [1,2,3,4,5,6]

In [8]: l1 = iList([1,2,3,4,5,6])
   ...: l2 = iList([4,5,6])
   ...: assert l1 - l2 == [1,2,3]

In [9]: l1 = iList([1,2,3,4,5,6])
   ...: l2 = list([4,5,6])
   ...: assert l1 - l2 == [1,2,3]

In [10]: l1 = iList([1,2,3])
    ...: assert l1 * 3 == [1,2,3,1,2,3,1,2,3]

In [11]: l1 = iList([1,2,3,4,5,6])
    ...: l2 = iList([4,5,6])
    ...: assert ( l1 & l2 ) == [4,5,6]

In [12]: l1 = iList([1,4,2,5,3,6])
    ...: l2 = list([6,4,5])
    ...: assert (l1 & l2 ) == [4,5,6]

In [13]: l = iList([1,2,3,[4,5]])
    ...: assert l.find(2) == [1]

In [14]: l = iList([1,2,3,[4,5]])
    ...: assert l.find(0) == None

In [15]: l = iList([1,2,3,[4,5]])
    ...: assert l.find([2,3]) == [1,2]

In [16]: l1 = iList([])
    ...: l1.append(4)
    ...: assert l1 == [4]

In [17]: l1 = iList([])
    ...: l1.append([4,5,6])
    ...: assert l1 == [[4,5,6]]

In [18]: l1 = iList([1,2,3,4,5,6])
    ...: _ = l1.pop()
    ...: assert l1 == [1,2,3,4,5]

In [19]: l1 = iList([1,2,3,4,5,6])
    ...: _ = l1.pop(0)
    ...: assert l1 == [2,3,4,5,6]

In [20]: l1 = iList([1,2,3,4,5,6])
    ...: l1.remove(3)
    ...: assert l1 == [1,2,4,5,6]

In [21]: l1 = iList([1,2,3,4,5,6,7,8,9])
    ...: v = l1[3:5]
    ...: assert v == [4, 5]

In [22]:
```


## iList もイミュータブルオブジェクトにできる
aDictを利用してfrozenなオブジェクトにできるため、タプルのようにイミュータブルな挙動をさせることができます。

```python
In [54]: i1
Out[54]: iList([1, 2, 3])

In [55]: i1.freeze()

In [56]: try:
    ...:     i1.append(4)
    ...: except AttributeError as e:
    ...:     print(e)
    ...:
iList frozen object cannot be modified.

In [57]:
```

また、frozen なオブジェクトはハッシュ可能なため、辞書のキーに配置できます。


```python
In [60]: hash(i1)
Out[60]: -3783003762566077833

In [61]: d = { i1: 'happy'}

In [62]: d
Out[62]: {iList([1, 2, 3]): 'happy'}

In [63]:
```

## iList の　copy() 

`copy()`メソッドは、リストをコピーしますが、アトリビュートはコピーしません。
`freeze=True` を与えると、frozen なオブジェクトとして生成されます。

```python
In [63]: i1
Out[63]: iList([1, 2, 3])

In [64]: i2 = i1.copy()

In [65]: i2._attrs
Out[65]: aDict({})

In [66]: i2 = i1.copy(freeze=True)

In [67]: hash(i2)
Out[67]: -3783003762566077833

In [68]: i2
Out[68]: iList([1, 2, 3])

In [69]:
```

## iList の　clone() 

`clone()` メソッドは、リストと共にアトリビュートもコピーします。
`empty=True`を与えると、アトリビュートだけがコピーされます。


```python
In [69]: i3 = i1.clone()

In [70]: i3
Out[70]: iList([1, 2, 3])

In [71]: i3._attrs
Out[71]: aDict({'python': 'Osaka'})

In [72]: i3 = i1.clone(empty=True)

In [73]: i3
Out[73]: iList([])

In [74]: i3._attrs
Out[74]: aDict({'python': 'Osaka'})

In [75]:
```

## iLst の without() 
与えた要素を含まないリストを作成して、イテラルを返します。
要素は複数与えることができます。

```python
In [1]: from datajuggler import iList

In [2]: l1 = iList([1,2,3,4,5,6])
   ...: result = l1.without([3])
   ...: assert result == [1,2,4,5,6]

In [3]: l1 = iList([1,2,3,4,5,6])
   ...: result = l1.without([0])
   ...: assert result == l1

In [4]: l1 = iList([1,2,3,4,5,6])
   ...: l2 = [3,4,5]
   ...: result = l1.without(l2)
   ...: assert result == [1,2,6]

In [5]: l1 = iList([1,2,3,4,5,6])
   ...: l2 = [3,4]
   ...: l3 = [5,6]
   ...: result = l1.without(l2, l3)
   ...: assert result == [1,2]

In [6]:
```

## uDictについて
uDict はネストされた辞書データの値参照が容易になる辞書です。

```python
In [1]: from datajuggler import uDict, Keylist, Keypath

In [2]: data = { "a": 1,
   ...:          "b": { "c": { "x": 2, "y": 3, },
   ...:                 "d": { "x": 4, "y": 5, },
   ...:                 "e": [ { "x": 1, "y": -1, "z": [101, 102, 103], },
   ...:                        { "x": 2, "y": -2, "z": [201, 202, 203], },
   ...:                        { "x": 3, "y": -3, "z": [301, 302, 303], },
   ...:                      ],
   ...:               },
   ...:       }

In [3]: d = uDict(data)

In [4]: d['a']
Out[4]: 1

In [5]: d[Keylist(['b', 'e[1]', 'z[2]'])]
Out[5]: 203

In [6]: d[Keypath('b.e[1].z[2]')]
Out[6]: 203

In [7]:
```

Keylist オブジェクトは値を特定するキーをリストで保持します。
同様に、Keypath オブジェクトは値を特定するキーを文字列として保持します。

そもそも、こんなデータ構造になっていることが問題です。とわいえ、場合によってはネストしたデータ構造を使用することもあります。


## uDict のメソッド
uDict には便利なメソッドが多数提供されています。

 - `clean(d1: dict, strings=True, collections=True,
          inplace=False, factory=dict)`
 - `clone(d1: dict, empty=False, memo=None)`
 - `compare(d1: dict, d2: dict, keys=None, thrown_error=False)`
 - `counts(pattern, d=None, count_for"key", wild=False, verbatim=False)`
 - `filter(predicate, d=None, factory=dict)`
 - `get_keys(d=None, output_as=None)`
 - `get_values(keys, d=None)`
 - `groupby(seq, key, factory=dict)`
 - `invert(d=None, flat=False, inplace=False, factory=dict)`
 - `keylists(d=None, indexes=False)`
 - `keypaths(d=None, indexes=False, separator=".")`
 - `map(func, d=None, map_for=None, inplace=False, factory=dict)`
 - `merge(other, d=None, overwrite=False, inplace=False, factory=dict)`
 - `move(key_src, key_dest, d=None, overwrite=False, inplace=False, factory=dict)`
 - `nest(items, key, patrent_key, children_key)`
 - `rename(key, key_new, d=None, case_name=None, overwrite=False,
           inplace=False, factory=dict)`
 - `remove(keys, d=None, inplace=False, factory=dict)`
 - `subset(keys, d=None, default=None, use_keypath=False,
           separator=".", inplace=False, factory=dict)`
 - `find(keys, d=None, default=None, first_one=True, factory=dict)`
 - `search(query, d=None, search_for="key", exact=False, ignore_case=False)`
 - `sort(d=None, sort_by="key", reverse=False, inplace=False, factory=dict)`
 - `swap(key1, key2, d=None, inplace=False, factory=dict)`
 - `flatten(d=None, separator=".", inplace=False, factory=dict)`
 - `unflatten(d=None, default=None, separator=".", inplace=False, factory=dict)`
 - `traverse(callback, d=None, parents=[], *args, **kwargs)`
 - `unique(d=None)`
 - `get_items(loc, value, d=None, func=None,
                   separator='.',inplace=False,  factory=dict)`
 - `pop_items(loc, value, d=None, func=None,
                   separator='.',inplace=False,  factory=dict)`
 - `del_items(loc, value, d=None, func=None,
                   separator='.',inplace=False,  factory=dict)`
 - `set_items(loc, value, d=None, func=None,
                   separator='.',inplace=False,  factory=dict)`

実は、これらのメソッドは　datajuggler.dicthelper で定義されている次のヘルパー関数を呼び出しているだけなので、ノーマルな辞書やaDictでも利用することができます。

 - `d_clean()`
 - `d_clone()`
 - `d_compare()`
 - `d_counts()`
 - `d_filter()`
 - `d_groupby()`
 - `d_invert()`
 - `d_map()`
 - `d_merge()`
 - `d_move()`
 - `d_rename()`
 - `d_remove()`
 - `d_nest()`
 - `d_subset()`
 - `d_find()`
 - `d_sort()`
 - `d_search()`
 - `d_swap()`
 - `d_flatten()`
 - `d_unflatten()`
 - `d_traverse()`
 - `d_unique()`
 - `get_keys()`
 - `get_values()`
 - `get_items()`
 - `pop_items()`
 - `del_items()`
 - `set_items()`
 

## Srializer

datajuggler がサポートしているシリアル化のフォーマットは次の17種類です。

   - bson, dill, json, msgpack, phpserialize, pickle, serpent, yaml
   - json:cusom, yaml:cusom, msgpack:custom, toml, xml
   - querystring, ini, csv, cloudpickle,
   - base64(support encrypt/decrypt)



次のパッケージがインストールされていれば、それを認識して有効になります。

 - cloudpickle
 - bson
 - dill
 - msgpack
 - serpent
 - PyYAML
 - toml
 - xmllibtodict


それぞれのシリアル化ライブラリは、統一的なAPIで操作できようになっています。

```python
In [1]: from datajuggler import serializer as io

In [2]: import decimal

In [3]: import datetime

In [4]: data = {
   ...:   'a': 1,
   ...:   'b': decimal.Decimal('2'),
   ...:   'c': datetime.datetime(2020, 5, 24, 8, 20),
   ...:   'd': datetime.date(1962, 1, 13),
   ...:   'e': datetime.time(11, 12, 13),
   ...:   'f': [1, 2, 3, decimal.Decimal('4')]
   ...:   }

In [5]: io.dumps(data, format='json')
Out[5]: b'{"a": 1, "b": {"__class_name__": "<class \'decimal.Decimal\'>", "__dumped_obj__": {"__type__": "Decimal", "value": "2"}}, "c": {"__class_name__": "<class \'datetime.datetime\'>", "__dumped_obj__": {"__type__": "datetime", "value": [2020, 5, 24, 8, 20, 0]}}, "d": {"__class_name__": "<class \'datetime.date\'>", "__dumped_obj__": {"__type__": "date", "value": [1962, 1, 13]}}, "e": {"__class_name__": "<class \'datetime.time\'>", "__dumped_obj__": {"__type__": "time", "value": [11, 12, 13]}}, "f": [1, 2, 3, {"__class_name__": "<class \'decimal.Decimal\'>", "__dumped_obj__": {"__type__": "Decimal", "value": "4"}}]}'

In [6]: io.dumps(data, format='msgpack')
Out[6]: b"\x86\xa1a\x01\xa1b\x82\xae__class_name__\xb9<class 'decimal.Decimal'>\xae__dumped_obj__\x82\xa8__type__\xa7Decimal\xa5value\xa12\xa1c\x82\xae__class_name__\xbb<class 'datetime.datetime'>\xae__dumped_obj__\x82\xa8__type__\xa8datetime\xa5value\x96\xcd\x07\xe4\x05\x18\x08\x14\x00\xa1d\x82\xae__class_name__\xb7<class 'datetime.date'>\xae__dumped_obj__\x82\xa8__type__\xa4date\xa5value\x93\xcd\x07\xaa\x01\r\xa1e\x82\xae__class_name__\xb7<class 'datetime.time'>\xae__dumped_obj__\x82\xa8__type__\xa4time\xa5value\x93\x0b\x0c\r\xa1f\x94\x01\x02\x03\x82\xae__class_name__\xb9<class 'decimal.Decimal'>\xae__dumped_obj__\x82\xa8__type__\xa7Decimal\xa5value\xa14"

In [7]: io.dumps(data, format='pickle')
Out[7]: b"\x80\x04\x95\xa8\x01\x00\x00\x00\x00\x00\x00}\x94(\x8c\x01a\x94K\x01\x8c\x01b\x94\x8c\x08builtins\x94\x8c\x07getattr\x94\x93\x94\x8c'datajuggler.serializer.class_serializer\x94\x8c\x16DecimalClassSerializer\x94\x93\x94)\x81\x94\x8c\x06decode\x94\x86\x94R\x94}\x94(\x8c\x08__type__\x94\x8c\x07Decimal\x94\x8c\x05value\x94\x8c\x012\x94u\x85\x94R\x94\x8c\x01c\x94h\x05h\x06\x8c\x17DatetimeClassSerializer\x94\x93\x94)\x81\x94h\n\x86\x94R\x94}\x94(h\x0e\x8c\x08datetime\x94h\x10]\x94(M\xe4\x07K\x05K\x18K\x08K\x14K\x00eu\x85\x94R\x94\x8c\x01d\x94h\x05h\x06\x8c\x13DateClassSerializer\x94\x93\x94)\x81\x94h\n\x86\x94R\x94}\x94(h\x0e\x8c\x04date\x94h\x10]\x94(M\xaa\x07K\x01K\reu\x85\x94R\x94\x8c\x01e\x94h\x05h\x06\x8c\x13TimeClassSerializer\x94\x93\x94)\x81\x94h\n\x86\x94R\x94}\x94(h\x0e\x8c\x04time\x94h\x10]\x94(K\x0bK\x0cK\reu\x85\x94R\x94\x8c\x01f\x94]\x94(K\x01K\x02K\x03h\x0c}\x94(h\x0eh\x0fh\x10\x8c\x014\x94u\x85\x94R\x94eu."

In [8]: io.dumps(data, format='yaml')
Out[8]: b"a: 1\nb: !!python/object/apply:decimal.Decimal\n- '2'\nc: 2020-05-24 08:20:00\nd: 1962-01-13\ne: !!python/object/apply:datetime.time\n- !!binary |\n  CwwNAAAA\nf:\n- 1\n- 2\n- 3\n- !!python/object/apply:decimal.Decimal\n  - '4'\n"

In [9]:
```

## Base64 とサブフォーマット
Base64シリアライザはサブフォーマットを受付ます。。もし`"base64, json"` が、`format`に与えられると、サブフォーマットは　json と認識されます。

- dumps(): もしサブフォーマットが指定されていると、はじめにサブフォーマットでエンコードされ、次にBas64でエンコードされます。
- loads()：もしサブフォーマットが指定されていると、はじめにBase64でデコードされて、次にサブフォーマットでデコードされます。

## Base4の暗号化/復号化
Base64シリアライザは`password`引数も受け付けます。もし`password` が設定されていると、dups()/loads() で暗号化/復号化の処理が行われます。

- dumps()
    - raw_data -> subformat エンコード -> 暗号化 -> Base64エンコード
 - loads()
     -Base64デコード -> 復号化 -> sabformat デコード -> raw_data

## シリアル化ライブラリの追加をサポート

AbstractSerializer を継承したサブクラスを定義することで、
別フォーマットでのシリアル化を（比較的）容易になります。
datajuggler での cloudpickle の登録をみてみてください。

```python
from datajuggler.serializer.abstract import (
    AbstractSerializer, register_serializer
)

try:
    import cloudpickle
    cloudpickle_enable = True
except ImportError:  # pragma: no cover
    cloudpickle_enable = False
    cloudpickle = AbstractSerializer()

class CloudpickleSerializer(AbstractSerializer):
    def __init__(self):
        super().__init__(format='cloudpickle',
                         extension=['pickle', 'cpickle'],
                         package='cloudpickle',
                         enable=cloudpickle_enable,
                         overwrite=True)

    def loads(self, s, **kwargs):
        data = cloudpickle.loads(s, **kwargs)
        return data

    def dumps(self, d, **kwargs):
        data = cloudpickle.dumps(d, **kwargs)
        return data

register_serializer(CloudpickleSerializer)
```

cloudpickle パッケージがない場合でもエラーにはならずに、
呼び出し時に　`NotImplementedError` が発生します。

このクラスで定義するべきメソッドは次のものです。
`dumps()`、`dump()`、`loads()` `load()` 

全てを定義する必要はなく該当する他方（dumps() なら dump())を使って自動的に補間定義がされます。

## カスタムなオブジェクトのシリアル化をサポート
datajuggler のシリアル化では共通の `encode()` / `decode()` を経由しているため、
独自に定義したクラスなどのシリアル化についても（比較的）容易になります。

datajuggler で datetime オブジェクトをシリアル化を登録をみてみてください。

```python
import datetime
from datajuggler.serializer.abstract import (
    AbstractClassSerializer, register_serializer
)
from datajuggler.validator import TypeValidator as _type

class DatetimeClassSerializer(io.AbstractClassSerializer):
    def __init__(self, cls=datetime.datetime):
        super().__init__(cls)

    def encode(self, obj):
        if _type.is_datetime(obj):
            return {
                "__type__": "datetime",
                "value": [
                     obj.year,
                     obj.month,
                     obj.day,
                     obj.hour,
                     obj.minute,
                     obj.second,
                    ],
                }
        else:
            super().encode(obj)

    def decode(self, obj):
        v = obj.get("__type__")
        if v == "datetime":
            return datetime.datetime(*obj["value"])

        self.raise_error(obj)

register_serializer(DatetimeClassSerializer)
```

## IODict についても紹介しておきます。

aDict と uDict は IODict のサブクラスとして定義しています。このIODict は、
Seiralzier をサポートしているため次のメソッドを持っています。

 - `from_base64(cls, s, subformat="json", encoding="utf-8", **kwargs)`
 - `from_csv(cls, s, columns=None, columns_row=True, **kwargs)`
 - `from_ini(cls, s, **kwargs)`
 - `from_json(self, s, **kwargs)`
 - `from_pickle(cls, s, **kwargs)`
 - `from_plist(cls, s, **kwargs)`
 - `from_query_string(cls, s, **kwargs)`
 - `from_toml(cls, s, **kwargs)`
 - `from_xml(cls, s, **kwargs)`
 - `from_yaml(cls, s, **kwargs)`
 - `from_serializer(cls, s, format, **kwargs)`

 - `to_base64(cls, s, subformat="json", encoding="utf-8", **kwargs)`
 - `to_csv(cls, s, columns=None, columns_row=True, **kwargs)`
 - `to_ini(cls, s, **kwargs)`
 - `to_json(self, s, **kwargs)`
 - `to_pickle(cls, s, **kwargs)`
 - `to_plist(cls, s, **kwargs)`
 - `to_query_string(cls, s, **kwargs)`
 - `to_toml(cls, s, **kwargs)`
 - `to_xml(cls, s, **kwargs)`
 - `to_yaml(cls, s, **kwargs)`
 - `to_serializer(cls, s, format, **kwargs)`
 - 

## datajuggler のいけてないところ
今時点で認識している弱点は次のものです。

 - サポート体制がなっていない（弱小プロジェクトなのです）
 - pure python での実装のため、ノーマルな dict に比べて遅い。
 - Out-of-Core 処理ができない。
 - マルチスレッド対応になっていない
 - 非同期処理は考慮されていない
 

## まとめ
datajuggler の aDict, uDict, iList と Srializer について紹介しました。
他にも、冒頭の `read_contents()` のように地味に便利ではないかと考えている機能もありますので、一度触ってみてもらいご意見をいただけると幸いです。