colorama icon indicating copy to clipboard operation
colorama copied to clipboard

colorama is not working with Travis CI under specific circumstance

Open funilrys opened this issue 6 years ago • 3 comments

Hello there!

This issue is more likely a help wanted than anything. I'm writing PyFunceble (in this issue I'm referencing to the dev branch) and since more than 2 months, I'm trying to find why colorama is not working under a specific circumstance.

So back to our issue:

Case that it is working

As PyFunceble is running for more than 6 months every day under Travis CI, server and sometimes PC, I consider the following as safe to mention.

Locally

If I run PyFunceble (the executable) locally it's working.

Same if I create a script like the following, it's working.

#!/usr/bin/bash

PyFunceble -d github.com

Travis CI

Under Travis CI if I run PyFunceble directly for testing, it is working as you can see in the following link.

Case that it is not working

Locally

No case found after months of testing and using.

Travis CI

If we create a .py file or a bash script, and run PyFunceble from inside the script, it is not working.

Examples:

  • All 50+ Travis CI builds of https://travis-ci.org/Ultimate-Hosts-Blacklist (.py)
  • All 15+ Travis CI build of https://travis-ci.org/dead-hosts (.py)
  • https://travis-ci.org/mitchellkrogza/Phishing.Database (bash script)

Maybe I forgot to configure something, but I'm still not understanding this issue...

So, does anyone know what am I missing and why it is not working?

Thanks in advance for taking the time to read this issue and for the time you may take to answer this issue.

Have a nice day/night.

Cheers, Nissar

funilrys avatar May 26 '18 15:05 funilrys

This is probably because the users of PyFunceble run it in a subprocess, where "stdout" is not a tty, so colorama does not recognize stdout as a wrap-able stream and therefore strips the ANSI color codes.

Consider this simple script (let's put it in a file named "a.py"):

import colorama
colorama.init()
print(colorama.Fore.RED + 'hello' + colorama.Fore.RESET)

And this script that executes "a.py" in a subprocess (let's call it "b.py")

import subprocess
proc = subprocess.Popen("python a.py", stdout=subprocess.PIPE, shell=True)
output, error = proc.communicate()
print(output)

If you run the first script directly (python a.py) you will get a red "hello". If you run b.py, which runs a.py, you will get a "regular" non-red "hello". The stdout of the subprocess is a pipe, i.e. not a "terminal stream" (tty), so the colors will be stripped. I can't think of a good work around for you. Technically this is the intended behavior.

wiggin15 avatar May 26 '18 20:05 wiggin15

Hi @wiggin15, I tried to find a workaround based on your example and I finally found one. I hope that this may help improve colorama or help others who are in the same situation.

a.py

import colorama

print(colorama.Fore.RED + "hello" + colorama.Fore.RESET)

b.py

import sys
from subprocess import PIPE, Popen


def run_command(command, encoding="utf-8"):
    """
    Run a command and return each line one by one.

    :param command: The command to run
    :type command: str|list

    :param encoding: The encoding to use to decode the output of the command.
    :type encoding: str

    :return: A line of the command.
    :rtype: str
    """

    if isinstance(command, list):
        # The given command is a list.

        # We convert it to string.
        command = " ".join(command)

    # We launch the command.
    process = Popen(command, stdout=PIPE, shell=True)

    while True:
        # We loop infinity because we want to get the output
        # until there is not output anymore.

        # We get the current line from the process stdout.
        #
        # Note: we use rstrip because we may have spaces after the output.
        current_line = process.stdout.readline().rstrip()

        if not current_line:
            # The current line is empty or equal to None.

            # We break the loop.
            break

        # We decode and return the current line to upstream.
        yield current_line.decode(encoding)


if __name__ == "__main__":
    # The script is executed directory.

    for line in run_command("python a.py"):
        # We loop through each line from the commands output.

        # We print to stdout.
        sys.stdout.write(line + "\n")

Proof:

2018-10-10-203529_1600x900_scrot

"Concept"

Because proc.communicate() return the output after the execution of the command as stated in the Documentation:

Wait for process to terminate.

I used Popen.stdout instead which allow us to get line "correctly".

Strangely if we use colorama.init() the coloration does not show up can you explain that @wiggin15 ?

Cheers, Nissar

funilrys avatar Oct 10 '18 18:10 funilrys

Reading directly from stdout attribute or using communicate method makes no difference. The stdout "PIPE" is still not a tty and will be stripped from colorama. What happens in your case is that you don't actually use colorama, since you don't call colorama.init so the streams are not wrapped. This will not work on Windows.

wiggin15 avatar Oct 11 '18 09:10 wiggin15

Closing as this is old enough but people can alternatively just set the PYCHARM_HOSTED environment variable.

Stay safe and healthy.

funilrys avatar Nov 02 '22 17:11 funilrys