From 82721abf89ce309bb6161e0c4f38439de690790c Mon Sep 17 00:00:00 2001 From: Alexander Sergeev Date: Tue, 21 Mar 2023 12:09:13 +0100 Subject: [PATCH] Flattened doc structure and beautiful modules (#91) * module index generator scripts changed * example links generation code and interface changed; examples made flat. * lint fixed * conf file reformatted * library source patching added * lint applied * patching called before `sphinx-build` * double patching fixed * example title in nbgalleries patched * linted * format tests fixed * patching docs added * lint fixed * TODO added * type hints fixed * one element tuples removed * optional replaced with unions * contribution fixed --- CONTRIBUTING.md | 5 +- docs/source/conf.py | 27 +++- docs/source/examples.rst | 2 +- docs/source/utils/custom_directives.py | 1 + docs/source/utils/generate_examples.py | 134 ++++++++++++++++++ docs/source/utils/generate_notebook_links.py | 114 --------------- docs/source/utils/patching.py | 94 ++++++++++++ docs/source/utils/regenerate_apiref.py | 24 ++-- .../telegram/10_no_pipeline_advanced.py | 2 +- examples/messengers/telegram/1_basic.py | 2 +- examples/messengers/telegram/2_buttons.py | 2 +- .../telegram/3_buttons_with_callback.py | 2 +- examples/messengers/telegram/4_conditions.py | 2 +- .../telegram/5_conditions_with_media.py | 2 +- .../telegram/6_conditions_extras.py | 2 +- .../messengers/telegram/7_polling_setup.py | 2 +- .../messengers/telegram/8_webhook_setup.py | 2 +- examples/messengers/telegram/9_no_pipeline.py | 2 +- examples/script/core/1_basics.py | 2 +- examples/script/core/2_conditions.py | 2 +- examples/script/core/3_responses.py | 2 +- examples/script/core/4_transitions.py | 2 +- examples/script/core/5_global_transitions.py | 2 +- .../script/core/6_context_serialization.py | 2 +- .../script/core/7_pre_response_processing.py | 2 +- examples/script/core/8_misc.py | 2 +- .../core/9_pre_transitions_processing.py | 2 +- examples/script/responses/1_basics.py | 2 +- examples/script/responses/2_buttons.py | 2 +- examples/script/responses/3_media.py | 2 +- examples/script/responses/4_multi_message.py | 2 +- makefile | 1 + tests/examples/test_format.py | 2 +- 33 files changed, 292 insertions(+), 158 deletions(-) create mode 100644 docs/source/utils/generate_examples.py delete mode 100644 docs/source/utils/generate_notebook_links.py create mode 100644 docs/source/utils/patching.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1de7a7170..cdbe7baca 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,10 @@ by activating the virtual environment and then running make doc ``` -After that `docs/build` dir will be created and you can open index file `docs/build/index.html` in your browser of choice. +After that `docs/build` dir will be created and you can open index file `docs/build/index.html` in your browser of choice. +WARNING! Because of the current patching solution, `make doc` modifies some of the source library code (`nbsphinx` and `autosummary`), +so it is strongly advised to use it carefully and in virtual environment only. +However, this behavior is likely to be changed in the future. ### Style For style supporting we propose `black`, which is a PEP 8 compliant opinionated formatter. diff --git a/docs/source/conf.py b/docs/source/conf.py index 25175e821..2882700e5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,7 +6,7 @@ sys.path.append(os.path.abspath(".")) from utils.notebook import insert_installation_cell_into_py_example # noqa: E402 -from utils.generate_notebook_links import generate_example_links_for_notebook_creation # noqa: E402 +from utils.generate_examples import generate_example_links_for_notebook_creation # noqa: E402 from utils.regenerate_apiref import regenerate_apiref # noqa: E402 from utils.pull_release_notes import pull_release_notes_from_github # noqa: E402 @@ -28,6 +28,7 @@ extensions = [ "sphinx.ext.autodoc", + "sphinx.ext.autosummary", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.todo", @@ -84,6 +85,7 @@ html_show_sourcelink = False +autosummary_generate_overwrite = False # Finding examples directories nbsphinx_custom_formats = {".py": insert_installation_cell_into_py_example()} @@ -140,11 +142,24 @@ def setup(_): generate_example_links_for_notebook_creation( [ - "examples/context_storages/*.py", - "examples/messengers/*.py", - "examples/pipeline/*.py", - "examples/script/*.py", - "examples/utils/*.py", + ("examples.context_storages", "Context Storages"), + ( + "examples.messengers", + "Messengers", + [ + ("telegram", "Telegram"), + ], + ), + ("examples.pipeline", "Pipeline"), + ( + "examples.script", + "Script", + [ + ("core", "Core"), + ("responses", "Responses"), + ], + ), + ("examples.utils", "Utils"), ] ) regenerate_apiref( diff --git a/docs/source/examples.rst b/docs/source/examples.rst index 3b0fed74c..f9e25ca39 100644 --- a/docs/source/examples.rst +++ b/docs/source/examples.rst @@ -5,4 +5,4 @@ Examples :name: examples :glob: - examples/*/index + examples/index_* diff --git a/docs/source/utils/custom_directives.py b/docs/source/utils/custom_directives.py index 2e816ea79..b6b915be6 100644 --- a/docs/source/utils/custom_directives.py +++ b/docs/source/utils/custom_directives.py @@ -1,3 +1,4 @@ +# TODO: legacy from pytorch theme, remove everything not required by our docs from docutils.parsers.rst import Directive, directives from docutils.statemachine import StringList from docutils import nodes diff --git a/docs/source/utils/generate_examples.py b/docs/source/utils/generate_examples.py new file mode 100644 index 000000000..bbe1dd284 --- /dev/null +++ b/docs/source/utils/generate_examples.py @@ -0,0 +1,134 @@ +from pathlib import Path +from typing import List, Optional, Set, Union, Tuple + + +def create_notebook_link(source: Path, destination: Path): + """ + Create a symlink between two files. + Used to create links to examples under docs/source/examples/ root. + + :param source: Path to source file (in examples/ dir). + :param destination: Path to link file (in docs/source/examples/ dir). + """ + destination.unlink(missing_ok=True) + destination.parent.mkdir(exist_ok=True, parents=True) + destination.symlink_to(source.resolve(), False) + + +def generate_nb_gallery(package: str, files: List[Path]) -> str: + """ + Generate a gallery of examples. + + :param package: Package to join into a gallery (effectively a common example link prefix). + :param files: List of all example links. + """ + included = "\n ".join(file.name for file in files if file.name.startswith(package)) + return f""" +.. nbgallery:: + {included} +""" + + +def create_index_file( + included: Union[Tuple[str, str], Tuple[str, str, List[Tuple[str, str]]]], + files: List[Path], + destination: Path +): + """ + Create a package index file. + Contains nbgalleries of files inside the package (and subpackages). + + :param included: A pair of package path and alias with or without list of subpackages. + :param files: List of all example links. + :param destination: Path to the index file. + """ + title = included[1] + contents = f""":orphan: + +.. This is an auto-generated RST index file representing examples directory structure + +{title} +{"=" * len(title)} +""" + if len(included) == 2: + contents += generate_nb_gallery(included[0], files) + else: + for subpackage in included[2]: + contents += f"\n{subpackage[1]}\n{'-' * len(subpackage[1])}\n" + contents += generate_nb_gallery(f"{included[0]}.{subpackage[0]}", files) + + destination.parent.mkdir(exist_ok=True, parents=True) + destination.write_text(contents) + + +def sort_example_file_tree(files: Set[Path]) -> List[Path]: + """ + Sort files alphabetically; for the example files (whose names start with number) numerical sort is applied. + + :param files: Files list to sort. + """ + examples = {file for file in files if file.stem.split("_")[0].isdigit()} + return sorted(examples, key=lambda file: int(file.stem.split("_")[0])) + sorted(files - examples) + + +def iterate_examples_dir_generating_links(source: Path, dest: Path, base: str) -> List[Path]: + """ + Recursively travel through examples directory, creating links for all files under docs/source/examples/ root. + Created link files have dot-path name matching source file tree structure. + + :param source: Examples root (usually examples/). + :param dest: Examples destination (usually docs/source/examples/). + :param base: Dot path to current dir (will be used for link file naming). + """ + if not source.is_dir(): + raise Exception(f"Entity {source} appeared to be a file during processing!") + links = list() + for entity in [obj for obj in sort_example_file_tree(set(source.glob("./*"))) if not obj.name.startswith("__")]: + base_name = f"{base}.{entity.name}" + if entity.is_file() and entity.suffix in (".py", ".ipynb"): + base_path = Path(base_name) + create_notebook_link(entity, dest / base_path) + links += [base_path] + elif entity.is_dir() and not entity.name.startswith("_"): + links += iterate_examples_dir_generating_links(entity, dest, base_name) + return links + + +def generate_example_links_for_notebook_creation( + include: Optional[List[Union[Tuple[str, str], Tuple[str, str, List[Tuple[str, str]]]]]] = None, + exclude: Optional[List[str]] = None, + source: str = "examples", + destination: str = "docs/source/examples", +): + """ + Generate symbolic links to examples files (examples/) in docs directory (docs/source/examples/). + That is required because Sphinx doesn't allow to include files from parent directories into documentation. + Also, this function creates index files inside each generated folder. + That index includes each folder contents, so any folder can be imported with 'folder/index'. + + :param include: Files to copy (supports file templates, like *). + :param exclude: Files to skip (supports file templates, like *). + :param source: Examples root, default: 'examples/'. + :param destination: Destination root, default: 'docs/source/examples/'. + """ + include = [("examples", "Examples")] if include is None else include + exclude = list() if exclude is None else exclude + dest = Path(destination) + + flattened = list() + for package in include: + if len(package) == 2: + flattened += [package[0]] + else: + flattened += [f"{package[0]}.{subpackage[0]}" for subpackage in package[2]] + + links = iterate_examples_dir_generating_links(Path(source), dest, source) + filtered_links = list() + for link in links: + link_included = len(list(flat for flat in flattened if link.name.startswith(flat))) > 0 + link_excluded = len(list(pack for pack in exclude if link.name.startswith(pack))) > 0 + if link_included and not link_excluded: + filtered_links += [link] + + for included in include: + create_index_file(included, filtered_links, dest / Path(f"index_{included[1].replace(' ', '_').lower()}.rst")) diff --git a/docs/source/utils/generate_notebook_links.py b/docs/source/utils/generate_notebook_links.py deleted file mode 100644 index dd47ee7bf..000000000 --- a/docs/source/utils/generate_notebook_links.py +++ /dev/null @@ -1,114 +0,0 @@ -from fnmatch import fnmatch -from pathlib import Path -from typing import List, Optional, Set - - -def create_notebook_link(file: Path, notebook_path: Path): - """ - Create a symlink between two files. - Used to create links to examples under docs/source/examples/ root. - - :param file: File to create link from (a code example). - :param notebook_path: Path to create the link. - """ - file.parent.mkdir(exist_ok=True, parents=True) - file.symlink_to(notebook_path.resolve(), False) - - -def create_directory_index_file(file: Path, index: List[str]): - """ - Create a directory index file. - Contains a nbgallery of files inside the directory. - - :param file: Path to directory index file (file name is usually 'index.rst'). - :param index: List of the files to include into the directory, should be sorted previously. - """ - title = " ".join(word.capitalize() for word in file.parent.stem.split("_")) - directories = "\n ".join(directory for directory in index) - contents = f""":orphan: - -.. This is an auto-generated RST index file representing examples directory structure - -{title} -{"=" * len(title)} - -.. nbgallery:: - :glob: - - {directories} -""" - file.parent.mkdir(exist_ok=True, parents=True) - file.write_text(contents) - - -def sort_example_file_tree(files: Set[Path]) -> List[Path]: - """ - Sort files alphabetically; for the example files (whose names start with number) numerical sort is applied. - - :param files: Files list to sort. - """ - examples = {file for file in files if file.stem.split("_")[0].isdigit()} - return sorted(examples, key=lambda file: int(file.stem.split("_")[0])) + sorted(files - examples) - - -def iterate_dir_generating_notebook_links( - current: Path, source: str, dest: str, include: List[str], exclude: List[str] -) -> List[str]: - """ - Recursively travel through examples directory, creating links for all files under docs/source/examples/ root. - Also creates indexes for all created links for them to be easily included into RST documentation. - - :param current: Path being searched currently. - :param source: Examples root (usually examples/). - :param dest: Examples destination (usually docs/source/examples/). - :param include: List of files to include to search (is applied before exclude list). - :param exclude: List of files to exclude from search (is applied after include list). - """ - dest_path = Path(dest) - if not current.is_dir(): - raise Exception(f"Entity {current} appeared to be a file during processing!") - includes = list() - for entity in sort_example_file_tree(set(current.glob("./*"))): - doc_path = dest_path / entity.relative_to(source) - if not entity.name.startswith("__"): - if ( - entity.is_file() - and entity.suffix in (".py", ".ipynb") - and any(fnmatch(str(entity.relative_to(".")), inc) for inc in include) - and not any(fnmatch(str(entity.relative_to(".")), exc) for exc in exclude) - ): - if not entity.name.startswith("_"): - includes.append(doc_path.name) - create_notebook_link(doc_path, entity) - elif entity.is_dir() and not entity.name.startswith("_"): - if len(iterate_dir_generating_notebook_links(entity, source, dest, include, exclude)) > 0: - includes.append(f"{doc_path.name}/index") - if len(includes) > 0: - create_directory_index_file(dest_path / current.relative_to(source) / Path("index.rst"), includes) - return includes - - -def generate_example_links_for_notebook_creation( - include: Optional[List[str]] = None, - exclude: Optional[List[str]] = None, - source: str = "examples/", - destination: str = "docs/source/examples/", -): - """ - Generate symbolic links to examples files (examples/) in docs directory (docs/source/examples/). - That is required because Sphinx doesn't allow to include files from parent directories into documentation. - Also, this function creates index files inside each generated folder. - That index includes each folder contents, so any folder can be imported with 'folder/index'. - - :param include: Files to copy (supports file templates, like *). - :param exclude: Files to skip (supports file templates, like *). - :param source: Examples root, default: 'examples/'. - :param destination: Destination root, default: 'docs/source/examples/'. - """ - iterate_dir_generating_notebook_links( - Path(source), - source, - destination, - ["**"] if include is None else include, - [] if exclude is None else exclude, - ) diff --git a/docs/source/utils/patching.py b/docs/source/utils/patching.py new file mode 100644 index 000000000..fa949e1d4 --- /dev/null +++ b/docs/source/utils/patching.py @@ -0,0 +1,94 @@ +from hashlib import sha256 +from logging import INFO, getLogger, StreamHandler +from typing import Callable, Optional, Any +from inspect import signature, getsourcefile, getsourcelines + +from sphinx.ext.autosummary import extract_summary +from nbsphinx import depart_gallery_html + +logger = getLogger(__name__) +logger.addHandler(StreamHandler()) +logger.setLevel(INFO) + + +def patch_source_file(module: str, patch: str, patch_payload: Optional[str] = None) -> bool: + """ + Patch library source file. + New code is appended to the library source code file, so use it in `venv` only! + Function can be called multiple times, it won't re-apply the same patches. + + :param module: Module name (file name) to apply the patch to. Should be writable. + :type module: str + :param patch: Python source code to append (a.k.a. patch). + :type patch: str + :param patch_payload: Unique patch identifier string (used to prevent patch re-applying). + If not provided, `patch` string will be used for identification instead. + :type patch_payload: str, optional + :return: True if patch was applied, False if the file is already patched before. + :rtype: bool + """ + patch_payload = patch if patch_payload is None else patch_payload + patch_comment = f"# Patched with: {sha256(patch_payload.encode('utf-8')).hexdigest()}" + patch = f"\n\n\n{patch_comment}\n{patch}\n" + with open(module, "r") as file: + if any(patch_comment in line for line in file.readlines()): + return False + with open(module, "a") as file: + file.write(patch) + return True + + +def wrap_source_function(source: Callable, wrapper: Callable[[Callable], Any]): + """ + Wrap library function. + Works just like `patch_source_file`. + Has some limitations on library and wrapper functions (should be customized for your particular case). + Let library function name be `[source]`, then: + 1. Library file should NOT have functions called `[source]_wrapper` and `[source]_old`. + Otherwise, these functions will be overwritten and unavailable. + 2. Wrapper function shouldn't have type hints that are not defined in the library file. + No imports are added along with patch function, and its definition and code is copied literally. + 3. Wrapper function shouldn't have neither docstring nor multiline definition. + Its definition is considered to be (and is copied as) single line, + anything starting from the second line should be code. + + :param source: Library function to wrap (exported from the module patch will be applied to). + :type source: callable + :param wrapper: Wrapper function, should accept `source` + function as single parameter and return whatever it returns. + :type wrapper: callable + """ + src_file = getsourcefile(source) + src_name = getattr(source, "__name__") + logger.info(f"Wrapping function '{src_name}'...") + wrap_body = "".join(getsourcelines(wrapper)[0][1:]) + wrap_sign = f"def {src_name}_wrapper{signature(wrapper)}" + patch = f"{src_name}_old = {src_name}\n{wrap_sign}:\n{wrap_body}\n{src_name} = {src_name}_wrapper({src_name}_old)" + if patch_source_file(src_file, patch, patch_payload=f"{signature(wrapper)}:\n{wrap_body}"): + logger.info("Function wrapped successfully!") + else: + logger.info("Function already wrapped, skipping.") + + +# And here are our patches: + + +def extract_summary_wrapper(func): + return lambda doc, document: func(doc, document).split("\n\n")[-1] + + +def depart_gallery_html_wrapper(func): + def wrapper(self, node): + entries = node["entries"] + for i in range(len(entries)): + entries[i] = list(entries[i]) + title_split = entries[i][0].split(": ") + entries[i][0] = entries[i][0] if len(title_split) == 1 else title_split[-1] + return func(self, node) + + return wrapper + + +if __name__ == "__main__": + wrap_source_function(extract_summary, extract_summary_wrapper) + wrap_source_function(depart_gallery_html, depart_gallery_html_wrapper) diff --git a/docs/source/utils/regenerate_apiref.py b/docs/source/utils/regenerate_apiref.py index 104125d08..35d09b5d2 100644 --- a/docs/source/utils/regenerate_apiref.py +++ b/docs/source/utils/regenerate_apiref.py @@ -3,7 +3,7 @@ from typing import List, Optional, Tuple, Dict -def generate_doc_container(file: Path, includes: List[Path]): +def generate_doc_container(file: Path, alias: str, includes: List[Path]): """ Generates source files index. The generated file contains a toctree of included files. @@ -11,20 +11,19 @@ def generate_doc_container(file: Path, includes: List[Path]): It is also has maximum depth of 1 (only filenames) and includes titles only. :param file: Path to directory index file (file name will be prefixed 'index_'). + :param alias: Module name alias. :param includes: List of the files to include into the directory, should be sorted previously. """ - title = file.stem sources = "\n ".join(str(include.stem) for include in includes) contents = f""":orphan: .. This is an auto-generated RST file representing documentation source directory structure -{title} -{"=" * len(title)} +{alias} +{"=" * len(alias)} -.. toctree:: - :maxdepth: 1 - :titlesonly: +.. autosummary:: + :toctree: {sources} """ @@ -45,8 +44,8 @@ def regenerate_apiref(paths: Optional[List[Tuple[str, str]]] = None, destination :param destination: Apiref root path, default: apiref. """ paths = list() if paths is None else paths - source = Path(f"./docs/source/{destination.lower().replace(' ', '_')}") - doc_containers: Dict[str, List[Path]] = dict() + source = Path(f"./docs/source/{destination}") + doc_containers: Dict[str, Tuple[str, List[Path]]] = dict() for doc_file in iter(source.glob("./*.rst")): contents = doc_file.read_text() @@ -59,11 +58,12 @@ def regenerate_apiref(paths: Optional[List[Tuple[str, str]]] = None, destination doc_file.unlink() continue else: - doc_containers[container] = doc_containers.get(container, list()) + [doc_file] + filename = container.replace(" ", "_").lower() + doc_containers[filename] = container, doc_containers.get(filename, ("", list()))[1] + [doc_file] with open(doc_file, "r+") as file: contents = file.read() doc_file.write_text(f":source_name: {join(*doc_file.stem.split('.'))}\n\n{contents}") - for name, files in doc_containers.items(): - generate_doc_container(source / Path(f"{name}.rst"), files) + for name, (alias, files) in doc_containers.items(): + generate_doc_container(source / Path(f"{name}.rst"), alias, files) diff --git a/examples/messengers/telegram/10_no_pipeline_advanced.py b/examples/messengers/telegram/10_no_pipeline_advanced.py index e8745835f..113156bf7 100644 --- a/examples/messengers/telegram/10_no_pipeline_advanced.py +++ b/examples/messengers/telegram/10_no_pipeline_advanced.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 10. No Pipeline Advanced +# Telegram: 10. No Pipeline Advanced This example demonstrates how to connect to Telegram without the `pipeline` API. diff --git a/examples/messengers/telegram/1_basic.py b/examples/messengers/telegram/1_basic.py index a53c71e32..1dfe05947 100644 --- a/examples/messengers/telegram/1_basic.py +++ b/examples/messengers/telegram/1_basic.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 1. Basic +# Telegram: 1. Basic The following example shows how to run a regular DFF script in Telegram. It asks users for the '/start' command and then loops in one place. diff --git a/examples/messengers/telegram/2_buttons.py b/examples/messengers/telegram/2_buttons.py index 62a899c49..8e7ab8409 100644 --- a/examples/messengers/telegram/2_buttons.py +++ b/examples/messengers/telegram/2_buttons.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 2. Buttons +# Telegram: 2. Buttons This example shows how to display and hide a basic keyboard in Telegram. diff --git a/examples/messengers/telegram/3_buttons_with_callback.py b/examples/messengers/telegram/3_buttons_with_callback.py index bea784c62..0b8c2ff58 100644 --- a/examples/messengers/telegram/3_buttons_with_callback.py +++ b/examples/messengers/telegram/3_buttons_with_callback.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 3. Buttons with Callback +# Telegram: 3. Buttons with Callback This example demonstrates, how to add an inline keyboard and utilize diff --git a/examples/messengers/telegram/4_conditions.py b/examples/messengers/telegram/4_conditions.py index 07a0b0097..273a4cec9 100644 --- a/examples/messengers/telegram/4_conditions.py +++ b/examples/messengers/telegram/4_conditions.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 4. Conditions +# Telegram: 4. Conditions This example shows how to process Telegram updates in your script and reuse handler triggers from the `pytelegrambotapi` library. diff --git a/examples/messengers/telegram/5_conditions_with_media.py b/examples/messengers/telegram/5_conditions_with_media.py index 9666357a6..7c95013ec 100644 --- a/examples/messengers/telegram/5_conditions_with_media.py +++ b/examples/messengers/telegram/5_conditions_with_media.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 5. Conditions with Media +# Telegram: 5. Conditions with Media This example shows how to use media-related logic in your script. """ diff --git a/examples/messengers/telegram/6_conditions_extras.py b/examples/messengers/telegram/6_conditions_extras.py index 263c91b00..c91ebfa47 100644 --- a/examples/messengers/telegram/6_conditions_extras.py +++ b/examples/messengers/telegram/6_conditions_extras.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 6. Conditions Extras +# Telegram: 6. Conditions Extras This example shows how to use additional update filters inherited from the `pytelegrambotapi` library. diff --git a/examples/messengers/telegram/7_polling_setup.py b/examples/messengers/telegram/7_polling_setup.py index 73e45d0e9..f8e55fbca 100644 --- a/examples/messengers/telegram/7_polling_setup.py +++ b/examples/messengers/telegram/7_polling_setup.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 7. Polling Setup +# Telegram: 7. Polling Setup The following example shows how to configure `PollingTelegramInterface`. diff --git a/examples/messengers/telegram/8_webhook_setup.py b/examples/messengers/telegram/8_webhook_setup.py index c61c8586b..2a907abd4 100644 --- a/examples/messengers/telegram/8_webhook_setup.py +++ b/examples/messengers/telegram/8_webhook_setup.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 8. Webhook Setup +# Telegram: 8. Webhook Setup The following example shows how to use `CallbackTelegramInterface` that makes your bot accessible through a public webhook. diff --git a/examples/messengers/telegram/9_no_pipeline.py b/examples/messengers/telegram/9_no_pipeline.py index 18fa53510..6cbf63163 100644 --- a/examples/messengers/telegram/9_no_pipeline.py +++ b/examples/messengers/telegram/9_no_pipeline.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 9. No Pipeline +# Telegram: 9. No Pipeline This example shows how to connect to Telegram without the `pipeline` API. diff --git a/examples/script/core/1_basics.py b/examples/script/core/1_basics.py index ce2a2e268..cf5863746 100644 --- a/examples/script/core/1_basics.py +++ b/examples/script/core/1_basics.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 1. Basics +# Core: 1. Basics This notebook shows basic example of creating a simple dialog bot (agent). Let's do all the necessary imports from `DFF`: diff --git a/examples/script/core/2_conditions.py b/examples/script/core/2_conditions.py index 9feb610b9..f3431e874 100644 --- a/examples/script/core/2_conditions.py +++ b/examples/script/core/2_conditions.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 2. Conditions +# Core: 2. Conditions This example shows different options for setting transition conditions from one node to another. diff --git a/examples/script/core/3_responses.py b/examples/script/core/3_responses.py index 9e98cfb49..0cd7c0a7d 100644 --- a/examples/script/core/3_responses.py +++ b/examples/script/core/3_responses.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 3. Responses +# Core: 3. Responses This example shows different options for setting responses. Let's do all the necessary imports from `DFF`. diff --git a/examples/script/core/4_transitions.py b/examples/script/core/4_transitions.py index 390f56756..41d6388c8 100644 --- a/examples/script/core/4_transitions.py +++ b/examples/script/core/4_transitions.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 4. Transitions +# Core: 4. Transitions This example shows settings for transitions between flows and nodes. First of all, let's do all the necessary imports from `DFF`. diff --git a/examples/script/core/5_global_transitions.py b/examples/script/core/5_global_transitions.py index 703299186..7204634f3 100644 --- a/examples/script/core/5_global_transitions.py +++ b/examples/script/core/5_global_transitions.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 5. Global transitions +# Core: 5. Global transitions This example shows the global setting of transitions. First of all, let's do all the necessary imports from `DFF`. diff --git a/examples/script/core/6_context_serialization.py b/examples/script/core/6_context_serialization.py index 5bb66ba52..8360cc864 100644 --- a/examples/script/core/6_context_serialization.py +++ b/examples/script/core/6_context_serialization.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 6. Context serialization +# Core: 6. Context serialization This example shows context serialization. First of all, let's do all the necessary imports from `DFF`. diff --git a/examples/script/core/7_pre_response_processing.py b/examples/script/core/7_pre_response_processing.py index 5bdb15a7d..06296e973 100644 --- a/examples/script/core/7_pre_response_processing.py +++ b/examples/script/core/7_pre_response_processing.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 7. Pre-response processing +# Core: 7. Pre-response processing This example shows pre-response processing feature. First of all, let's do all the necessary imports from `DFF`. diff --git a/examples/script/core/8_misc.py b/examples/script/core/8_misc.py index 07ff73362..92a3653b0 100644 --- a/examples/script/core/8_misc.py +++ b/examples/script/core/8_misc.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 8. Misc +# Core: 8. Misc This example shows `MISC` (miscellaneous) keyword usage. First of all, let's do all the necessary imports from `DFF`. diff --git a/examples/script/core/9_pre_transitions_processing.py b/examples/script/core/9_pre_transitions_processing.py index 786e9ffc4..defbb4ccb 100644 --- a/examples/script/core/9_pre_transitions_processing.py +++ b/examples/script/core/9_pre_transitions_processing.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 9. Pre-transitions processing +# Core: 9. Pre-transitions processing This example shows pre-transitions processing feature. First of all, let's do all the necessary imports from `DFF`. diff --git a/examples/script/responses/1_basics.py b/examples/script/responses/1_basics.py index 35d4b84d8..a5925e0e5 100644 --- a/examples/script/responses/1_basics.py +++ b/examples/script/responses/1_basics.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 1. Basics +# Responses: 1. Basics """ diff --git a/examples/script/responses/2_buttons.py b/examples/script/responses/2_buttons.py index 8e91dafcf..99412a866 100644 --- a/examples/script/responses/2_buttons.py +++ b/examples/script/responses/2_buttons.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 2. Buttons +# Responses: 2. Buttons """ diff --git a/examples/script/responses/3_media.py b/examples/script/responses/3_media.py index 75356c40b..a27512dee 100644 --- a/examples/script/responses/3_media.py +++ b/examples/script/responses/3_media.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 3. Media +# Responses: 3. Media """ diff --git a/examples/script/responses/4_multi_message.py b/examples/script/responses/4_multi_message.py index 21acec26d..3e74ad6f1 100644 --- a/examples/script/responses/4_multi_message.py +++ b/examples/script/responses/4_multi_message.py @@ -1,6 +1,6 @@ # %% [markdown] """ -# 4. Multi Message +# Responses: 4. Multi Message This example shows Multi Message usage. Let's do all the necessary imports from `DFF`. diff --git a/makefile b/makefile index 5c768a878..dd4969bab 100644 --- a/makefile +++ b/makefile @@ -62,6 +62,7 @@ test_all: venv wait_db test lint .PHONY: test_all doc: venv clean_docs + python3 docs/source/utils/patching.py sphinx-apidoc -e -E -f -o docs/source/apiref dff sphinx-build -M clean docs/source docs/build source <(cat .env_file | sed 's/=/=/' | sed 's/^/export /') && export DISABLE_INTERACTIVE_MODE=1 && sphinx-build -b html -W --keep-going docs/source docs/build diff --git a/tests/examples/test_format.py b/tests/examples/test_format.py index d65dbb111..66aa78ac6 100644 --- a/tests/examples/test_format.py +++ b/tests/examples/test_format.py @@ -13,7 +13,7 @@ re.compile(r"# %%\n"), # check python block ] -start_pattern = re.compile(r'# %% \[markdown\]\n"""\n# \d+\. .*\n\n(?:[\S\s]*\n)?"""\n') +start_pattern = re.compile(r'# %% \[markdown\]\n"""\n#(?: .*:)? \d+\. .*\n\n(?:[\S\s]*\n)?"""\n') def regexp_format_checker(dff_example_py_file: pathlib.Path):