未验证 提交 277646b9 编写于 作者: HansBug's avatar HansBug 😆 提交者: GitHub

Merge pull request #5 from opendilab/dev/graphviz

Development for the issue #2 and issue #4 
......@@ -11,7 +11,7 @@ jobs:
strategy:
matrix:
os:
- 'ubuntu-latest'
- 'ubuntu-18.04'
python-version:
- '3.6'
- '3.7'
......@@ -27,8 +27,9 @@ jobs:
if: ${{ runner.os == 'Linux' }}
run: |
sudo apt-get update
sudo apt-get install -y tree cloc wget curl make
sudo apt-get install -y tree cloc wget curl make graphviz
sudo apt-get install -y libxml2-dev libxslt-dev python-dev # need by pypy3
dot -V
- name: Set up python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
......
......@@ -1193,6 +1193,7 @@ fabric.properties
.idea/caches/build_file_checksums.ser
/test_*
/*.btv
.python-version
/docs/build
/public
......
......@@ -63,6 +63,10 @@ The result should be
└── 'd' --> 4
```
And `t` is structure should be like this
![t_graph](https://opendilab.github.io/treevalue/main/_images/simple_demo.dat.svg)
For more quick start explanation and further usage, take a look at:
* [Quick Start](https://opendilab.github.io/treevalue/main/tutorials/quick_start/index.html)
......
......@@ -14,9 +14,15 @@ GRAPHVIZ := $(MAKE) -f "${GRAPHVIZ_MK}" SOURCE=${SOURCEDIR}
DEMOS_MK := ${SOURCEDIR}/demos.mk
DEMOS := $(MAKE) -f "${DEMOS_MK}" SOURCE=${SOURCEDIR}
_CURRENT_PATH := ${PATH}
_PROJ_DIR := $(shell readlink -f ${CURDIR}/..)
_LIBS_DIR := $(shell readlink -f ${SOURCEDIR}/_libs)
_SHIMS_DIR := $(shell readlink -f ${SOURCEDIR}/_shims)
.EXPORT_ALL_VARIABLES:
PYTHONPATH = $(shell readlink -f ${CURDIR}/..)
PYTHONPATH = ${_PROJ_DIR}:${_LIBS_DIR}
PATH = ${_SHIMS_DIR}:${_CURRENT_PATH}
NO_CONTENTS_BUILD = true
# Catch-all target: route all unknown targets to Sphinx using the new
......
import importlib.util
import os
from types import ModuleType
def get_module(filename) -> ModuleType:
_, _simple_filename = os.path.split(filename)
_module_name, _ = _simple_filename.split('.', maxsplit=1)
spec = importlib.util.spec_from_file_location(_module_name, filename)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
#!/usr/bin/env python
from treevalue.entry.cli import treevalue_cli
if __name__ == '__main__':
treevalue_cli()
......@@ -3,4 +3,36 @@ treevalue.config.meta
.. automodule:: treevalue.config.meta
\_\_TITLE\_\_
------------------
.. autodata:: treevalue.config.meta.__TITLE__
:annotation:
\_\_VERSION\_\_
------------------
.. autodata:: treevalue.config.meta.__VERSION__
:annotation:
\_\_DESCRIPTION\_\_
----------------------
.. autodata:: treevalue.config.meta.__DESCRIPTION__
:annotation:
\_\_AUTHOR\_\_
------------------
.. autodata:: treevalue.config.meta.__AUTHOR__
:annotation:
\_\_AUTHOR_EMAIL\_\_
----------------------
.. autodata:: treevalue.config.meta.__AUTHOR_EMAIL__
:annotation:
......@@ -7,7 +7,7 @@ BaseTree
-----------
.. autoclass:: treevalue.tree.common.base.BaseTree
:members: __getitem__, __setitem__, __delitem__, json, view, clone, items, keys, values, actual, __len__, __hash__, __eq__, __repr__, __bool__, __str__
:members: __getitem__, __setitem__, __delitem__, json, view, clone, items, keys, values, actual, __len__, __hash__, __eq__, __repr__, __bool__, __str__, copy_from
.. _apidoc_tree_common_tree:
......@@ -16,7 +16,7 @@ Tree
---------
.. autoclass:: treevalue.tree.common.tree.Tree
:members: __init__, __getitem__, __setitem__, __delitem__, json, view, clone, items, keys, values, actual, __len__, __hash__, __eq__, __repr__, __bool__, __str__
:members: __init__, __getitem__, __setitem__, __delitem__, json, view, clone, items, keys, values, actual, __len__, __hash__, __eq__, __repr__, __bool__, __str__, copy_from
.. _apidoc_tree_common_raw:
......@@ -33,5 +33,5 @@ TreeView
-------------
.. autoclass:: treevalue.tree.common.view.TreeView
:members: __init__, __getitem__, __setitem__, __delitem__, json, view, clone, items, keys, values, actual, __len__, __hash__, __eq__, __repr__, __bool__, __str__
:members: __init__, __getitem__, __setitem__, __delitem__, json, view, clone, items, keys, values, actual, __len__, __hash__, __eq__, __repr__, __bool__, __str__, copy_from
......@@ -50,20 +50,12 @@ TreeMode
MISSING_NOT_ALLOW
-------------------------
.. data:: treevalue.tree.func.MISSING_NOT_ALLOW
.. autodata:: treevalue.tree.func.func.MISSING_NOT_ALLOW
:annotation:
Default value of the ``missing`` arguments \
of ``func_treelize``, ``method_treelize`` and \
``classmethod_treelize``, \
which means missing is not allowed \
(raise ``KeyError`` when missing is detected).
AUTO_DETECT_RETURN_TYPE
----------------------------
.. data:: treevalue.tree.func.AUTO_DETECT_RETURN_TYPE
Default value of the ``return_type`` arguments \
of ``method_treelize`` and ``classmethod_treelize``, \
which means return type will be auto configured to
the current class.
.. autodata:: treevalue.tree.func.func.AUTO_DETECT_RETURN_TYPE
:annotation:
......@@ -7,7 +7,7 @@ FastTreeValue
-------------------
.. autoclass:: treevalue.tree.general.FastTreeValue
:members: _attr_extern, json, view, clone, __add__, __radd__, __sub__, __rsub__, __mul__, __rmul__, __matmul__, __rmatmul__, __truediv__, __rtruediv__, __floordiv__, __rfloordiv__, __mod__, __rmod__, __pow__, __rpow__, __and__, __rand__, __or__, __ror__, __xor__, __rxor__, __lshift__, __rlshift__, __rshift__, __rrshift__, __pos__, __neg__, __invert__, __getitem__, __setitem__, __delitem__, __call__, __getattr__, __setattr__, __delattr__, __repr__, __iter__, __hash__, __eq__, map, type, mask, filter, __str__, reduce, rise, union, subside
:members: _attr_extern, json, view, clone, __add__, __radd__, __sub__, __rsub__, __mul__, __rmul__, __matmul__, __rmatmul__, __truediv__, __rtruediv__, __floordiv__, __rfloordiv__, __mod__, __rmod__, __pow__, __rpow__, __and__, __rand__, __or__, __ror__, __xor__, __rxor__, __lshift__, __rlshift__, __rshift__, __rrshift__, __pos__, __neg__, __invert__, __getitem__, __setitem__, __delitem__, __call__, __getattr__, __setattr__, __delattr__, __repr__, __iter__, __hash__, __eq__, map, type, mask, filter, __str__, reduce, rise, union, subside, __getstate__, __setstate__, __iadd__, __isub__, __imul__, __imatmul__, __ifloordiv__, __itruediv__, __ipow__, __imod__, __iand__, __ior__, __ixor__, __ilshift__, __irshift__, graph, graphics, func
.. _apidoc_tree_general_generaltreevalue:
......
import numpy as np
from treevalue import FastTreeValue, graphics, TreeValue
class MyFastTreeValue(FastTreeValue):
pass
if __name__ == '__main__':
t = MyFastTreeValue({
'a': 1,
'b': np.array([[5, 6], [7, 8]]),
'x': {
'c': 3,
'd': 4,
'e': np.array([[1, 2], [3, 4]])
},
})
t2 = TreeValue({'ppp': t.x, 'x': {'t': t, 'y': t.x}})
g = graphics(
(t, 't'), (t2, 't2'),
title="This is a demo of 2 trees.",
cfg={'bgcolor': '#ffffff00'},
)
g.render('graphics.dat.gv', format='svg')
import numpy as np
from treevalue import FastTreeValue, graphics
class MyFastTreeValue(FastTreeValue):
pass
if __name__ == '__main__':
t = MyFastTreeValue({
'a': [4, 3, 2, 1],
'b': np.array([[5, 6], [7, 8]]),
'x': {
'c': np.array([[5, 7], [8, 6]]),
'd': {'a', 'b', 'c'},
'e': np.array([[1, 2], [3, 4]])
},
})
t1 = MyFastTreeValue({
'aa': t.a,
'bb': np.array([[5, 6], [7, 8]]),
'xx': {
'cc': t.x.c,
'dd': t.x.d,
'ee': np.array([[1, 2], [3, 4]])
},
})
g = graphics(
(t, 't'), (t1, 't1'),
(MyFastTreeValue({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
# Here is the dup value, with several types
# np.ndarray and list type will use the same value node,
# but set type is not in this tuple, so will not share the same node.
dup_value=(np.ndarray, list),
title="This is a demo of 2 trees with dup value.",
cfg={'bgcolor': '#ffffff00'},
)
g.render('graphics_dup_value.dat.gv', format='svg')
......@@ -7,7 +7,7 @@ TreeValue
---------------
.. autoclass:: treevalue.tree.tree.tree.TreeValue
:members: __init__, __getattr__, __setattr__, __delattr__, __contains__, __repr__, __iter__, __hash__, __eq__, _attr_extern, __len__, __bool__, __str__
:members: __init__, __getattr__, __setattr__, __delattr__, __contains__, __repr__, __iter__, __hash__, __eq__, _attr_extern, __len__, __bool__, __str__, __getstate__, __setstate__
.. _apidoc_tree_tree_jsonify:
......@@ -101,8 +101,87 @@ reduce\_
NO_RISE_TEMPLATE
--------------------
.. data:: treevalue.tree.tree.utils.NO_RISE_TEMPLATE
.. autodata:: treevalue.tree.tree.utils.NO_RISE_TEMPLATE
:annotation:
.. _apidoc_tree_tree_graphics:
graphics
----------------
.. autofunction:: treevalue.tree.tree.graph.graphics
Here is an example of ``graphics`` function. The source code is
.. literalinclude:: graphics.demo.py
:language: python
:linenos:
The generated graphviz source code should be
.. literalinclude:: graphics.dat.gv
:language: text
:linenos:
The graph should be
.. image:: graphics.dat.gv.svg
:align: center
Also, ``graphics`` function can support value duplication. For if \
the value nodes are using the same object, they will be displayed \
in the same node of the generated graph, such as the source code
below
.. literalinclude:: graphics_dup_value.demo.py
:language: python
:linenos:
The graph of the case with ``dup_value`` should be
.. image:: graphics_dup_value.dat.gv.svg
:align: center
The return value's type of function ``graphics`` is \
class ``graphviz.dot.Digraph``, from the opensource \
library ``graphviz``, for further information of \
this project and ``graphviz.dot.Digraph``'s usage, \
take a look at:
* `Official site of Graphviz <https://graphviz.org/>`_.
* `User Guide of Graphviz <https://graphviz.readthedocs.io/en/stable/manual.html#formats>`_.
* `API Reference of Graphviz <https://graphviz.readthedocs.io/en/stable/api.html>`_.
.. _apidoc_tree_tree_dump:
dump
-------------
.. autofunction:: treevalue.tree.tree.io.dump
.. _apidoc_tree_tree_dumps:
dumps
-------------
.. autofunction:: treevalue.tree.tree.io.dumps
.. _apidoc_tree_tree_load:
load
-------------
.. autofunction:: treevalue.tree.tree.io.load
.. _apidoc_tree_tree_loads:
loads
-------------
.. autofunction:: treevalue.tree.tree.io.loads
Means no template is given to the rise function, \
and the decorated function will automatically try \
to match the format patterns as template.
from treevalue.utils import build_graph
if __name__ == '__main__':
t = {'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}}
g = build_graph((t, 't'), graph_title="Demo of build_graph.")
print(g.source)
print(g.render('build_graph_demo.dat.gv', format='svg'))
from treevalue.utils import build_graph
if __name__ == '__main__':
t1 = {'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}}
t2 = {'f': 4, 'y': t1['x'], 'z': {'e': [5, 7], 'f': "string"}}
g = build_graph((t1, 't1'), (t2, 't2'), graph_title="Complex demo of build_graph.")
print(g.source)
print(g.render('build_graph_complex_demo.dat.gv', format='svg'))
......@@ -24,3 +24,9 @@ common_direct_base
.. autofunction:: treevalue.utils.clazz.common_direct_base
get_class_full_name
----------------------
.. autofunction:: treevalue.utils.clazz.get_class_full_name
treevalue.utils.color
===========================
Color
----------------
.. autoclass:: treevalue.utils.color.Color
:members: __init__, alpha, rgb, hsv, hls, __repr__, __str__, __getstate__, __setstate__, __hash__, __eq__, from_hsv, from_hls
treevalue.utils.exception
================================
str_traceback
-----------------
.. autofunction:: treevalue.utils.exception.str_traceback
......@@ -12,3 +12,39 @@ dynamic_call
.. autofunction:: treevalue.utils.func.dynamic_call
static_call
------------------
.. autofunction:: treevalue.utils.func.static_call
pre_process
------------------
.. autofunction:: treevalue.utils.func.pre_process
post_process
------------------
.. autofunction:: treevalue.utils.func.post_process
raising
-------------------
.. autofunction:: treevalue.utils.func.raising
warning\_
---------------------
.. autofunction:: treevalue.utils.func.warning_
freduce
--------------
.. autofunction:: treevalue.utils.func.freduce
treevalue.utils.imports
============================
import\_object
---------------------
.. autofunction:: treevalue.utils.imports.import_object
quick\_import\_object
----------------------------
.. autofunction:: treevalue.utils.imports.quick_import_object
iter\_import\_objects
-------------------------
.. autofunction:: treevalue.utils.imports.iter_import_objects
......@@ -5,8 +5,12 @@ treevalue.utils
:maxdepth: 3
clazz
color
enum
exception
final
func
imports
random
singleton
tree
treevalue.utils.random
============================
seed_random
------------------
.. autofunction:: treevalue.utils.random.seed_random
random_hex
-------------------
.. autofunction:: treevalue.utils.random.random_hex
random_hex_with_timestamp
---------------------------
.. autofunction:: treevalue.utils.random.random_hex_with_timestamp
......@@ -5,3 +5,48 @@ build_tree
-----------------
.. autofunction:: treevalue.utils.tree.build_tree
build_graph
-------------------
.. autofunction:: treevalue.utils.tree.build_graph
Here is an example of ``build_graph`` function. The source code is
.. literalinclude:: build_graph.demo.py
:language: python
:linenos:
The generated graphviz source code should be
.. literalinclude:: build_graph_demo.dat.gv
:language: text
:linenos:
The graph should be
.. image:: build_graph_demo.dat.gv.svg
:align: center
Also, multiple rooted graph is supported, this function will detect
the pointer of the objects. Just like another complex source code below.
.. literalinclude:: build_graph_complex.demo.py
:language: python
:linenos:
The exported graph should be
.. image:: build_graph_complex_demo.dat.gv.svg
:align: center
The return value's type of function ``graphics`` is \
class ``graphviz.dot.Digraph``, from the opensource \
library ``graphviz``, for further information of \
this project and ``graphviz.dot.Digraph``'s usage, \
take a look at:
* `Official site of Graphviz <https://graphviz.org/>`_.
* `User Guide of Graphviz <https://graphviz.readthedocs.io/en/stable/manual.html#formats>`_.
* `API Reference of Graphviz <https://graphviz.readthedocs.io/en/stable/api.html>`_.
import numpy as np
from treevalue import FastTreeValue, func_treelize
from treevalue import FastTreeValue
T, B = 3, 4
power = func_treelize()(np.power)
stack = func_treelize(subside=True)(np.stack)
split = func_treelize(rise=True)(np.split)
@func_treelize()
def astype(item, *args):
return item.astype(*args)
power = FastTreeValue.func()(np.power)
stack = FastTreeValue.func(subside=True)(np.stack)
split = FastTreeValue.func(rise=True)(np.split)
def with_treevalue(batch_):
batch_ = [FastTreeValue(b) for b in batch_]
batch_ = stack(batch_)
batch_ = astype(batch_, np.float32)
batch_ = batch_.astype(np.float32)
batch_.b = power(batch_.b, 2) + 1.0
batch_.c.noise = np.random.random(size=(B, 3, 4, 5))
mean_b = batch_.b.mean()
......
......@@ -28,6 +28,8 @@ from packaging import version as version_
# Get current location
_DOC_PATH = os.path.dirname(os.path.abspath(__file__))
_PROJ_PATH = os.path.abspath(os.path.join(_DOC_PATH, '..', '..'))
_LIBS_PATH = os.path.join(_DOC_PATH, '_libs')
_SHIMS_PATH = os.path.join(_DOC_PATH, '_shims')
os.chdir(_PROJ_PATH)
# Set environment, remove the pre-installed package
......@@ -40,7 +42,8 @@ for modname in modnames:
if not os.environ.get("NO_CONTENTS_BUILD"):
_env = dict(os.environ)
_env.update(dict(
PYTHONPATH=_PROJ_PATH,
PYTHONPATH=':'.join([_PROJ_PATH, _LIBS_PATH]),
PATH=':'.join([_SHIMS_PATH, os.environ.get('PATH', '')]),
))
pip_cmd = (where.first('pip'), 'install', '-r', os.path.join(_PROJ_PATH, 'requirements.txt'))
......
......@@ -23,10 +23,12 @@ SHELL_RESULTS := $(addsuffix .sh.txt, $(basename ${SHELL_DEMOS} ${SHELL_DEMOXS}
%.demo.sh.txt: %.demo.sh
cd "$(shell dirname $(shell readlink -f $<))" && \
PYTHONPATH="$(shell dirname $(shell readlink -f $<)):${PYTHONPATH}" \
$(SHELL) "$(shell readlink -f $<)" > "$(shell readlink -f $@)"
%.demox.sh.txt: %.demox.sh
cd "$(shell dirname $(shell readlink -f $<))" && \
PYTHONPATH="$(shell dirname $(shell readlink -f $<)):${PYTHONPATH}" \
$(SHELL) "$(shell readlink -f $<)" 1> "$(shell readlink -f $@)" \
2> "$(shell readlink -f $(addsuffix .err, $(basename $@)))"; \
echo $$? > "$(shell readlink -f $(addsuffix .exitcode, $(basename $@)))"
......
......@@ -18,6 +18,7 @@ structure processing when the calculation is tree-based.
tutorials/main_idea/index
tutorials/basic_usage/index
tutorials/advanced_usage/index
tutorials/cli_usage/index
.. toctree::
......
import os
from treevalue import classmethod_treelize, TreeValue, method_treelize
......@@ -21,8 +23,8 @@ if __name__ == '__main__':
t2 = TreeValue({'a': -14, 'b': 9, 'x': {'c': 3, 'd': 8}})
t3 = TreeValue({'a': 6, 'b': 0, 'x': {'c': -5, 'd': 17}})
print('t1.append(t2).append(t3):')
print(t1.append(t2).append(t3))
print('t1.append(t2).append(t3):',
t1.append(t2).append(t3), sep=os.linesep)
print('MyTreeValue.sum(t1, t2, t3):')
print(MyTreeValue.sum(t1, t2, t3))
print('MyTreeValue.sum(t1, t2, t3):',
MyTreeValue.sum(t1, t2, t3), sep=os.linesep)
import os
from treevalue import TreeValue, method_treelize
class MyTreeValue(TreeValue):
# return type will be automatically detected as `MyTreeValue`
@method_treelize(self_copy=True)
def append(self, b):
return self + b
if __name__ == '__main__':
t1 = MyTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
t2 = TreeValue({'a': -14, 'b': 9, 'x': {'c': 3, 'd': 8}})
t3 = TreeValue({'a': 6, 'b': 0, 'x': {'c': -5, 'd': 17}})
print('t1:', t1, sep=os.linesep)
_t1_id = id(t1)
print('t1.append(t2).append(t3):',
t1.append(t2).append(t3), sep=os.linesep)
assert id(t1) == _t1_id
import os
from treevalue import general_tree_value
class MyTreeValue(general_tree_value()):
pass
if __name__ == '__main__':
t1 = MyTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
t2 = MyTreeValue({'a': 11, 'b': 24, 'x': {'c': 30, 'd': 47}})
# __add__ operator can be directly used
print('t1 + t2:', t1 + t2, sep=os.linesep)
import os
from treevalue import general_tree_value, FastTreeValue
class AddZeroTreeValue(general_tree_value(methods=dict(
__add__=dict(missing=0, mode='outer'),
__radd__=dict(missing=0, mode='outer'),
__iadd__=dict(missing=0, mode='outer'),
))):
pass
class MulOneTreeValue(general_tree_value(methods=dict(
__mul__=dict(missing=1, mode='outer'),
__rmul__=dict(missing=1, mode='outer'),
__imul__=dict(missing=1, mode='outer'),
))):
pass
if __name__ == '__main__':
t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3}})
t2 = FastTreeValue({'a': 11, 'x': {'c': 30, 'd': 47}})
# __add__ with default value 0
at1 = t1.type(AddZeroTreeValue)
at2 = t2.type(AddZeroTreeValue)
print('at1 + at2:', at1 + at2, sep=os.linesep)
# __mul__ with default value 1
mt1 = t1.type(MulOneTreeValue)
mt2 = t2.type(MulOneTreeValue)
print('mt1 * mt2:', mt1 * mt2, sep=os.linesep)
import os
from treevalue import general_tree_value
class DemoTreeValue(general_tree_value(methods=dict(
# - operator will be disabled
__sub__=NotImplemented,
__rsub__=NotImplemented,
__isub__=NotImplemented,
# / operator will raise ArithmeticError
__truediv__=ArithmeticError('True div is not supported'),
__rtruediv__=ArithmeticError('True div is not supported'),
__itruediv__=ArithmeticError('True div is not supported'),
# +t will be changed to t * 2
__pos__=lambda x: x * 2,
))):
pass
if __name__ == '__main__':
t1 = DemoTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
t2 = DemoTreeValue({'a': 11, 'b': 24, 'x': {'c': 30, 'd': 47}})
# __add__ can be used normally
print('t1 + t2:', t1 + t2, sep=os.linesep)
# __sub__ will cause TypeError due to the NotImplemented
try:
_ = t1 - t2
except TypeError as err:
print('t1 - t2:', err, sep=os.linesep)
else:
assert False, 'Should not reach here!'
# __truediv__ will cause ArithmeticError
try:
_ = t1 / t2
except ArithmeticError as err:
print('t1 / t2:', err, sep=os.linesep)
else:
assert False, 'Should not reach here!'
# __pos__ will be like t1 * 2
print('+t1:', +t1, sep=os.linesep)
import pathlib
import sys
from treevalue import FastTreeValue
if __name__ == '__main__':
_module = sys.modules[FastTreeValue.__module__]
print(pathlib.Path(_module.__file__).read_text())
import gzip
import os
from treevalue import FastTreeValue, dumps, loads
if __name__ == '__main__':
t = FastTreeValue({'a': 'ab', 'b': 'Cd', 'x': {'c': 'eF', 'd': 'GH'}})
st = t.upper
print('st:', st, sep=os.linesep) # st is a function tree
print('st():', st(), sep=os.linesep) # st() if an upper-case-string tree
print()
binary = dumps(st, compress=(gzip.compress, gzip.decompress))
print('Length of binary:', len(binary))
# compression function is not needed here
dst = loads(binary, type_=FastTreeValue)
print('dst:', dst, sep=os.linesep)
assert st() == dst() # st() should be equal to dst()
print('dst():', dst(), sep=os.linesep)
import os
import tempfile
from treevalue import TreeValue, dump, load
if __name__ == '__main__':
t = TreeValue({'a': 'ab', 'b': 'Cd', 'x': {'c': 'eF', 'd': 'GH'}})
print('t:', t, sep=os.linesep)
with tempfile.NamedTemporaryFile() as tf:
with open(tf.name, 'wb') as wf: # dump t to file
dump(t, file=wf)
with open(tf.name, 'rb') as rf: # load dt from file
dt = load(file=rf)
assert dt == t
print('dt:', dt, sep=os.linesep)
import os
from treevalue import dumps, FastTreeValue, loads
if __name__ == '__main__':
t = FastTreeValue({'a': 'ab', 'b': 'Cd', 'x': {'c': 'eF', 'd': 'GH'}})
print('t:', t, sep=os.linesep)
binary = dumps(t)
dt = loads(binary, type_=FastTreeValue)
assert dt == t
print('dt:', dt, sep=os.linesep)
......@@ -914,6 +914,83 @@ For further informaon of function ``typetrans``, \
take a look at :ref:`apidoc_tree_tree_typetrans`.
IO Utilities
-----------------------
Simple Serialize and Deserialize
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In this version, ``pickle.dumps`` and ``pickle.loads`` operations \
are supported, just like the code below.
.. literalinclude:: pickle_demo_1.demo.py
:language: python
The output is
.. literalinclude:: pickle_demo_1.demo.py.txt
:language: text
Advanced Dump and Load
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In this project, we implemented our ``load`` and ``dump`` \
function which can be used like this
.. literalinclude:: dump_demo_1.demo.py
:language: python
The output should be
.. literalinclude:: dump_demo_1.demo.py.txt
:language: text
.. note::
In this ``load`` (``loads`` function is the same), \
you must assign a ``TreeValue`` class by the ``type_`` \
argument because the dumped data will not \
save the type of trees. When the type is not assigned, the \
default class will be simply ``TreeValue``.
Also, ``dumps`` function and ``loads`` function are provided \
to deal with binary data.
.. literalinclude:: dump_demo_2.demo.py
:language: python
The output should be
.. literalinclude:: dump_demo_2.demo.py.txt
:language: text
.. note::
The main differences between our ``dump`` / ``load`` and \
``pickle`` 's ``dump`` / ``load`` are:
* Ours are based on \
`dill <https://github.com/uqfoundation/dill>`_ project, \
it can serialize and deserialize more types than \
native ``pickle``, such as functions and classes.
* Compression and decompression can be defined when dumping, \
and when the compressed binary data is going to be loaded, \
decompression function is not necessary, the decompression \
can be carried on with the function defined in dumping \
process.
Here is an example:
.. literalinclude:: dump_compression_demo.demo.py
:language: python
The output should be
.. literalinclude:: dump_compression_demo.demo.py.txt
:language: text
Object Oriented Usage
----------------------------
......@@ -955,6 +1032,24 @@ The result should be like below.
:language: text
:linenos:
.. note::
Actually, self-calculation method can be defined like the \
code below
.. literalinclude:: diy_class_self_demo.demo.py
:language: python
The result should be
.. literalinclude:: diy_class_self_demo.demo.py.txt
:language: text
We can see that the calculation result will replace the \
``self`` object, without changes of the address. Just \
enable the ``self_copy`` option is okay.
For further information of function ``method_treelize`` \
and ``classmethod_treelize``, just take a look at:
......@@ -962,6 +1057,78 @@ and ``classmethod_treelize``, just take a look at:
* :ref:`apidoc_tree_func_classmethodtreelize`
More Flexible Ways to DIY Class
---------------------------------
When you implement ``TreeValue`` class like the section above \
(take a look at here :ref:`tutorials_advancedusage_diy`), \
you have to define your own methods and operators one by one, \
because in class ``TreeValue``, only the minimum necessary \
methods and operators are provided. But when you use \
``FastTreeValue`` class, operators like ``+`` and methods like \
``map`` can be used directly. Let's see how ``FastTreeValue`` \
is defined in source code:
.. literalinclude:: diy_class_x_tv.demo.py.txt
:language: python
:linenos:
The definition is quite simple, just inherit from the \
return value of function ``general_tree_value``, and most of \
the operators and methods can be used.
Like the ``FastTreeValue`` class, we can define our ``TreeValue`` \
class like the code below:
.. literalinclude:: diy_class_x_demo_1.demo.py
:language: python
:linenos:
The output should be
.. literalinclude:: diy_class_x_demo_1.demo.py.txt
:language: text
:linenos:
In some cases, you can update the behaviours of some \
operator or methods, like the code below
.. literalinclude:: diy_class_x_demo_2.demo.py
:language: python
:linenos:
The output should be
.. literalinclude:: diy_class_x_demo_2.demo.py.txt
:language: text
:linenos:
More than this, you can do the following things \
in your DIY ``TreeValue`` class based on \
function ``general_tree_value``:
- change treelize arguments (like the example code above)
- disable some operators and methods
- change behaviour of some operators and methods
Here is another complex demo code
.. literalinclude:: diy_class_x_demo_3.demo.py
:language: python
:linenos:
The output should be
.. literalinclude:: diy_class_x_demo_3.demo.py.txt
:language: text
:linenos:
For more details about ``general_tree_value``, \
take a look at :ref:`apidoc_tree_general_generaltreevalue` \
and its source code to find out all the implemented \
operators and methods.
DIY TreeValue Utility Class
-------------------------------
......@@ -986,3 +1153,43 @@ and ``classmethod_treelize``, just take a look at:
* :ref:`apidoc_tree_func_utilsclass`
* :ref:`apidoc_tree_func_classmethodtreelize`
Draw Graph For TreeValue
-----------------------------
You can easily draw a graph of the ``TreeValue`` objects \
with function ``graphics``. Like the following example \
code.
.. literalinclude:: ../../api_doc/tree/graphics.demo.py
:language: python
:linenos:
The generated code and graph should be like below ( \
name of the image file should be ``graphics.dat.gv.svg``, \
name of the graphviz source code file should be \
``graphics.dat.gv`` ).
.. literalinclude:: ../../api_doc/tree/graphics.dat.gv
:language: text
:linenos:
.. image:: ../../api_doc/tree/graphics.dat.gv.svg
:align: center
.. note::
The return value's type of function ``graphics`` is \
class ``graphviz.dot.Digraph``, from the opensource \
library ``graphviz``, for further information of \
this project and ``graphviz.dot.Digraph``'s usage, \
take a look at:
* `Official site of Graphviz <https://graphviz.org/>`_.
* `User Guide of Graphviz <https://graphviz.readthedocs.io/en/stable/manual.html#formats>`_.
* `API Reference of Graphviz <https://graphviz.readthedocs.io/en/stable/api.html>`_.
For further information of function ``graphics``, \
just take a look at
* :ref:`apidoc_tree_tree_graphics`.
import os
import pickle
from treevalue import TreeValue
if __name__ == '__main__':
t = TreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': [3, 4]}})
binary = pickle.dumps(t)
print('t:', t, sep=os.linesep)
tx = pickle.loads(binary)
assert tx == t
print('tx:', tx, sep=os.linesep)
......@@ -7,7 +7,7 @@ if __name__ == '__main__':
t3 = TreeValue({'a': 6, 'b': 0, 'x': {'c': -5, 'd': 17}})
t4 = TreeValue({'a': 0, 'b': -17, 'x': {'c': -8, 'd': 15}})
t5 = TreeValue({'a': 3, 'b': 9, 'x': {'c': 11, 'd': -17}})
st = {'first': (t1, t2), 'second': [t3, {'x': t4, 'y': t5}]}
st = {'first': (t1, t2), 'second': (t3, {'x': t4, 'y': t5})}
tx = subside(st)
# Rising process
......
......@@ -2,23 +2,28 @@ from treevalue import TreeValue, subside, rise
if __name__ == '__main__':
# The same demo as the subside docs
t1 = TreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
t2 = TreeValue({'a': -14, 'b': 9, 'x': {'c': 3, 'd': 8}})
t11 = TreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
t12 = TreeValue({'a': 3, 'b': 9, 'x': {'c': 15, 'd': 41}})
t21 = TreeValue({'a': -14, 'b': 9, 'x': {'c': 3, 'd': 8}})
t22 = TreeValue({'a': -31, 'b': 82, 'x': {'c': 47, 'd': 32}})
t3 = TreeValue({'a': 6, 'b': 0, 'x': {'c': -5, 'd': 17}})
t4 = TreeValue({'a': 0, 'b': -17, 'x': {'c': -8, 'd': 15}})
t5 = TreeValue({'a': 3, 'b': 9, 'x': {'c': 11, 'd': -17}})
st = {'first': (t1, t2), 'second': [t3, {'x': t4, 'y': t5}]}
st = {'first': [(t11, t21), (t12, t22)], 'second': (t3, {'x': t4, 'y': t5})}
tx = subside(st)
# Rising process, with template
# only the top-leveled dict will be extracted,
# neither tuple, list nor low-leveled dict will not be extracted
# because they are not defined in `template` argument
st2 = rise(tx, template={'first': None, 'second': [None, None]})
st2 = rise(tx, template={'first': [None], 'second': (None, None)})
print('st2:', st2)
print("st2['first']:")
print(st2['first'])
print("st2['first'][0]:")
print(st2['first'][0])
print("st2['first'][1]:")
print(st2['first'][1])
print("st2['second'][0]:")
print(st2['second'][0])
......
import os
from treevalue import FastTreeValue
if __name__ == '__main__':
t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
t2 = FastTreeValue({'a': 3, 'b': 7, 'x': {'c': 14, 'd': -5}})
print('t1:', t1, sep=os.linesep)
print('t2:', t2, sep=os.linesep)
print('t1 + t2:', t1 + t2, sep=os.linesep)
_original_ids = (id(t1), id(t2))
print()
t1 += t2
print('After t1 += t2')
print('t1:', t1, sep=os.linesep)
print('t2:', t2, sep=os.linesep)
assert (id(t1), id(t2)) == _original_ids
......@@ -19,11 +19,6 @@ The result should be
:language: text
:linenos:
A simple tree value structure is created successfully with the structure below.
.. image:: ../quick_start/create_a_tree.gv.svg
:align: center
Edit the tree
------------------
......@@ -99,6 +94,23 @@ The structures of the trees involved in ``__add__`` calculation is like below.
Actually, More common operators are supported in treevalue.
.. note::
In newer versions of treevalue, self operations \
are supported like the code below.
.. literalinclude:: calculation_self.demo.py
:language: python
The output should be
.. literalinclude:: calculation_self.demo.py.txt
:language: text
We can see that when ``t1 + t2`` is called, \
a new tree with the sums will be created \
without ``t1``'s change, but when ``t1 += t2`` is called, \
the values in ``t1`` will be replaced to the sums.
.. _tutorials_basicusage_func:
......
treevalue export -t 'tree_demo.t1' -o export_t1.dat.btv
ls -al export_t1.dat.btv
treevalue export -t 'tree_demo.*' -o 'me_t1.dat.btv' -o 'me_t2.dat.btv' -o 'me_t3.dat.btv'
ls -al me_*.btv
treevalue export -t 'tree_demo.t1' -c gzip -o compress_t1.dat.btv
ls -al compress_t1.dat.btv
treevalue export -t 'tree_demo.t1' -r -o raw_t1.dat.btv
ls -al raw_t1.dat.btv
treevalue export -t 'large_tree_demo.t1' -r -o c_o_large_t1.dat.btv
treevalue export -t 'large_tree_demo.t1' -o c_x_large_t1.dat.btv
ls -al c_*_large_t1.dat.btv
treevalue export -h
\ No newline at end of file
treevalue graph -t 'tree_demo.t1' -o 'only_t1.dat.svg'
\ No newline at end of file
treevalue graph \
-t 'share_demo.*' \
-o 'shared_values.dat.svg' \
-T 'Shared Values' \
-c 'bgcolor=#ffffff00' \
-d list -d numpy.ndarray
treevalue graph -t 'tree_demo.*' -o 't1_t2_t3.dat.svg'
\ No newline at end of file
treevalue graph -t 'node_share_demo.*' -o 'shared_nodes.dat.svg'
treevalue graph \
-t 'node_share_demo.*' \
-o 'shared_nodes_with_cfg.dat.svg' \
-T 'Graph to Show the Shared Nodes' \
-c 'bgcolor=#ffffff00'
treevalue graph \
-t 'node_share_demo.*' \
-o 'shared_nodes.dat.png' \
-T 'PNG Formatted Graph' \
-c 'bgcolor=#ffffff00'
treevalue graph \
-t 'node_share_demo.*' \
-o 'shared_nodes.dat.gv' \
-T 'PNG Formatted Graph' \
-c 'bgcolor=#ffffff00'
treevalue graph \
-t 'node_share_demo.*' \
-O \
-T 'PNG Formatted Graph' \
-c 'bgcolor=#ffffff00'
treevalue graph \
-t 'share_demo.*' \
-o 'no_shared_values.dat.svg' \
-T 'No Shared Values' \
-c 'bgcolor=#ffffff00'
treevalue graph \
-t 'share_demo.*' \
-o 'shared_all_values.dat.svg' \
-T 'Shared All Values' \
-c 'bgcolor=#ffffff00' \
-D
treevalue graph -h
\ No newline at end of file
treevalue -h
\ No newline at end of file
CLI Usage
======================
From treevalue version ``0.1.0``, simple CLI (Command \
Line Interface) is provided to simplify some operations and \
optimize the use experience. This page will introduce \
the usage of cli.
Version Display
-------------------------------
You can see the version information with the following \
command typing into the terminal:
.. literalinclude:: version_demo.demo.sh
:language: shell
:linenos:
The output should be
.. literalinclude:: version_demo.demo.sh.txt
:language: text
:linenos:
.. note::
You can check if the treevalue package is installed \
properly with this command line since version ``0.1.0``.
Help Information Display
-------------------------------
You can see the help information of this cli by the \
following command line:
.. literalinclude:: help_demo.demo.sh
:language: shell
:linenos:
The output should be
.. literalinclude:: help_demo.demo.sh.txt
:language: text
:linenos:
There are several sub commands in ``treevalue`` cli, and they \
will be introduced in the following sections.
Exporting Trees to Binary
---------------------------------
First, let's see what is in ``treevalue export`` sub command.
.. literalinclude:: export_help_demo.demo.sh.txt
:language: text
:linenos:
In this sub command, we can export a tree value object to \
binary file with :ref:`apidoc_tree_tree_dump`, \
:ref:`apidoc_tree_tree_dumps` function define in \
module :doc:`../../api_doc/tree/tree`.
.. note::
For further information, take a look at:
* API Documentation of :ref:`apidoc_tree_tree_dump`.
* API Documentation of :ref:`apidoc_tree_tree_dumps`.
Export One Tree
~~~~~~~~~~~~~~~~~~~~~~~
Before exporting, we prepare a python source code \
file ``tree_demo.py`` with the content of
.. literalinclude:: tree_demo.py
:language: python
:linenos:
And then we can to export ``t1`` to binary file, like this
.. literalinclude:: export_demo_1.demo.sh
:language: shell
:linenos:
A binary file named ``export_t1.dat.btv`` will be exported.
.. literalinclude:: export_demo_1.demo.sh.txt
:language: text
:linenos:
The binary file is exported with \
the :ref:`apidoc_tree_tree_dump` function, and the \
compression algorithm used is the default \
one ``zlib``.
Export Multiple Trees Once
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We can export multiple trees at one time with the following \
command
.. literalinclude:: export_demo_2.demo.sh
:language: shell
:linenos:
Three binary files named ``me_t1.dat.btv``, \
``me_t2.dat.btv`` and ``me_t3.dat.btv`` will be exported.
.. literalinclude:: export_demo_2.demo.sh.txt
:language: text
:linenos:
.. _cli_usage_export:
Export Tree with Compression
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We can assign compression algorithm by the ``-c`` option, \
like this
.. literalinclude:: export_demo_3.demo.sh
:language: shell
:linenos:
The binary file named ``compress_t1.dat.btv`` will be exported \
with the ``gzip`` compression, and will be able to be loaded \
with :ref:`treevalue graph cli <cli_usage_graph>` \
and :ref:`apidoc_tree_tree_load` function without \
the assignment of decompression algorithm.
.. literalinclude:: export_demo_3.demo.sh.txt
:language: text
:linenos:
I DO NOT NEED COMPRESSION
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you do not need compression due to the reasons like \
the time scarcity, you can easily disable the compression.
.. literalinclude:: export_demo_4.demo.sh
:language: shell
:linenos:
The dumped binary file ``raw_t1.dat.btv`` will not be \
compressed due to the ``-r`` option.
.. literalinclude:: export_demo_4.demo.sh.txt
:language: text
:linenos:
.. note::
You may feel puzzled that why the size of the \
uncompressed version is even smaller \
than the compressed version. The reason is easy, when the \
:ref:`apidoc_tree_tree_load` and :ref:`apidoc_tree_tree_loads` \
functions are called, it will crack the decompression \
function from the binary data, which means the decompression \
function is actually stored in binary data.
Because of this principle, when the original data is not \
big enough, the compressed version may be a little bit \
bigger than the uncompressed one. But when the original \
data is huge, the compression will show its advantage, \
like this source file ``large_tree_demo.py``.
.. literalinclude:: large_tree_demo.py
:language: python
:linenos:
When we try to dump the ``t1`` in it, the result will be \
satisfactory.
.. literalinclude:: export_demo_5.demo.sh
:language: python
:linenos:
.. literalinclude:: export_demo_5.demo.sh.txt
:language: text
:linenos:
All the binary file generated in this section (\
:ref:`cli_usage_export`) can:
* Be loaded by function :ref:`apidoc_tree_tree_load` \
and :ref:`apidoc_tree_tree_loads` in python source code.
* Be loaded by :ref:`treevalue graph cli <cli_usage_graph>` \
and generate its graph.
.. _cli_usage_graph:
Generate Graph from Trees
---------------------------------
First, let's see what is in ``treevalue graph`` sub command.
.. literalinclude:: graph_help_demo.demo.sh.txt
:language: text
:linenos:
In this sub command, we can draw a tree value object to \
a graph formatted ``png``, ``svg`` or just ``gv`` code \
with :ref:`apidoc_tree_tree_graphics` function define in \
module :doc:`../../api_doc/tree/tree`. Also, the dumped \
binary trees can be imported and then drawn to graphs.
.. note::
For further information, take a look at:
* API Documentation of :ref:`apidoc_tree_tree_load`.
* API Documentation of :ref:`apidoc_tree_tree_loads`.
* API Documentation of :ref:`apidoc_tree_tree_graphics`.
Draw One Graph For One Tree
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before drawing the first graph, we prepare the ``tree_demo.py`` \
mentioned in :ref:`cli_usage_export`.
.. literalinclude:: tree_demo.py
:language: python
:linenos:
We can draw the graph of ``t1`` with the following command
.. literalinclude:: graph_demo_1.demo.sh
:language: shell
:linenos:
The dumped graph ``only_t1.dat.svg`` should be like this
.. image:: only_t1.dat.svg
:align: center
Draw One Graph For Multiple Trees
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Actually we can put several trees into one graph, just like \
the following command
.. literalinclude:: graph_demo_2.demo.sh
:language: shell
:linenos:
The dumped graph ``t1_t2_t3.dat.svg`` should contains 3 trees, \
like this
.. image:: t1_t2_t3.dat.svg
:align: center
Sometime, the trees will share some common nodes (with the same \
``Tree`` object id), this relation will also be displayed in \
graph. In another python script ``node_share_demo.py``
.. literalinclude:: node_share_demo.py
:language: python
:linenos:
The dumped graph ``shared_nodes.dat.svg`` should be like
.. image:: shared_nodes.dat.svg
:align: center
The arrow from ``nt3`` named ``first`` is pointed to ``nt1``, \
the arrow named ``another`` is pointed to ``nt1.x``, and so on.
Graph with Configurations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sometime we need to assign the title of the dumped graph, \
or you may think that the white background look prominent in \
the grey page background. So you can dump the graph like this
.. literalinclude:: graph_demo_4.demo.sh
:language: shell
:linenos:
The dumped graph ``shared_nodes_with_cfg.dat.svg`` should be \
like this
.. image:: shared_nodes_with_cfg.dat.svg
:align: center
We can see the title and the background color is changed \
because of the ``-T`` and ``-c`` command. The transparent \
background looks better than the ``shared_nodes.dat.svg`` \
in the grey page background.
Graph of Different Formats
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you do not need svg format, you can dump it to png format \
like this
.. literalinclude:: graph_demo_5.demo.sh
:language: python
:linenos:
The dumped graph ``shared_nodes.dat.png`` should be like this, \
its format is ``png``.
.. image:: shared_nodes.dat.png
:align: center
Besides, if the graphviz code (``gv`` format) is just all \
what you need, you can dump the ``gv`` source code with the \
following command line.
.. literalinclude:: graph_demo_6.demo.sh
:language: python
:linenos:
The dumped source code ``shared_nodes.dat.gv`` should be like \
this
.. literalinclude:: shared_nodes.dat.gv
:language: graphviz
:linenos:
Or if you need to put it to the ``stdout``, you can do like this
.. literalinclude:: graph_demo_7.demo.sh
:language: python
:linenos:
The output in ``stdout`` should almost the same like the \
source code file ``shared_nodes.dat.gv``.
.. note::
When ``-O`` option is used, the ``-o`` will be ignored.
Reuse the Value Nodes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In some cases, the values in the trees' nodes is the same object \
(using the same memory id). So it's better to put them together \
in the dumped graph.
For example, in the python code file ``share_demo.py``
.. literalinclude:: share_demo.py
:language: python
:linenos:
You can run the following command
.. literalinclude:: graph_demo_8.demo.sh
:language: shell
:linenos:
The dumped graph ``no_shared_values.dat.svg`` should be like \
this, all the value nodes are separated in the default \
options.
.. image:: no_shared_values.dat.svg
:align: center
We can solve this problem by adding ``-D`` option in the command, \
which means duplicate all the value nodes.
.. literalinclude:: graph_demo_9.demo.sh
:language: shell
:linenos:
In the dumped graph ``shared_all_values.dat.svg``, all the value \
nodes with the same id are put together.
.. image:: shared_all_values.dat.svg
:align: center
.. note::
When ``-D`` is used, all the values in leaf node with the \
same id will share exactly one leaf node in the dumped \
graph.
But actually in python, most of the basic data \
types are immutable, which means all the ``1`` in python \
code are actually the same object, for their ids are \
the same. So in the image ``shared_all_values.dat.svg``, \
even the leaf node ``1`` are shared.
This phenomenon may reduce the intuitiveness of the \
image in many cases. Please notice this when you are \
deciding to use ``-D`` option.
Because of the problem mentioned in the note above, in most of \
the cases, it's a better idea to use ``-d`` option to assign \
which types should be duplicated and which should not. Like the \
example below.
.. literalinclude:: graph_demo_10.demo.sh
:language: shell
:linenos:
The dumped graph ``shared_values.dat.svg`` should be like this, \
the ``1`` is not duplicated any more, but ``list`` \
and ``np.ndarray`` will be duplicated.
.. image:: shared_values.dat.svg
:align: center
from treevalue import FastTreeValue
t1 = FastTreeValue({
'a': 1,
'b': [2] * 1000, # huge array
'x': {
'c': b'aklsdfj' * 2000, # huge bytes
'd': 4
}
})
from treevalue import FastTreeValue
nt1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
nt2 = FastTreeValue({'a': 11, 'b': 24, 'x': {'c': 30, 'd': 47}})
nt3 = FastTreeValue({
'first': nt1,
'second': nt2,
'another': nt1.x,
'sum': nt1 + nt2,
})
import numpy as np
from treevalue import FastTreeValue
st1 = FastTreeValue({
'a': 1,
'b': [1, 2],
'x': {
'c': 3,
'd': np.zeros((3, 2)),
}
})
st2 = FastTreeValue({
'np': st1.x.d,
'ar': st1.b,
'a': st1.a,
'arx': [1, 2],
})
from treevalue import FastTreeValue
t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
t2 = FastTreeValue({'a': 11, 'b': 24, 'x': {'c': 30, 'd': 47}})
t3 = t1 + t2
treevalue -v
\ No newline at end of file
treevalue -v &&
echo '' &&
treevalue -h
......@@ -15,16 +15,18 @@ You can also install with the newest version through GitHub:
pip install -U git+https://github.com/opendilab/treevalue.git@main
After installation, open your python console and type
After installation, open your shell console and use \
the cli like the script below.
.. literalinclude:: install_check.demo.py
:language: python
.. literalinclude:: cli_demo.demo.sh
:language: shell
:linenos:
If the output is like below and no error occurs, you have successfully installed Treevalue.
.. literalinclude:: install_check.demo.py.txt
.. literalinclude:: cli_demo.demo.sh.txt
:language: text
:linenos:
In newest version of treevalue, cli is supported to do some \
data processing. Here is the version and help display.
Treevalue is still under development, you can also check out the documents in stable version through `https://opendilab.github.io/treevalue/ <https://opendilab.github.io/treevalue/>`_.
from treevalue.config.meta import __VERSION__, __TITLE__, __AUTHOR__
if __name__ == '__main__':
print(__TITLE__, __VERSION__)
print("Powered by", __AUTHOR__)
import os
from dm import get_module
from treevalue import TreeValue
_module = get_module(os.path.abspath('create_a_complex_tree.demo.py'))
for key, value in _module.__dict__.items():
if isinstance(value, TreeValue):
locals()[key] = value
from treevalue import FastTreeValue
t1 = FastTreeValue({'a': 's1', 'b': 2, 'x': {'c': 3, 'd': [1, 2]}})
t2 = FastTreeValue({'a': 'str11', 'b': 2, 'x': {'c': 33, 'd': t1.x.d}})
t3 = FastTreeValue({'t1': t1, 't2': t2, 'sum': t1 + t2})
if __name__ == '__main__':
print('t1:')
print(t1)
print('t2:')
print(t2)
print('t3:')
print(t3)
from treevalue import FastTreeValue
t = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
if __name__ == '__main__':
t = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
print(t)
digraph D {
label = "Created new tree 't'"
graph [bgcolor = "#ffffff00"];
root [label = "t"];
n1 [label = "1"];
n2 [label = "2"];
n3 [label = "t.x"];
n4 [label = "3"];
n5 [label = "4"];
root -> n1 [label = "a"];
root -> n2 [label = "b"];
root -> n3 [label = "x"];
n3 -> n4 [label = "c"];
n3 -> n5 [label = "d"];
}
\ No newline at end of file
treevalue graph -t 'complex_demo.*' -c 'bgcolor=#ffffff00' -d list -o complex_demo.dat.svg
treevalue graph -t 'simple_demo.*' -c 'bgcolor=#ffffff00' -d list -o simple_demo.dat.svg
Quick Start
==================
Create a tree
-----------------
Create a Simplest Tree
-------------------------------------
You can easily create a tree value object based on ``FastTreeValue``.
You can create a simplest tree like this
.. literalinclude:: create_a_tree.demo.py
:language: python
:linenos:
The result should be
A tree value object with the following structure will be \
created
.. image:: simple_demo.dat.svg
:align: center
The output of the code above should be
.. literalinclude:: create_a_tree.demo.py.txt
:language: text
:linenos:
A simple tree value structure is created successfully with the structure below.
.. image:: create_a_tree.gv.svg
Create a Slightly Complex Tree
-------------------------------------
You can easily create a tree value object which is \
slightly complex, based on ``FastTreeValue``.
.. literalinclude:: create_a_complex_tree.demo.py
:language: python
:linenos:
The result should be
.. literalinclude:: create_a_complex_tree.demo.py.txt
:language: text
:linenos:
Three simple treevalue structures are created. \
Then save the code above to ``demo.py``, and then input \
this shell command in your terminal.
.. literalinclude:: display_complex_tree.demo.sh
:language: shell
:linenos:
A graph named ``demo.dat.svg`` will be generated, like this.
.. image:: complex_demo.dat.svg
:align: center
Now you are successfully started.
import os
from dm import get_module
from treevalue import TreeValue
_module = get_module(os.path.abspath('create_a_tree.demo.py'))
for key, value in _module.__dict__.items():
if isinstance(value, TreeValue):
locals()[key] = value
sphinx>=2.2.1
sphinx~=3.2.0
sphinx_rtd_theme~=0.4.3
enum_tools
sphinx-toolbox
......
enum_tools
treelib>=1.5,<2
\ No newline at end of file
treelib>=1.5,<2
graphviz~=0.17
dill~=0.3.4
click>=7.1.0
......@@ -36,6 +36,7 @@ setup(
packages=find_packages(
include=(_package_name, "%s.*" % _package_name)
),
description=meta['__DESCRIPTION__'],
long_description=readme,
long_description_content_type='text/markdown',
author=meta['__AUTHOR__'],
......@@ -55,11 +56,15 @@ setup(
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: Implementation :: PyPy'
],
entry_points={
'console_scripts': [
'treevalue=treevalue.entry.cli:treevalue_cli'
]
},
)
from .config import *
from .entry import *
from .tree import *
from .utils import *
from .cli import *
from .script import *
from .test_export import TestEntryCliExport
from .test_graph import TestEntryCliGraph
from .test_version import TestEntryCliVersion
import bz2
import gzip
import os
import zlib
import pytest
from click.testing import CliRunner
from treevalue import FastTreeValue, load
from treevalue.entry.cli import treevalue_cli
t1 = FastTreeValue({'a': 1, 'b': [2] * 1000, 'x': {'c': 3, 'd': 4}})
t2 = FastTreeValue({'a': 1, 'b': {2, 4}, 'x': {'c': [1, 3] * 500, 'd': 4}})
t3 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': t1.b, 'd': t2.x.c}})
def _my_compress(d):
return bz2.compress(gzip.compress(zlib.compress(d)))
def _my_decompress(d):
return zlib.decompress(gzip.decompress(bz2.decompress(d)))
# noinspection DuplicatedCode
@pytest.mark.unittest
class TestEntryCliExport:
def test_simple_export(self):
runner = CliRunner(mix_stderr=False)
with runner.isolated_filesystem():
result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.t1'])
assert result.exit_code == 0
assert os.path.exists('t1.btv')
assert 150 < os.path.getsize('t1.btv') < 220
with open('t1.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t1
def test_multiple_export(self):
runner = CliRunner(mix_stderr=False)
with runner.isolated_filesystem():
result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.*'])
assert result.exit_code == 0
assert os.path.exists('t1.btv')
assert 150 < os.path.getsize('t1.btv') < 220
assert os.path.exists('t2.btv')
assert 200 < os.path.getsize('t2.btv') < 280
assert os.path.exists('t3.btv')
assert 210 < os.path.getsize('t3.btv') < 250
with open('t1.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t1
with open('t2.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t2
with open('t3.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t3
with runner.isolated_filesystem():
result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.t[23]'])
assert result.exit_code == 0
assert not os.path.exists('t1.btv')
assert os.path.exists('t2.btv')
assert 200 < os.path.getsize('t2.btv') < 280
assert os.path.exists('t3.btv')
assert 210 < os.path.getsize('t3.btv') < 250
with open('t2.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t2
with open('t3.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t3
def test_multiple_export_without_compression(self):
runner = CliRunner(mix_stderr=False)
with runner.isolated_filesystem():
with pytest.warns(None):
result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.*', '-r'])
assert result.exit_code == 0
assert os.path.exists('t1.btv')
assert 2120 < os.path.getsize('t1.btv') < 2160
assert os.path.exists('t2.btv')
assert 2120 < os.path.getsize('t2.btv') < 2220
assert os.path.exists('t3.btv')
assert 4120 < os.path.getsize('t3.btv') < 4160
with open('t1.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t1
with open('t2.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t2
with open('t3.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t3
with runner.isolated_filesystem():
with pytest.warns(RuntimeWarning):
result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.*',
'-r', '-c', 'zlib'])
assert result.exit_code == 0
assert os.path.exists('t1.btv')
assert 2120 < os.path.getsize('t1.btv') < 2160
assert os.path.exists('t2.btv')
assert 2120 < os.path.getsize('t2.btv') < 2220
assert os.path.exists('t3.btv')
assert 4120 < os.path.getsize('t3.btv') < 4160
with open('t1.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t1
with open('t2.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t2
with open('t3.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t3
def test_multiple_export_with_compress_define(self):
runner = CliRunner(mix_stderr=False)
with runner.isolated_filesystem():
result = runner.invoke(treevalue_cli, ['export', '-t', 'test.entry.cli.test_export.*',
'-c', 'gzip'])
assert result.exit_code == 0
assert os.path.exists('t1.btv')
assert 150 < os.path.getsize('t1.btv') < 220
assert os.path.exists('t2.btv')
assert 160 < os.path.getsize('t2.btv') < 240
assert os.path.exists('t3.btv')
assert 160 < os.path.getsize('t3.btv') < 200
with open('t1.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t1
with open('t2.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t2
with open('t3.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t3
with runner.isolated_filesystem():
result = runner.invoke(treevalue_cli, [
'export', '-t', 'test.entry.cli.test_export.*',
'-c', 'test.entry.cli.test_export._my_compress:test.entry.cli.test_export._my_decompress'
])
assert result.exit_code == 0
assert os.path.exists('t1.btv')
assert 260 < os.path.getsize('t1.btv') < 300
assert os.path.exists('t2.btv')
assert 280 < os.path.getsize('t2.btv') < 370
assert os.path.exists('t3.btv')
assert 280 < os.path.getsize('t3.btv') < 320
with open('t1.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t1
with open('t2.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t2
with open('t3.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t3
def test_with_od(self):
runner = CliRunner(mix_stderr=False)
with runner.isolated_filesystem():
os.mkdir('subpath')
with pytest.warns(RuntimeWarning):
result = runner.invoke(treevalue_cli, [
'export', '-t', 'test.entry.cli.test_export.t[23]',
'-o', 'subpath/t1.btv',
'-o', 'subpath/t2.btv',
'-o', 'subpath/t3.btv',
])
assert result.exit_code == 0
assert os.path.exists('subpath/t1.btv')
assert 200 < os.path.getsize('subpath/t1.btv') < 280
assert os.path.exists('subpath/t2.btv')
assert 210 < os.path.getsize('subpath/t2.btv') < 250
assert not os.path.exists('subpath/t3.btv')
with open('subpath/t1.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t2
with open('subpath/t2.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t3
with runner.isolated_filesystem():
os.mkdir('subpath')
with pytest.warns(RuntimeWarning):
result = runner.invoke(treevalue_cli, [
'export', '-t', 'test.entry.cli.test_export.t[23]',
'-o', 't1.btv',
'-o', 't2.btv',
'-o', 't3.btv',
'-d', 'subpath',
])
assert result.exit_code == 0
assert os.path.exists('subpath/t1.btv')
assert 200 < os.path.getsize('subpath/t1.btv') < 280
assert os.path.exists('subpath/t2.btv')
assert 210 < os.path.getsize('subpath/t2.btv') < 250
assert not os.path.exists('subpath/t3.btv')
with open('subpath/t1.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t2
with open('subpath/t2.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t3
with runner.isolated_filesystem():
os.mkdir('subpath')
with pytest.warns(None):
result = runner.invoke(treevalue_cli, [
'export', '-t', 'test.entry.cli.test_export.t[23]',
'-d', 'subpath',
])
assert result.exit_code == 0
assert not os.path.exists('subpath/t1.btv')
assert os.path.exists('subpath/t2.btv')
assert 200 < os.path.getsize('subpath/t2.btv') < 280
assert os.path.exists('subpath/t3.btv')
assert 210 < os.path.getsize('subpath/t3.btv') < 250
with open('subpath/t2.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t2
with open('subpath/t3.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t3
with runner.isolated_filesystem():
with pytest.warns(None):
result = runner.invoke(treevalue_cli, [
'export', '-t', 'test.entry.cli.test_export.t[23]',
'-o', 'ppp.btv'
])
assert result.exit_code == 0
assert not os.path.exists('t1.btv')
assert not os.path.exists('t2.btv')
assert os.path.exists('ppp.btv')
assert 200 < os.path.getsize('ppp.btv') < 280
assert os.path.exists('t3.btv')
assert 210 < os.path.getsize('t3.btv') < 250
with open('ppp.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t2
with open('t3.btv', 'rb') as file:
assert load(file, type_=FastTreeValue) == t3
import os
import pathlib
import pickle
import warnings
import zlib
import pytest
from click.testing import CliRunner
from treevalue import FastTreeValue, dump, graphics
from treevalue.entry.cli import treevalue_cli
t1 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4}})
t2 = FastTreeValue({'a': 1, 'b': {2, 4}, 'x': {'c': [1, 3], 'd': 4}})
t3 = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': t2.b, 'd': t2.x.c}})
g = graphics(
(t1, 't1'), (t2, 't2'), (t3, 't3'),
title='This is title for g.',
cfg=dict(bgcolor='#ffffff00'),
dup_value=(list,),
)
@pytest.mark.unittest
class TestEntryCliGraph:
def test_simple_code_graph(self):
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', 'test.entry.cli.test_graph.t1',
'-o', 'test_graph.svg', '-o', 'test_graph.gv'],
)
assert result.exit_code == 0
assert os.path.exists('test_graph.svg')
assert 5830 <= os.path.getsize('test_graph.svg') <= 5930
assert os.path.exists('test_graph.gv')
assert 1800 <= os.path.getsize('test_graph.gv') <= 1900
def test_simple_code_graph_to_stdout(self):
runner = CliRunner()
with runner.isolated_filesystem():
with pytest.warns(RuntimeWarning):
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', 'test.entry.cli.test_graph.t1',
'-o', 'test_graph.svg', '-o', 'test_graph.gv', '-O'],
)
assert result.exit_code == 0
assert not os.path.exists('test_graph.svg')
assert not os.path.exists('test_graph.gv')
assert 1800 <= len(result.output) <= 1900
def test_simple_code_multiple_graph(self):
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', 'test.entry.cli.test_graph.t[12]', '-o', 'test_graph.svg'],
)
assert result.exit_code == 0
assert os.path.exists('test_graph.svg')
assert 11000 <= os.path.getsize('test_graph.svg') <= 12000
with runner.isolated_filesystem():
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', 'test.entry.cli.test_graph.*', '-o', 'test_graph.svg'],
)
assert result.exit_code == 0
assert os.path.exists('test_graph.svg')
assert 16150 <= os.path.getsize('test_graph.svg') <= 16300
def test_simple_binary_graph(self):
runner = CliRunner()
with runner.isolated_filesystem():
with open('g1.bg', 'wb') as file:
dump(t1, file, compress=zlib)
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', 'g1.bg', '-o', 'test_graph.svg'],
)
assert result.exit_code == 0
assert os.path.exists('test_graph.svg')
assert 5830 <= os.path.getsize('test_graph.svg') <= 5930
with runner.isolated_filesystem():
with open('g1.bg', 'wb') as file:
dump(t1, file, compress=zlib)
with open('g2.bg', 'wb') as file:
dump(t2, file, compress=zlib)
with open('g3.bg', 'wb') as file:
dump(t3, file, compress=zlib)
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', '*.bg', '-o', 'test_graph.svg'],
)
assert result.exit_code == 0
assert os.path.exists('test_graph.svg')
assert 16150 <= os.path.getsize('test_graph.svg') <= 16300
with runner.isolated_filesystem():
with open('test.entry.cli.test_graph.t1', 'wb') as file:
dump(t2, file, compress=zlib)
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', 'test.entry.cli.test_graph.t1', '-o', 'test_graph.svg'],
)
assert result.exit_code == 0
assert os.path.exists('test_graph.svg')
assert 11000 <= os.path.getsize('test_graph.svg') <= 12000
with runner.isolated_filesystem():
with open('test.entry.cli.test_graph.t1', 'wb') as file:
pickle.dump([1, 2, 3], file)
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', 'test.entry.cli.test_graph.t1', '-o', 'test_graph.svg'],
)
assert result.exit_code == 0
assert os.path.exists('test_graph.svg')
assert 5830 <= os.path.getsize('test_graph.svg') <= 5930
def test_duplicates(self):
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', 'test.entry.cli.test_graph.t[23]', '-D',
'-o', 'test_graph.svg'],
)
assert result.exit_code == 0
assert os.path.exists('test_graph.svg')
assert 10000 <= os.path.getsize('test_graph.svg') <= 10430
_p = os.path.abspath(os.curdir)
with runner.isolated_filesystem():
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', 'test.entry.cli.test_graph.t[23]',
'-d', 'list', '-d', 'set',
'-o', 'test_graph.svg'],
)
assert result.exit_code == 0
assert os.path.exists('test_graph.svg')
import shutil
shutil.copy('test_graph.svg', os.path.join(_p, 'test_graph.svg'))
assert 10550 <= os.path.getsize('test_graph.svg') <= 10650
def test_graph(self):
runner = CliRunner()
with runner.isolated_filesystem():
with pytest.warns(RuntimeWarning):
result = runner.invoke(
treevalue_cli,
args=['graph', '-g', 'test.entry.cli.test_graph.g',
'-t', 'first title', '-o', 'test_graph.svg'],
)
assert result.exit_code == 0
assert os.path.exists('test_graph.svg')
assert 15670 <= os.path.getsize('test_graph.svg') <= 15770
content = pathlib.Path('test_graph.svg').read_text()
assert 'first title' not in content
assert 'This is title for g.' in content
def test_cfg(self):
runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', 'test.entry.cli.test_graph.*',
'-c', 'bgcolor=#ffffff00', '-O'],
)
assert result.exit_code == 0
assert 5320 <= len(result.output) <= 5420
assert '#ffffff00' in result.output
with runner.isolated_filesystem():
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', 'test.entry.cli.test_graph.*',
'-c', 'bgcolor#ffffff00', '-O'],
)
assert result.exit_code != 0
assert "Configuration should be KEY=VALUE, but 'bgcolor#ffffff00' found." in result.output
def test_file_with_invalid_permission(self):
runner = CliRunner()
with runner.isolated_filesystem():
with open('g1.bg', 'wb') as file:
dump(t1, file, compress=zlib)
try:
os.chmod('g1.bg', int('000', base=8))
except PermissionError:
warnings.warn(RuntimeWarning('Permission denied when changing the permission, skip this test.'))
else:
result = runner.invoke(
treevalue_cli,
args=['graph', '-t', 'g1.bg', '-o', 'test_graph.svg'],
)
assert result.exit_code == 0
assert os.path.exists('test_graph.svg')
assert 700 <= os.path.getsize('test_graph.svg') <= 800
import pytest
from click.testing import CliRunner
from treevalue.config.meta import __TITLE__, __VERSION__
from treevalue.entry.cli import treevalue_cli
@pytest.mark.unittest
class TestEntryCliVersion:
def test_version(self):
runner = CliRunner()
result = runner.invoke(treevalue_cli, args=['-v'])
assert result.exit_code == 0
assert __TITLE__.lower() in result.stdout.lower()
assert __VERSION__.lower() in result.stdout.lower()
from .utils import float_eq
from functools import wraps
from typing import Callable
def eq_extend(func: Callable[..., bool]):
@wraps(func)
def _new_func(a, b, *args, **kwargs):
if isinstance(a, dict) and isinstance(b, dict):
aks, bks = set(a.keys()), set(b.keys())
if aks != bks:
return False
else:
return all([_new_func(a[key], b[key], *args, **kwargs) for key in aks])
elif (isinstance(a, tuple) and isinstance(b, tuple)) \
or (isinstance(a, list) and isinstance(b, list)):
length_a, length_b = len(a), len(b)
if length_a != length_b:
return False
else:
return all([_new_func(ai, bi, *args, **kwargs) for ai, bi in zip(a, b)])
else:
return func(a, b, *args, **kwargs)
return _new_func
@eq_extend
def float_eq(a, b, eps=1e-5):
return abs(a - b) < abs(eps)
......@@ -37,6 +37,20 @@ class TestTreeCommonTree:
assert h[t1] == 2
assert h[Tree(t1.json())] == 2
def test_copy_from(self):
t1 = Tree({'a': 1, 'x': {'b': 1, 'c': 2}})
t2 = Tree({'a': 11, 'b': 24, 'x': {'b': 12, 'e': {'dfkj': 892374}}})
original_id = id(t1.actual())
original_id_x = id(t1['x'].actual())
t1.copy_from(t2)
assert t1 == t2
assert t1 is not t2
assert id(t1.actual()) == original_id
assert id(t1['x'].actual()) == original_id_x
assert t1['x']['e'] == t2['x']['e']
assert t1['x']['e'] is not t2['x']['e']
def test_tree_items(self):
t = Tree({'a': 1, 'x': {'b': 1, 'c': 2}})
assert t['a'] == 1
......@@ -123,6 +137,20 @@ class TestTreeCommonTree:
assert t['x']['c'] == {'a': 5, 'b': 6}
assert t['x']['d'] == {'a': 7, 'b': 8}
def test_invalid_key(self):
with pytest.raises(KeyError):
_ = Tree({'a': 123, '\uffff': 321})
t = Tree({'a': 1, 'x': {'b': 1, 'c': 2}})
with pytest.raises(KeyError):
t['\uffff'] = 233
with pytest.raises(KeyError):
t['a' * 0x1ffff] = 233
with pytest.raises(KeyError):
t[''] = 233
with pytest.raises(KeyError):
t['0'] = 233
def test_deep_clone(self):
t = Tree({
'a': raw({'a': 1, 'b': 2}),
......
......@@ -52,6 +52,19 @@ class TestTreeCommonView:
3, 2, 1, Tree({'t': -1, 'q': -2})
}
def test_copy_from(self):
t = Tree({'a': 1, 'x': {'b': 233, 'c': 'sdklfgjl', 'f': {'t': 2, 'p': 3}}})
tv1 = t.view(['x'])
t2 = Tree({'bf': 233, 'c': 'sdklfgjl', 'f': {'t': 2, 'p': 3, 'tp': 3}})
original_id_x = id(t['x'].actual())
original_id_x_f = id(t['x']['f'].actual())
tv1.copy_from(t2)
assert tv1 == t2
assert id(tv1.actual()) == original_id_x
assert id(tv1['f'].actual()) == original_id_x_f
def test_deep_clone(self):
tx = Tree({
'a': {
......
此差异已折叠。
......@@ -17,6 +17,22 @@ class NonDefaultTreeNumber(general_tree_value(base=dict(), methods=dict(
pass
class BanAndOverrideTreeNumber(general_tree_value(methods=dict(
__add__=NotImplemented,
__radd__=NotImplemented,
__iadd__=NotImplemented,
__mul__=KeyError("lksdjfkl"),
__rmul__=KeyError("lsdfjkldks"),
__imul__=KeyError("dklfgjsl"),
__matmul__=KeyError,
__rmatmul__=KeyError,
__imatmul__=KeyError,
__pos__=(lambda sp: sp // 2),
__truediv__=(lambda sp: sp - 2),
))):
pass
@pytest.mark.unittest
class TestTreeGeneralGeneral(get_tree_test(TreeNumber)):
def test_numeric_append(self):
......@@ -43,3 +59,16 @@ class TestTreeGeneralGeneral(get_tree_test(TreeNumber)):
assert (t1 + t2) == NonDefaultTreeNumber({'a': 12, 'b': 24, 'c': 4, 'x': {'c': 36, 'd': 9, 'e': 7}})
with pytest.raises(KeyError):
_ = t1 - t2
def test_ban_add(self):
t1 = BanAndOverrideTreeNumber({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4, 'e': 7}})
t2 = BanAndOverrideTreeNumber({'a': 11, 'b': 22, 'c': 4, 'x': {'c': 33, 'd': 5}})
with pytest.raises(TypeError):
_ = t1 + t2
with pytest.raises(KeyError):
_ = t1 * t2
with pytest.raises(KeyError):
_ = t1 @ t2
assert +t1 == BanAndOverrideTreeNumber({'a': 1, 'b': 2, 'x': {'c': 3, 'd': 4, 'e': 7}}) // 2
assert t1 / 3 == BanAndOverrideTreeNumber({'a': 1 - 2, 'b': 2 - 2, 'x': {'c': 3 - 2, 'd': 4 - 2, 'e': 7 - 2}})
......@@ -6,51 +6,23 @@ from treevalue.tree import func_treelize, FastTreeValue
@pytest.mark.unittest
def test_for_torch_support():
@func_treelize(inherit=True)
def add(a, b):
return a + b
sin = func_treelize()(torch.sin)
cos = func_treelize()(torch.cos) # the same sin function
@func_treelize()
def sin(a):
return torch.sin(a)
cat = func_treelize(subside=True)(torch.cat)
stack = func_treelize(subside=True)(torch.stack)
split = func_treelize(rise=True)(torch.split)
cos = func_treelize()(torch.cos)
@func_treelize(inherit=True)
def cat(a, b, dim):
return torch.cat([a, b], dim=dim)
@func_treelize(inherit=True)
def stack(a, b, dim):
return torch.stack([a, b], dim=dim)
@func_treelize(inherit=True)
def stack_all(items, dim):
return torch.stack(items, dim=dim)
@func_treelize(inherit=True)
def split(a, split_size_or_sections, dim):
return torch.split(a, split_size_or_sections, dim)
@func_treelize(inherit=True)
def mean(a, *args, **kwargs):
return torch.mean(a, *args, **kwargs)
@func_treelize(inherit=True)
def clamp(a, *args, **kwargs):
return torch.clamp(a, *args, **kwargs)
@func_treelize(inherit=True)
def to(a, *args, **kwargs):
return a.to(*args, **kwargs)
mean = func_treelize()(torch.mean)
clamp = func_treelize()(torch.clamp)
t1 = FastTreeValue({'a': torch.randn(4, 3), 'b': torch.randn(5)})
t2 = FastTreeValue({'a': torch.randn(4, 3), 'b': torch.randn(5)})
t3 = add(t1, t2)
t3 = t1 + t2
assert t3.a.shape == (4, 3)
assert t3.b.shape == (5,)
t4 = add(1, t2)
t4 = 1 + t2
assert t4.a.shape == (4, 3)
assert t4.b.shape == (5,)
assert t4.b.eq(t2.b + 1).all()
......@@ -66,6 +38,7 @@ def test_for_torch_support():
assert t6.a.shape == (3,)
assert t6.b.shape == ()
# list not available now
# t1a = FastTreeValue({'a': torch.randn(4, 3), 'b': torch.randn(6, 9), 'c': {'d': torch.randn(8, 2)}, 'e': [torch.randn(2, 3), torch.randn(2, 4)]})
# t2a = FastTreeValue({'a': torch.randn(4, 3), 'b': torch.randn(6, 9), 'c': {'d': torch.randn(8, 2)}, 'e': [torch.randn(2, 3), torch.randn(2, 4)]})
t1a = FastTreeValue({'a': torch.randn(4, 3), 'b': torch.randn(6, 9), 'c': {'d': torch.randn(8, 2)}})
......@@ -80,29 +53,29 @@ def test_for_torch_support():
t6 = t1a[slice(0, 3)]
assert t6.a.shape == (3, 3)
assert t6.a[1].eq(t1a.a[1]).all()
# t6 = t1a[..., 1]
# assert t6.a.shape == (4, )
# t6 = t1a[..., 1:2]
# assert t6.a.shape == (4, 1)
t6 = t1a[..., 1]
assert t6.a.shape == (4,)
t6 = t1a[..., 1:2]
assert t6.a.shape == (4, 1)
t7 = cat(t1a, t2a, dim=0)
t7 = cat([t1a, t2a], dim=0)
assert t7.a.shape == (8, 3)
assert t7.b.shape == (12, 9)
t7 = cat(t1a, t2a, dim=1)
t7 = cat([t1a, t2a], dim=1)
assert t7.a.shape == (4, 6)
assert t7.b.shape == (6, 18)
t7 = stack(t1a, t2a, dim=0)
t7 = stack([t1a, t2a], dim=0)
assert t7.a.shape == (2, 4, 3)
assert t7.b.shape == (2, 6, 9)
t7 = stack(t1a, t2a, dim=1)
t7 = stack([t1a, t2a], dim=1)
assert t7.a.shape == (4, 2, 3)
assert t7.b.shape == (6, 2, 9)
t8 = split(t1a, 2, 0)
# assert isinstance(t8.a, tuple)
# assert t8.a[0].shape == (2, 3)
# assert isinstance(t8, tuple)
assert isinstance(t8.a, tuple)
assert t8.a[0].shape == (2, 3)
# assert isinstance(t8, tuple) # cannot be risen due to the different lengths of tuples
# assert t8[0].a.shape == (2, 3)
t9 = mean(t1a)
......@@ -114,11 +87,8 @@ def test_for_torch_support():
assert t10.c.d.eq(t1a.c.d.clamp(0, 0.2)).all()
assert t1a.a.dtype == torch.float32
t11 = to(t1a, torch.int64)
t11 = t1a.to(torch.int64)
assert t11.a.dtype == torch.int64
def stack_spec(items, *args, **kwargs):
return stack_all(FastTreeValue.subside(items), *args, **kwargs)
t12 = stack_spec([t1a, t2a, t1a], dim=0)
t12 = stack([t1a, t2a, t1a], dim=0)
assert t12.a.shape == (3, 4, 3)
from .test_graph import TestTreeTreeGraph
from .test_io import TestTreeTreeIo
from .test_tree import TestTreeTreeTree
from .test_utils import TestTreeTreeUtils
import numpy as np
import pytest
from treevalue import FastTreeValue, graphics
class MyFastTreeValue(FastTreeValue):
pass
@pytest.mark.unittest
class TestTreeTreeGraph:
def test_graphics(self):
t = MyFastTreeValue({
'a': [4, 3, 2, 1],
'b': np.array([[5, 6], [7, 8]]),
'x': {
'c': np.array([[5, 7], [8, 6]]),
'd': {'a', 'b', 'c'},
'e': np.array([[1, 2], [3, 4]])
},
})
t1 = MyFastTreeValue({
'aa': t.a,
'bb': np.array([[5, 6], [7, 8]]),
'xx': {
'cc': t.x.c,
'dd': t.x.d,
'ee': np.array([[1, 2], [3, 4]])
},
})
graph_1 = graphics(
(t, 't'), (t1, 't1'),
(MyFastTreeValue({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
dup_value=(np.ndarray, list),
title="This is a demo of 2 trees with dup value.",
cfg={'bgcolor': '#ffffffff'},
)
assert 4910 <= len(graph_1.source) <= 4960
graph_2 = graphics(
(t, 't'), (t1, 't1'),
(MyFastTreeValue({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
dup_value=False,
title="This is a demo of 2 trees with dup value.",
cfg={'bgcolor': '#ffffffff'},
)
assert 5420 <= len(graph_2.source) <= 5480
graph_3 = graphics(
(t, 't'), (t1, 't1'),
(MyFastTreeValue({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
dup_value=lambda x: id(x),
title="This is a demo of 2 trees with dup value.",
cfg={'bgcolor': '#ffffffff'},
)
assert 4700 <= len(graph_3.source) <= 4760
graph_4 = graphics(
(t, 't'), (t1, 't1'),
(MyFastTreeValue({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
dup_value=lambda x: type(x).__name__,
title="This is a demo of 2 trees with dup value.",
cfg={'bgcolor': '#ffffffff'},
)
assert 3720 <= len(graph_4.source) <= 3780
graph_6 = graphics(
(t, 't'), (t1, 't1'),
(MyFastTreeValue({'a': t, 'b': t1, 'c': [1, 2], 'd': t1.xx}), 't2'),
dup_value=True,
title="This is a demo of 2 trees with dup value.",
cfg={'bgcolor': '#ffffffff'},
)
assert 4700 <= len(graph_6.source) <= 4760
import zlib
import pytest
from treevalue.tree import loads, dumps, FastTreeValue
class MyTreeValue(FastTreeValue):
pass
# noinspection DuplicatedCode
@pytest.mark.unittest
class TestTreeTreeIo:
def test_dumps_and_loads(self):
t = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': [3] * 1000, 'd': 4}})
_dumped_data = dumps(t)
t1 = loads(_dumped_data, type_=FastTreeValue)
assert 2130 < len(_dumped_data) < 2150
assert isinstance(t1, FastTreeValue)
assert t1 == t
t2 = loads(_dumped_data, type_=MyTreeValue)
assert isinstance(t2, MyTreeValue)
assert t2 != t
assert FastTreeValue(t2) == t
with pytest.warns(UserWarning):
t1 = loads(_dumped_data, decompress=zlib.decompress, type_=FastTreeValue)
assert isinstance(t1, FastTreeValue)
assert t1 == t
def test_dumps_and_loads_with_zip(self):
t = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': [3] * 1000, 'd': 4}})
_dumped_data = dumps(t, compress=zlib)
t1 = loads(_dumped_data, type_=FastTreeValue)
assert 210 < len(dumps(t, compress=zlib)) < 220
assert isinstance(t1, FastTreeValue)
assert t1 == t
t2 = loads(_dumped_data, type_=MyTreeValue)
assert isinstance(t2, MyTreeValue)
assert t2 != t
assert FastTreeValue(t2) == t
with pytest.warns(UserWarning):
t1 = loads(_dumped_data, decompress=zlib.decompress, type_=FastTreeValue)
assert isinstance(t1, FastTreeValue)
assert t1 == t
def test_dumps_and_loads_with_zip_tuple(self):
t = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': [3] * 1000, 'd': 4}})
_dumped_data = dumps(t, compress=(zlib.compress, zlib.decompress))
t1 = loads(_dumped_data, type_=FastTreeValue)
assert 210 < len(_dumped_data) < 220
assert isinstance(t1, FastTreeValue)
assert t1 == t
t2 = loads(_dumped_data, type_=MyTreeValue)
assert isinstance(t2, MyTreeValue)
assert t2 != t
assert FastTreeValue(t2) == t
def test_dumps_and_loads_with_compress_only(self):
t = FastTreeValue({'a': 1, 'b': 2, 'x': {'c': [3] * 1000, 'd': 4}})
_dumped_data = dumps(t, compress=zlib.compress)
t1 = loads(_dumped_data, decompress=zlib.decompress, type_=FastTreeValue)
assert 130 < len(_dumped_data) < 150
assert isinstance(t1, FastTreeValue)
assert t1 == t
with pytest.raises(RuntimeError):
loads(_dumped_data, type_=FastTreeValue)
import pickle
import re
import pytest
......@@ -6,6 +7,30 @@ from treevalue.tree.tree import TreeValue
from treevalue.tree.tree.tree import get_data_property
class _Container:
def __init__(self, value):
self.__value = value
@property
def value(self):
return self.__value
def __repr__(self):
return '<{cls} {id} value: {value}>'.format(
cls=self.__class__.__name__, id=hex(id(self)), value=repr(self.__value))
def __eq__(self, other):
if other is self:
return True
elif type(other) == _Container:
return other.__value == self.value
else:
return False
def __hash__(self):
return hash((self.__value,))
@pytest.mark.unittest
class TestTreeTreeTree:
def test_tree_value_init(self):
......@@ -131,3 +156,19 @@ class TestTreeTreeTree:
assert d[tv1] == 1
assert d[TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}})] == 1
assert d[TreeValue({'x': 2, 'y': 3})] == 2
def test_serialize_support(self):
tv1 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}})
bt1 = pickle.dumps(tv1)
assert pickle.loads(bt1) == tv1
tv2 = TreeValue({
'a': _Container(1),
'b': _Container(2),
'x': {
'c': _Container(3),
'd': _Container(4),
}
})
bt2 = pickle.dumps(tv2)
assert pickle.loads(bt2) == tv2
......@@ -257,26 +257,61 @@ class TestTreeTreeUtils:
MyTreeValue({'x': 3, 'y': 8}),
]
}
assert rise(t3, template={'a': [None, None], 'b': [None, None]}) == {
'a': [
t4 = MyTreeValue({'x': raw({'a': (1, 2), 'b': (2, 3)}), 'y': raw({'a': (5, 6), 'b': (7, 8)})})
assert rise(t4) == {
'a': (
MyTreeValue({'x': 1, 'y': 5}),
MyTreeValue({'x': 2, 'y': 6}),
],
'b': [
),
'b': (
MyTreeValue({'x': 2, 'y': 7}),
MyTreeValue({'x': 3, 'y': 8}),
]
)
}
assert rise(t3, template={'a': None, 'b': None}) == {
'a': MyTreeValue({'x': [1, 2], 'y': [5, 6]}),
'b': MyTreeValue({'x': [2, 3], 'y': [7, 8]}),
assert rise(t4, template={'a': (None, None), 'b': (None, None)}) == {
'a': (
MyTreeValue({'x': 1, 'y': 5}),
MyTreeValue({'x': 2, 'y': 6}),
),
'b': (
MyTreeValue({'x': 2, 'y': 7}),
MyTreeValue({'x': 3, 'y': 8}),
)
}
assert rise(t4, template={'a': None, 'b': None}) == {
'a': MyTreeValue({'x': (1, 2), 'y': (5, 6)}),
'b': MyTreeValue({'x': (2, 3), 'y': (7, 8)}),
}
assert rise(t3, template=None) == t3
with pytest.raises(ValueError):
rise(t2, template={'a': [None, None], 'b': [None, None]})
rise(t4, template={'a': [], 'b': None})
t5 = TreeValue({
'x': raw([{'a': 1, 'b': 2}, {'a': 2, 'b': 3}, {'a': 3, 'b': 5}]),
'y': raw([{'a': 21, 'b': 32}, {'a': -2, 'b': 23}, {'a': 53, 'b': 25}]),
})
assert rise(t5) == [
{'a': TreeValue({'x': 1, 'y': 21}), 'b': TreeValue({'x': 2, 'y': 32})},
{'a': TreeValue({'x': 2, 'y': -2}), 'b': TreeValue({'x': 3, 'y': 23})},
{'a': TreeValue({'x': 3, 'y': 53}), 'b': TreeValue({'x': 5, 'y': 25})},
]
assert rise(t5, template=[{'a': None, 'b': None}]) == [
{'a': TreeValue({'x': 1, 'y': 21}), 'b': TreeValue({'x': 2, 'y': 32})},
{'a': TreeValue({'x': 2, 'y': -2}), 'b': TreeValue({'x': 3, 'y': 23})},
{'a': TreeValue({'x': 3, 'y': 53}), 'b': TreeValue({'x': 5, 'y': 25})},
]
assert rise(t5, template=[]) == [
TreeValue({'x': raw({'a': 1, 'b': 2}), 'y': raw({'a': 21, 'b': 32})}),
TreeValue({'x': raw({'a': 2, 'b': 3}), 'y': raw({'a': -2, 'b': 23})}),
TreeValue({'x': raw({'a': 3, 'b': 5}), 'y': raw({'a': 53, 'b': 25})}),
]
assert rise(t5, template=[None]) == [
TreeValue({'x': raw({'a': 1, 'b': 2}), 'y': raw({'a': 21, 'b': 32})}),
TreeValue({'x': raw({'a': 2, 'b': 3}), 'y': raw({'a': -2, 'b': 23})}),
TreeValue({'x': raw({'a': 3, 'b': 5}), 'y': raw({'a': 53, 'b': 25})}),
]
with pytest.raises(ValueError):
rise(t3, template={'a': (None, None), 'b': [None, None]})
rise(t5, template=[None, None])
def test_reduce(self):
class MyTreeValue(TreeValue):
......
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册