{"id":46199993,"url":"https://github.com/mutating/printo","last_synced_at":"2026-03-17T01:09:49.445Z","repository":{"id":243208830,"uuid":"811267918","full_name":"mutating/printo","owner":"mutating","description":"Print objects with data beautifully","archived":false,"fork":false,"pushed_at":"2026-02-21T12:41:02.000Z","size":73,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-21T18:40:36.500Z","etag":null,"topics":["print","serialization"],"latest_commit_sha":null,"homepage":"","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/mutating.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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-06-06T09:10:46.000Z","updated_at":"2026-02-21T12:40:48.000Z","dependencies_parsed_at":"2024-06-07T11:10:42.586Z","dependency_job_id":"939cd2dc-ec19-4e59-a9e2-2350b66331da","html_url":"https://github.com/mutating/printo","commit_stats":null,"previous_names":["pomponchik/printo"],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/mutating/printo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutating%2Fprinto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutating%2Fprinto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutating%2Fprinto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutating%2Fprinto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mutating","download_url":"https://codeload.github.com/mutating/printo/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mutating%2Fprinto/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30032086,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-03T03:27:35.548Z","status":"ssl_error","status_checked_at":"2026-03-03T03:27:09.213Z","response_time":61,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5: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":["print","serialization"],"created_at":"2026-03-03T04:42:31.055Z","updated_at":"2026-03-17T01:09:49.440Z","avatar_url":"https://github.com/mutating.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdetails\u003e\n  \u003csummary\u003eⓘ\u003c/summary\u003e\n\n[![Downloads](https://static.pepy.tech/badge/printo/month)](https://pepy.tech/project/printo)\n[![Downloads](https://static.pepy.tech/badge/printo)](https://pepy.tech/project/printo)\n[![Coverage Status](https://coveralls.io/repos/github/mutating/printo/badge.svg?branch=main)](https://coveralls.io/github/mutating/printo?branch=main)\n[![Lines of code](https://sloc.xyz/github/mutating/printo/?category=code)](https://github.com/boyter/scc/)\n[![Hits-of-Code](https://hitsofcode.com/github/mutating/printo?branch=main)](https://hitsofcode.com/github/mutating/printo/view?branch=main)\n[![Test-Package](https://github.com/mutating/printo/actions/workflows/tests_and_coverage.yml/badge.svg)](https://github.com/mutating/printo/actions/workflows/tests_and_coverage.yml)\n[![Python versions](https://img.shields.io/pypi/pyversions/printo.svg)](https://pypi.python.org/pypi/printo)\n[![PyPI version](https://badge.fury.io/py/printo.svg)](https://badge.fury.io/py/printo)\n[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)\n[![DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/mutating/printo)\n\n\u003c/details\u003e\n\n![logo](https://raw.githubusercontent.com/mutating/printo/develop/docs/assets/logo_1.svg)\n\n\nPythonistas follow an implicit convention to create special [`__repr__`](https://docs.python.org/3/reference/datamodel.html#object.__repr__) methods that return text closely resembling the code used to construct the object. With this library, you can easily implement `__repr__` for your own classes.\n\n\n## Table of contents\n\n- [**Installation**](#installation)\n- [**Basic usage**](#basic-usage)\n- [**Filtering**](#filtering)\n- [**Custom display of objects**](#custom-display-of-objects)\n- [**Placeholders**](#placeholders)\n- [**Auto mode**](#auto-mode)\n\n\n## Installation\n\nYou can install [`printo`](https://pypi.org/project/printo) with `pip`:\n\n```bash\npip install printo\n```\n\nYou can also use [`instld`](https://github.com/pomponchik/instld) to quickly try this package and others without installing them.\n\n\n## Basic usage\n\nThe main function in this library is `describe_data_object`; it returns a string representing the initialization code for your object. There are three required positional parameters:\n\n- A class name.\n- A `list` or `tuple` of positional arguments.\n- A `dict` of keyword arguments, where the keys are the names of the arguments, and the values are arbitrary objects.\n\nHere's a simple example of how it works:\n\n```python\nfrom printo import describe_data_object\n\nprint(\n    describe_data_object(\n        'MyClassName',\n        (1, 2, 'some text'),\n        {'variable_name': 1, 'second_variable_name': 'kek'},\n    )\n)\n#\u003e MyClassName(1, 2, 'some text', variable_name=1, second_variable_name='kek')\n```\n\n\n## Filtering\n\nYou can prevent individual parameters from being displayed. To do this, pass a `dict` to the `filters` parameter. The keys identify arguments by index or name. The values are functions that return a `bool` — `True` keeps the argument and `False` skips it:\n\n```python\nprint(\n    describe_data_object(\n        'MyClassName',\n        (1, 2, 'some text'),\n        {'variable_name': 1, 'second_variable_name': 'kek'},\n        filters={1: lambda x: False if x == 2 else True, 'second_variable_name': lambda x: False},\n    )\n)\n#\u003e MyClassName(1, 'some text', variable_name=1)\n```\n\nYou can also use the provided `not_none` filter to automatically exclude `None` values:\n\n```python\nfrom printo import not_none\n\nprint(\n    describe_data_object(\n        'MyClassName',\n        (1, None),\n        {},\n        filters={1: not_none},\n    )\n)\n#\u003e MyClassName(1)\n```\n\n\n## Custom display of objects\n\nBy default, all argument values are represented in the same way as the standard [`repr`](https://docs.python.org/3/library/functions.html#repr) function would show them. There are only three exceptions:\n\n- For regular functions, the function name is displayed.\n- For classes, the class name is displayed.\n- For lambda functions, the complete source code is displayed. However, if a single line of source code contains more than one lambda function, only the `λ` symbol is displayed (this is a technical limitation of source code reflection in Python).\n\nYou can provide a custom serialization function for each argument value via the `serializer` parameter:\n\n```python\nprint(\n    describe_data_object(\n        'MyClassName',\n        (1, 2, 'lol'),\n        {'variable_name': 1, 'second_variable_name': 'kek'},\n        serializer=lambda x: repr(x * 2),\n    )\n)\n#\u003e MyClassName(2, 4, 'lollol', variable_name=2, second_variable_name='kekkek')\n```\n\n\n## Placeholders\n\nFor individual parameters, you can pass arbitrary strings that will be displayed instead of the actual values. This can be useful, for example, to hide the values of sensitive fields when serializing objects.\n\nPass a `dict` to the `placeholders` parameter, where the keys are argument names (for keyword arguments) or indices (for positional parameters, zero-indexed), and the values are strings:\n\n```python\nprint(\n    describe_data_object(\n        'MySuperClass',\n        (1, 2, 'lol'),\n        {'variable_name': 1, 'second_variable_name': 'kek'},\n        placeholders={\n            1: '***',\n            'variable_name': '***',\n        },\n    )\n)\n#\u003e MySuperClass(1, ***, 'lol', variable_name=***, second_variable_name='kek')\n```\n\n\u003e 🤓 If you set a placeholder for a parameter, the [custom serializer](#custom-display-of-objects) will not be applied to it.\n\n\n## Auto mode\n\n\u003e ⚠️ Auto mode is currently experimental, so there may be some bugs.\n\nYou can remove the boilerplate code by using the `@repred` decorator for your class:\n\n```python\nfrom printo import repred\n\n@repred\nclass SomeClass:\n    def __init__(self, a, b, c, *args, **kwargs):\n        self.a = a\n        self.b = b\n        self.c = c\n        self.args = args\n        self.kwargs = kwargs\n\nprint(SomeClass(1, 2, 3))\n#\u003e SomeClass(1, 2, 3)\nprint(SomeClass(1, 2, 3, 4, 5))\n#\u003e SomeClass(1, 2, 3, 4, 5)\nprint(SomeClass(1, 2, 3, 4, 5, d=lambda x: x))\n#\u003e SomeClass(1, 2, 3, 4, 5, d=lambda x: x)\n```\n\nHow does it work? Behind the scenes, the decorator uses AST analysis to generate code. The decorator attempts to determine which arguments passed to `__init__` are stored in which attributes. In other words, it looks for direct assignments of the form `self.a = a` in the `__init__` method.\n\nIf there is no *direct assignment* of a specific argument, an exception will be raised:\n\n```python\n@repred\nclass SomeClass:\n    def __init__(self, a):\n        ...\n\n#\u003e ...\n#\u003e printo.errors.ParameterMappingNotFoundError: No internal object property or custom getter was found for the parameter a.\n```\n\n\u003e ↑ The error occurs when the class is decorated.\n\nIf, for some reason, you are unable to specify this mapping in the body of the `__init__` method, you can pass a function for a specific parameter that will extract it:\n\n```python\n@repred(getters={'a': lambda x: x.a})\nclass SomeClass:\n    def __init__(self, a):\n        self.a = self.convert_a(a)\n\n    def convert_a(self, a):\n        return a\n\nprint(SomeClass(123))\n#\u003e SomeClass(a=123)\n```\n\nBy default, `@repred` displays all arguments as keywords in most cases. However, you can pass the `prefer_positional` argument to the decorator, which will cause it to prefer omitting argument names in such cases:\n\n```python\n@repred\nclass Class1:\n    def __init__(self, a, b):\n        self.a = a\n        self.b = b\n\n@repred(prefer_positional=True)\nclass Class2:\n    def __init__(self, a, b):\n        self.a = a\n        self.b = b\n\nprint(Class1(123, 456))\n#\u003e Class1(a=123, b=456)\nprint(Class2(123, 456))\n#\u003e Class2(123, 456)\n```\n\nYou can also choose to display only certain parameters as positional:\n\n```python\n@repred(positionals=['a'])\nclass SomeClass:\n    def __init__(self, a, b):\n        self.a = a\n        self.b = b\n\nprint(SomeClass(123, 456))\n#\u003e SomeClass(123, b=456)\n```\n\nIf you want to prevent certain `__init__` parameters from being displayed, you can add their names to the ignore list:\n\n```python\n@repred(ignore=['a'])\nclass SomeClass:\n    def __init__(self, a, b):\n        self.a = a\n        self.b = b\n\nprint(SomeClass(123, 456))\n#\u003e SomeClass(b=456)\n```\n\nYou can also add value-based filters for individual arguments by passing a `dict` of filters, similar to how it works in [manual mode](#filtering):\n\n```python\nfrom printo import not_none\n\n@repred(filters={'a': not_none})\nclass SomeClass:\n    def __init__(self, a, b):\n        self.a = a\n        self.b = b\n\nprint(SomeClass(None, None))\n#\u003e SomeClass(b=None)\nprint(SomeClass(123, 456))\n#\u003e SomeClass(a=123, b=456)\n```\n\nBy default, the class name is displayed based on its `__name__` attribute, but you can configure it to use the `__qualname__` attribute instead:\n\n```python\ndef function():\n    @repred(qualname=True)\n    class SomeClass:\n        def __init__(self, a, b):\n            self.a = a\n            self.b = b\n    return SomeClass\n\nprint(function()(123, 456))\n#\u003e function.\u003clocals\u003e.SomeClass(a=123, b=456)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmutating%2Fprinto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmutating%2Fprinto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmutating%2Fprinto/lists"}