fastapi / typer

Typer, build great CLIs. Easy to code. Based on Python type hints.
https://typer.tiangolo.com/
MIT License
15.6k stars 664 forks source link

[QUESTION] Disable traceback globally on production #525

Closed moracabanas closed 1 year ago

moracabanas commented 1 year ago

First Check

Commit to Help

Example Code

import typer
import requests
from rich import print
import logging
from rich.logging import RichHandler

logging.basicConfig(
    level="DEBUG",
    format="%(message)s",
    datefmt="[%X]",
    handlers=[RichHandler(rich_tracebacks=True)],
)

log = logging.getLogger("rich")

app = typer.Typer(pretty_exceptions_show_locals=False)

greetings_login = "Login with your Portainer Url, Username and Password to deploy projects to your Portainer i.e.: https://my-portainer.example.com"

@app.command()
def login(
    url:      str = typer.Option(..., prompt=f'{greetings_login}\nUrl'), 
    username: str = typer.Option(..., prompt=True),
    password: str = typer.Option(..., prompt=True, hide_input=True)
):
    url = f'{url}/api/auth'

    payload = {
        "Username": username,
        "Password": password
    }

    try:
        response = requests.request("POST", url, json=payload)
        if (response.status_code == 200):
            print('[green]Successful login โœ…[/green] saving credentials...')
            with open("./.jwt", "w") as f:
                f.write(response.text)
        else:
            print(f'Error: {response.reason}')
    except Exception as e:
        log.exception(e)

@app.command()
def logout(
    force: bool = typer.Option(..., prompt="Are you sure you want to log out?"),
):
    if force:
        print(f"Deleting credentials")
        #TODO implement check .jwt exist then delete
    else:
        print("Operation cancelled")

if __name__ == "__main__":
    app()

Description

I am noob at python trying to write a Portainer CLI client inspired by docker CLI, for learning purposes and speed up personal deployments.

The code shown is just my first try at create a persisted login based on a .jwt local file once a succesful 200 response (and etc... future checks)

Obviously there are tons of unhandled errors and you might notice I am a completely uneducated illiterate when it comes to error handling. My application will burst in imaginative ways I am not even prepared to unit test them

Anyway, when I am checking the login() flow, I test it for breaking inputs like bad Url, bad credentials etc... Then I got several raised errors from requests or whatever imported lib which is giving up:

