Repository: gaogaotiantian/coredumpy

Issue:
Allow an arbirary script to run when coredumpy is loaded

Discussion:

Solution patch:
diff --git a/README.md b/README.md
index 6354382..bbf99ec 100644
--- a/README.md
+++ b/README.md
@@ -90,6 +90,64 @@ coredumpy peek <your_dump_directory>
 coredumpy peek <your_dump_file1> <your_dump_file2>
 ```
 
+## Type support
+
+`coredumpy` supports the common built-in types like `float`, `int`, `str`, `list`,
+`dict` etc. For all the other types that it can't recognize, it will treat them as
+a Python object, which means `coredumpy` will iterate and store all the attributes
+of the object.
+
+You can add support for any arbitrary types by creating a class that inherits
+`coredumpy.TypeSupportBase`. You need to finish the following class methods in
+order to make it work:
+
+```python
+@classmethod
+def get_type(cls) -> tuple[Union[type, Callable], str]:
+    # returns a tuple with two elements:
+    # 0. type (or a callable for lazy load) for dump
+    #    coredumpy will dispatch the objects with this type to the dump method
+    # 1. a type string for load
+    #    coredumpy will dispatch the data with this type to the load method
+
+@classmethod
+def dump(cls, obj) -> tuple[dict, Optional[list]]:
+    # takes the object to be dumped
+    # returns a tuple with two elements:
+    # 0. a json-serializable dict, which will be stored in the dump file
+    # 1. a list that contains the objects needed to be dumped for this object
+    #    if none needed (the object is not a container), use None
+
+@classmethod
+def load(cls, data: dict, objects: dict) -> tuple[object, Optional[list[str]]]:
+    # takes the dict data from `dump` method and a dict of all objects with the ids
+    # as keys
+    # returns a tuple with two elements:
+    # 0. the restored object. If not ready, return coredumpy.NotReady
+    # 1. a list of the ids of dependent objects, if not applicable, use None
+```
+
+If the type is a container, inherit `coredumpy.TypeSupportContainerBase` and
+implement an extra method:
+
+```python
+@classmethod
+def reload(cls, container, data, objects: dict) -> tuple[object, Optional[list[str]]]:
+    # takes the already built container, the other arguments are the same as `load`
+    # returns the same as `load`
+    # This is helpful to create a placeholder first with `load` so the other objects
+    # can reference to it, and build the placeholder later
+```
+
+You only need to create the class, it will be automatically registered.
+
+## Startup script
+
+In order to import the type supports and do some customization, `coredumpy` provides
+a way to run an arbitrary script after importing `coredumpy`. You can put a
+`conf_coredumpy.py` file in your current working directory. If `coredumpy` discovers
+it, the script will be executed. You can put anything you need in the script.
+
 ## About the data
 
 Besides a couple of builtin types, coredumpy treats almost every object as an
diff --git a/src/coredumpy/__init__.py b/src/coredumpy/__init__.py
index 9fab282..436f4f1 100644
--- a/src/coredumpy/__init__.py
+++ b/src/coredumpy/__init__.py
@@ -8,8 +8,11 @@ import coredumpy.pytest_hook as pytest_hook
 from .coredumpy import Coredumpy, dump, dumps, load
 from .except_hook import patch_except
 from .main import main
-from .type_support import TypeSupportBase
+from .type_support import TypeSupportBase, TypeSupportContainerBase, NotReady
 from .unittest_hook import patch_unittest
+from .conf_hook import startup_conf
+
+startup_conf()
 
 
 __all__ = [
@@ -22,4 +25,6 @@ __all__ = [
     "patch_unittest",
     "pytest_hook",
     "TypeSupportBase",
+    "TypeSupportContainerBase",
+    "NotReady",
 ]
diff --git a/src/coredumpy/conf_hook.py b/src/coredumpy/conf_hook.py
new file mode 100644
index 0000000..313c768
--- /dev/null
+++ b/src/coredumpy/conf_hook.py
@@ -0,0 +1,11 @@
+# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
+# For details: https://github.com/gaogaotiantian/coredumpy/blob/master/NOTICE.txt
+
+import os
+import runpy
+
+
+def startup_conf():
+    cwd = os.getcwd()
+    if os.path.exists(os.path.join(cwd, "conf_coredumpy.py")):
+        runpy.run_path(os.path.join(cwd, "conf_coredumpy.py"))


Test patch:
diff --git a/tests/test_basic.py b/tests/test_basic.py
index 95cba60..651898a 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -2,6 +2,8 @@
 # For details: https://github.com/gaogaotiantian/coredumpy/blob/master/NOTICE.txt
 
 
+import contextlib
+import io
 import os
 import tempfile
 
@@ -94,6 +96,38 @@ class TestBasic(TestBase):
             self.assertEqual(len(os.listdir(tmpdir)), 3)
             self.assertEqual(len(os.listdir(child_dir)), 1)
 
+    def test_conf(self):
+        with tempfile.TemporaryDirectory() as tmpdir:
+            try:
+                cwd = os.getcwd()
+                os.chdir(tmpdir)
+
+                with open("conf_coredumpy.py", "w") as f:
+                    f.write("print('hello world')")
+
+                script = """
+                    import coredumpy
+                    def f():
+                        coredumpy.dump(path="coredumpy_dump")
+                    f()
+                """
+                stdout, _ = self.run_test(script, "coredumpy_dump", [
+                    "q"
+                ])
+
+                self.assertIn("hello world", stdout)
+
+                stdout, _ = self.run_script("import coredumpy")
+                self.assertIn("hello world", stdout)
+
+                from coredumpy.conf_hook import startup_conf
+                buf = io.StringIO()
+                with contextlib.redirect_stdout(buf):
+                    startup_conf()
+                self.assertIn("hello world", buf.getvalue())
+            finally:
+                os.chdir(cwd)
+
     def test_except(self):
         script = """
             import coredumpy