Skip to main content

Generate PlantUML class diagrams to document your Python application.

Project description

Python logo PlantUML logo

Python to PlantUML

Generate PlantUML class diagrams to document your Python application.

pre-commit.ci status

py2puml uses pre-commit hooks and pre-commit.ci Continuous Integration to enforce commit messages, code formatting and linting for quality and consistency sake. See the code conventions section if you would like to contribute to the project.

Installation

py2puml is a command-line interface (CLI) documentation tool that can be installed as a dependency of your project, or installed globally on your system, or even run in an isolated way.

Install as a project dependency

Install py2puml from PyPI with your favorite installation tool:

pip install py2puml

uv add py2puml

poetry add py2puml

pipenv install py2puml

Run as an isolated binary

Uv can download and install py2puml on your system and run it in an isolated way (no influence on your other Python tools):

uvx --isolated py2puml --help

Usage

The primary purpose of py2puml is to document domain models as PlantUML class diagrams, it focuses on data structures attributes and relationships (inheritance and composition/association). Documenting methods may come later.

Once py2puml is installed, an eponymous CLI is available in your environment shell.

Generate documentation in the standard output

Give py2puml a package (a folder) or a module (a .py file) to inspect and it will generate the PlantUML diagram either in the standard output or in a file path:

To document the domain model used by py2puml to model data structures:

# at the root of the py2puml project
py2puml --path py2puml/domain
# short-flag version:
py2puml -p py2puml/domain

This outputs the following PlantUML content:

@startuml py2puml.domain
!pragma useIntermediatePackages false

class py2puml.domain.umlitem.UmlItem {
  name: str
  fqn: str
}
class py2puml.domain.umlrelation.UmlRelation {
  source_fqn: str
  target_fqn: str
  type: RelType
}
class py2puml.domain.inspection.Inspection {
  items_by_fqn: Any
  relations: Any
}
class py2puml.domain.umlclass.UmlAttribute {
  name: str
  type: str
  static: bool
}
class py2puml.domain.umlclass.UmlClass {
  attributes: List[UmlAttribute]
  is_abstract: bool
}
class py2puml.domain.umlenum.Member {
  name: str
  value: str
}
class py2puml.domain.umlenum.UmlEnum {
  members: List[Member]
}
enum py2puml.domain.umlrelation.RelType {
  COMPOSITION: * {static}
  INHERITANCE: <| {static}
}
py2puml.domain.umlrelation.UmlRelation *-- py2puml.domain.umlrelation.RelType
py2puml.domain.umlclass.UmlClass *-- py2puml.domain.umlclass.UmlAttribute
py2puml.domain.umlitem.UmlItem <|-- py2puml.domain.umlclass.UmlClass
py2puml.domain.umlenum.UmlEnum *-- py2puml.domain.umlenum.Member
py2puml.domain.umlitem.UmlItem <|-- py2puml.domain.umlenum.UmlEnum
footer Generated by //py2puml//
@enduml

Using PlantUML (online or with IDE extensions) renders this content as follows:

py2puml domain UML Diagram

Pipe the diagram in a local PlantUML server

Pipe the result of the CLI with a PlantUML server for instantaneous documentation (rendered by ImageMagick):

# runs a local PlantUML server from a docker container:
docker run -d --rm -p 1234:8080 --name plantumlserver plantuml/plantuml-server:jetty

py2puml -p py2puml/domain | curl -X POST --data-binary @- http://localhost:1234/svg/ --output - | display

# stop the container when you don't need it anymore, restart it later
docker stop plantumlserver
docker start plantumlserver

Generate documentation in a file

py2puml --path py2puml/domain --output-file py2puml-domain.puml
# short-flag version:
py2puml -p py2puml/domain -o py2puml-domain.puml

Generate documentation for a specific module

py2puml --path py2puml/domain/umlitem.py
@startuml py2puml.domain.umlitem
!pragma useIntermediatePackages false

class py2puml.domain.umlitem.UmlItem {
  name: str
  fqn: str
}
footer Generated by //py2puml//
@enduml

