RSale
RSale

Reputation: 510

Adding variables to dataclass from json config file

After watching ArjanCodes video on dataclasses,

I've been trying to add variables to a python dataclass from a json config file to format the font style of a print function printT in Jupyterlab.

I use ANSI escapes for the formatting which doesn't work anymore if I import the variables to the dataclass. Instead of formatting the text, the ANSI code get's printed out.

# config.json
{
    "lb" : "\n",
    "solid_line"  : "'___'*20 + config.lb",
    "dotted_line" : "'---'*20 + config.lb",
    
    "BOLD" : "\\033[1m",
    "END" : "\\033[0m"
}
# config.py
from dataclasses import dataclass
import json

@dataclass
class PrintConfig:
    lb : str
    solid_line  : str 
    dotted_line : str 

    BOLD : str
    END : str 
        
def read_config(config_file : str) -> PrintConfig:
 
    with open(config_file, 'r') as file:      
        data = json.load(file)      
        return(PrintConfig(**data))
# helper.py
from config import read_config

config = read_config('config.json')

def printT(title,linebreak= True,addLine = True, lineType = config.solid_line,toDisplay = None):
    '''
    Prints a line break, the input text and a solid line.
        
    Inputs:
    title     = as string 
    linebreak = True(default) or False; Adds a line break before printing the title 
    addLine   = True(default) or False; Adds a line after printing the title
    lineType  = solid_line(default) or dotted_line; Defines line type 
    toDisplay = displays input, doesnt work with df.info(),because info executes during input
    '''
    if linebreak:
        print(config.lb)

    print(config.BOLD + title + config.END)

    if addLine:
        print(lineType)

    if toDisplay is not None:
        display(toDisplay)
# test.ipynb

from helper import printT
printT('Hello World')

Output



\033[1mHello World\033[0m
'___'*20 + config.lb

Desired result

Hello World


It works if I use eval if addLine: print(eval(lineType)) but I'd like to get deeper insights into the mechanics here. Is there a way of getting it to work without eval?

Also this part "solid_line" : "'___'*20 + config.lb" feels wrong.

Markdown as alternative to ANSI

Upvotes: 1

Views: 1095

Answers (2)

alexis
alexis

Reputation: 50220

This string consists of an actual backslash followed by the digits 033, etc.

"BOLD" : "\\033[1m",

To turn on bold on an ansi terminal, you need an escape character (octal 33) followed by [1m. In Python, you can write those escape codes with a single backslash: "\033[1m". In a json file, you must provide the unicode codepoint of the escape character, \u001b. If the rest is in order, you'll see boldface.

"BOLD" : "\u001b[1m",
"END" : "\u001b[0m"

As for the eval part, you have a string containing the expression you need to evaluate. I assume you wrote it this way because you first tried without the double quotes, e.g. ,

"dotted_line" : '---'*20 + config.lb,

and you got a json syntax error. That's not surprising: Json files are data, not code, and they cannot incorporate expressions or variable references. Either place your config in a python file that you include instead of loading json, or move the dependencies to the code. Or both.

In a python file, config.py:

config = {
    "lb": "\n",
    "solid_line"  : '___'*20,
    ...

In helper.py:

...
if addLine:
    print(lineType + config.lb)

Upvotes: 2

JL Peyret
JL Peyret

Reputation: 12214

Here's a basic configuration system. I won't add the output since it would need a screenshot but it works on bash/macos. Inspired by and [tip_colors_and_formatting]

And from (https://misc.flogisoft.com/bash/tip_colors_and_formatting):

In Bash, the character can be obtained with the following syntaxes:

\e \033 \x1B

\e didn't work, so I went on to use to \x1B since that worked in the linked SE answer. \033 works too, I checked.


from dataclasses import dataclass

PREFIX = "\x1B["

#these aren't configurable, they are ANSI constants so probably
#not useful to put them in a config json
CODES = dict(
    prefix = PREFIX,
    bold = f"1",
    reset = f"{PREFIX}0m",
    red = "31",
    green = "32",
)

@dataclass
class PrintConfig:
    bold : bool = False
    color : str = ""

    def __post_init__(self):
        
        # these are calculated variables, none of client code's
        # business:
        self.start = self.end = ""
        start = ""

        if self.bold:
            start += CODES["bold"] + ";"

        if self.color:
            start += CODES[self.color.lower()] + ";"

        if start:
            self.end = CODES["reset"]
            #add the escape prefix, then the codes and close with m
            self.start = f"{CODES['prefix']}{start}".rstrip(";") + "m"

    def print(self,v):
        print(f"{self.start}{v}{self.end}")


normal = PrintConfig()
normal.print("Hello World")

bold = PrintConfig(bold=1)
print(f"{bold=}:")
bold.print("  Hello World")

boldred = PrintConfig(bold=1,color="red")
print(f"{boldred=}:")
boldred.print("  Hello bold red")

#this is how you would do it from json
green = PrintConfig(**dict(color="green"))

green.print("  Little Greenie")



#inspired from https://stackoverflow.com/a/287934
print("\n\ninspired by...")
CSI = "\x1B["
print(CSI+"31;40m" + "Colored Text" + CSI + "0m")
print(CSI+"1m" + "Colored Text" + CSI + "0m")

Upvotes: 1

Related Questions