{"id":25149534,"url":"https://github.com/lucsorel/pydoctrace","last_synced_at":"2025-04-28T15:36:13.213Z","repository":{"id":65746562,"uuid":"586097265","full_name":"lucsorel/pydoctrace","owner":"lucsorel","description":"Generate architecture diagrams by tracing Python code execution","archived":false,"fork":false,"pushed_at":"2025-02-04T08:46:42.000Z","size":352,"stargazers_count":13,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-04T09:32:56.899Z","etag":null,"topics":["architecture","doc-as-code","documentation","python3","tracing"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/pydoctrace/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lucsorel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-01-06T23:52:55.000Z","updated_at":"2025-02-04T08:46:38.000Z","dependencies_parsed_at":"2023-02-26T11:15:58.672Z","dependency_job_id":"31601a1a-8a9e-4537-9863-859edb84af74","html_url":"https://github.com/lucsorel/pydoctrace","commit_stats":{"total_commits":23,"total_committers":2,"mean_commits":11.5,"dds":0.04347826086956519,"last_synced_commit":"36d51334fd37e6ac206e99e24bcc9012bf32f88e"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucsorel%2Fpydoctrace","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucsorel%2Fpydoctrace/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucsorel%2Fpydoctrace/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucsorel%2Fpydoctrace/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lucsorel","download_url":"https://codeload.github.com/lucsorel/pydoctrace/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":237876317,"owners_count":19380336,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["architecture","doc-as-code","documentation","python3","tracing"],"created_at":"2025-02-08T21:29:24.354Z","updated_at":"2025-02-08T21:29:24.878Z","avatar_url":"https://github.com/lucsorel.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/lucsorel/pydoctrace/main.svg)](https://results.pre-commit.ci/latest/github/lucsorel/pydoctrace/main)\n\n`pydoctrace` uses [pre-commit hooks](https://pre-commit.com/) and [pre-commit.ci continuous integration](https://pre-commit.ci/) to enforce commit messages, code formatting and linting for quality and consistency sake.\nSee the [code conventions](#code-conventions) section if you would like to contribute to the project.\n\n# pydoctrace\n\nGenerate diagrams by tracing Python code execution.\n\nHere are the diagrams produced by 2 different implementations of the `factorial` function (*n * n-1 * n-2 * ... * 1*):\n\n- a **sequence diagram** for the recursive implementation\n- a **component diagram** for the looping implementation\n\n\u003ctable\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd valign=\"bottom\"\u003e\n\n```python\nfrom pydoctrace.doctrace import trace_to_sequence_puml\n\n@trace_to_sequence_puml\ndef factorial_recursive(value: int) -\u003e int:\n    if value \u003c= 1:\n        return value\n\n    return value * factorial_recursive(value - 1)\n```\n\n\u003c/td\u003e\n\u003ctd valign=\"bottom\"\u003e\n\n```python\nfrom pydoctrace.doctrace import trace_to_component_puml\n\n@trace_to_component_puml\ndef factorial_reduce_multiply(value: int) -\u003e int:\n    if value \u003c= 1:\n        return value\n\n    def multiply(agg: int, value: int) -\u003e int:\n        return agg * value\n\n    return reduce(multiply, range(1, value + 1), 1)\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd valign=\"top\"\u003e\u003ccode\u003etests.modules.factorial.factorial_recursive.puml\u003c/code\u003e\u003cbr /\u003e\n\u003cimg src=\"https://www.plantuml.com/plantuml/svg/xP31QYf144Nt_HM5Mz5nv3qJ90JHHNP1oD90AAThjB6dIgghWu8Vdu2OI8o8PEkkwNkuzr2ZPAYMcmX6oLAt4PyZfMwDbOa6ZD-lDwKgQmhlvD8gy1eL6nZBPehU1rv0sJlwdw9QgC8QsGxv_wFuMOp6MAqMAfdzHA8eJ4GvXRZwYObwqZto4eWPtJ9uWbh4vh9nRYQTHsYTqN_bN_nRZiK8D2oMDeGOc63Wt7KLSFKejDlxtKZrORRitLHkAdzIKRAi3ELfUEzskzqNq3y5FXkFYS55el_l8bBsU-UPsKEdS-KbXd1tfj7L8aOAJyIaQEHXleMYM6-zLAPOKL6u4R7FJNGV\" /\u003e\u003c/td\u003e\n\u003ctd valign=\"top\"\u003e\u003ccode\u003etests.modules.factorial.factorial_reduce_multiply.puml\u003c/code\u003e\u003cbr /\u003e\n\u003cimg src=\"https://www.plantuml.com/plantuml/svg/dP3DQiCm48JlUeeXPyMEq_yIGkYbrqAFfPYjj34ciYIaDTIKl7lT7Dpcq9BciB3C_dp3RB9Gahvp4CwIYoxOtd4kjcGaf9RSTrSdjhtXkkkTjD4DSnEw63nxKNdN-aY9EZo4zoUojlKDgiKFVTfzbi4n4XiXtpXMAfBPKSF7V7meO3iUCYR-GGDU_ctq5PGn-tKymsg5ZIGQDGdrvBIENx6irtzJZo7JJmNirLwTOtO-Nv-2kqUbSQ5nfN6ZSQTHLMVXLaLn6cwfSQbnhN4xiXPZBPNQXY2SyCYU4mbRl0qeopZOO0w1bgNQiBTZaEeJ\" /\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\nThe **sequence diagram** gives more details about the execution but can easily grow large, the **component diagram** is more concise and helps viewing the architecture behind the calls.\n\n## Installation\n\nUse your favorite dependency manager to install `pydoctrace`:\n\n```sh\n# using pip\npip install pydoctrace\n\n# using poetry\npoetry add pydoctrace\n\n# using pipenv\npipenv install pydoctrace\n```\n\n## Usage\n\nImport a decorator and apply it to the functions whom execution you want to trace:\n\n```python\n# in my_package/my_module.py\nfrom pydoctrace.doctrace import trace_to_component_puml, trace_to_sequence_puml\n\ndef main(parameter):\n    validate(parameter)\n    do_something(parameter)\n\n@trace_to_component_puml\ndef validate(parameter):\n    validate_is_not_none(parameter)\n    validate_is_in_database(parameter)\n\n@trace_to_sequence_puml\ndef do_something(parameter):\n    stuff = load_stuff(parameter)\n    save_work(process_job(stuff))\n```\n\nExecuting `main()` will create 2 diagram files in the current working directory:\n\n* `validate-component.puml`: a PlantUML **component** diagram showing the interactions between `validate()` as the start, and `validate_is_not_none()` and `validate_is_in_database()` (and all their inner function calls, if any)\n* `do_something-sequence.puml`: a PlantUML **sequence** diagram showing the interactions between `do_something()` as the start, and `load_stuff()`, `process_job()` and `save_work()` (and all their inner function calls, if any)\n\nYou can then view these files either:\n\n* using an extension for your IDE: `jebbs.plantuml` for Codium / vsCode, `7017-plantuml-integration` for PyCharm\n* online at www.plantuml.com/plantuml/uml/\n\n### Customize the filename\n\nThe `export_file_path_tpl` attribute of the decorators allows you to define another file path and name:\n\n```python\n# in my_package/my_module.py\nfrom pydoctrace.doctrace import trace_to_component_puml, trace_to_sequence_puml\n\n# will create a 'validate-component.puml' file in the 'doc/my_package-my_module' folder\n# (pydoctrace will create the subdirectories if necessary)\n@trace_to_component_puml(\n    export_file_path_tpl='doc/my_package-my_module/validate-component.puml'\n)\ndef validate(parameter):\n    ...\n\n# will create a 'my_package.my_module/do_something-2024-02-27_19.16.09_473-sequence.puml' file\n@trace_to_sequence_puml(\n    export_file_path_tpl='${function_module}/${function_name}-${datetime_millis}-sequence.puml'\n)\ndef do_something(parameter):\n    ...\n```\n\nAs you can see, you can use the following template tags in the file name:\n\n* `${function_module}`: which resolves to `do_something.__module__`, `'my_package.my_module'` in the example\n* `${function_name}`: which resolves to `do_something.__name__`, `'do_something'` in the example\n* `${datetime_millis}`: the datetime in ISO-ish format compatible with filenames (Windows does not support `':'` in filenames).\nIf the traced function is called several times during the execution, including `${datetime_millis}` in the filename template will generate different files that won't overwrite themselves\n\n### Filter what is traced\n\nTo keep the generated diagrams useful and legible, you probably want to exclude some calls from the tracing (such as calls to `print(...)` or `json.load(...)`).\n`pydoctrace` comes with ready-to-use [presets](pydoctrace/callfilter/presets.py) that you can combine to filter out some calls you do not want to document:\n\n* `EXCLUDE_BUILTINS_PRESET`: prevents from tracing calls to [built-in functions](https://docs.python.org/3/library/functions.html) (`print`, `len`, `all`, `any`, etc.)\n* `EXCLUDE_STDLIB_PRESET`: prevents from tracing calls to all [the standard library modules](https://docs.python.org/3/library/index.html) (`csv`, `json`, `dataclass`, etc.)\n* `EXCLUDE_TESTS_PRESET`: prevents from tracing calls to functions belonging to the `tests`, `_pytest`, `pytest`, `unittest`, `doctest` modules.\nThis one is particularly useful when you want to generate some documentation from an automated tests\n* `EXCLUDE_DEPTH_BELOW_5_PRESET`: prevents from tracing any calls that involve a call stack of more than five levels after the traced function\n\nBy default, `EXCLUDE_STDLIB_PRESET` and `EXCLUDE_TESTS_PRESET` are activated.\nSpecify something else at the decorator level to change the call filtering.\n\n```python\nfrom pydoctrace.doctrace import trace_to_component_puml\nfrom pydoctrace.callfilter.presets import (\n    EXCLUDE_BUILTINS_PRESET,\n    EXCLUDE_CALL_DEPTH_PRESET_FACTORY,\n    EXCLUDE_DEPTH_BELOW_5_PRESET,\n    EXCLUDE_STDLIB_PRESET,\n    EXCLUDE_TESTS_PRESET,\n    TRACE_ALL_PRESET,\n)\n\n# an empty presets list traces everything\n@trace_to_component_puml(filter_presets=[])\ndef validate(parameter):\n    ...\n# or you can be explicit\nfrom pydoctrace.callfilter.presets import TRACE_ALL_PRESET\n@trace_to_component_puml(filter_presets=[TRACE_ALL_PRESET])\ndef validate(parameter):\n    ...\n\n# filter at a custom callstack level\nfrom pydoctrace.callfilter.presets import EXCLUDE_CALL_DEPTH_PRESET_FACTORY\nABOVE_3_PRESET = EXCLUDE_CALL_DEPTH_PRESET_FACTORY(3)\n@trace_to_component_puml(filter_presets=[ABOVE_3_PRESET])\ndef call():\n    call_1()\n\ndef call_1():\n    call_2()\n\ndef call_2():\n    call_3()\n\ndef call_3(): # will not be traced in the diagram\n    ...\n\n# documents the behavior of do_something without the noise of\n# the test framework nor the standard libray\nfrom my_package.my_module import do_something\nfrom pydoctrace.callfilter.presets import EXCLUDE_STDLIB_PRESET, EXCLUDE_TESTS_PRESET\n@trace_to_component_puml(filter_presets=[EXCLUDE_STDLIB_PRESET, EXCLUDE_TESTS_PRESET])\ndef test_do_something():\n    assert do_something('param') is not None\n\n# for test-generated documentation, you can also use the decorator programmatically\ndef test_do_something():\n    traceable_do_something = trace_to_component_puml(\n        export_file_path_tpl='doc/${function_module}/${function_name}-component.puml',\n        filter_presets=[EXCLUDE_STDLIB_PRESET, EXCLUDE_TESTS_PRESET, ABOVE_3_PRESET]\n    )(do_something)\n\n    assert traceable_do_something('param') is not None\n```\n\n## Purposes and mechanisms\n\nThe purpose of `pydoctrace` is to document the execution of some code to illustrate the behavior and the structure of the code base.\n\n- use one of the provided decorators (diagram type and format) to decorate the function whose execution you want to document\n- run your code (with a unit test, for instance): when the execution hits the decorated function, the execution is traced and a diagram is drawn to show how the functions are called, how the values are returned and how the errors are handled\n\n`pydoctrace` is a pure Python tool relying on no other 3rd-party library to work.\nThe project uses only development libraries for testing and documentation purposes.\n\n### Doc-tracing\n\nThis approach, which I called \"**doc-tracing**\" (tracing code execution for documentation purposes), is complementary of other approaches which generate documentation from static code analysis.\n**Static code analysis** reads the source code to detect and document data structures (classes, dataclasses, named tuples, enumeration, etc.), functions names and signatures (parameters name and types, returned value types).\n\nUseful as it is, static code analysis does not help much to understand how the code pieces work together; doc-tracing attempts to complement this approach by producing documentation while the code runs.\nSome use cases:\n\n- you start working on a legacy codebase: study the behavior of a particular function by temporarily tracing its executions\n- you finished a user story, document its implementation by tracing the execution of some **integration tests** (*\"given ... when ... then ...\"*) to illustrate how right cases and errors are handled.\n- generally, the sequence diagrams illustrate how the modules and functions interacts; and as such, **they help discussing architecture**\n- tracing code execution can also be useful when teaching computer programming to illustrate how algorithms work\n\n\n### How is the code execution traced?\n\nWhen a function decorated by `pydoctrace` is called:\n\n1. a context manager is created\n2. during which a **tracing function** is passed to [sys.settrace](https://docs.python.org/3/library/sys.html#sys.settrace), which is called when different events happen in the execution stack:\n    - when functions are called\n    - when values are returned\n    - when exceptions are raised or handled\n3. the sequence diagram is drawn and exported in a stream fashion (when possible) so that its memory footprint is minimal\n4. once the decorated function stops its execution, the tracing function is removed from the code execution\n\n⚠️ **Caveat**: `pydoctrace` uses the `sys.settrace` API, which is meant to be used by debuggers.\nTherefore, a warning is emitted when `pydoctrace` is used in a debug mode (and does not trace the decorated function anymore).\n\n# Tests\n\n```sh\n# directly with poetry\npoetry run pytest -v\n\n# in an activated virtual environment\npytest -v\n```\n\nCode coverage (with [missed branch statements](https://pytest-cov.readthedocs.io/en/latest/config.html?highlight=--cov-branch)):\n\n```sh\npoetry run pytest -v --cov=pydoctrace --cov-branch --cov-report term-missing --cov-fail-under 82\n```\n\n# Changelog\n\n* `0.3.0`: added configurable filters to exclude function calls from the tracing process (do not trace calls to the standard library modules and functions by default), templating for output file names.\nThank you [Philippe Tony](https://github.com/philippetony) for suggesting filtering out calls above a certain calls depth from the traced function.\nStarted integration tests\n* `0.2.0`: PlantUML exporter for component diagrams, added unit tests\n* `0.1.2`: added Github actions for the automated tests and interaction with pre-commit.ci for the code linting\n* `0.1.1`: [deleted release: wrong contents] added Github actions for the automated tests and interaction with pre-commit.ci for the code linting\n* `0.1.0`: ✨ first release, PlantUML exporter for sequence diagrams; diagram files are saved in the current working directory\n\n# Licence\n\nUnless stated otherwise, all works are licensed under the [MIT license](http://spdx.org/licenses/MIT.html), a copy of which is included [here](LICENSE).\n\n# Contributions\n\n* [Luc Sorel-Giffo](https://github.com/lucsorel)\n\n## Pull requests\n\nPull-requests are welcome and will be processed on a best-effort basis.\n\nPull requests must follow the guidelines enforced by the `pre-commit` hooks:\n\n- commit messages must follow the Angular conventions enforced by the `commitlint` hook\n- code formatting must follow the conventions enforced by the `isort` and `ruff-formatter` hooks\n- code linting should not detect code smells in your contributions, this is checked by the `ruff` hook\n\nWhen requesting a feature, please consider involving yourself in the review process once it is developed.\n\n## Code conventions\n\nThe code conventions are described and enforced by [pre-commit hooks](https://pre-commit.com/hooks.html) to maintain style and quality consistency across the code base.\nThe hooks are declared in the [.pre-commit-config.yaml](.pre-commit-config.yaml) file.\n\nWhen you contribute, set the git hooks (`pre-commit` and `commit-msg` types) on your development environment:\n\n```sh\npoetry run pre-commit install\n```\n\nBefore committing, you can check your changes manually with:\n\n```sh\n# put all your changes in the git staging area (or add the changes manually and skip this)\ngit add -A\n\n# run all hooks\npoetry run pre-commit run --all-files\n\n# run a specific hook\npoetry run pre-commit run ruff --all-files\n```\n\n### Commit messages\n\nPlease, follow the [conventions of the Angular team](https://github.com/angular/angular/blob/main/CONTRIBUTING.md#-commit-message-format) for commit messages.\nWhen merging your pull-request, the new version of the project will be derived from the messages.\n\nI use the [redjue.git-commit-plugin](https://marketplace.visualstudio.com/items?itemName=redjue.git-commit-plugin) codium/vsCode extension to help me write commit messages.\n\n### Code formatting\n\nThis project uses `isort` and `ruff` to format the code.\nThe guidelines are expressed in their respective sections in the [pyproject.toml](pyproject.toml) file.\n\n### Best practices\n\nThis project uses the `ruff` linter, which is configured in its section in the [pyproject.toml](pyproject.toml) file.\n\n# Similar tools and bibliography\n\n- https://stackoverflow.com/questions/45238329/it-is-possible-to-generate-sequence-diagram-from-python-code\n- https://github.com/gaogaotiantian/viztracer\n- https://9to5answer.com/how-to-generate-a-sequence-diagram-from-java-source-code\n- https://medium.com/javarevisited/how-to-generate-sequence-diagrams-in-intellij-e2bb7cec2b0b\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucsorel%2Fpydoctrace","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flucsorel%2Fpydoctrace","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucsorel%2Fpydoctrace/lists"}