Generate documentation for a project with a src folder

Use the --path flag to indicate the path to the root namespace of the project and the --namespace flag to indicate that the "src" part should be ignored:

py2puml -p src/project -n project

Note: py2puml won't handle automatically the "src" part if it is in the middle of the path to inspect.

Use py2puml outside the namespace root

By default, py2puml derives the Python namespace from the given path, assuming the command is called from the root namespace:

py2puml --path py2puml/domain
# is equivalent to:
py2puml --path py2puml/domain --namespace py2puml.domain
# short-flag version
py2puml -p py2puml/domain -n py2puml.domain

But sometimes your shell may be positionned out of the namespace folder, or within it. In such cases, it is necessary to specify the namespace of the domain to inspect so that py2puml can inspect it properly and follow the imports in the inspected package or modules:

# from your home folder:
# - for a package
py2puml --path repositories/py2puml/py2puml/domain --namespace py2puml.domain
# -> py2puml will move down its "inspection working directory" to repositories/py2puml

# - for a module
py2puml -p repositories/py2puml/py2puml/domain/item.py -n py2puml.domain.umlitem

# from a sub-package of the project to inspect (in py2puml/domain)
# - for a package
py2puml --path . --namespace py2puml.domain
# -> py2puml will move its "inspection working directory" up 2 folders in order to be at the root namespace
# - for a module
py2puml -p umlitem.py -n py2puml.domain.umlitem

Help commands

For a full overview of the CLI, run:

# documents the available flags and their description
py2puml --help

# displays the installed version
py2puml --version
# -> py2puml 0.11.0

Python API

To programatically create the diagram of the py2puml domain classes, import the py2puml function in your script:

from py2puml.py2puml import py2puml

if __name__ == '__main__':
    # 1. outputs the PlantUML content in the terminal
    print(''.join(py2puml('py2puml/domain', 'py2puml.domain')))

    # 2. or writes the PlantUML content in a file
    with open('py2puml/py2puml.domain.puml', 'w', encoding='utf8') as puml_file:
        puml_file.writelines(py2puml('py2puml/domain', 'py2puml.domain'))

How it works

py2puml internally uses code inspection (also called reflexion in other programming languages) and abstract tree parsing to retrieve relevant information.

Features

From a given path corresponding to a folder containing Python code, py2puml inspects each Python module and generates a PlantUML diagram from the definitions of various data structures using:

  • inspection and type annotations to detect:

    • static class attributes and dataclass fields
    • fields of namedtuples
    • members of enumerations
    • composition and inheritance relationships. The detection of composition relationships relies on type annotations only, assigned values or expressions are never evaluated to prevent unwanted side-effects
  • parsing abstract syntax trees to detect the instance attributes defined in __init__ constructors

py2puml outputs diagrams in PlantUML syntax, which can be:

from py2puml.asserts import assert_py2puml_command_args

def test_assert_domain_documentation():
    assert_py2puml_command_args('-p py2puml/domain', DOCUMENTATION_PATH / 'py2puml.domain.puml')
    # temporarily add the `overwrite_expected_output=True` argument to update the file containing the expected contents
    assert_py2puml_command_args('-p py2puml/domain', DOCUMENTATION_PATH / 'py2puml.domain.puml', overwrite_expected_output=True)
  • generated and hosted along other code documentation (better option: generated documentation should not be versioned with the codebase)

If you like tools related with PlantUML, you may also be interested in this lucsorel/plantuml-file-loader project: a webpack loader which converts PlantUML files into images during the webpack processing (useful to include PlantUML diagrams in your slides with RevealJS or RemarkJS).

Changelog and versions

See CHANGELOG.md.

Licence

Unless stated otherwise all works are licensed under the MIT license, a copy of which is included here.

Contributions

I'm thankful to all the people who have contributed to this project:

Pull requests and code conventions

Pull-requests are welcome and will be processed on a best-effort basis.

