{"id":49795553,"url":"https://github.com/varkenvarken/gentrypy","last_synced_at":"2026-05-12T09:31:15.899Z","repository":{"id":305015979,"uuid":"1016598391","full_name":"varkenvarken/gentrypy","owner":"varkenvarken","description":"A generic Tree implementation and Visitor class with clear visualization in markdown/mermaid","archived":false,"fork":false,"pushed_at":"2025-07-22T19:17:53.000Z","size":126,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-02T06:55:49.215Z","etag":null,"topics":["mermaid","tree","visitor"],"latest_commit_sha":null,"homepage":"https://varkenvarken.github.io/gentrypy/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/varkenvarken.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,"zenodo":null}},"created_at":"2025-07-09T08:45:48.000Z","updated_at":"2025-07-22T19:17:57.000Z","dependencies_parsed_at":"2025-07-17T23:38:17.802Z","dependency_job_id":"eab9ae34-cd3d-4771-8b9d-25798460e322","html_url":"https://github.com/varkenvarken/gentrypy","commit_stats":null,"previous_names":["varkenvarken/gentrypy"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/varkenvarken/gentrypy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varkenvarken%2Fgentrypy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varkenvarken%2Fgentrypy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varkenvarken%2Fgentrypy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varkenvarken%2Fgentrypy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/varkenvarken","download_url":"https://codeload.github.com/varkenvarken/gentrypy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/varkenvarken%2Fgentrypy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32932290,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-12T09:19:52.626Z","status":"ssl_error","status_checked_at":"2026-05-12T09:17:33.438Z","response_time":102,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["mermaid","tree","visitor"],"created_at":"2026-05-12T09:31:13.341Z","updated_at":"2026-05-12T09:31:15.894Z","avatar_url":"https://github.com/varkenvarken.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gentry\n![Python](https://raw.githubusercontent.com/varkenvarken/gentrypy/refs/heads/main/python.svg) [![Test Status](https://github.com/varkenvarken/gentrypy/actions/workflows/test_all.yml/badge.svg)](https://github.com/varkenvarken/gentrypy/actions/workflows/test_all.yml) ![Coverage](https://raw.githubusercontent.com/varkenvarken/gentrypy/refs/heads/main/coverage.svg) ![PyPI - Version](https://raw.githubusercontent.com/varkenvarken/gentrypy/refs/heads/main/pypi.svg)\n\n![gentry  logo](docs/logo.svg)\n\n**gentry** is a Python package for representing and manipulating generic tree structures with support for extensible child groups, visitor patterns, and convenient representations in the form of [Markdown](https://www.markdownguide.org/) / [Mermaid](https://mermaid.js.org/syntax/flowchart.html) diagrams.\n\nThe name **gentry** is a weak pun on 'generic tree' that is too lame to explain 😄 (See [Wikipedia](https://en.wikipedia.org/wiki/Gentry))\n\n----\nTable of contents\n- [gentry](#gentry)\n  - [Features](#features)\n  - [Example Usage](#example-usage)\n  - [Installation](#installation)\n  - [License](#license)\n  - [Documentation](#documentation)\n  - [Project Structure](#project-structure)\n  - [Contributions](#contributions)\n----\n\n## Features\n\nThe **gentry** package centers around a core [`gentry.tree.Tree`](gentry/tree.py) class and a reusable [`gentry.tree.Vistor`](gentry/tree.py) class.\nA mixin class [`gentry.mermaid.Mermaid`](gentry/mermaid.py) is also provided.\n\nThe `Tree` class implements a node in a tree. It features\n\n- a label\n  \n  that identifies a node, but that may be empty\n\n- any number of children, organized in groups\n  \n  those children are implemented as a dict[str,list] and can be accessed via the `_children` atrribute, that will be initialized when\n  instancing a node, but those groups of children will normally be accessed via group attributes (see next point). The `Visitor` class will iterate over all objects in `_children`, regardless which group they are in.\n\n- group attributes\n  \n  a particular group of children can be accessed by using there name as an attribute to a node,\n  by defining a class variable `_groups` with a set of group names when inheriting from `Tree`.\n  See the section [Example Usage](#example-usage) for some examples.\n\n- a `properties` attribute\n\n  that should be a dict[str,Any] and can hold node specific arbitrary information.\n\nThe `Mermaid` mixin class can be added to the base classes when inheriting from `Tree`. \n\nIt will add a `__str__()` method that will return Markdown containing a Mermaid block that presents the node and\nall its children a graph.\n\nIt is a cooperative mixin, i.e. it depends on the existance of the `label`, `_children`, and `properties` attributes,\nand its `__str__()` method will recursively create nodes and connectors for a `Tree` node and all its children. It will\nput each group of children in its own frame. An example:\n\n```mermaid\ngraph TD\n        classDef keyword fill:#dFd\n        classDef function fill:#bff,font-size:20px,stroke-width:2px,font-weight:bold\n        classDef loop fill:#fdd\n        classDef operator font-weight:bold,font-size:20px\n        classDef constant color:#3a3\n        classDef variable color:#a33\n        classDef subgraph_even fill:#eff\n        classDef subgraph_odd fill:#eee\n\n    subgraph0:::subgraph_odd\n    Family0@{shape: rounded, label: \"The Andersons\"} --\u003e subgraph0\n    subgraph subgraph0[matriarch]\n            direction TB\n\n        subgraph2:::subgraph_even\n        GrandMother1:::function@{shape: braces, label: \"Granny\"} --\u003e subgraph2\n        subgraph subgraph2[children]\n                direction TB\n\n            subgraph4:::subgraph_odd\n            Mother2@{shape: rounded, label: \"Anna\"} --\u003e subgraph4\n            subgraph subgraph4[girls]\n                    direction TB\n\n            Child6@{shape: rounded, label: \"Alice\\n()\"}\n            Child7@{shape: rounded, label: \"Cherryl\\n(chess master=ELO 2235)\"}\n            end\n            subgraph7:::subgraph_odd\n            Mother2@{shape: rounded, label: \"Anna\"} --\u003e subgraph7\n            subgraph subgraph7[boys]\n                    direction TB\n\n            Child9@{shape: rounded, label: \"Bob\\n()\"}\n            Child10@{shape: rounded, label: \"Dick\\n()\"}\n            end\n            subgraph11:::subgraph_odd\n            Mother2@{shape: rounded, label: \"Beatrice\"} --\u003e subgraph11\n            subgraph subgraph11[girls]\n                    direction TB\n\n            Child13@{shape: rounded, label: \"Ellen\\n()\"}\n            Child14@{shape: rounded, label: \"Gladys\\n(drivers license=2023,\\nnose piercing=2024)\"}\n            end\n            subgraph14:::subgraph_odd\n            Mother2@{shape: rounded, label: \"Beatrice\"} --\u003e subgraph14\n            subgraph subgraph14[boys]\n                    direction TB\n\n            Child16@{shape: rounded, label: \"Fergal\\n()\"}\n            Child17@{shape: rounded, label: \"Hank\\n()\"}\n            end\n        end\n    end\n```\n\nIndividual nodes can be given distict shapes and styles, and the alternating colors of the frames can be configured as well.\nSee [Example Usage](#example-usage) for more.\n\nFinally we have a `Visitor` class that can be inherited from to implement a visitor pattern. It can be given a `Tree` node and its children will be iterated over in depth-first fashion, after which the type of the node will be used to find a specific vistor method for that node type (a tree can have nodes of different types as long as the inherit from `Tree`), or default to a general visit method.\n\n## Example Usage\n\nThis is the code that was used to generate the example diagram.\nIt can be found in [__main__.py](gentry/__main__.py)\n\nIt creates different subclasses of `Tree`, each with their own way of grouping children: a `Person` in general may simple group all their children using the attribute `children`, while `Mother`s are the exceptions and have different attributes for `boys` and `girls`.\n\nA `GrandMother` also has a different node shape when rendered as a Mermaid graph, and a different style (Styles reflect to original purpose of the **gentry** package, i.e. Abstract Syntax Trees, so the have names like Shape.function)\n\nFor a `Child` we enable the display of properties in a Mermaid graph, but these are only set on some children (Cherryl and Gladys)\n\nWe also define a `FamilyCount` class that inherits from the `Count` class (provided in the package) that in turn inherits from the base `Visitor` class. It simply counts all nodes in a tree, but because we don't want to count a `Family` node as we would a `Person`, we provide a specialized `_do_count_Family()` method that simply returns 0.\n\nThe Mermaid diagram is produced in the final print statement. That's a one lines because it is implement in the `__str__()` method.\n\n```python\nfrom .tree import Tree, Count\nfrom .mermaid import Mermaid, Shape, Style\n\nclass Family(Tree, Mermaid):...\n\nclass Person(Tree, Mermaid):\n    _groups = { \"children\"}\n    \nclass GrandMother(Person):\n    _style = Style.function\n    _shape = Shape.braces\n\nclass Mother(Person):\n    _groups = {\"girls\", \"boys\"}\n \nclass Child(Person):\n    _include_properties = True\n\nclass FamilyCount(Count):\n    \"\"\"\n    We only count real persons, so we define a specialized member function\n    for any Family node that returns 0 and will not be counted.\n    \"\"\"\n    def _do_count_Family(self, tree:Family):\n        return 0\n\n# define a few children  \nc1 = Child(\"Alice\")\nc2 = Child(\"Bob\")\nc3 = Child(\"Cherryl\", properties={\"chess master\": \"ELO 2235\"})\nc4 = Child(\"Dick\")\nc5 = Child(\"Ellen\")\nc6 = Child(\"Fergal\")\nc7 = Child(\"Gladys\", properties={\"drivers license\": 2023, \"nose piercing\": 2024})\nc8 = Child(\"Hank\")\n\n# Mothers have girls and boys attributes defined, so those can be assigned directly\nm1 = Mother(\"Anna\")\nm1.girls = [c1,c3]\nm1.boys = [c2,c4]\n\nm2 = Mother(\"Beatrice\")\nm2.girls.append(c5)     # under the hood they are all items in a defaultdict(list) so we can append directly\nm2.girls.append(c7)\nm2.boys = [c6,c8]\n\n# Grandmothers do not make the distinction\ng1 = GrandMother(\"Granny\")\ng1.children = [m1, m2]\n# so this would fail:\n# g1.girls = [m1, m2]\n\n# Family does not have any direct access attributes defined, but anything\n# that is passed as the children argument (and can be converted to a defaultdict(list))\n# will be added to the _children attribute, so will still be automatically discovered\n# by Visitor derived classes.\nf1 = Family(\"The Andersons\", children={\"matriarch\":[g1]})\n\ncounter = FamilyCount(f1, strict=False)\n\nassert counter.count() == 11\n\nprint(f1)\n```\n\n## Installation\n\n```sh\npip install gentry\n```\n\n## License\n\nThis project is licensed under the [GNU GPLv3](LICENSE), with the exception of any artwork, including the logo, which are licensed under [CC BY-NC-ND 4.0.](https://creativecommons.org/licenses/by-nc-nd/4.0/)\n\n\n## Documentation\n\nThe API documentation is a work in progress and can be found on \nhttps://varkenvarken.github.io/gentrypy/\n\n## Project Structure\n\n- [`gentry/tree.py`](gentry/tree.py): Core tree and visitor classes\n- [`gentry/mermaid.py`](gentry/mermaid.py): Mermaid/Markdown mixin\n- [`tests/`](tests/): Test suite, will be discovered automatically by VScode if [configured correctly](.vscode/settings.json), but can also be run from the command line with `pytest tests --cov=gentry --cov-report=xml`\n\nThe repository is a reflection of my Vscode environment and contains:\n\n- [A Dev container configuration](.devcontainer/devcontainer.json) based on\n- [A Dockerfile](./Dockerfile) that builds a Python 3.13.5 enviroment from source (because that is not yet provided as a feature), along with the necessary [dependencies](./requirements.txt) to test and record test coverage.\n\nTo package the **gentry** package [setup.py](./setup.py) is provided, which is used by a [GitHub action](.github/workflows/publish_package.yml) to package and publish to [pypi](https://pypi.org/project/gentry/) when a new release is tagged. \n\n## Contributions\n\nContributions are wellcome, either by suggesting improvements in [Issues](https://github.com/varkenvarken/gentrypy/issues), or even better, submitting a PR. \n\nPlease make sure that any PR:\n\n- is suitable for inclusion under a GPLv3 license\n- passes all tests\n- provides new unit tests if new functionality is implemented\n- contains only code that is formatted by ruff (with the [default settings](https://docs.astral.sh/ruff/configuration/), import sorting enabled)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvarkenvarken%2Fgentrypy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvarkenvarken%2Fgentrypy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvarkenvarken%2Fgentrypy/lists"}