{"id":13815129,"url":"https://github.com/mimseyedi/pymg","last_synced_at":"2026-01-16T12:57:16.850Z","repository":{"id":161245219,"uuid":"635934626","full_name":"mimseyedi/pymg","owner":"mimseyedi","description":"pymg is a CLI that can interpret Python files by the Python interpreter and display the error message in a more readable way if an exception occurs.","archived":false,"fork":false,"pushed_at":"2023-08-28T15:09:23.000Z","size":3427,"stargazers_count":86,"open_issues_count":0,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-11-29T15:23:40.601Z","etag":null,"topics":["cli","debugger","debugging-tool","exception","python"],"latest_commit_sha":null,"homepage":"","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/mimseyedi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.txt","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}},"created_at":"2023-05-03T19:17:14.000Z","updated_at":"2024-09-09T08:55:50.000Z","dependencies_parsed_at":"2024-01-15T13:35:36.596Z","dependency_job_id":null,"html_url":"https://github.com/mimseyedi/pymg","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mimseyedi%2Fpymg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mimseyedi%2Fpymg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mimseyedi%2Fpymg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mimseyedi%2Fpymg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mimseyedi","download_url":"https://codeload.github.com/mimseyedi/pymg/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227971891,"owners_count":17849421,"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":["cli","debugger","debugging-tool","exception","python"],"created_at":"2024-08-04T04:03:00.081Z","updated_at":"2024-12-03T18:18:05.446Z","avatar_url":"https://github.com/mimseyedi.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"[![pypi](https://img.shields.io/pypi/v/pymg.svg)](https://pypi.org/project/pymg/) [![support-version](https://img.shields.io/pypi/pyversions/pymg)](https://img.shields.io/pypi/pyversions/pymg) [![license](https://img.shields.io/github/license/mimseyedi/pymg.svg)](https://github.com/mimseyedi/pymg/blob/master/LICENSE) [![commit](https://img.shields.io/github/last-commit/mimseyedi/pymg)](https://github.com/mimseyedi/pymg/commits/master)\n\n\n![img1](https://raw.githubusercontent.com/mimseyedi/pymg/master/docs/images/pymg-poster.png)\n\n\n## Table of Contents: \u003ca class=\"anchor\" id=\"contents\"\u003e\u003c/a\u003e\n* [Introduction](#intro)\n* [Installation](#install)\n* [How to use pymg?](#usage)\n  * [Using the --help option](#help)\n  * [Interpret the file without options](#no_option)\n  * [Syntax validation using the --syntax option](#syntax)\n  * [Combination of options](#combine)\n    * [Combination of --trace and --inner options with --locals](#T_i_L)\n  * [Using the --recent option](#recent)\n  * [Search for a solution with the --search option](#search)\n  * [Write the output to the file with the --output option](#custom_excepthook)\n* [How does pymg work?](#work)\n  * [How does pymg check syntax?](#syntaxx)\n  * [Prioritizing options](#pri_options)\n  * [Recipe file](#recipe)\n  * [Source information file](#source_info)\n  * [Mirror file](#mirror)\n  * [Interpret the mirror file](#interpret_mirror)\n  * [Customized excepthook](#customexcepthook)\n* [Bugs/Requests](#cont)\n* [License](#license)\n\n\n## Introduction \u003ca class=\"anchor\" id=\"intro\"\u003e\u003c/a\u003e\n **pymg** is a **CLI** tool that can **interpret** Python files by the **Python interpreter** and display the error message in a **more readable** way if an **exception** occurs.\n\n\n## Installation \u003ca class=\"anchor\" id=\"install\"\u003e\u003c/a\u003e\nYou can use **pip** to install:\n```\npython3 -m pip install pymg\n```\n\nAnd also to upgrade:\n```\npython3 -m pip install --upgrade pymg\n```\n\n##  \u003ca class=\"anchor\" id=\"usage\"\u003e\u003c/a\u003e\n![img1](https://raw.githubusercontent.com/mimseyedi/pymg/master/docs/images/how-to-use-pymg.png)\n\n## Using the --help option \u003ca class=\"anchor\" id=\"help\"\u003e\u003c/a\u003e\nWith the help of the (-h, --help) option, you can easily see how to use pymg and the explanations of the options.\n\n```\nUsage: pymg [OPTIONS] [PYTHON_FILE]...\n\n  pymg is a CLI tool that can interpret Python files by the Python interpreter\n  and display the error message in a more readable way if an exception occurs.\n\nOptions:\n  -x, --syntax       It checks the syntax of the selected Python file. If\n                     there is a syntax problem, an error message will be\n                     displayed, otherwise 'INTACT' will be displayed.\n  -t, --type         The type of exception that occurred will be displayed.\n  -m, --message      The message of exception that occurred will be displayed.\n  -f, --file         The full path of the Python file where the exception\n                     occurred will be displayed.\n  -s, --scope        The scope where the exception occurred will be displayed.\n  -l, --line         The line number that caused the exception will be\n                     displayed.\n  -c, --code         The code that caused the exception will be displayed.\n  -T, --trace        All paths that contributed to the creation of the\n                     exception will be tracked, and then, with separation,\n                     each created stack will be displayed.\n  -i, --inner        Just like the --trace option, The exception that occurred\n                     will be tracked and the result will be limited and\n                     displayed to the internal content of the selected Python\n                     file.\n  -L, --locals       The last value of each scope's local variables before the\n                     exception occurs will be displayed. This option can be\n                     combined with --trace and --inner.\n  -S, --search       With the help of stackoverflow api, the links of answered\n                     posts related to the exception that occurred will be\n                     displayed.\n  -o, --output PATH  Writes the output to a text file. It has an argument that\n                     contains the path of the text file.\n  -r, --recent       Redisplays the last operation performed.\n  -v, --version      Displays the current version of pymg installed on the\n                     system.\n  -h, --help         Show this message and exit.\n```\n## Interpret the file without options \u003ca class=\"anchor\" id=\"no_option\"\u003e\u003c/a\u003e\nBy default, (-i, --inner) and (-L, --locals) will happen if you don't select any options. Combining these two options will make an effective form of error message.\n\nLet's check the test.py file as an example:\n```python\nimport sys\n\ndef div(a, b):\n    return a / b\n\nprint(div(int(sys.argv[1]), int(sys.argv[2])))\n```\n\nThe task of this program is very simple. It passes the two values it receives from the command line arguments to the div function, and the div function divides them.\n\nNow let us interpret the test.py file with pymg so that the ZeroDivisionError exception occurs.\n```\npymg test.py 4 0\n```\n\nOutput:\n![img1](https://raw.githubusercontent.com/mimseyedi/pymg/master/docs/images/exc-no-option.png)\n\n## Syntax validation using the --syntax option \u003ca class=\"anchor\" id=\"syntax\"\u003e\u003c/a\u003e\nLet's create an intentional syntax problem in the test.py file:\n```python\nimport sys\n\ndef div(a, b):\n    return (a / b\n\nprint(div(int(sys.argv[1]), int(sys.argv[2])))\n```\n\nNow we will use the (-x, --syntax) option:\n```\npymg test.py 4 0 -x\n```\n\nOutput:\n![img1](https://raw.githubusercontent.com/mimseyedi/pymg/master/docs/images/exc-syntax.png)\n\n\nSyntaxError always precedes exceptions, and even if you don't use the (-x, --syntax) option, it will be checked at interpret time. \n\nPay attention to the following command, which displays a similar output:\n```\npymg test.py 4 0\n```\n\nOutput:\n![img1](https://raw.githubusercontent.com/mimseyedi/pymg/master/docs/images/exc-syntax.png)\n\n**IndentationError** and **TabError** will also be checked in the syntax checking stage:\n```python\nimport sys\n\ndef div(a, b):\nreturn a / b\n\nprint(div(int(sys.argv[1]), int(sys.argv[2])))\n```\n\nOutput:\n![img1](https://raw.githubusercontent.com/mimseyedi/pymg/master/docs/images/exc-indentation.png)\n\n## Combination of options \u003ca class=\"anchor\" id=\"combine\"\u003e\u003c/a\u003e\npymg allows you to combine options to access all the features of the exception separately and get different outputs:\n```\npymg test.py 4 0 -f -s -l -c -m\n```\n\nOutput:\n![img1](https://raw.githubusercontent.com/mimseyedi/pymg/master/docs/images/exc-comb.png)\n\nBut some options are ahead of others, you can see the prioritization of options \u003ca href=\"https://github.com/mimseyedi/pymg/blob/master/docs/guide/how_does_pymg_work.md#pri_options\"\u003ehere\u003c/a\u003e.\n\nFor example, using the (-T, --trace) option, other options will not work (Because all the options are included in this option):\n```\npymg test.py 4 0 -f -s -l -c -m -T\n```\n\nOutput:\n![img1](https://raw.githubusercontent.com/mimseyedi/pymg/master/docs/images/exc-trace.png)\n\n## Combination of --trace and --inner options with --locals \u003ca class=\"anchor\" id=\"T_i_L\"\u003e\u003c/a\u003e\nThe (-T, --trace) and (-i, --inner) options can be combined with (-L, --locals) option:\n```\npymg test.py -i -L\n```\n\nOutput:\n![img1](https://raw.githubusercontent.com/mimseyedi/pymg/master/docs/images/exc-inner-locals.png)\n\n## Using the --recent option \u003ca class=\"anchor\" id=\"recent\"\u003e\u003c/a\u003e\nBy using the --recent option, you can re-execute the last operation you have done. pymg saves your last move.\n\n## Search for a solution with the --search option \u003ca class=\"anchor\" id=\"search\"\u003e\u003c/a\u003e\nYou can search for solutions to your problems in stackoverflow by using the (-S, --search) option. pymg searches stackoverflow for the exception and shows you the title and link of the posts that got the answer:\n```\npymg test.py 4 0 -S\n```\n\nOutput:\n![img1](https://raw.githubusercontent.com/mimseyedi/pymg/master/docs/images/exc-search.png)\n\n## Write the output to the file with the --output option \u003ca class=\"anchor\" id=\"syntax\"\u003e\u003c/a\u003e\nYou can use the (-o, --output) option to write the generated output in a text file:\n```\npymg test.py -T -L -o output.txt\n```\n\n**output.txt**\n```\n╭─────────────────────────────────────────── Exception ───────────────────────────────────────────╮\n│ Exception Type ❱ JSONDecodeError                                                                │\n│ Exception Message ❱ Expecting value: line 1 column 1 (char 0)                                   │\n│                                                                                                 │\n│ ╭─ Trace[1] - \u003cmodule\u003e ───────────────────────────────────────────────────────────────────────╮ │\n│ │                                                                                             │ │\n│ │ File: /Users/mimseyedi/Desktop/test.py                                                      │ │\n│ │                                                                                             │ │\n│ │ ❱ 8 print(read_json_file(json_file_path))                                                   │ │\n│ │           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                    │ │\n│ │                                                                                             │ │\n│ │ ╭───────────────────────── locals ──────────────────────────╮                               │ │\n│ │ │ read_json_file = \u003cfunction read_json_file at 0x1012705e0\u003e │                               │ │\n│ │ │ json_file_path = json_file.json                           │                               │ │\n│ │ ╰───────────────────────────────────────────────────────────╯                               │ │\n│ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ │\n│                                                                                                 │\n│ ╭─ Trace[2] - read_json_file ─────────────────────────────────────────────────────────────────╮ │\n│ │                                                                                             │ │\n│ │ File: /Users/mimseyedi/Desktop/test.py                                                      │ │\n│ │                                                                                             │ │\n│ │ ❱ 5 return json.load(jsonf)                                                                 │ │\n│ │            ^^^^^^^^^^^^^^^^                                                                 │ │\n│ │                                                                                             │ │\n│ │ ╭────────────────────────────────── locals ───────────────────────────────────╮             │ │\n│ │ │ json_file = json_file.json                                                  │             │ │\n│ │ │ jsonf = \u003c_io.TextIOWrapper name='json_file.json' mode='r' encoding='UTF-8'\u003e │             │ │\n│ │ ╰─────────────────────────────────────────────────────────────────────────────╯             │ │\n│ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ │\n│                                                                                                 │\n│ ╭─ Trace[3] - load ───────────────────────────────────────────────────────────────────────────╮ │\n│ │                                                                                             │ │\n│ │ File: /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/json/__init__.py    │ │\n│ │                                                                                             │ │\n│ │ ❱ 293 return loads(fp.read(),                                                               │ │\n│ │       ^^^^^^^^^^^^^^^^^^^^^^^                                                               │ │\n│ │                                                                                             │ │\n│ │ NO LOCALS WERE FOUND IN THIS TRACE                                                          │ │\n│ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ │\n│                                                                                                 │\n│ ╭─ Trace[4] - loads ──────────────────────────────────────────────────────────────────────────╮ │\n│ │                                                                                             │ │\n│ │ File: /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/json/__init__.py    │ │\n│ │                                                                                             │ │\n│ │ ❱ 346 return _default_decoder.decode(s)                                                     │ │\n│ │       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                                     │ │\n│ │                                                                                             │ │\n│ │ NO LOCALS WERE FOUND IN THIS TRACE                                                          │ │\n│ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ │\n│                                                                                                 │\n│ ╭─ Trace[5] - decode ─────────────────────────────────────────────────────────────────────────╮ │\n│ │                                                                                             │ │\n│ │ File: /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/json/decoder.py     │ │\n│ │                                                                                             │ │\n│ │ ❱ 337 obj, end = self.raw_decode(s, idx=_w(s, 0).end())                                     │ │\n│ │       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                                     │ │\n│ │                                                                                             │ │\n│ │ NO LOCALS WERE FOUND IN THIS TRACE                                                          │ │\n│ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ │\n│                                                                                                 │\n│ ╭─ Trace[6] - raw_decode ─────────────────────────────────────────────────────────────────────╮ │\n│ │                                                                                             │ │\n│ │ File: /Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/json/decoder.py     │ │\n│ │                                                                                             │ │\n│ │ ❱ 355 raise JSONDecodeError(\"Expecting value\", s, err.value) from None                      │ │\n│ │       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^                      │ │\n│ │                                                                                             │ │\n│ │ NO LOCALS WERE FOUND IN THIS TRACE                                                          │ │\n│ ╰─────────────────────────────────────────────────────────────────────────────────────────────╯ │\n╰─────────────────────────────────────────────────────────────────────────────────────────────────╯\n```\n\n## How does pymg work? \u003ca class=\"anchor\" id=\"work\"\u003e\u003c/a\u003e\n![img1](https://raw.githubusercontent.com/mimseyedi/pymg/master/docs/images/pymg-works.png)\n\n## How does pymg check syntax? \u003ca class=\"anchor\" id=\"syntaxx\"\u003e\u003c/a\u003e\n**pymg** uses the **py_compile** module and the **Python interpreter** to make sure that the **syntax** of the Python file is correct.\n\nThis will be done with the help of the **subprocess** module and the **output** will be **captured**:\n\n```python\nsyntax_err: str = subprocess.run(\n        [python_interpreter, '-m', 'py_compile', source_file],\n        capture_output=True).stderr.decode()\n```\n\n## Prioritizing options \u003ca class=\"anchor\" id=\"pri_options\"\u003e\u003c/a\u003e\nDue to the better display of the output, the options selected by the user are **prioritized**, which you will see in the **table** below.\n\n| OPTION        | PRIORITY | DESCRIPTION |\n|---------------|----------|-|\n| -x, --syntax  | group 1  |It checks the syntax of the selected Python file. If there is a syntax problem, an error message will be displayed, otherwise 'INTACT' will be displayed.|\n| -T, --trace   | group 2  |All paths that contributed to the creation of the exception will be tracked, and then, with separation, each created stack will be displayed.|\n| -i, --inner   | group 2  |Just like the --trace option, The exception that occurred will be tracked and the result will be limited and displayed to the internal content of the selected Python file.|\n| -t, --type    | group 3  |The type of exception that occurred will be displayed.|\n| -m, --message | group 3  |The message of exception that occurred will be displayed.|\n| -f, --file    | group 3  |The full path of the Python file where the exception occurred will be displayed.|\n| -s, --scope   | group 3  |The scope where the exception occurred will be displayed.|\n| -l, --line    | group 3  |The line number that caused the exception will be displayed.|\n| -c, --code    | group 3  |The code that caused the exception will be displayed.|\n| -L, --locals  | group 3  |The last value of each scope's local variables before the exception occurs will be displayed. This option can be combined with --trace and --inner.|\n| -S, --search  | group 4  |With the help of stackoverflow api, the links of answered posts related to the exception that occurred will be displayed.|\n\n\n## Recipe file \u003ca class=\"anchor\" id=\"recipe\"\u003e\u003c/a\u003e\nAfter **prioritization** and modification, the options are **stored** as pointers to a **template** (the function that creates the specified template) in a file called **recipe**.\n\nThe **recipe** later helps the called function to create the **templates** according to the **recipe** when an **exception** occurs.\n\n## Source information file \u003ca class=\"anchor\" id=\"source_info\"\u003e\u003c/a\u003e\nIn order to **access** the **information** of the **main file (source)**, such as the **file name** and command line **arguments**, a file containing the source information is created to **replace** the information related to the **mirror** file.\n\n## Mirror file \u003ca class=\"anchor\" id=\"mirror\"\u003e\u003c/a\u003e\nA **mirror** file is a file that **imitates** the **source**.\n\nIn order to **capture** the data of the **exception** that occurred, a piece of code must be **added** to the source:\n\n```python\nimport sys\nfrom pymg import display_error_message\nsys.excepthook = display_error_message\n```\n\nThe task of this piece of code is to **replace** the **excepthook** function from the **sys** module with a **customized** function.\n\n**Note: When an exception occurs, the excepthook function of the sys module will be executed.**\n\nSince you should not **touch** the **main file (source)** and make **changes** in it, a file named **mirror** file will be **created**, which contains a **header** (the piece of code you saw above) and the **source content**. This file is interpreted **instead** of the **source** file so that the source remains **isolated** and **not damaged**.\n\n## Interpret the mirror file \u003ca class=\"anchor\" id=\"interpret_mirror\"\u003e\u003c/a\u003e\nThe **mirror** file will be **interpreted** (executed) by the **Python interpreter**, and if an **exception** occurs, the **display_error_message** function will be called **instead** of **excepthook**. Next, by reading the **recipe**, this function will find out what functions to call to **generate** the **template**, and at the end, it will display the **template** that contains the information **requested** by the user.\n\n## Customized excepthook \u003ca class=\"anchor\" id=\"customexcepthook\"\u003e\u003c/a\u003e\nThe task of this function is to read the **recipe** and **link** the **commands** to the **functions** whose job is to produce a specific **template**. This function **sends** the **data** related to the **exception** that occurred to the functions that must **generate** the **templates** so that they can easily access this data and **create** the **templates**.\nAt the end, the **templates** will be **combined** and the output will be displayed.\n\n## Bugs/Requests \u003ca class=\"anchor\" id=\"cont\"\u003e\u003c/a\u003e\nPlease send bug reports and feature requests through \u003ca href=\"https://github.com/mimseyedi/pymg/issues\"\u003egithub issue tracker\u003c/a\u003e.\n\n## License \u003ca class=\"anchor\" id=\"license\"\u003e\u003c/a\u003e\npymg is a free and open source project under the `GPL-3 LICENSE`. Any contribution is welcome. You can do this by registering a pull request.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmimseyedi%2Fpymg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmimseyedi%2Fpymg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmimseyedi%2Fpymg/lists"}