Skip to content

Commit bca033e

Browse files
authored
Use rich theme in argparse help menu (#58)
* - Add AstroTheme class in a new theme.py file - AstroTheme will now hold all color theme related info - Some private class methods have been privated using the double underscore convention - A theme was defined for argparse, and implemented by AstroTheme - Modified conftest.py to add fixture for returning AstroThme() object - Modified logger() fixture to pass AstroTheme() object to logger.astro_config() method - update requirements.txt to include rich_argparse package
1 parent 26dde1f commit bca033e

File tree

7 files changed

+92
-38
lines changed

7 files changed

+92
-38
lines changed

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pytz==2024.2
3636
regex==2024.9.11
3737
requests==2.32.3
3838
rich==13.9.2
39+
rich-argparse==1.5.2
3940
rsa==4.9
4041
six==1.16.0
4142
tqdm==4.66.5

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
py_modules=[
1111
"src/log",
1212
"src/astro_db",
13-
"src/progress"],
13+
"src/progress",
14+
"src/theme"],
1415

1516
# Packages required to run the app
1617
install_requires=[

src/astro.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from data_collection.sentiment import SentimentAnalysis
1212
from log import AstroLogger
1313
from astro_db import AstroDB
14+
from theme import AstroTheme
15+
from rich_argparse import ArgumentDefaultsRichHelpFormatter
1416

1517

1618
def extract_video_id_from_url(url: str) -> str:
@@ -27,26 +29,31 @@ def extract_video_id_from_url(url: str) -> str:
2729
return video_id
2830

2931

30-
def parse_args():
32+
def parse_args(astro_theme):
3133
"""
3234
Argument parsing logic. Returns the arguments parsed from the CLI
3335
"""
36+
description = "A tool for YouTube data collection."
3437

35-
parser = argparse.ArgumentParser()
38+
parser = argparse.ArgumentParser(description=description,
39+
formatter_class=ArgumentDefaultsRichHelpFormatter)
3640

3741
parser.add_argument("youtube_url", type=str, help="youtube video URL")
3842
parser.add_argument("-l", "--log", type=str, choices=['debug', 'info', 'warn', 'error'],
39-
help='Set the logging level')
43+
help='Set the logging level', default='info')
4044
parser.add_argument("--api-key", type=str, help="YouTube Data API key")
41-
parser.add_argument("--db-file", type=str, help="database filename")
45+
parser.add_argument("--db-file", type=str, help="database filename", default='astro.db')
4246
args = parser.parse_args()
4347

4448
return args
4549

4650

4751
def main():
52+
# load astro color scheme
53+
astro_theme = AstroTheme()
54+
4855
# parse arguments
49-
args = parse_args()
56+
args = parse_args(astro_theme)
5057
video_id = extract_video_id_from_url(args.youtube_url)
5158

5259
# load environment variables
@@ -60,7 +67,7 @@ def main():
6067
# set up logging
6168
logging.setLoggerClass(AstroLogger)
6269
logger = logging.getLogger(__name__)
63-
logger.astro_config(log_level)
70+
logger.astro_config(log_level, astro_theme)
6471

6572
logger.info('Collecting video data...')
6673

src/log.py

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ class AstroLogger(logging.Logger):
2020
astro_theme: Theme
2121
progress: AstroProgress
2222

23-
def astro_config(self, log_level_str: str):
23+
def astro_config(self, log_level_str: str, astro_theme):
2424
"""
2525
Custom logging config.
2626
"""
@@ -29,21 +29,10 @@ def astro_config(self, log_level_str: str):
2929

3030
self.setLevel(self.log_level)
3131

32-
# define astro color theme
33-
self.astro_text_color = 'sky_blue3'
34-
self.astro_theme = Theme({
35-
"log.message": self.astro_text_color,
36-
"logging.level.info": 'blue_violet',
37-
"logging.level.warning": "orange_red1",
38-
"logging.level.error": "red",
39-
"bar.finished": "green",
40-
"progress.elapsed": self.astro_text_color,
41-
"progress.remaining": self.astro_text_color,
42-
"progress.percentage": "green",
43-
"table.title": "bold " + self.astro_text_color})
32+
self.astro_theme = astro_theme
4433

4534
# create console using the asto theme
46-
self.console = Console(theme=self.astro_theme)
35+
self.console = self.astro_theme.get_console()
4736

4837
# configure logging
4938
logging.basicConfig(format='%(message)s',
@@ -52,9 +41,9 @@ def astro_config(self, log_level_str: str):
5241
console=self.console)])
5342

5443
# suppress google logs
55-
self.suppress_logs('google', logging.WARNING)
44+
self.__suppress_logs('google', logging.WARNING)
5645

57-
def suppress_logs(self, name, level):
46+
def __suppress_logs(self, name, level):
5847
"""
5948
Suppress logging of external modules.
6049
"""
@@ -67,7 +56,7 @@ def progress_bar(self, task_str: str, steps: int):
6756
"""
6857
Display a progress bar on the console.
6958
"""
70-
self.progress = AstroProgress(task_str, steps, console=self.console, style=self.astro_text_color)
59+
self.progress = AstroProgress(task_str, steps, console=self.console)
7160
return self.progress
7261

7362
def get_log_level(self, log_level: str) -> int:
@@ -87,14 +76,15 @@ def get_log_level(self, log_level: str) -> int:
8776

8877
return log_level
8978