Pull requests must follow the guidelines enforced by pre-commit hooks (see the .pre-commit-config.yaml configuration file):

  • commit messages must follow the the conventional-commit rules enforced by the commitlint hook
  • code formatting must follow the conventions enforced by the isort and ruff-format hooks
  • code linting should not detect code smells in your contributions, this is checked by the ruff-check hook

Please also follow the contributing guide to ease your contribution.

Pre-commit hooks

Activate the git hooks

Set the git hooks (pre-commit and commit-msg types):

uv run pre-commit install

Run the hooks locally

Before committing, you can check your changes with:

# all hooks on the staged files
uv run pre-commit run

# all hooks on all files
uv run pre-commit run --all-files

# a specific hook on all files
uv run pre-commit run ruff-format --all-files

Code formatting

This project uses isort and ruff-format to format the code. The guidelines are expressed in their respective sections in the pyproject.toml file.

Static analysis and best practices

This project uses the ruff-check linter, which is configured in its section in the pyproject.toml file.

Commit messages

Please, follow the conventional commit guidelines for commit messages. When merging your pull-request, the new version of the project will be derived from the commit messages.

Tests

Add automated tests on your contributions, which can be run with the vollowing commands:

# directly with poetry
uv run pytest -v

# in a virtual environment
python3 -m pytest -v

# a specific test suite file or a given test
uv run pytest -v tests/py2puml/test_cli_controller.py
uv run pytest -v -k test_controller_stdout_and_in_file

Code coverage (with missed branch statements):

uv run pytest -v --cov=src/py2puml --cov-branch --cov-report term-missing --cov-fail-under 93

Current limitations

  • regarding inspection

    • type hinting is optional when writing Python code and discarded when it is executed, as mentionned in the typing official documentation. The quality of the diagram output by py2puml depends on the reliability of the type annotations

    The python runtime does not enforce function and variable type annotations. They can be used by third party tools such as type checkers, IDEs, linters, etc.

    • inspection implies that the python interpreter parses your .py files, make sure that your executable code is guarded by if __name__ == '__main__': clauses so that it won't be executed during a py2puml inspection
  • regarding the detection of instance attributes with AST parsing:

    • only constructors are visited, attributes assigned in other functions won't be documented
    • attribute types are inferred from type annotations:
      • of the attribute itself
      • of the variable assigned to the attribute: a signature parameter or a locale variable
      • to avoid side-effects, no code is executed nor interpreted

Alternatives

If py2puml does not meet your needs (suggestions and pull-requests are welcome), you can have a look at these projects which follow other approaches (AST, linting, modeling):

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

py2puml-0.11.0.tar.gz (20.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

py2puml-0.11.0-py3-none-any.whl (27.1 kB view details)

Uploaded Python 3

File details

Details for the file py2puml-0.11.0.tar.gz.

File metadata

  • Download URL: py2puml-0.11.0.tar.gz
  • Upload date:
  • Size: 20.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for py2puml-0.11.0.tar.gz
Algorithm Hash digest
SHA256 a08ce1988e163ebcb9e0f2fbc6e6b1251e0abd1f9cf1f2e32a32744cb85a99ea
MD5 472bc4612f514f422a68bc8a8312ea68
BLAKE2b-256 876551eb076157c34ca7686726c45a92da1bc2cb72e8d95c292f4cb86f0982ab

See more details on using hashes here.

Provenance

The following attestation bundles were made for py2puml-0.11.0.tar.gz:

Publisher: release.yml on lucsorel/py2puml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file py2puml-0.11.0-py3-none-any.whl.

File metadata

  • Download URL: py2puml-0.11.0-py3-none-any.whl
  • Upload date:
  • Size: 27.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for py2puml-0.11.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a64689d149db7d6d3253a719be4050a2b4e2802758481bd0ca1da2400f8d9f49
MD5 52a8f86d46c76e4d7ca012904539ebed
BLAKE2b-256 1e8715d07aabfbd2ff1e6836aa7871c675a1b709d85a1cb46342622efefebdf0

See more details on using hashes here.

Provenance

The following attestation bundles were made for py2puml-0.11.0-py3-none-any.whl:

Publisher: release.yml on lucsorel/py2puml

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page