[21:07:29] ERROR    Invalid URL 'asdfasdf/api/auth': No scheme supplied. Perhaps you meant http://asdfasdf/api/auth?                   main.py:52
                    โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ Traceback (most recent call last) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ           
                    โ”‚ /home/becareo/passbolt/main.py:44 in login                                                                     โ”‚           
                    โ”‚                                                                                                                โ”‚           
                    โ”‚   41 โ”‚   }                                                                                                     โ”‚           
                    โ”‚   42 โ”‚                                                                                                         โ”‚           
                    โ”‚   43 โ”‚   try:                                                                                                  โ”‚           
                    โ”‚ โฑ 44 โ”‚   โ”‚   response = requests.request("POST", url, json=payload)                                            โ”‚           
                    โ”‚   45 โ”‚   โ”‚   if (response.status_code == 200):                                                                 โ”‚           
                    โ”‚   46 โ”‚   โ”‚   โ”‚   print('[green]Successful login โœ…[/green] saving credentials...')                             โ”‚           
                    โ”‚   47 โ”‚   โ”‚   โ”‚   with open("./.jwt", "w") as f:                                                                โ”‚           
                    โ”‚                                                                                                                โ”‚           
                    โ”‚ /home/becareo/passbolt/.venv/lib/python3.11/site-packages/requests/api.py:59 in request                        โ”‚           
                    โ”‚                                                                                                                โ”‚           
                    โ”‚    56 โ”‚   # avoid leaving sockets open which can trigger a ResourceWarning in some                             โ”‚           
                    โ”‚    57 โ”‚   # cases, and look like a memory leak in others.                                                      โ”‚           
                    โ”‚    58 โ”‚   with sessions.Session() as session:                                                                  โ”‚           
                    โ”‚ โฑ  59 โ”‚   โ”‚   return session.request(method=method, url=url, **kwargs)                                         โ”‚           
                    โ”‚    60                                                                                                          โ”‚           
                    โ”‚    61                                                                                                          โ”‚           
                    โ”‚    62 def get(url, params=None, **kwargs):                                                                     โ”‚           
                    โ”‚                                                                                                                โ”‚           
                    โ”‚ /home/becareo/passbolt/.venv/lib/python3.11/site-packages/requests/sessions.py:573 in request                  โ”‚           
                    โ”‚                                                                                                                โ”‚           
                    โ”‚   570 โ”‚   โ”‚   โ”‚   cookies=cookies,                                                                             โ”‚           
                    โ”‚   571 โ”‚   โ”‚   โ”‚   hooks=hooks,                                                                                 โ”‚           
                    โ”‚   572 โ”‚   โ”‚   )                                                                                                โ”‚           
                    โ”‚ โฑ 573 โ”‚   โ”‚   prep = self.prepare_request(req)                                                                 โ”‚           
                    โ”‚   574 โ”‚   โ”‚                                                                                                    โ”‚           
                    โ”‚   575 โ”‚   โ”‚   proxies = proxies or {}                                                                          โ”‚           
                    โ”‚   576                                                                                                          โ”‚           
                    โ”‚                                                                                                                โ”‚           
                    โ”‚ /home/becareo/passbolt/.venv/lib/python3.11/site-packages/requests/sessions.py:484 in prepare_request          โ”‚           
                    โ”‚                                                                                                                โ”‚           
                    โ”‚   481 โ”‚   โ”‚   โ”‚   auth = get_netrc_auth(request.url)                                                           โ”‚           
                    โ”‚   482 โ”‚   โ”‚                                                                                                    โ”‚           
                    โ”‚   483 โ”‚   โ”‚   p = PreparedRequest()                                                                            โ”‚           
                    โ”‚ โฑ 484 โ”‚   โ”‚   p.prepare(                                                                                       โ”‚           
                    โ”‚   485 โ”‚   โ”‚   โ”‚   method=request.method.upper(),                                                               โ”‚           
                    โ”‚   486 โ”‚   โ”‚   โ”‚   url=request.url,                                                                             โ”‚           
                    โ”‚   487 โ”‚   โ”‚   โ”‚   files=request.files,                                                                         โ”‚           
                    โ”‚                                                                                                                โ”‚           
                    โ”‚ /home/becareo/passbolt/.venv/lib/python3.11/site-packages/requests/models.py:368 in prepare                    โ”‚           
                    โ”‚                                                                                                                โ”‚           
                    โ”‚    365 โ”‚   โ”‚   """Prepares the entire request with the given parameters."""                                    โ”‚           
                    โ”‚    366 โ”‚   โ”‚                                                                                                   โ”‚           
                    โ”‚    367 โ”‚   โ”‚   self.prepare_method(method)                                                                     โ”‚           
                    โ”‚ โฑ  368 โ”‚   โ”‚   self.prepare_url(url, params)                                                                   โ”‚           
                    โ”‚    369 โ”‚   โ”‚   self.prepare_headers(headers)                                                                   โ”‚           
                    โ”‚    370 โ”‚   โ”‚   self.prepare_cookies(cookies)                                                                   โ”‚           
                    โ”‚    371 โ”‚   โ”‚   self.prepare_body(data, files, json)                                                            โ”‚           
                    โ”‚                                                                                                                โ”‚           
                    โ”‚ /home/becareo/passbolt/.venv/lib/python3.11/site-packages/requests/models.py:439 in prepare_url                โ”‚           
                    โ”‚                                                                                                                โ”‚           
                    โ”‚    436 โ”‚   โ”‚   โ”‚   raise InvalidURL(*e.args)                                                                   โ”‚           
                    โ”‚    437 โ”‚   โ”‚                                                                                                   โ”‚           
                    โ”‚    438 โ”‚   โ”‚   if not scheme:                                                                                  โ”‚           
                    โ”‚ โฑ  439 โ”‚   โ”‚   โ”‚   raise MissingSchema(                                                                        โ”‚           
                    โ”‚    440 โ”‚   โ”‚   โ”‚   โ”‚   f"Invalid URL {url!r}: No scheme supplied. "                                            โ”‚           
                    โ”‚    441 โ”‚   โ”‚   โ”‚   โ”‚   f"Perhaps you meant http://{url}?"                                                      โ”‚           
                    โ”‚    442 โ”‚   โ”‚   โ”‚   )                                                                                           โ”‚           
                    โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ           
                    MissingSchema: Invalid URL 'asdfasdf/api/auth': No scheme supplied. Perhaps you meant http://asdfasdf/api/auth? 

I will pack this CLI with pyinstaller hiding its implementation and most important thing, hiding stressful tracebacks for the average user, preserving usefull error like the last raised one:

MissingSchema: Invalid URL 'asdfasdf/api/auth': No scheme supplied. Perhaps you meant http://asdfasdf/api/auth?

I have searched for the correct way to preserve useful errors for the user, hiding the tracebacks and I have come across sys.excepthook exception handlers with no luck.

Tracebacks are enabled with any code hack I've tried. I am a bit lost with providing a usefull error handling for the user here.

Would be awesome if someone more experience could point me to the right direction with useful tips about this

Operating System

Linux, Windows

Operating System Details

Windows 11 WSL2 Poetry

Typer Version

extras = ["all"], version = "^0.7.0"

Python Version

3.11.0

Additional Context

No response

jonaslb commented 1 year ago

Well, you're a self-admitted noob, so take this advice first: It's really much easier if you Google your question first :)

