├── .gitattributes
├── .github
├── FUNDING.yml
└── workflows
│ └── codeql-analysis.yml
├── .gitignore
├── CHANGELIST.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.bat
├── example.png
├── example2.png
├── exampleMono.png
├── multiexcept.py
├── pretty_errors
├── __init__.py
└── __main__.py
├── setup.py
├── test.py
└── upload.bat
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: onelivesleft
4 | custom: ['https://twitch.tv/onelivesleft']
5 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | name: "CodeQL"
7 |
8 | on:
9 | push:
10 | branches: [master]
11 | pull_request:
12 | # The branches below must be a subset of the branches above
13 | branches: [master]
14 | schedule:
15 | - cron: '0 4 * * 0'
16 |
17 | jobs:
18 | analyze:
19 | name: Analyze
20 | runs-on: ubuntu-latest
21 |
22 | strategy:
23 | fail-fast: false
24 | matrix:
25 | # Override automatic language detection by changing the below list
26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
27 | language: ['python']
28 | # Learn more...
29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
30 |
31 | steps:
32 | - name: Checkout repository
33 | uses: actions/checkout@v2
34 | with:
35 | # We must fetch at least the immediate parents so that if this is
36 | # a pull request then we can checkout the head.
37 | fetch-depth: 2
38 |
39 | # If this run was triggered by a pull request event, then checkout
40 | # the head of the pull request instead of the merge commit.
41 | - run: git checkout HEAD^2
42 | if: ${{ github.event_name == 'pull_request' }}
43 |
44 | # Initializes the CodeQL tools for scanning.
45 | - name: Initialize CodeQL
46 | uses: github/codeql-action/init@v1
47 | with:
48 | languages: ${{ matrix.language }}
49 | # If you wish to specify custom queries, you can do so here or in a config file.
50 | # By default, queries listed here will override any specified in a config file.
51 | # Prefix the list here with "+" to use these queries and those in the config file.
52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
53 |
54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
55 | # If this step fails, then you should remove it and run the build manually (see below)
56 | - name: Autobuild
57 | uses: github/codeql-action/autobuild@v1
58 |
59 | # ℹ️ Command-line programs to run using the OS shell.
60 | # 📚 https://git.io/JvXDl
61 |
62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63 | # and modify them (or add more) to build your code if your project
64 | # uses a compiled language
65 |
66 | #- run: |
67 | # make bootstrap
68 | # make release
69 |
70 | - name: Perform CodeQL Analysis
71 | uses: github/codeql-action/analyze@v1
72 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .ropeproject
2 | __pycache__
3 | build
4 | pretty_errors.egg-info
5 | dist
6 | .vscode
7 | *.pyc
8 |
--------------------------------------------------------------------------------
/CHANGELIST.md:
--------------------------------------------------------------------------------
1 | # v1.2.25
2 |
3 | * Fixed problem installing into virtualenv paths.
4 |
5 |
6 | # v1.2.24
7 |
8 | * Fixed problem when running in container which reports a terminal width of 0
9 |
10 |
11 | # v1.2.23
12 |
13 | * Fixed problem installing into virtualenv paths.
14 |
15 |
16 | # v1.2.22
17 |
18 | * Fixed problem installing into virtualenv paths.
19 |
20 |
21 | # v1.2.21
22 |
23 | * Fixed problem installing into paths with `\` in them.
24 |
25 |
26 | # v1.2.20
27 |
28 | * Added `ExceptionWriter.write_link`
29 |
30 |
31 | # v1.2.19
32 |
33 | * Fixed line number discrepancies in example image, added example image for `mono()` mode.
34 | * Autogenerated `configure()` call now has each entry commented out.
35 | * Added environment variable `PYTHON_PRETTY_ERRORS_ISATTY_ONLY`. If set to non-zero then `pretty_errors` will check if it is outputing to an interactive terminal, and only activate if it is (so logging to file will retain the standard Python exception formatting).
36 | * Added `__version__`
37 |
38 |
39 | # v1.2.18
40 |
41 | * Now outputs `filename` and `filename2` attributes of exceptions (e.g. in `FileNotFoundError`)
42 | * Added `exception_file_color` config option to color them.
43 |
44 |
45 | # v1.2.17
46 |
47 | * Suppressed exceptions no longer displayed (i.e. respects `suppress_context` flag)
48 | * Added `show_suppressed` config option to override this behaviour.
49 |
50 |
51 | # v1.2.16
52 |
53 | * Added support for when exceptions are thrown within exceptions.
54 | * Added `inner_exception_message` and `inner_exception_separator` config options.
55 |
56 |
57 | # v1.2.15
58 |
59 | * Really fix bug when used in interactive interpreter.
60 |
61 |
62 | # v1.2.14
63 |
64 | * Fix bug when used in interactive interpreter.
65 |
66 |
67 | # v1.2.13
68 |
69 | * No, really, fix the README URLs
70 |
71 |
72 | # v1.2.12
73 |
74 | * Fix README image URLs
75 |
76 |
77 | # v1.2.11
78 |
79 | * Fix bug when reporting SyntaxError in interactive interpreter.
80 |
81 |
82 | # v1.2.10
83 |
84 | * Minor fixes
85 |
86 |
87 | # v1.2.9
88 |
89 | * Added `replace_stderr` function to allow for situation where `pretty_errors` cannot gain access to `sys.excepthook`.
90 | * Moved installer script entirely into `__main__`.
91 | * Fixed not working with `venv`.
92 | * Improved automatic cleaning of startup files.
93 |
94 |
95 | # v1.2.8
96 |
97 | * Added check for startup files left over after uninstall.
98 | * Added `clean` option when running module directly.
99 |
100 |
101 | # v1.2.7
102 |
103 | * Fixed `point_at` bug.
104 | * Added color constants: `BLACK`, `RED`, `GREEN`, `YELLOW`, `BLUE`, `MAGENTA`, `CYAN`, `WHITE`, `BRIGHT_BLACK`, `BRIGHT_RED`, `BRIGHT_GREEN`, `BRIGHT_YELLOW`, `BRIGHT_BLUE`, `BRIGHT_MAGENTA`, `BRIGHT_CYAN`, `BRIGHT_WHITE`, `BLACK_BACKGROUND`, `RED_BACKGROUND`, `GREEN_BACKGROUND`, `YELLOW_BACKGROUND`, `BLUE_BACKGROUND`, `MAGENTA_BACKGROUND`, `CYAN_BACKGROUND`, `WHITE_BACKGROUND`
105 |
106 |
107 | # v1.2.6
108 |
109 | * Fixed `pathed_config` bug.
110 | * Added `activate` call.
111 | * Added arrow pointing to syntax error.
112 | * Added config options `display_arrow`, `arrow_head_character`, `arrow_head_color`, `arrow_tail_character`, `arrow_tail_color`, `syntax_error_color`
113 |
114 |
115 | # v1.2.5
116 |
117 | * Fix comment skeleton in *customize.py output.
118 |
119 |
120 | # v1.2.4
121 |
122 | * README updated.
123 |
124 |
125 | # v1.2.3
126 |
127 | * Added `ExceptionWriter` class to allow for overriding.
128 | * Added `pathed_config` call to allow for multiple configs, activated by path of code file in frame.
129 | * Added environment variable `PYTHON_PRETTY_ERRORS`. If set to `0` then `pretty_errors` will be disabled.
130 |
131 |
132 | # v1.2.2
133 |
134 | * `separator_character` can now be set to `None` or `''` to disable header.
135 | * Improved install wizard.
136 | * Spelling corrections.
137 |
138 |
139 | # v1.2.1
140 |
141 | * Fix for Python 2
142 |
143 |
144 | # v1.2.0
145 |
146 | * Added `mono` function to set useful config options for a monochrome terminal.
147 | * Added config option `timestamp_function`
148 | * Added `default_config` for reference.
149 | * May now use any characters in color prefixes (not just escape sequences)
150 | * Removed config options `line_prefix`, `code_prefix`, `line_prefix_color`, `code_prefix_color` - no longer needed because of above.
151 |
152 |
153 | # v 1.1.11
154 |
155 | * Fix for python 2
156 |
157 |
158 | # v1.1.10
159 |
160 | * Added `__main__` for running with `-m`
161 | * Removed post-install code (it doesn't work with `pip` - `pip` has no post-install hook)
162 |
163 |
164 | # v1.1.9
165 |
166 | * Automated adding to `sitecustomize.py`
167 |
168 |
169 | # v1.1.8
170 |
171 | * Added config options `always_display_bottom`, `truncate_locals`, `truncate_code`, `line_prefix`, `code_prefix`, `line_prefix_color`, `code_prefix_color`, `local_len_color`
172 |
173 |
174 | # v1.1.7
175 |
176 | * Site customize instructions.
177 |
178 |
179 | # v1.1.6
180 |
181 | * Added config options `display_locals`, `display_trace_locals`, `local_name_color`, `local_value_color`
182 |
183 |
184 | # v1.1.5
185 |
186 | * Nada
187 |
188 |
189 | # v1.1.4
190 |
191 | * `colorama` dependency
192 |
193 |
194 | # v1.1.3
195 |
196 | * Added `whitelist` and `blacklist`
197 |
198 |
199 | # v1.1.2
200 |
201 | * Fix README
202 |
203 |
204 | # v1.1.1
205 |
206 | * Python 2.7 compatibility
207 |
208 |
209 | # v1.1.0
210 |
211 | * Reworked to replace `sys.excepthook` instead of `sys.stderr`
212 | * Now automatically works out line length if line length is `0` (which is the default)
213 | * Added config options: `top_first`, `stack_depth`, `exception_above`, `exception_below`, `lines_before`, `lines_after`, `trace_lines_before`, `trace_lines_after`, `line_color`, `code_color` , `exception_color`, `exception_arg_color`, `prefix`, `infix`, `postfix`, `line_number_first`
214 | * Removed config option: `default_color`
215 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Pretty Errors in now in a fairly stable place, and I don't see it expanding in scope or gaining many new features. I don't recommend submitting PRs, as they are unlikely to be merged.
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 onelivesleft
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # pretty-errors
2 |
3 | Prettifies Python exception output to make it legible. Install it with
4 | ```bash
5 | python -m pip install pretty_errors
6 | ```
7 |
8 | If you want `pretty_errors` to be used whenever you run a python script you must add it to your python startup procedure. You can do so easily by running:
9 | ```bash
10 | python -m pretty_errors
11 | ```
12 | This is the recommended way to use `pretty_errors`; apart from being simpler and universal, using it will mean `SyntaxError` exceptions also get formatted prettily (which doesn't work if you are manually importing `pretty_errors`).
13 |
14 | ---
15 |
16 | 
17 |
18 | ---
19 |
20 | If you have not installed it universally you can use it in your project simply by importing it:
21 | ```python
22 | import pretty_errors
23 | ```
24 | Note you need to be running in a terminal capable of colour output in order to get colour output: in Windows this means powershell, cmder, etc. If you must use a monochrome terminal then you can call the helper function `pretty_errors.mono()`, which will set some config options in a way that is useful for monochrome output.
25 |
26 | 
27 |
28 | If you want to configure the output then use `pretty_errors.configure()`, `pretty_errors.whitelist()`, `pretty_errors.blacklist()`, `pretty_errors.pathed_config()`. For example:
29 | ```python
30 | import pretty_errors
31 | pretty_errors.configure(
32 | separator_character = '*',
33 | filename_display = pretty_errors.FILENAME_EXTENDED,
34 | line_number_first = True,
35 | display_link = True,
36 | lines_before = 5,
37 | lines_after = 2,
38 | line_color = pretty_errors.RED + '> ' + pretty_errors.default_config.line_color,
39 | code_color = ' ' + pretty_errors.default_config.line_color,
40 | truncate_code = True,
41 | display_locals = True
42 | )
43 | pretty_errors.blacklist('c:/python')
44 | ```
45 |
46 | 
47 |
48 | ---
49 |
50 | ##### Scraping STDERR
51 |
52 | Sometimes it will be impossible for `pretty_errors` to utilize `sys.excepthook`: for instance, if you are using a framework which installs its own logging (such as `uvicorn`). In such cases, you can make `pretty_errors` scrape the output to `stderr` instead, replacing it with its own. To do so simple call:
53 | ```python
54 | pretty_errors.replace_stderr()
55 | ```
56 | Note that this will lose some functionality, since `pretty_errors` will only have access to what is being output on screen, rather then the entire stack trace. A good API will generally have a way to interact with the exception stack, which will allow for using `excepthook`: `replace_stderr` should be the last resort. [See this comment for an example](https://github.com/onelivesleft/PrettyErrors/issues/16#issuecomment-751463605)
57 |
58 | ---
59 |
60 | ##### Whitelist / Blacklist:
61 |
62 | You may use the functions `whitelist(path)` and `blacklist(path)` to add paths which will be necessary (`whitelist`) or excluded (`blacklist`). The top frame of the stack is never excluded.
63 |
64 | ---
65 |
66 | ##### Pathed Configurations
67 |
68 | You may set up alternate configurations, which are triggered by the path to the code file of the frame. For example, if you were not interested in the system frames (those under 'c:/python') but did not want to hide them completely by using the `blacklist` you could do this:
69 |
70 | ```python
71 | meh = pretty_errors.config.copy()
72 | meh.line_color = meh.code_color = meh.filename_color = meh.function_color = meh.line_number_color = (
73 | pretty_errors.GREY
74 | )
75 | pretty_errors.pathed_config(meh, 'c:/python')
76 | ```
77 |
78 | ---
79 |
80 | ##### Environment Variables
81 |
82 | * PYTHON_PRETTY_ERRORS
83 | You may disable `pretty_errors` by setting the environment variable `PYTHON_PRETTY_ERRORS` to `0`. i.e. at a command prompt:
84 | ```bash
85 | set PYTHON_PRETTY_ERRORS=0
86 | ```
87 |
88 | Calling `pretty_errors.activate()` will override this.
89 |
90 | If you wish to selectively utilize `pretty_errors`, then use the above, and in your code perform your calculation to determine whether or not to call `pretty_errors.activate()`.
91 |
92 | * PYTHON_PRETTY_ERRORS_ISATTY_ONLY
93 | It may be desirable to disable `pretty_errors` when you are redirecting output to a file (to keep error logs, for instance). If you wish to do so, then setting `PYTHON_PRETTY_ERRORS_ISATTY_ONLY` to non-zero will cause `pretty_errors` to check if it is running in an interactive terminal, and only activate if so.
94 |
95 | ```bash
96 | set PYTHON_PRETTY_ERRORS_ISATTY_ONLY=1
97 | ```
98 |
99 | Setting this will disable `replace_stderr()` in the same situations, unless you call it with the `force` parameter: `replace_stderr(force=True)`.
100 |
101 | Calling `pretty_errors.activate()` will override this.
102 |
103 | You may check `pretty_errors.terminal_is_interactive` to see if the terminal is interactive (`pretty_errors` sets this by checking `sys.stderr.isatty()`). You can use this to select a different config. For example:
104 |
105 | ```python
106 | if not pretty_errors.terminal_is_interactive:
107 | pretty_errors.mono()
108 | ```
109 |
110 |
111 | ---
112 |
113 | ##### Configuration settings:
114 |
115 | Configuration settings are stored in `pretty_errors.config`, though should be set using `pretty_errors.configure()`. A reference for the default config is stored in `pretty_errors.default_config`.
116 |
117 | * `name`
118 | Optional field to store config name in.
119 |
120 | * `line_length`
121 | Output will be wrapped at this point. If set to `0` (which is the default) it will automatically match your console width.
122 |
123 | * `full_line_newline`
124 | Insert a hard newline even if the line is full. If `line_length` is the same as your console width and this is enabled then you will see double newlines when the line is exactly full, so usually you would only set this if they are different.
125 |
126 | * `separator_character`
127 | Character used to create the header line. Hyphen is used by default. If set to `None` or `''` then header will be disabled.
128 |
129 | * `display_timestamp`
130 | When enabled a timestamp is written in the traceback header.
131 |
132 | * `timestamp_function`
133 | Function called to generate timestamp. Default is `time.perf_counter`.
134 |
135 | * `exception_above`
136 | When enabled the exception is displayed above the stack trace.
137 |
138 | * `exception_below`
139 | When enabled the exception is displayed below the stack trace.
140 |
141 | * `stack_depth`
142 | The maximum number of entries from the stack trace to display. When `0` will display the entire stack, which is the default.
143 |
144 | * `top_first`
145 | When enabled the stack trace will be reversed, displaying the top of the stack first.
146 |
147 | * `always_display_bottom`
148 | When enabled (which is the default) the bottom frame of the stack trace will always be displayed.
149 |
150 | * `show_suppressed`
151 | When enabled all suppressed exceptions in the stack trace will be shown (typically they are suppressed because an exception above them has replaced them). The normal python behaviour is to hide them.
152 |
153 | * `filename_display`
154 | How the filename is displayed: may be `pretty_errors.FILENAME_COMPACT`, `pretty_errors.FILENAME_EXTENDED`, or `pretty_errors.FILENAME_FULL`
155 |
156 | * `line_number_first`
157 | When enabled the line number will be displayed first, rather than the filename.
158 |
159 | * `display_link`
160 | When enabled a link is written below the error location, which VSCode will allow you to click on.
161 |
162 | * `lines_after`, `lines_before`
163 | How many lines of code to display for the top frame, before and after the line the exception occurred on.
164 |
165 | * `trace_lines_after`, `trace_lines_before`
166 | How many lines of code to display for each other frame in the stack trace, before and after the line the exception occurred on.
167 |
168 | * `truncate_code`
169 | When enabled each line of code will be truncated to fit the line length.
170 |
171 | * `display_locals`
172 | When enabled, local variables appearing in the top stack frame code will be displayed with their values.
173 |
174 | * `display_trace_locals`
175 | When enabled, local variables appearing in other stack frame code will be displayed with their values.
176 |
177 | * `truncate_locals`
178 | When enabled the values of displayed local variables will be truncated to fit the line length.
179 |
180 | * `display_arrow`
181 | When enabled an arrow will be displayed for syntax errors, pointing at the offending token.
182 |
183 | * `arrow_head_character`, `arrow_tail_character`
184 | Characters used to draw the arrow which points at syntax errors.
185 |
186 | * `inner_exception_message`
187 | Message displayed when one exception occurs inside another, between the two exceptions. Default is `None`, which will simply display the exceptions separated by the header. If you want to emulate the default non-pretty behaviour, use this:
188 |
189 | `inner_exception_message = pretty_errors.MAGENTA + "\n During handling of the above exception, another exception occurred:\n"`
190 |
191 | Note that if you use `top_first` then the order will be reversed, so you should use a message like this instead:
192 |
193 | `inner_exception_message = pretty_errors.MAGENTA + "\n The above exception occurred during another exception:\n"`
194 |
195 | * `inner_exception_separator`
196 | Default is `False`. When set to `True` a header will be written before the `inner_exception_message`.
197 |
198 | * `prefix`
199 | Text string which is displayed at the top of the report, just below the header.
200 |
201 | * `infix`
202 | Text string which is displayed between each frame of the stack.
203 |
204 | * `postfix`
205 | Text string which is displayed at the bottom of the exception report.
206 |
207 | * `reset_stdout`
208 | When enabled the reset escape sequence will be written to stdout as well as stderr; turn this on if your console is being left with the wrong color.
209 |
210 | ---
211 |
212 | These color strings will be output before the relevant part of the exception message. You may include non-escape sequence strings if you wish; if you do not have a terminal which supports color output, or simply want to include extra demarcation.
213 |
214 | * `header_color`
215 | Escape sequence to set header color.
216 |
217 | * `timestamp_color`
218 | Escape sequence to set timestamp color.
219 |
220 | * `exception_color`
221 | Escape sequence to set exception color.
222 |
223 | * `exception_arg_color`
224 | Escape sequence to set exception arguments color.
225 |
226 | * `exception_file_color`
227 | Escape sequence to set color of filenames in exceptions (for example, in a FileNotFoundError).
228 |
229 | * `filename_color`
230 | Escape sequence to set filename color.
231 |
232 | * `line_number_color`
233 | Escape sequence to set line number color.
234 |
235 | * `function_color`
236 | Escape sequence to set function color.
237 |
238 | * `link_color`
239 | Escape sequence to set link color.
240 |
241 | * `line_color`
242 | Escape sequence to set the color of the line of code which caused the exception.
243 |
244 | * `code_color`
245 | Escape sequence to set the color of other displayed lines of code.
246 |
247 | * `arrow_head_color`, `arrow_tail_color`
248 | Escape sequence to set the color of the arrow which points at syntax errors.
249 |
250 | * `syntax_error_color`
251 | Escape sequence to set the color of the syntax error token.
252 |
253 | * `local_name_color`
254 | Escape sequence to set the color of local variable names.
255 |
256 | * `local_value_color`
257 | Escape sequence to set the color of local variable values.
258 |
259 | * `local_len_color`
260 | Escape sequence to set the color of local value length when local is truncated.
261 |
262 | `pretty_errors` has some built in escape sequence constants you can use when setting these colors:
263 |
264 | * `BLACK`
265 | * `GREY`
266 | * `RED`
267 | * `GREEN`
268 | * `YELLOW`
269 | * `BLUE`
270 | * `MAGENTA`
271 | * `CYAN`
272 | * `WHITE`
273 |
274 | For each color there is a matching `BRIGHT_` variant (i.e. `pretty_errors.BRIGHT_RED`), as well as a `_BACKGROUND` variant to set the background color (i.e. `pretty_errors.RED_BACKGROUND`).
275 |
276 | For example:
277 | ```python
278 | pretty_errors.configure(
279 | line_color = pretty_errors.CYAN_BACKGROUND + pretty_errors.BRIGHT_WHITE
280 | )
281 | ```
282 |
283 | ---
284 |
285 | ##### Further customization
286 |
287 | For the most extensive customization (short of forking the package) you may override the default `ExceptionWriter` class, allowing you to tailor the output however you wish. Typically you will only need to override the `write_` methods.
288 |
289 | For example:
290 |
291 | ```python
292 | class MyExceptionWriter(pretty_errors.ExceptionWriter):
293 | def write_header(self):
294 | self.output_text('######## ERROR ########')
295 |
296 | pretty_errors.exception_writer = MyExceptionWriter()
297 | ```
298 |
299 | Run `help(pretty_errors.ExceptionWriter)` in the python interpreter for more details.
300 |
--------------------------------------------------------------------------------
/build.bat:
--------------------------------------------------------------------------------
1 | del dist\*.* /q
2 | \python3\python setup.py sdist bdist_wheel
3 |
--------------------------------------------------------------------------------
/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onelivesleft/PrettyErrors/f61574e7fec2b7118fee5a59de61a8a734459ca1/example.png
--------------------------------------------------------------------------------
/example2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onelivesleft/PrettyErrors/f61574e7fec2b7118fee5a59de61a8a734459ca1/example2.png
--------------------------------------------------------------------------------
/exampleMono.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/onelivesleft/PrettyErrors/f61574e7fec2b7118fee5a59de61a8a734459ca1/exampleMono.png
--------------------------------------------------------------------------------
/multiexcept.py:
--------------------------------------------------------------------------------
1 | import pretty_errors
2 |
3 |
4 | try:
5 | myval = [1,2]
6 | print(myval[3])
7 | except:
8 | try:
9 | print(foo)
10 | except:
11 | print("First exception")
12 | raise OSError
13 |
--------------------------------------------------------------------------------
/pretty_errors/__init__.py:
--------------------------------------------------------------------------------
1 | import sys, re, colorama, os, time, linecache
2 | colorama.init()
3 | output_stderr = sys.stderr
4 | terminal_is_interactive = sys.stderr.isatty()
5 |
6 | name = "pretty_errors"
7 | __version__ = "1.2.25" # remember to update version in setup.py!
8 |
9 | active = 'PYTHON_PRETTY_ERRORS' not in os.environ or os.environ['PYTHON_PRETTY_ERRORS'] != '0'
10 | interactive_tty_only = 'PYTHON_PRETTY_ERRORS_ISATTY_ONLY' in os.environ and os.environ['PYTHON_PRETTY_ERRORS_ISATTY_ONLY'] != '0'
11 |
12 |
13 | FILENAME_COMPACT = 0
14 | FILENAME_EXTENDED = 1
15 | FILENAME_FULL = 2
16 |
17 | RESET_COLOR = '\033[m'
18 |
19 | BLACK = '\033[0;30m'
20 | RED = '\033[0;31m'
21 | GREEN = '\033[0;32m'
22 | YELLOW = '\033[0;33m'
23 | BLUE = '\033[0;34m'
24 | MAGENTA = '\033[0;35m'
25 | CYAN = '\033[0;36m'
26 | WHITE = '\033[0;37m'
27 |
28 | BRIGHT_BLACK = GREY = '\033[1;30m'
29 | BRIGHT_RED = '\033[1;31m'
30 | BRIGHT_GREEN = '\033[1;32m'
31 | BRIGHT_YELLOW = '\033[1;33m'
32 | BRIGHT_BLUE = '\033[1;34m'
33 | BRIGHT_MAGENTA = '\033[1;35m'
34 | BRIGHT_CYAN = '\033[1;36m'
35 | BRIGHT_WHITE = '\033[1;37m'
36 |
37 | BLACK_BACKGROUND = '\033[40m'
38 | RED_BACKGROUND = '\033[41m'
39 | GREEN_BACKGROUND = '\033[42m'
40 | YELLOW_BACKGROUND = '\033[43m'
41 | BLUE_BACKGROUND = '\033[44m'
42 | MAGENTA_BACKGROUND = '\033[45m'
43 | CYAN_BACKGROUND = '\033[46m'
44 | WHITE_BACKGROUND = '\033[47m'
45 |
46 |
47 | ansi_escape = re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
48 | whitelist_paths = []
49 | blacklist_paths = []
50 | config_paths = {}
51 |
52 |
53 | class PrettyErrorsConfig():
54 | def __init__(self, instance = None):
55 | if instance is None:
56 | self.name = "default"
57 | self.line_length = 0
58 | self.full_line_newline = False
59 | self.filename_display = FILENAME_COMPACT
60 | self.display_timestamp = False
61 | try:
62 | self.timestamp_function = time.perf_counter
63 | except AttributeError:
64 | self.timestamp_function = time.time
65 | self.display_link = False
66 | self.separator_character = '-'
67 | self.line_number_first = False
68 | self.top_first = False
69 | self.always_display_bottom = True
70 | self.stack_depth = 0
71 | self.exception_above = False
72 | self.exception_below = True
73 | self.trace_lines_before = 0
74 | self.trace_lines_after = 0
75 | self.lines_before = 0
76 | self.lines_after = 0
77 | self.display_locals = False
78 | self.display_trace_locals = False
79 | self.truncate_locals = True
80 | self.truncate_code = False
81 | self.display_arrow = True
82 | self.arrow_tail_character = '-'
83 | self.arrow_head_character = '^'
84 | self.header_color = GREY
85 | self.timestamp_color = GREY
86 | self.line_color = BRIGHT_WHITE
87 | self.code_color = GREY
88 | self.filename_color = BRIGHT_CYAN
89 | self.line_number_color = BRIGHT_GREEN
90 | self.function_color = BRIGHT_BLUE
91 | self.link_color = GREY
92 | self.local_name_color = BRIGHT_MAGENTA
93 | self.local_value_color = RESET_COLOR
94 | self.local_len_color = GREY
95 | self.exception_color = BRIGHT_RED
96 | self.exception_arg_color = BRIGHT_YELLOW
97 | self.exception_file_color = BRIGHT_MAGENTA
98 | self.syntax_error_color = BRIGHT_GREEN
99 | self.arrow_tail_color = BRIGHT_GREEN
100 | self.arrow_head_color = BRIGHT_GREEN
101 | self.inner_exception_message = None
102 | self.inner_exception_separator = False
103 | self.prefix = None
104 | self.infix = None
105 | self.postfix = None
106 | self.reset_stdout = False
107 | self.show_suppressed = False
108 | else:
109 | self.name = instance.name
110 | self.line_length = instance.line_length
111 | self.full_line_newline = instance.full_line_newline
112 | self.filename_display = instance.filename_display
113 | self.display_timestamp = instance.display_timestamp
114 | self.timestamp_function = instance.timestamp_function
115 | self.display_link = instance.display_link
116 | self.separator_character = instance.separator_character
117 | self.line_number_first = instance.line_number_first
118 | self.top_first = instance.top_first
119 | self.always_display_bottom = instance.always_display_bottom
120 | self.stack_depth = instance.stack_depth
121 | self.exception_above = instance.exception_above
122 | self.exception_below = instance.exception_below
123 | self.trace_lines_before = instance.trace_lines_before
124 | self.trace_lines_after = instance.trace_lines_after
125 | self.lines_before = instance.lines_before
126 | self.lines_after = instance.lines_after
127 | self.display_locals = instance.display_locals
128 | self.display_trace_locals = instance.display_trace_locals
129 | self.truncate_locals = instance.truncate_locals
130 | self.truncate_code = instance.truncate_code
131 | self.display_arrow = instance.display_arrow
132 | self.arrow_tail_character = instance.arrow_tail_character
133 | self.arrow_head_character = instance.arrow_head_character
134 | self.header_color = instance.header_color
135 | self.timestamp_color = instance.timestamp_color
136 | self.line_color = instance.line_color
137 | self.code_color = instance.code_color
138 | self.filename_color = instance.filename_color
139 | self.line_number_color = instance.line_number_color
140 | self.function_color = instance.function_color
141 | self.link_color = instance.link_color
142 | self.local_name_color = instance.local_name_color
143 | self.local_value_color = instance.local_value_color
144 | self.local_len_color = instance.local_len_color
145 | self.exception_color = instance.exception_color
146 | self.exception_arg_color = instance.exception_arg_color
147 | self.exception_file_color = instance.exception_file_color
148 | self.syntax_error_color = instance.syntax_error_color
149 | self.arrow_tail_color = instance.arrow_tail_color
150 | self.arrow_head_color = instance.arrow_head_color
151 | self.inner_exception_message = instance.inner_exception_message
152 | self.inner_exception_separator = instance.inner_exception_separator
153 | self.prefix = instance.prefix
154 | self.infix = instance.infix
155 | self.postfix = instance.postfix
156 | self.reset_stdout = instance.reset_stdout
157 | self.show_suppressed = instance.show_suppressed
158 |
159 |
160 | def configure(self, **kwargs):
161 | """Configure settings governing how exceptions are displayed."""
162 | for setting in kwargs:
163 | if kwargs[setting] is not None: setattr(self, setting, kwargs[setting])
164 |
165 |
166 | def copy(self):
167 | c = PrettyErrorsConfig()
168 | c.name = self.name
169 | c.line_length = self.line_length
170 | c.full_line_newline = self.full_line_newline
171 | c.filename_display = self.filename_display
172 | c.display_timestamp = self.display_timestamp
173 | c.timestamp_function = self.timestamp_function
174 | c.display_link = self.display_link
175 | c.separator_character = self.separator_character
176 | c.line_number_first = self.line_number_first
177 | c.top_first = self.top_first
178 | c.always_display_bottom = self.always_display_bottom
179 | c.stack_depth = self.stack_depth
180 | c.exception_above = self.exception_above
181 | c.exception_below = self.exception_below
182 | c.trace_lines_before = self.trace_lines_before
183 | c.trace_lines_after = self.trace_lines_after
184 | c.lines_before = self.lines_before
185 | c.lines_after = self.lines_after
186 | c.display_locals = self.display_locals
187 | c.display_trace_locals = self.display_trace_locals
188 | c.truncate_locals = self.truncate_locals
189 | c.truncate_code = self.truncate_code
190 | c.display_arrow = self.display_arrow
191 | c.arrow_tail_character = self.arrow_tail_character
192 | c.arrow_head_character = self.arrow_head_character
193 | c.header_color = self.header_color
194 | c.timestamp_color = self.timestamp_color
195 | c.line_color = self.line_color
196 | c.code_color = self.code_color
197 | c.filename_color = self.filename_color
198 | c.line_number_color = self.line_number_color
199 | c.function_color = self.function_color
200 | c.link_color = self.link_color
201 | c.local_name_color = self.local_name_color
202 | c.local_value_color = self.local_value_color
203 | c.local_len_color = self.local_len_color
204 | c.exception_color = self.exception_color
205 | c.exception_arg_color = self.exception_arg_color
206 | c.exception_file_color = self.exception_file_color
207 | c.syntax_error_color = self.syntax_error_color
208 | c.arrow_tail_color = self.arrow_tail_color
209 | c.arrow_head_color = self.arrow_head_color
210 | c.inner_exception_message = self.inner_exception_message
211 | c.inner_exception_separator = self.inner_exception_separator
212 | c.prefix = self.prefix
213 | c.infix = self.infix
214 | c.postfix = self.postfix
215 | c.reset_stdout = self.reset_stdout
216 | c.show_suppressed = self.show_suppressed
217 | return c
218 |
219 | __copy__ = copy
220 |
221 |
222 | config = PrettyErrorsConfig()
223 | default_config = PrettyErrorsConfig()
224 |
225 |
226 | def configure(
227 | always_display_bottom = None,
228 | arrow_head_character = None,
229 | arrow_tail_character = None,
230 | arrow_head_color = None,
231 | arrow_tail_color = None,
232 | code_color = None,
233 | display_arrow = None,
234 | display_link = None,
235 | display_locals = None,
236 | display_timestamp = None,
237 | display_trace_locals = None,
238 | exception_above = None,
239 | exception_arg_color = None,
240 | exception_below = None,
241 | exception_color = None,
242 | exception_file_color = None,
243 | filename_color = None,
244 | filename_display = None,
245 | full_line_newline = None,
246 | function_color = None,
247 | header_color = None,
248 | infix = None,
249 | inner_exception_message = None,
250 | inner_exception_separator = None,
251 | line_color = None,
252 | line_length = None,
253 | line_number_color = None,
254 | line_number_first = None,
255 | lines_after = None,
256 | lines_before = None,
257 | link_color = None,
258 | local_len_color = None,
259 | local_name_color = None,
260 | local_value_color = None,
261 | name = None,
262 | postfix = None,
263 | prefix = None,
264 | reset_stdout = None,
265 | separator_character = None,
266 | show_suppressed = None,
267 | stack_depth = None,
268 | syntax_error_color = None,
269 | timestamp_color = None,
270 | timestamp_function = None,
271 | top_first = None,
272 | trace_lines_after = None,
273 | trace_lines_before = None,
274 | truncate_code = None,
275 | truncate_locals = None
276 | ):
277 | """Configure settings governing how exceptions are displayed."""
278 | config.configure(
279 | always_display_bottom = always_display_bottom,
280 | arrow_head_character = arrow_head_character,
281 | arrow_tail_character = arrow_tail_character,
282 | arrow_head_color = arrow_head_color,
283 | arrow_tail_color = arrow_tail_color,
284 | code_color = code_color,
285 | display_arrow = display_arrow,
286 | display_link = display_link,
287 | display_locals = display_locals,
288 | display_timestamp = display_timestamp,
289 | display_trace_locals = display_trace_locals,
290 | exception_above = exception_above,
291 | exception_arg_color = exception_arg_color,
292 | exception_below = exception_below,
293 | exception_color = exception_color,
294 | exception_file_color = exception_file_color,
295 | filename_color = filename_color,
296 | filename_display = filename_display,
297 | full_line_newline = full_line_newline,
298 | function_color = function_color,
299 | header_color = header_color,
300 | infix = infix,
301 | inner_exception_message = inner_exception_message,
302 | inner_exception_separator = inner_exception_separator,
303 | line_color = line_color,
304 | line_length = line_length,
305 | line_number_color = line_number_color,
306 | line_number_first = line_number_first,
307 | lines_after = lines_after,
308 | lines_before = lines_before,
309 | link_color = link_color,
310 | local_len_color = local_len_color,
311 | local_name_color = local_name_color,
312 | local_value_color = local_value_color,
313 | name = name,
314 | postfix = postfix,
315 | prefix = prefix,
316 | reset_stdout = reset_stdout,
317 | separator_character = separator_character,
318 | show_suppressed = show_suppressed,
319 | stack_depth = stack_depth,
320 | syntax_error_color = syntax_error_color,
321 | timestamp_color = timestamp_color,
322 | timestamp_function = timestamp_function,
323 | top_first = top_first,
324 | trace_lines_after = trace_lines_after,
325 | trace_lines_before = trace_lines_before,
326 | truncate_code = truncate_code,
327 | truncate_locals = truncate_locals
328 | )
329 |
330 |
331 | def mono():
332 | global RESET_COLOR
333 | RESET_COLOR = ''
334 | configure(
335 | name = "mono",
336 | infix = '\n---\n',
337 | line_number_first = True,
338 | code_color = '| ',
339 | exception_arg_color = '',
340 | exception_color = '',
341 | exception_file_color = '',
342 | filename_color = '',
343 | function_color = '',
344 | header_color = '',
345 | line_color = '> ',
346 | line_number_color = '',
347 | link_color = '',
348 | local_len_color = '',
349 | local_name_color = '= ',
350 | local_value_color = '',
351 | timestamp_color = '',
352 | arrow_head_color = '',
353 | arrow_tail_color = '',
354 | syntax_error_color = ''
355 | )
356 |
357 |
358 | def whitelist(*paths):
359 | """If the whitelist has any entries, then only files which begin with
360 | one of its entries will be included in the stack trace.
361 | """
362 | for path in paths:
363 | whitelist_paths.append(os.path.normpath(path).lower())
364 |
365 |
366 | def blacklist(*paths):
367 | """Files which begin with a path on the blacklist will not be
368 | included in the stack trace.
369 | """
370 | for path in paths:
371 | blacklist_paths.append(os.path.normpath(path).lower())
372 |
373 |
374 | def pathed_config(configuration, *paths):
375 | """Use alternate configuration for files in the stack trace whose path
376 | begins with one of these paths."""
377 | for path in paths:
378 | config_paths[os.path.normpath(path).lower()] = configuration
379 |
380 |
381 |
382 | class ExceptionWriter():
383 | """ExceptionWriter class for outputing exceptions to the screen.
384 | Methods beginning 'write_' are the primary candidates for overriding.
385 |
386 | Inherit from this class, then set:
387 | pretty_errors.exception_writer = MyExceptionWriter()
388 | """
389 | def __init__(self):
390 | self.config = None
391 |
392 |
393 | def get_terminal_width(self):
394 | """Width of terminal in characters."""
395 | try:
396 | width = os.get_terminal_size()[0]
397 | return 79 if width <= 0 else width
398 | except Exception:
399 | return 79
400 |
401 |
402 | def get_line_length(self):
403 | """Calculated line length."""
404 | if self.config.line_length <= 0:
405 | return self.get_terminal_width()
406 | else:
407 | return self.config.line_length
408 |
409 |
410 | def visible_length(self, s):
411 | """Visible length of string (i.e. without ansi escape sequences)"""
412 | return len(ansi_escape.sub('', s))
413 |
414 |
415 | def output_text(self, texts):
416 | """Write list of texts to stderr.
417 | Use this function for all output.
418 |
419 | texts: a string or a list of strings
420 | """
421 | if not isinstance(texts, (list, tuple)):
422 | texts = [texts]
423 | count = 0
424 | for text in texts:
425 | text = str(text)
426 | output_stderr.write(text)
427 | count += self.visible_length(text)
428 | line_length = self.get_line_length()
429 | if count == 0 or count % line_length != 0 or self.config.full_line_newline:
430 | output_stderr.write('\n')
431 | output_stderr.write(RESET_COLOR)
432 | if self.config.reset_stdout:
433 | sys.stdout.write(RESET_COLOR)
434 |
435 |
436 | def write_header(self):
437 | """Write stack trace header to screen.
438 |
439 | Should make use of:
440 | self.config.separator_character
441 | self.config.display_timestamp
442 | self.config.timestamp_function()
443 | self.config.header_color"""
444 | if not self.config.separator_character: return
445 | line_length = self.get_line_length()
446 | if self.config.display_timestamp:
447 | timestamp = str(self.config.timestamp_function())
448 | separator = (line_length - len(timestamp)) * self.config.separator_character + timestamp
449 | else:
450 | separator = line_length * self.config.separator_character
451 | self.output_text('')
452 | self.output_text([self.config.header_color, separator])
453 |
454 |
455 | def write_location(self, path, line, function):
456 | """Write location of frame to screen.
457 |
458 | Should make use of:
459 | self.config.filename_display
460 | self.config.filename_color
461 | self.config.line_number_color
462 | self.config.function_color
463 | self.config.line_number_first
464 | self.config.function_color
465 | self.config.display_link
466 | self.config.link_color
467 | """
468 | line_number = str(line) + ' '
469 | self.output_text('')
470 | if self.config.filename_display == FILENAME_FULL:
471 | filename = ""
472 | self.output_text([self.config.filename_color, path])
473 | self.output_text([self.config.line_number_color, line_number, self.config.function_color, function])
474 | else:
475 | if self.config.filename_display == FILENAME_EXTENDED:
476 | line_length = self.get_line_length()
477 | filename = path[-(line_length - len(line_number) - len(function) - 4):]
478 | if filename != path:
479 | filename = '...' + filename
480 | else:
481 | filename = os.path.basename(path)
482 | if self.config.line_number_first:
483 | self.output_text([
484 | self.config.line_number_color, line_number,
485 | self.config.function_color, function + ' ',
486 | self.config.filename_color, filename
487 | ])
488 | else:
489 | self.output_text([
490 | self.config.filename_color, filename + ' ',
491 | self.config.line_number_color, line_number,
492 | self.config.function_color, function
493 | ])
494 | if self.config.display_link:
495 | self.write_link(path, line)
496 |
497 |
498 | def write_link(self, filepath, line):
499 | """Write link of location to screen. Default version is clickable in VSCode.
500 |
501 | Should make use of:
502 | self.config.link_color
503 | """
504 |
505 | self.output_text([self.config.link_color, '"%s", line %s' % (filepath, line)])
506 |
507 |
508 | def write_code(self, filepath, line, module_globals, is_final, point_at = None):
509 | """Write frame code to screen.
510 | Parameters:
511 | filepath: path to code file
512 | line: line number in file
513 | module_globals: pass to linecache.getline()
514 | is_final: True if this is the last frame
515 | point_at: character position to point at
516 |
517 | Should make use of:
518 | self.config.lines_before
519 | self.config.lines_after
520 | self.config.trace_lines_before
521 | self.config.trace_lines_after
522 | self.config.truncate_code
523 | self.config.display_arrow
524 | self.config.arrow_head_character
525 | self.config.arrow_tail_character
526 | self.config.line_color
527 | self.config.code_color
528 | self.config.arrow_head_color
529 | self.config.arrow_tail_color
530 | self.config.syntax_error_color
531 | """
532 |
533 | lines = []
534 | if filepath == '':
535 | lines.append(str(line).rstrip())
536 | line = target_line = start = end = 0
537 | else:
538 | if is_final:
539 | target_line = self.config.lines_before
540 | start = line - self.config.lines_before
541 | end = line + self.config.lines_after
542 | else:
543 | target_line = self.config.trace_lines_before
544 | start = line - self.config.trace_lines_before
545 | end = line + self.config.trace_lines_after
546 |
547 | if start < 1:
548 | target_line -= (1 - start)
549 | start = 1
550 |
551 | for i in range(start, end + 1):
552 | lines.append(linecache.getline(filepath, i, module_globals).rstrip())
553 |
554 | min_lead = None
555 | for line in lines:
556 | if line.strip() == '': continue
557 | c = 0
558 | while c < len(line) and line[c] in (' ', '\t'):
559 | c += 1
560 | if min_lead is None or c < min_lead:
561 | min_lead = c
562 | if min_lead is None:
563 | min_lead = 0
564 | if min_lead > 0:
565 | lines = [line[min_lead:] for line in lines]
566 |
567 | line_length = self.get_line_length()
568 |
569 | for i, line in enumerate(lines):
570 | if i == target_line:
571 | color = self.config.line_color
572 | if point_at is not None:
573 | point_at -= (min_lead + 1)
574 | else:
575 | color = self.config.code_color
576 | color_length = self.visible_length(color)
577 | if self.config.truncate_code and len(line) + color_length > line_length:
578 | line = line[:line_length - color_length + 3] + '...'
579 | if i == target_line and point_at is not None:
580 | if point_at >= line_length:
581 | point_at = line_length - 1
582 | start_char = point_at
583 | while start_char > 0 and line[start_char - 1] not in (' ', '\t'):
584 | start_char -= 1
585 | end_char = point_at + 1
586 | while end_char < len(line) - 1 and line[end_char] not in (' ', '\t'):
587 | end_char += 1
588 | self.output_text([
589 | color, line[:start_char], RESET_COLOR,
590 | self.config.syntax_error_color, line[start_char:end_char], RESET_COLOR,
591 | color, line[end_char:]
592 | ])
593 | if self.config.display_arrow:
594 | self.output_text([
595 | self.config.arrow_tail_color, self.config.arrow_tail_character * point_at,
596 | self.config.arrow_head_color, self.config.arrow_head_character
597 | ])
598 | else:
599 | self.output_text([color, line])
600 |
601 | return '\n'.join(lines)
602 |
603 |
604 | def exception_name(self, exception):
605 | """Name of exception."""
606 | label = str(exception)
607 | if label.startswith(" 0:
621 | output = [
622 | self.config.exception_color, self.exception_name(exception_type), ':\n',
623 | self.config.exception_arg_color, '\n'.join((str(x) for x in exception_value.args))
624 | ]
625 | else:
626 | output = [self.config.exception_color, self.exception_name(exception_type)]
627 |
628 | for attr in ("filename", "filename2"):
629 | if hasattr(exception_value, attr):
630 | path = getattr(exception_value, attr)
631 | if path is not None:
632 | output.append('\n')
633 | output.append(self.config.exception_file_color)
634 | output.append(path)
635 |
636 | self.output_text(output)
637 |
638 |
639 |
640 | exception_writer = ExceptionWriter()
641 |
642 |
643 |
644 | def excepthook(exception_type, exception_value, traceback):
645 | "Replaces sys.excepthook to output pretty errors."
646 |
647 | writer = exception_writer
648 | writer.config = writer.default_config = config
649 |
650 |
651 | if (not writer.config.top_first and exception_value.__context__
652 | and (not exception_value.__suppress_context__ or config.show_suppressed)):
653 | excepthook(type(exception_value.__context__), exception_value.__context__, exception_value.__context__.__traceback__)
654 | writer.config = writer.default_config
655 | if writer.config.inner_exception_message != None:
656 | if writer.config.inner_exception_separator:
657 | writer.write_header()
658 | output_stderr.write(writer.config.inner_exception_message)
659 |
660 | def check_for_pathed_config(path):
661 | writer.config = writer.default_config
662 | for config_path in config_paths:
663 | if path.startswith(config_path):
664 | writer.config = config_paths[config_path]
665 | break
666 |
667 | if traceback:
668 | tb = traceback
669 | while tb.tb_next != None:
670 | tb = tb.tb_next
671 | check_for_pathed_config(os.path.normpath(tb.tb_frame.f_code.co_filename).lower())
672 | writer.default_config = writer.config
673 |
674 | writer.write_header()
675 |
676 | if writer.config.prefix != None:
677 | output_stderr.write(writer.config.prefix)
678 |
679 | syntax_error_info = None
680 | if exception_type == SyntaxError:
681 | syntax_error_info = exception_value.args[1]
682 | exception_value.args = [exception_value.args[0]]
683 |
684 | if writer.config.exception_above:
685 | writer.output_text('')
686 | writer.write_exception(exception_type, exception_value)
687 |
688 | if syntax_error_info:
689 | check_for_pathed_config(os.path.normpath(syntax_error_info[0]).lower())
690 | writer.write_location(syntax_error_info[0], syntax_error_info[1], '')
691 | if(syntax_error_info[0] == ''):
692 | writer.write_code(syntax_error_info[0], syntax_error_info[3], [], True, syntax_error_info[2])
693 | else:
694 | writer.write_code(syntax_error_info[0], syntax_error_info[1], [], True, syntax_error_info[2])
695 | else:
696 | tracebacks = []
697 | while traceback != None:
698 | path = os.path.normpath(traceback.tb_frame.f_code.co_filename).lower()
699 | if traceback.tb_next == None or (writer.config.always_display_bottom and tracebacks == []):
700 | tracebacks.append(traceback)
701 | else:
702 | if whitelist_paths:
703 | for white in whitelist_paths:
704 | if path.startswith(white): break
705 | else:
706 | traceback = traceback.tb_next
707 | continue
708 | for black in blacklist_paths:
709 | if path.startswith(black): break
710 | else:
711 | tracebacks.append(traceback)
712 | traceback = traceback.tb_next
713 |
714 | if writer.config.top_first:
715 | tracebacks.reverse()
716 | if writer.config.stack_depth > 0:
717 | if writer.config.always_display_bottom and len(tracebacks) > 1:
718 | tracebacks = tracebacks[:writer.config.stack_depth] + tracebacks[-1:]
719 | else:
720 | tracebacks = tracebacks[:writer.config.stack_depth]
721 | final = 0
722 | else:
723 | if writer.config.stack_depth > 0:
724 | if writer.config.always_display_bottom and len(tracebacks) > 1:
725 | tracebacks = tracebacks[:1] + tracebacks[-writer.config.stack_depth:]
726 | else:
727 | tracebacks = tracebacks[-writer.config.stack_depth:]
728 | final = len(tracebacks) - 1
729 |
730 | for count, traceback in enumerate(tracebacks):
731 | path = os.path.normpath(traceback.tb_frame.f_code.co_filename).lower()
732 | check_for_pathed_config(path)
733 |
734 | if writer.config.infix != None and count != 0:
735 | output_stderr.write(writer.config.infix)
736 |
737 | frame = traceback.tb_frame
738 | code = frame.f_code
739 | writer.write_location(code.co_filename, traceback.tb_lineno, code.co_name)
740 | code_string = writer.write_code(code.co_filename, traceback.tb_lineno, frame.f_globals, count == final)
741 |
742 | if (writer.config.display_locals and count == final) or (writer.config.display_trace_locals and count != final):
743 | local_variables = [(code_string.find(x), x) for x in frame.f_locals]
744 | local_variables.sort()
745 | local_variables = [x[1] for x in local_variables if x[0] >= 0]
746 | if local_variables:
747 | writer.output_text('')
748 | spacer = ': '
749 | len_spacer = '... '
750 | line_length = writer.get_line_length()
751 | for local in local_variables:
752 | value = str(frame.f_locals[local])
753 | output = [writer.config.local_name_color, local, spacer, writer.config.local_value_color]
754 | if writer.config.truncate_locals and len(local) + len(spacer) + len(value) > line_length:
755 | length = '[' + str(len(value)) + ']'
756 | value = value[:line_length - (len(local) + len(spacer) + len(len_spacer) + len(length))]
757 | output += [value, len_spacer, writer.config.local_len_color, length]
758 | else:
759 | output += [value]
760 | writer.output_text(output)
761 |
762 | writer.config = writer.default_config
763 |
764 | if writer.config.exception_below:
765 | writer.output_text('')
766 | writer.write_exception(exception_type, exception_value)
767 |
768 | if writer.config.postfix != None:
769 | output_stderr.write(writer.config.postfix)
770 |
771 | if (writer.config.top_first and exception_value.__context__ and
772 | (not exception_value.__suppress_context__ or config.show_suppressed)):
773 | if writer.config.inner_exception_message != None:
774 | if writer.config.inner_exception_separator:
775 | writer.write_header()
776 | output_stderr.write(writer.config.inner_exception_message)
777 | excepthook(type(exception_value.__context__), exception_value.__context__, exception_value.__context__.__traceback__)
778 |
779 |
780 |
781 | location_expression = re.compile(r'.*File "([^"]*)", line ([0-9]+), in (.*)')
782 |
783 | class StdErr():
784 | """Replaces sys.stderr in order to scrape it, when capturing stack trace is unavailable."""
785 | def __init__(self):
786 | self.__in_exception = False
787 | self.__awaiting_code = False
788 | self.__awaiting_end = 0
789 | self.__frames = []
790 | self.__point_at = None
791 |
792 |
793 | def __getattr__(self, name):
794 | return getattr(output_stderr, name)
795 |
796 |
797 | def __enter__(self, *args, **kwargs):
798 | return output_stderr.__enter__(*args, **kwargs)
799 |
800 |
801 | def __is_header(self, text):
802 | """Is text a traceback header?"""
803 | return text.startswith('Traceback (most recent call last):')
804 |
805 |
806 | def __is_outer_exception(self, text):
807 | """Is text a notification for an outer exception?"""
808 | return text.startswith('During handling of the above exception, another exception occurred:')
809 |
810 |
811 | def __get_location(self, text):
812 | """Extract location of exception. If it returns None then text was not a location identifier."""
813 | location = location_expression.match(text)
814 | if location:
815 | return (location.group(1), int(location.group(2)), location.group(3))
816 | else:
817 | return None
818 |
819 |
820 | def __write_exception(self):
821 | """Write exception, prettily."""
822 | if not self.__frames: return
823 |
824 | writer = exception_writer
825 | writer.config = writer.default_config = config
826 |
827 | def check_for_pathed_config(path):
828 | writer.config = writer.default_config
829 | for config_path in config_paths:
830 | if path.startswith(config_path):
831 | writer.config = config_paths[config_path]
832 | break
833 |
834 | path, line_number, function = self.__frames[-1]
835 | check_for_pathed_config(os.path.normpath(path.lower()))
836 | writer.default_config = writer.config
837 |
838 | writer.write_header()
839 |
840 | if writer.config.prefix != None:
841 | output_stderr.write(writer.config.prefix)
842 |
843 | exception_type = self.__exception
844 | if self.__exception_args:
845 | class ExceptionValue():
846 | def __init__(self):
847 | self.args = []
848 | exception_value = ExceptionValue()
849 | if self.__exception_args.startswith('('):
850 | for arg in self.__exception_args[1:-1].split(', '):
851 | exception_value.args.append(arg)
852 | else:
853 | exception_value.args.append(self.__exception_args)
854 | else:
855 | exception_value = None
856 |
857 | syntax_error_info = None
858 | if self.__exception == 'SyntaxError':
859 | syntax_error_info = self.__frames[-1]
860 | syntax_error_info[2] = self.__point_at
861 |
862 | if writer.config.exception_above:
863 | writer.output_text('')
864 | writer.write_exception(exception_type, exception_value)
865 |
866 | if syntax_error_info:
867 | check_for_pathed_config(os.path.normpath(syntax_error_info[0]).lower())
868 | writer.write_location(syntax_error_info[0], syntax_error_info[1], '')
869 | writer.write_code(syntax_error_info[0], syntax_error_info[1], [], True, syntax_error_info[2])
870 | else:
871 | tracebacks = []
872 | for i, frame in enumerate(self.__frames):
873 | path, line_number, function = frame
874 | path = os.path.normpath(path).lower()
875 | if (i == len(self.__frames) - 1) or (writer.config.always_display_bottom and tracebacks == []):
876 | tracebacks.append(frame)
877 | else:
878 | if whitelist_paths:
879 | for white in whitelist_paths:
880 | if path.startswith(white): break
881 | else:
882 | continue
883 | for black in blacklist_paths:
884 | if path.startswith(black): break
885 | else:
886 | tracebacks.append(frame)
887 |
888 | if writer.config.top_first:
889 | tracebacks.reverse()
890 | if writer.config.stack_depth > 0:
891 | if writer.config.always_display_bottom and len(tracebacks) > 1:
892 | tracebacks = tracebacks[:writer.config.stack_depth] + tracebacks[-1:]
893 | else:
894 | tracebacks = tracebacks[:writer.config.stack_depth]
895 | final = 0
896 | else:
897 | if writer.config.stack_depth > 0:
898 | if writer.config.always_display_bottom and len(tracebacks) > 1:
899 | tracebacks = tracebacks[:1] + tracebacks[-writer.config.stack_depth:]
900 | else:
901 | tracebacks = tracebacks[-writer.config.stack_depth:]
902 | final = len(tracebacks) - 1
903 |
904 | for count, traceback in enumerate(tracebacks):
905 | path, line_number, function = traceback
906 | normpath = os.path.normpath(path).lower()
907 | check_for_pathed_config(normpath)
908 |
909 | if writer.config.infix != None and count != 0:
910 | output_stderr.write(writer.config.infix)
911 |
912 | writer.write_location(path, line_number, function)
913 | writer.write_code(path, line_number, [], count == final)
914 |
915 | writer.config = writer.default_config
916 |
917 | if writer.config.exception_below:
918 | writer.output_text('')
919 | writer.write_exception(exception_type, exception_value)
920 |
921 | if writer.config.postfix != None:
922 | output_stderr.write(writer.config.postfix)
923 |
924 |
925 | def write(self, text):
926 | """Replaces sys.stderr.write, outputing pretty errors."""
927 | if not self.__in_exception and not text.split():
928 | output_stderr.write(text)
929 | else:
930 | for line in text.split('\n'):
931 | if self.__awaiting_end > 0:
932 | self.__awaiting_end -= 1
933 | if self.__awaiting_end == 0:
934 | self.__exception_args = line.strip()
935 | self.__in_exception = False
936 | self.__write_exception()
937 | elif self.__in_exception:
938 | if not line.strip():
939 | pass
940 | elif self.__awaiting_code:
941 | self.__awaiting_code = False
942 | else:
943 | location = self.__get_location(line)
944 | if location:
945 | self.__frames.append(location)
946 | self.__awaiting_code = True
947 | elif line.strip() == '^':
948 | self.__point_at = len(line) - 2
949 | else: # end of traceback
950 | if ': ' in line:
951 | self.__exception, self.__exception_args = line.strip().split(': ', 1)
952 | self.__in_exception = False
953 | self.__write_exception()
954 | else:
955 | self.__awaiting_end = 2
956 | self.__exception = line.strip()
957 | elif self.__is_header(line):
958 | self.__in_exception = True
959 | self.__awaiting_code = False
960 | self.__frames = []
961 | elif self.__is_outer_exception(line):
962 | if config.inner_exception_message != None:
963 | if config.inner_exception_separator:
964 | exception_writer.write_header()
965 | output_stderr.write(config.inner_exception_message)
966 | else:
967 | output_stderr.write(line)
968 |
969 |
970 |
971 | def replace_stderr(force = False):
972 | """Replace sys.stderr, for cases where standard use with activate() does not work."""
973 | if (force or not interactive_tty_only or terminal_is_interactive):
974 | sys.stderr = StdErr()
975 |
976 |
977 | def activate():
978 | """Set sys.excepthook to use pretty errors."""
979 | sys.excepthook = excepthook
980 |
981 |
982 | if active and (not interactive_tty_only or terminal_is_interactive):
983 | activate()
984 |
985 |
986 |
987 | if __name__ == "__main__":
988 | configure(
989 | filename_display = FILENAME_EXTENDED,
990 | line_number_first = True,
991 | display_link = True,
992 | lines_before = 5,
993 | lines_after = 2,
994 | line_color = RED + '> ' + default_config.line_color,
995 | code_color = ' ' + default_config.line_color,
996 | truncate_code = True,
997 | inner_exception_separator=True,
998 | inner_exception_message = MAGENTA + "\n During handling of the above exception, another exception occurred:\n",
999 | display_locals = True
1000 | )
1001 | blacklist('c:/python')
1002 | try:
1003 | myval = [1,2]
1004 | print(myval[3])
1005 | except:
1006 | a = "C" * "B"
1007 |
--------------------------------------------------------------------------------
/pretty_errors/__main__.py:
--------------------------------------------------------------------------------
1 | import pretty_errors, sys, os, re, site
2 | if '-h' in sys.argv or '--help' in sys.argv or '/?' in sys.argv:
3 | print("""\
4 | Run without options to use interactive mode, otherwise:
5 |
6 | python -m pretty_errors [-m] <-u|-s> [-p] OR <-f> OR <-c>
7 |
8 | -m = monochrome (do not use color escape codes in output of this menu)
9 | -u = install in default user location
10 | -s = install in default system location
11 | -p = with above, install in pretty_errors.pth instead of *customize.py
12 | -f = find where pretty_errors is installed
13 | -c = clean pretty_errors from python startup (do so before uninstalling)""")
14 |
15 | else:
16 | find = ('-f' in sys.argv)
17 | add_to_user = ('-u' in sys.argv)
18 | add_to_site = ('-s' in sys.argv)
19 | pth = ('-p' in sys.argv)
20 | clean = ('-c' in sys.argv)
21 | mono = ('-m' in sys.argv)
22 |
23 | if not mono:
24 | reset_color = pretty_errors.RESET_COLOR
25 | file_color = pretty_errors.BRIGHT_CYAN
26 | message_color = pretty_errors.BRIGHT_RED
27 | key_color = pretty_errors.YELLOW
28 | error_color = pretty_errors.RED
29 | else:
30 | reset_color = ''
31 | file_color = ''
32 | message_color = ''
33 | key_color = ''
34 | error_color = ''
35 |
36 | check = re.compile(r'^\s*import\s+\bpretty_errors\b', re.MULTILINE)
37 |
38 | in_virtualenv = 'VIRTUAL_ENV' in os.environ
39 | # in virtualenv, `site` has no attribute `getsitepackages` or `getusersitepackages`
40 | if in_virtualenv:
41 | def getsitepackages():
42 | if os.path.sep == "\\":
43 | sep = "\\\\"
44 | env_path = os.environ['VIRTUAL_ENV'].replace("\\", "\\\\")
45 | else:
46 | sep = os.path.sep
47 | env_path = os.environ['VIRTUAL_ENV']
48 | pattern1 = re.compile(r'^%s$' % sep.join([env_path, 'lib', 'python[0-9.]+', 'site-packages']))
49 | pattern2 = re.compile(r'^%s$' % sep.join([env_path, 'lib', 'site-packages']))
50 | paths = [path for path in set(sys.path) if pattern1.search(path) or pattern2.search(path)]
51 | return paths
52 |
53 | def getusersitepackages():
54 | return []
55 | else:
56 | getsitepackages = site.getsitepackages
57 |
58 | def getusersitepackages():
59 | return [site.getusersitepackages()]
60 |
61 | def getallsitepackages():
62 | return getsitepackages() + getusersitepackages()
63 |
64 |
65 | def readfile(path):
66 | try:
67 | return ''.join((x for x in open(path)))
68 | except IOError:
69 | return ''
70 |
71 |
72 | def find_install(quiet = False):
73 | found = False
74 | for path in getallsitepackages():
75 | for filename in 'usercustomize.py', 'sitecustomize.py', 'pretty_errors.pth':
76 | filepath = os.path.join(path, filename)
77 | if check.search(readfile(filepath)):
78 | if not found:
79 | print(message_color + '\n\npretty_errors found in:' + file_color)
80 | found = True
81 | print(filepath)
82 | print(reset_color)
83 | if not found and not quiet:
84 | print(message_color + '\npretty_errors not currently installed in any expected locations.\n' + reset_color)
85 | return found
86 |
87 | def clean_install():
88 | files_to_edit = []
89 | files_to_remove = []
90 | for path in getallsitepackages():
91 | for filename in 'usercustomize.py', 'sitecustomize.py', 'pretty_errors.pth':
92 | filepath = os.path.join(path, filename)
93 | data = readfile(filepath).strip()
94 | if check.search(data):
95 | if filename.endswith('.pth') or (
96 | data.startswith('### BEGIN PRETTY ERRORS') and
97 | data.endswith('### END PRETTY ERRORS')):
98 | files_to_remove.append(filepath)
99 | else:
100 | files_to_edit.append(filepath)
101 | if not files_to_edit and not files_to_remove:
102 | print(message_color + '\npretty_errors not currently installed in any expected locations.' + reset_color)
103 | return
104 | if files_to_remove:
105 | print('\nAttempting to remove the following files:\n')
106 | for filepath in files_to_remove:
107 | print(file_color + filepath + reset_color, end='... ')
108 | try:
109 | os.remove(filepath)
110 | except Exception as e:
111 | print(error_color + 'ERR\n' + str(e) + reset_color)
112 | else:
113 | print('OK')
114 | if files_to_edit:
115 | print('\n\nFound entry for pretty_errors in the following files.\n' +
116 | message_color +
117 | 'Please edit and remove the relevant section:\n')
118 | for filepath in files_to_edit:
119 | print(file_color + filepath + reset_color)
120 |
121 | if find:
122 | find_install()
123 | sys.exit(0)
124 | elif clean:
125 | clean_install()
126 | sys.exit(0)
127 |
128 | if add_to_user:
129 | if in_virtualenv:
130 | print('You are now in virtualenv, which has sitepackage but no user sitepackages, ' +
131 | message_color + '`-u`' + reset_color + ' is incompatible with virtualenv.')
132 | print('Please use ' + message_color + '`-s`' + reset_color + ' instead.')
133 | sys.exit(1)
134 | path = getusersitepackages()[0]
135 | filename = 'pretty_errors.pth' if pth else 'usercustomize.py'
136 | elif add_to_site:
137 | path = getsitepackages()[0]
138 | filename = 'pretty_errors.pth' if pth else 'sitecustomize.py'
139 | else:
140 |
141 | found = find_install(True)
142 |
143 | def prompt(key, caption, is_path = False):
144 | if is_path:
145 | print(key_color + key + reset_color + ': ' + file_color + caption + reset_color)
146 | else:
147 | print(key_color + key + reset_color + ': ' + caption + reset_color)
148 |
149 | def get_choice(query, choices, default = None):
150 | options = {}
151 | for i in range(len(choices)):
152 | options[str(i + 1)] = i
153 | while True:
154 | print()
155 | print(' ' + query)
156 | print()
157 | if found:
158 | prompt('C', 'Clean startup files (do so before uninstalling pretty_errors)')
159 | for option, choice in enumerate(choices):
160 | prompt('%d' % (option + 1), choice, True)
161 | prompt('0', 'Exit')
162 | if default is None:
163 | print('\nOption: ', end='')
164 | else:
165 | print('\nOption: [default: ' + message_color + str(default + 1) + reset_color + '] ', end='')
166 | choice = input()
167 | if choice == '' and default is not None:
168 | choice = str(default + 1)
169 | if choice == '0':
170 | sys.exit(0)
171 | elif choice.lower() == 'c':
172 | clean_install()
173 | sys.exit(0)
174 | elif choice in options:
175 | return options[choice]
176 |
177 | formatting = {'file': file_color, 'msg': message_color, 'reset': reset_color, 'key': key_color}
178 | print("""
179 | To have pretty_errors be used when you run any python file you may add it to your\
180 | %(file)s usercustomize.py %(reset)s(user level) or\
181 | %(file)s sitecustomize.py %(reset)s(system level), or to\
182 | %(file)s pretty_errors.pth%(reset)s.
183 |
184 | (just hit %(key)s%(reset)s to accept the defaults if you are unsure)
185 | """ % formatting)
186 |
187 | paths = getallsitepackages()
188 | path = paths[get_choice('Choose folder to install into:', paths, -1 if found else len(paths) - 1)]
189 |
190 | if path in getsitepackages():
191 | filenames = ['sitecustomize.py', 'pretty_errors.pth']
192 | else:
193 | filenames = ['usercustomize.py', 'pretty_errors.pth']
194 | filename = filenames[get_choice('Choose file to install into:', filenames, 0)]
195 |
196 | if filename.endswith('.pth'):
197 | output = (os.path.dirname(os.path.dirname(os.path.normpath(__file__))) +
198 | '\nimport pretty_errors; ' +
199 | '#pretty_errors.configure() ' +
200 | '# keep on one line, for options see ' +
201 | 'https://github.com/onelivesleft/PrettyErrors/blob/master/README.md'
202 | )
203 | else:
204 | output = []
205 | output.append('''
206 |
207 | ### BEGIN PRETTY ERRORS
208 |
209 | # pretty-errors package to make exception reports legible.
210 | # v%s generated this config: newer version may have added methods/options not present!
211 |
212 | try:
213 | import pretty_errors
214 | except ImportError:
215 | print(
216 | 'You have uninstalled pretty_errors but it is still present in your python startup.' +
217 | ' Please remove its section from file:\\n ' + __file__ + '\\n'
218 | )
219 | else:
220 | pass
221 |
222 | # Use if you do not have a color terminal:
223 | #pretty_errors.mono()
224 |
225 | # Use if you are using a framework which is handling all the exceptions before pretty_errors can:
226 | #if pretty_errors.active:
227 | # pretty_errors.replace_stderr()
228 |
229 | # Use to hide frames whose file begins with these paths:
230 | #pretty_errors.blacklist('/path/to/blacklist', '/other/path/to/blacklist', ...)
231 |
232 | # Use to only show frames whose file begins with these paths:
233 | #pretty_errors.whitelist('/path/to/whitelist', '/other/path/to/whitelist', ...)
234 |
235 | # Use to selectively set a config based on the path to the code of the current frame.
236 | #alternate_config = pretty_errors.config.copy()
237 | #pretty_errors.pathed_config(alternate_config, '/use/alternate/for/this/path')
238 |
239 | # Use to configure output: Uncomment each line to change that setting.
240 | """pretty_errors.configure(
241 | ''' % pretty_errors.__version__)
242 |
243 | options = []
244 | colors = []
245 | parameters = []
246 | max_length = 0
247 | for option in dir(pretty_errors.config):
248 | if len(option) > max_length:
249 | max_length = len(option)
250 | if (option not in ('configure', 'mono', 'copy') and
251 | not option.startswith('_')):
252 | if option.endswith('_color'):
253 | colors.append(option)
254 | elif option != 'name':
255 | options.append(option)
256 | indent = ' '
257 | def prefix(s):
258 | return indent + '#' + s.ljust(max_length) + ' = '
259 | for option in sorted(options):
260 | if option == 'filename_display':
261 | parameters.append(prefix(option) + 'pretty_errors.FILENAME_COMPACT, # FILENAME_EXTENDED | FILENAME_FULL')
262 | elif option == 'timestamp_function':
263 | parameters.append(prefix(option) + 'time.perf_counter')
264 | else:
265 | parameters.append(prefix(option) + repr(getattr(pretty_errors.config, option)))
266 | for option in sorted(colors):
267 | parameters.append(prefix(option) + repr(getattr(pretty_errors.config, option)))
268 | parameters.append('\n' + indent + 'name = "custom" # name it whatever you want')
269 | output.append(',\n'.join(parameters))
270 | output.append('\n )"""\n')
271 | output.append('### END PRETTY ERRORS\n')
272 | output = '\n'.join(output)
273 |
274 | print('\n--------------')
275 |
276 | filepath = os.path.join(path, filename)
277 | if check.search(readfile(filepath)):
278 | print('\npretty_errors already present in:\n' + file_color + '\n' + filepath +
279 | '\n' + reset_color + '\nEdit it to set config options.\n')
280 | sys.exit(0)
281 |
282 | try:
283 | os.makedirs(path)
284 | except Exception:
285 | pass
286 | try:
287 | out = open(filepath, 'a')
288 | out.write(output)
289 | out.close()
290 | except Exception:
291 | print('\nFailed to write to:\n' + filepath)
292 | else:
293 | print('\npretty_errors added to:\n' + file_color + '\n' + filepath + '\n' + reset_color + '\nEdit it to set config options.\n')
294 | if filepath.endswith('.pth'):
295 | print(error_color + '\n*** Delete this file when you uninstall pretty_errors! ***\n' + reset_color)
296 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 |
3 | with open("README.md", "r") as fh:
4 | long_description = fh.read()
5 |
6 | setuptools.setup(
7 | name="pretty_errors",
8 | version="1.2.25", # remember to also update __version__ in __init__.py!
9 | author="Iain King",
10 | author_email="iain.king@gmail.com",
11 | description="Prettifies Python exception output to make it legible.",
12 | long_description=long_description,
13 | long_description_content_type="text/markdown",
14 | url="https://github.com/onelivesleft/PrettyErrors/",
15 | packages=setuptools.find_packages(),
16 | install_requires=[
17 | "colorama",
18 | ],
19 | classifiers=[
20 | "Programming Language :: Python :: 3",
21 | "Programming Language :: Python :: 2",
22 | "License :: OSI Approved :: MIT License",
23 | "Operating System :: OS Independent",
24 | ],
25 | )
26 |
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 | import pretty_errors
2 |
3 | pretty_errors.configure(
4 | separator_character = '*',
5 | filename_display = pretty_errors.FILENAME_FULL,
6 | lines_before=10,
7 | code_color = pretty_errors.default_config.line_color,
8 | stack_depth=2,
9 | exception_file_color=pretty_errors.BRIGHT_BLUE
10 | )
11 | irrelevant = pretty_errors.config.copy()
12 | irrelevant.line_color = irrelevant.code_color = irrelevant.filename_color = irrelevant.function_color = irrelevant.line_number_color = (
13 | pretty_errors.GREY
14 | )
15 | pretty_errors.pathed_config(irrelevant, 'c:/python', 'c:/users')
16 |
17 |
18 | import os
19 | os.rename('nofile', 'stillnofile')
20 |
--------------------------------------------------------------------------------
/upload.bat:
--------------------------------------------------------------------------------
1 | rem twine upload --repository-url https://test.pypi.org/legacy/ dist/*
2 | twine upload dist/*
3 |
--------------------------------------------------------------------------------