Skip to content

Commit ed34865

Browse files
committed
ENH: Add in new snippet for script structure
1 parent 2a88b23 commit ed34865

File tree

2 files changed

+135
-0
lines changed

2 files changed

+135
-0
lines changed

python-script-structure/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# How Can You Structure Your Python Script?
2+
3+
This folder provides the code examples for the Real Python tutorial [How Can You Structure Your Python Script?](https://realpython.com/python-script-structure/).
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/usr/bin/env python3
2+
3+
# /// script
4+
# requires-python = ">=3.11"
5+
# dependencies = [
6+
# "click==8.1.8",
7+
# "pandas==2.2.3",
8+
# "rich==14.0.0",
9+
# "ucimlrepo==0.0.7",
10+
# ]
11+
# ///
12+
13+
import logging
14+
import sys
15+
from dataclasses import dataclass, field
16+
from enum import IntEnum, StrEnum, auto
17+
from pprint import pformat
18+
19+
import click
20+
import pandas as pd
21+
from rich.console import Console, RenderableType, Text
22+
from rich.logging import RichHandler
23+
from rich.table import Table
24+
from ucimlrepo import fetch_ucirepo
25+
26+
logging.basicConfig(
27+
level=logging.INFO,
28+
format="%(levelname)s - %(message)s",
29+
handlers=[RichHandler(rich_tracebacks=True)]
30+
)
31+
32+
class UCIDataset(IntEnum):
33+
IRIS = 53
34+
35+
class IrisVariable(StrEnum):
36+
PETAL_LENGTH = "petal length"
37+
PETAL_WIDTH = "petal width"
38+
SEPAL_WIDTH = "sepal width"
39+
SEPAL_LENGTH = "sepal length"
40+
41+
class Operation(StrEnum):
42+
SUMMARY = auto()
43+
METADATA = auto()
44+
45+
@dataclass
46+
class DescriptiveStatistics:
47+
data: pd.Series
48+
mean: float = field(init=False)
49+
median: float = field(init=False)
50+
mm_diff: float = field(init=False)
51+
52+
def __post_init__(self):
53+
if not isinstance(self.data, pd.Series):
54+
raise TypeError(
55+
f"data must be a pandas Series, not {type(self.data)}"
56+
)
57+
self.mean = self.data.mean()
58+
self.median = self.data.median()
59+
self.mm_diff = self.mean - self.median
60+
61+
def __str__(self):
62+
return pformat(self)
63+
64+
@click.command()
65+
@click.option(
66+
"--operation",
67+
default=Operation.SUMMARY,
68+
type=click.Choice(Operation),
69+
help="Operation to perform: variable summary or dataset metadata",
70+
)
71+
@click.option(
72+
"--variable",
73+
type=click.Choice(IrisVariable),
74+
help="Variable to summarize.",
75+
required=False,
76+
)
77+
def main(operation, variable):
78+
"""Fetch the Iris dataset from UCI."""
79+
console = Console()
80+
iris = fetch_iris()
81+
if operation is Operation.SUMMARY:
82+
if variable:
83+
table = generate_table(iris, variable)
84+
logging.info(format_rich_for_log(table))
85+
logging.info(f"{IrisVariable(variable)} summary:")
86+
logging.info(
87+
DescriptiveStatistics(
88+
iris.data.features[IrisVariable(variable).value]
89+
)
90+
)
91+
else:
92+
logging.info("All variables:")
93+
logging.info(pformat(iris.variables))
94+
elif operation is Operation.METADATA:
95+
logging.info("Metadata summary:")
96+
logging.info(pformat(iris.metadata))
97+
98+
def fetch_iris():
99+
"""Return the Iris dataset from the UCI ML Repository."""
100+
logging.info("Fetching Iris dataset...")
101+
try:
102+
iris_data = fetch_ucirepo(id=UCIDataset.IRIS.value)
103+
assert "data" in iris_data.keys(), \
104+
"Object does not have expected structure"
105+
except Exception as e:
106+
logging.critical(f"Failed to correctly fetch Iris dataset: {e}")
107+
sys.exit(1)
108+
else:
109+
logging.info("Iris dataset fetched successfully")
110+
return iris_data
111+
112+
def generate_table(dataset, variable):
113+
"""Generate a formatted table of descriptive statistics for a variable."""
114+
column = IrisVariable(variable).value
115+
stats = DescriptiveStatistics(dataset.data.features[column])
116+
table = Table(title=f"{column} summary")
117+
table.add_column("Metric", style="cyan", justify="right")
118+
table.add_column("Value", style="magenta")
119+
table.add_row("Mean", f"{stats.mean:.2f}")
120+
table.add_row("Median", f"{stats.median:.2f}")
121+
table.add_row("Mean-Median Diff", f"{stats.mm_diff:.2f}")
122+
return table
123+
124+
def format_rich_for_log(renderable, width=100):
125+
"""Render a rich object to a plain text string suitable for logging."""
126+
console = Console(width=width)
127+
with console.capture() as capture:
128+
console.print(renderable)
129+
return Text.from_ansi(capture.get())
130+
131+
if __name__ == "__main__":
132+
main()

0 commit comments

Comments
 (0)