diff --git a/.gitignore b/.gitignore index 6b5d676261f0d0f67d4d5cf50ba2c7b199169d70..a8b35e032981d5a175a7316a625a5042ebd738f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1207,4 +1207,5 @@ fabric.properties /docs/source/**/*.sh.txt /docs/source/**/*.sh.err /docs/source/**/*.sh.exitcode -/docs/source/**/*.dat.* \ No newline at end of file +/docs/source/**/*.dat.* +/docs/source/**/*.auto.rst \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile index 2509949ddf8d5bf204c22ec9367e84286f3cd6ad..98468a0c063a02785e455568b679435719693262 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -13,6 +13,8 @@ GRAPHVIZ_MK := ${SOURCEDIR}/graphviz.mk GRAPHVIZ := $(MAKE) -f "${GRAPHVIZ_MK}" SOURCE=${SOURCEDIR} DEMOS_MK := ${SOURCEDIR}/demos.mk DEMOS := $(MAKE) -f "${DEMOS_MK}" SOURCE=${SOURCEDIR} +APIDOC_MK := ${SOURCEDIR}/apidoc.mk +APIDOC := $(MAKE) -f "${APIDOC_MK}" SOURCE=${SOURCEDIR} _CURRENT_PATH := ${PATH} _PROJ_DIR := $(shell readlink -f ${CURDIR}/..) @@ -34,6 +36,7 @@ help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) contents: + @$(APIDOC) build @$(DIAGRAMS) build @$(GRAPHVIZ) build @$(DEMOS) build @@ -50,6 +53,7 @@ clean: @$(DIAGRAMS) clean @$(GRAPHVIZ) clean @$(DEMOS) clean + @$(APIDOC) clean @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) sourcedir: diff --git a/docs/source/api_doc/common/index.rst b/docs/source/api_doc/common/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..da2c8b032385b0f23d2637e8d05d01533e08eaa3 --- /dev/null +++ b/docs/source/api_doc/common/index.rst @@ -0,0 +1,8 @@ +treetensor.common +===================== + +.. toctree:: + :maxdepth: 3 + + trees.auto + wrappers.auto diff --git a/docs/source/api_doc/common/trees.rstc b/docs/source/api_doc/common/trees.rstc new file mode 100644 index 0000000000000000000000000000000000000000..f3dabcbbb3d35eb2c791f2c3735a1dbb01e6d4f2 --- /dev/null +++ b/docs/source/api_doc/common/trees.rstc @@ -0,0 +1 @@ +treetensor.common.trees \ No newline at end of file diff --git a/docs/source/api_doc/common/wrappers.rstc b/docs/source/api_doc/common/wrappers.rstc new file mode 100644 index 0000000000000000000000000000000000000000..bc2b3555564dde4e12dcb76194ed4e67ecb30e72 --- /dev/null +++ b/docs/source/api_doc/common/wrappers.rstc @@ -0,0 +1 @@ +treetensor.common.wrappers \ No newline at end of file diff --git a/docs/source/api_doc/tensor/funcs.rstc b/docs/source/api_doc/tensor/funcs.rstc new file mode 100644 index 0000000000000000000000000000000000000000..8cf5eaefc59333b720c8eb29a1cec3fc901d61fc --- /dev/null +++ b/docs/source/api_doc/tensor/funcs.rstc @@ -0,0 +1 @@ +treetensor.tensor.funcs \ No newline at end of file diff --git a/docs/source/api_doc/tensor/index.rst b/docs/source/api_doc/tensor/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..2a63245be7d01722638b78b981bb578c422337f1 --- /dev/null +++ b/docs/source/api_doc/tensor/index.rst @@ -0,0 +1,9 @@ +treetensor.tensor +===================== + +.. toctree:: + :maxdepth: 3 + + funcs.auto + size.auto + tensor.auto diff --git a/docs/source/api_doc/tensor/size.rstc b/docs/source/api_doc/tensor/size.rstc new file mode 100644 index 0000000000000000000000000000000000000000..7f2775e0f92233fa1968992b50516ffbc0bb7a83 --- /dev/null +++ b/docs/source/api_doc/tensor/size.rstc @@ -0,0 +1 @@ +treetensor.tensor.size \ No newline at end of file diff --git a/docs/source/api_doc/tensor/tensor.rstc b/docs/source/api_doc/tensor/tensor.rstc new file mode 100644 index 0000000000000000000000000000000000000000..9c3b7eabc99fd0be9470a41c5e0a53f5e92c7e40 --- /dev/null +++ b/docs/source/api_doc/tensor/tensor.rstc @@ -0,0 +1 @@ +treetensor.tensor.tensor \ No newline at end of file diff --git a/docs/source/apidoc.mk b/docs/source/apidoc.mk new file mode 100644 index 0000000000000000000000000000000000000000..8f1b71230ee092317469572f4920af3c168d2c76 --- /dev/null +++ b/docs/source/apidoc.mk @@ -0,0 +1,20 @@ +PYTHON := $(shell which python) + +SOURCE ?= . +RSTC_FILES := $(shell find ${SOURCE} -name *.rstc) +RST_RESULTS := $(addsuffix .auto.rst, $(basename ${RSTC_FILES})) + +APIDOC_GEN_PY := $(shell readlink -f ${SOURCE}/apidoc_gen.py) + +%.auto.rst: %.rstc ${APIDOC_GEN_PY} + cd "$(shell dirname $(shell readlink -f $<))" && \ + PYTHONPATH="$(shell dirname $(shell readlink -f $<)):${PYTHONPATH}" \ + cat "$(shell readlink -f $<)" | $(PYTHON) "${APIDOC_GEN_PY}" > "$(shell readlink -f $@)" + +build: ${RST_RESULTS} + +all: build + +clean: + rm -rf \ + $(shell find ${SOURCE} -name *.auto.rst) diff --git a/docs/source/apidoc_gen.py b/docs/source/apidoc_gen.py new file mode 100644 index 0000000000000000000000000000000000000000..939814799139cc511316d4fe86638684acef9294 --- /dev/null +++ b/docs/source/apidoc_gen.py @@ -0,0 +1,51 @@ +import importlib +import types +from typing import List + +_DOC_TAG = '__doc_names__' + + +def _is_tagged_name(clazz, name): + return name in set(getattr(clazz, _DOC_TAG, set())) + + +def _find_class_members(clazz: type) -> List[str]: + members = [] + for name in dir(clazz): + item = getattr(clazz, name) + if _is_tagged_name(clazz, name) and \ + getattr(item, '__name__', None) == name: # should be public or protected + members.append(name) + + return members + + +if __name__ == '__main__': + package_name = input().strip() + _module = importlib.import_module(package_name) + _alls = getattr(_module, '__all__') + + print(package_name) + print('=' * (len(package_name) + 5)) + print() + + print(f'.. automodule:: {package_name}') + print() + + for _name in sorted(_alls): + print(_name) + print('-' * (len(_name) + 5)) + print() + + _item = getattr(_module, _name) + if isinstance(_item, types.FunctionType): + print(f'.. autofunction:: {package_name}.{_name}') + print() + elif isinstance(_item, type): + print(f'.. autoclass:: {package_name}.{_name}') + print(f' :members: {", ".join(sorted(_find_class_members(_item)))}') + print() + else: + print(f'.. autodata:: {package_name}.{_name}') + print(f' :annotation:') + print() diff --git a/docs/source/conf.py b/docs/source/conf.py index 76135ffc93d11c39d482de0c795306846587ed67..6cdd842cf671efc0ef70f1ef6249285605a4c564 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -58,6 +58,12 @@ if not os.environ.get("NO_CONTENTS_BUILD"): if pip_docs.wait() != 0: raise ChildProcessError("Pip docs install failed with %d." % (pip.returncode,)) + apidoc_cmd = (where.first('make'), '-f', "apidoc.mk", "build") + print("Building apidoc {cmd} at {cp}...".format(cmd=repr(apidoc_cmd), cp=repr(_DOC_PATH))) + apidoc = Popen(apidoc_cmd, stdout=sys.stdout, stderr=sys.stderr, env=_env, cwd=_DOC_PATH) + if apidoc.wait() != 0: + raise ChildProcessError("Diagrams failed with %d." % (apidoc.returncode,)) + diagrams_cmd = (where.first('make'), '-f', "diagrams.mk", "build") print("Building diagrams {cmd} at {cp}...".format(cmd=repr(diagrams_cmd), cp=repr(_DOC_PATH))) diagrams = Popen(diagrams_cmd, stdout=sys.stdout, stderr=sys.stderr, env=_env, cwd=_DOC_PATH) diff --git a/docs/source/index.rst b/docs/source/index.rst index 5bc3c8b5f5954ffc9e9a9290591fe852b05ecaa2..101cbb545a7ea17a04b8d579b234202a0d1a4cff 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -19,7 +19,9 @@ module. :maxdepth: 2 :caption: API Documentation + api_doc/common/index api_doc/config/index + api_doc/tensor/index .. toctree:: diff --git a/treetensor/common/trees.py b/treetensor/common/trees.py index 347f7ed21131a50ed1217623defe946d7c8d8d24..352f3b92dbe7b44d6902c965b3031d6a7c32d876 100644 --- a/treetensor/common/trees.py +++ b/treetensor/common/trees.py @@ -2,6 +2,8 @@ from abc import ABCMeta from treevalue import general_tree_value, method_treelize +from ..utils import tag_names + __all__ = [ 'BaseTreeStruct', "TreeData", 'TreeObject', ] @@ -15,6 +17,7 @@ class BaseTreeStruct(general_tree_value(), metaclass=ABCMeta): pass +@tag_names(['__eq__', '__ne__', '__lt__', '__le__', '__gt__', '__ge__']) class TreeData(BaseTreeStruct, metaclass=ABCMeta): """ Overview: diff --git a/treetensor/numpy/numpy.py b/treetensor/numpy/numpy.py index eb7c3f585066225e5c83667675b9d49db6655e6a..00b2d328e0c040d0c4f8a37b3812d007e1a9fa3d 100644 --- a/treetensor/numpy/numpy.py +++ b/treetensor/numpy/numpy.py @@ -2,12 +2,15 @@ import numpy as np from treevalue import method_treelize from ..common import TreeObject, TreeData, ireduce +from ..utils import inherit_names, current_names __all__ = [ 'TreeNumpy' ] +@current_names() +@inherit_names(TreeData) class TreeNumpy(TreeData): """ Overview: diff --git a/treetensor/tensor/tensor.py b/treetensor/tensor/tensor.py index cb7a225c4d15af35ff393839fefc05bb728fff0c..671de780efd271616cdd981a76005a9811f4e00e 100644 --- a/treetensor/tensor/tensor.py +++ b/treetensor/tensor/tensor.py @@ -6,6 +6,7 @@ from treevalue.utils import pre_process from .size import TreeSize from ..common import TreeObject, TreeData, ireduce from ..numpy import TreeNumpy +from ..utils import inherit_names, current_names __all__ = [ 'TreeTensor' @@ -16,6 +17,8 @@ tireduce = pre_process(lambda rfunc: ((_reduce_tensor_wrap(rfunc),), {}))(ireduc # noinspection PyTypeChecker,PyShadowingBuiltins,PyArgumentList +@current_names() +@inherit_names(TreeData) class TreeTensor(TreeData): @method_treelize(return_type=TreeNumpy) def numpy(self: torch.Tensor) -> np.ndarray: diff --git a/treetensor/utils/__init__.py b/treetensor/utils/__init__.py index 17199931222a5c35b10f47fad127391c07c8476a..336167bf0935d43c33afb2e3b72b1e2371b56dd5 100644 --- a/treetensor/utils/__init__.py +++ b/treetensor/utils/__init__.py @@ -1 +1,2 @@ -from .func import replaceable_partial +from .clazz import * +from .func import * diff --git a/treetensor/utils/clazz.py b/treetensor/utils/clazz.py new file mode 100644 index 0000000000000000000000000000000000000000..181a4627dcad39e2adccd614645acd58b542a316 --- /dev/null +++ b/treetensor/utils/clazz.py @@ -0,0 +1,78 @@ +import types +from functools import reduce +from operator import __or__ +from typing import Iterable, TypeVar + +__all__ = [ + 'tag_names', 'inherit_names', 'current_names', +] + +_DOC_TAG = '__doc_names__' +_CLS_TYPE = TypeVar('_CLS_TYPE', bound=type) + + +def _get_names(clazz: type): + return set(getattr(clazz, _DOC_TAG, set())) + + +def _set_names(clazz: type, names: Iterable[str]): + setattr(clazz, _DOC_TAG, set(names)) + + +def tag_names(names: Iterable[str], keep: bool = True): + def _decorator(cls: _CLS_TYPE) -> _CLS_TYPE: + _old_names = _get_names(cls) if keep else set() + _set_names(cls, set(names) | _old_names) + + return cls + + return _decorator + + +def inherit_names(*clazzes: type, keep: bool = True): + def _decorator(cls: _CLS_TYPE) -> _CLS_TYPE: + _old_names = _get_names(cls) if keep else set() + _set_names(cls, reduce(__or__, [_old_names, *map(_get_names, clazzes)])) + return cls + + return _decorator + + +class _TempClazz: + @property + def prop(self): + return None + + +PropertyType = type(_TempClazz.prop) + + +# noinspection PyTypeChecker +def _is_func_property(clazz, name): + func = getattr(clazz, name) + return isinstance(func, (types.FunctionType, PropertyType)) and ( + not hasattr(clazz.__base__, name) or getattr(clazz.__base__, name) is not func + ) + + +def _is_classmethod(clazz, name): + method = getattr(clazz, name) + return isinstance(method, types.MethodType) and ( + not hasattr(clazz.__base__, name) or getattr(clazz.__base__, name).__func__ is not method.__func__ + ) + + +def current_names(keep: bool = True): + def _decorator(cls: _CLS_TYPE) -> _CLS_TYPE: + members = set() + for name in dir(cls): + item = getattr(cls, name) + if (_is_func_property(cls, name) or _is_classmethod(cls, name)) and \ + getattr(item, '__name__', None) == name: # should be public or protected + members.add(name) + + _old_names = _get_names(cls) if keep else set() + _set_names(cls, _old_names | set(members)) + return cls + + return _decorator diff --git a/treetensor/utils/func.py b/treetensor/utils/func.py index b7ec1dd5fece83d3c5d7cb205270194732cbfbeb..61c15f758b6af426e9bbe374068668da67a03cfd 100644 --- a/treetensor/utils/func.py +++ b/treetensor/utils/func.py @@ -1,3 +1,8 @@ +__all__ = [ + 'replaceable_partial', +] + + def replaceable_partial(func, **kws): def _new_func(*args, **kwargs): return func(*args, **{**kws, **kwargs})