90-
def rich_table(self, title=''):
79+
def __rich_table(self, title=''):
9180
"""
9281
Create a rich.Table object using the default project theme/style.
9382
"""
94-
table = Table(style=self.astro_text_color,
95-
border_style=self.astro_text_color,
96-
header_style=self.astro_text_color,
97-
row_styles=[self.astro_text_color],
83+
text_color = self.astro_theme.get_style()
84+
table = Table(style=text_color,
85+
border_style=text_color,
86+
header_style=text_color,
87+
row_styles=[text_color],
9888
title=title)
9989

10090
return table
@@ -110,7 +100,7 @@ def print_object(self, obj, title=''):
110100
if self.log_level > logging.INFO:
111101
return
112102

113-
table = self.rich_table(title)
103+
table = self.__rich_table(title)
114104
table.add_column("Attribute")
115105
table.add_column("Value")
116106

@@ -130,7 +120,7 @@ def print_dataframe(self, df, title=''):
130120
# ensure dataframe contains only string values
131121
df = df.astype(str)
132122

133-
table = self.rich_table(title)
123+
table = self.__rich_table(title)
134124

135125
# add dataframe columns
136126
for col in df.columns:

src/progress.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,21 @@ class AstroProgress(Progress):
1515
total_steps: int
1616
task: int
1717

18-
def __init__(self, task_str: str, steps: int, console=None, style=None):
18+
def __init__(self, task_str: str, steps: int, console=None):
1919
self.task_str = task_str
2020
self.total_steps = steps
2121

2222
super().__init__(
2323
TextColumn("{task.description} [progress.percentage]{task.percentage:>3.0f}%"),
2424
BarColumn(),
2525
MofNCompleteColumn(),
26-
TextColumn("•", style=style),
26+
TextColumn("•", style=console.get_style('log.message')),
2727
TimeElapsedColumn(),
28-
TextColumn("•", style=style),
28+
TextColumn("•", style=console.get_style('log.message')),
2929
TimeRemainingColumn(),
3030
console=console)
3131

32-
self.task = super().add_task(f'[{style}]' + task_str, total=self.total_steps)
32+
self.task = super().add_task(f"[{console.get_style('log.message')}]" + task_str, total=self.total_steps)
3333

3434
def advance(self, steps: int):
3535
super().update(self.task, advance=steps)

src/tests/conftest.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
import logging
55

66
import pandas as pd
7+
import src.tests.test_api_responses as api_responses
78
from unittest.mock import MagicMock
89
from src.log import AstroLogger
9-
import src.tests.test_api_responses as api_responses
10+
from src.theme import AstroTheme
1011

1112

1213
@pytest.fixture(scope='class')
@@ -31,10 +32,15 @@ def api_video_response():
3132

3233

3334
@pytest.fixture(scope='class')
34-
def logger():
35+
def astro_theme():
36+
return AstroTheme()
37+
38+
39+
@pytest.fixture(scope='class')
40+
def logger(astro_theme):
3541
logging.setLoggerClass(AstroLogger)
3642
astro_logger = logging.getLogger(__name__)
37-
astro_logger.astro_config('debug')
43+
astro_logger.astro_config('debug', astro_theme)
3844
return astro_logger
3945

4046

src/theme.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""
2+
This file defines the project color themes for anything printed to the console.
3+
"""
4+
from rich.theme import Theme
5+
from rich.console import Console
6+
from rich_argparse import ArgumentDefaultsRichHelpFormatter
7+
8+
9+
class AstroTheme:
10+
astro_theme: Theme
11+
astro_console: Console
12+
astro_text_color = 'sky_blue3'
13+
14+
def __init__(self):
15+
self.astro_theme = Theme({
16+
"log.message": self.astro_text_color,
17+
"logging.level.info": 'blue_violet',
18+
"logging.level.warning": "orange_red1",
19+
"logging.level.error": "red",
20+
"bar.finished": "green",
21+
"progress.elapsed": self.astro_text_color,
22+
"progress.remaining": self.astro_text_color,
23+
"progress.percentage": "green",
24+
"table.title": "bold " + self.astro_text_color})
25+
26+
self.astro_console = Console(theme=self.astro_theme)
27+
self.__set_argparse_theme()
28+
29+
def __set_argparse_theme(self):
30+
"""
31+
Set the theme of the argparse help menu.
32+
"""
33+
ArgumentDefaultsRichHelpFormatter.styles['argparse.args'] = self.astro_text_color # for positional-arguments
34+
ArgumentDefaultsRichHelpFormatter.styles['argparse.groups'] = self.astro_text_color # for group names
35+
ArgumentDefaultsRichHelpFormatter.styles['argparse.help'] = self.astro_text_color # for argument's help text
36+
ArgumentDefaultsRichHelpFormatter.styles['argparse.metavar'] = 'green' # for metavariables
37+
ArgumentDefaultsRichHelpFormatter.styles['argparse.prog'] = 'green' # for %(prog)s in the usage
38+
ArgumentDefaultsRichHelpFormatter.styles['argparse.syntax'] = 'bold' # for highlights of back-tick quoted text
39+
ArgumentDefaultsRichHelpFormatter.styles['argparse.text'] = self.astro_text_color # for descriptions
40+
ArgumentDefaultsRichHelpFormatter.styles['argparse.default'] = 'italic' # for %(default)s in the help
41+
42+
def get_style(self):
43+
return self.astro_text_color
44+
45+
def get_theme(self):
46+
return self.astro_theme
47+
48+
def get_console(self):
49+
return self.astro_console

0 commit comments

Comments
 (0)