This is the first answer for "python disable traceback", and by the sounds of things it's exactly what you need: https://stackoverflow.com/questions/27674602/hide-traceback-unless-a-debug-flag-is-set

Then you can check the docs about how to add a boolean value (flag) to your typer CLI afterwards :)

moracabanas commented 1 year ago

Thanks for posting. Not gonna lie I am a little bit mad about your response because, I am doing this to learn, and believe me, I have spent several days crawling the web to learn and understand a proper way (if any) to work with python environmental specific error logging. Still unclear I make an elaborated and humble question because my experience is low compared to other developers. Because I am specifically able to ask questions here, my wish is to find someone more experience than me on this topic, which could point me to the right direction. All the stackoverflow solutions I have tried were so unclear or didn't work at all. The fact I am here asking the community in the most humble form I can speak is just because I give up on stackoverflow trying to disable and understand trackbacks and neither solution was clear for me. So, please given an elaborated question, just to marking out the guy asking is a noob and pointing to the first google result could be an improper way to help people to grow as developers. Also this kind of situations leads people to avoid looking for help. Note that I am not disregarding your help try, I am just saying, when a developer is looking for help to get deeper insights on advanced language core topics there is manners and etiquette about it. I am just noob at python I am educated on google searching and other programming topics.. Blaming people's lack of experience when they are looking for help in specific topic with no lazy posting is unexpected by any open source community means. Hope you understand

jonaslb commented 1 year ago

Hi, sorry that you felt offended, that was not intended. But it is just a fact that Google result number 1 had your answer. I understand if you maybe didn't use those search terms though.

Regarding whether it works or not - it does, I tested it. You do need to set the excepthook (when using rich), because it is already overridden to not respect the tracebacklimit variable.

I wish you the best on your Python learning journey.

tiangolo commented 1 year ago

@moracabanas there's an undocumented feature in Rich to hide tracebacks below a certain level, by putting a variable _rich_traceback_guard with a boolean value (True or False).

You can see how it's used internally here: https://github.com/tiangolo/typer/blob/master/typer/main.py#L675

Maybe that can give you a hint about what to explore and play with. :nerd_face: