Skip to content

Commit 0deb4d8

Browse files
committed
Merge branch '2025-anti'
2 parents f535022 + bfca9cb commit 0deb4d8

18 files changed

Lines changed: 1327 additions & 0 deletions

2025/anti/abstraction.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import json
2+
from dataclasses import dataclass
3+
from typing import Callable, Protocol
4+
5+
6+
@dataclass
7+
class Report:
8+
title: str
9+
content: str
10+
11+
def to_csv(self) -> str:
12+
return f"{self.title}, {self.content}\n"
13+
14+
def to_json(self) -> str:
15+
return json.dumps(
16+
{
17+
"title": self.title,
18+
"content": self.content,
19+
},
20+
indent=4,
21+
)
22+
23+
24+
@dataclass
25+
class Budget:
26+
title: str
27+
amount: float
28+
29+
def to_csv(self) -> str:
30+
return f"{self.title}, {self.amount}\n"
31+
32+
def to_json(self) -> str:
33+
return json.dumps(
34+
{
35+
"title": self.title,
36+
"amount": self.amount,
37+
},
38+
indent=4,
39+
)
40+
41+
42+
type ExportFn = Callable[[str, Exportable], None]
43+
44+
45+
class Exportable(Protocol):
46+
def to_csv(self) -> str: ...
47+
48+
def to_json(self) -> str: ...
49+
50+
51+
def export_to_csv(filename: str, data: Exportable) -> None:
52+
print("Exporting to CSV...")
53+
with open(filename, "w") as f:
54+
f.write(data.to_csv())
55+
print("Done.")
56+
57+
58+
def export_to_json(filename: str, data: Exportable) -> None:
59+
print("Exporting to JSON...")
60+
with open(filename, "w") as f:
61+
json.dump(data.to_json(), f)
62+
print("Done.")
63+
64+
65+
EXPORTERS = {
66+
"csv": export_to_csv,
67+
"json": export_to_json,
68+
}
69+
70+
71+
def get_exporter(format: str) -> ExportFn:
72+
"""Factory function to get the appropriate exporter function based on format string."""
73+
if format in EXPORTERS:
74+
return EXPORTERS[format]
75+
else:
76+
raise ValueError(f"Unsupported export format: {format}")
77+
78+
79+
def main() -> None:
80+
report = Report(
81+
title="Quarterly Earnings",
82+
content="Here are the earnings for the last quarter...",
83+
)
84+
85+
export_fn = get_exporter("json")
86+
export_fn("report.json", report)
87+
88+
budget = Budget(
89+
title="Annual Budget",
90+
amount=1000000.00,
91+
)
92+
93+
export_fn = get_exporter("json")
94+
export_fn("budget.json", budget)
95+
96+
97+
if __name__ == "__main__":
98+
main()

2025/anti/config.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"learning_rate": 0.01,
3+
"batch_size": 32,
4+
"epochs": 10
5+
}

2025/anti/data_processing.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import pandas as pd
2+
3+
4+
def fill_missing_values(df: pd.DataFrame) -> pd.DataFrame:
5+
"""Fill missing numeric values with the median of each column."""
6+
return df.fillna(df.median(numeric_only=True))
7+
8+
9+
def normalize_columns(df: pd.DataFrame, columns: list[str]) -> pd.DataFrame:
10+
"""Min-max normalize specified columns in the DataFrame."""
11+
df = df.copy()
12+
for col in columns:
13+
min_val = df[col].min()
14+
max_val = df[col].max()
15+
if min_val == max_val:
16+
df[col] = 0.0 # avoid division by zero
17+
else:
18+
df[col] = (df[col] - min_val) / (max_val - min_val)
19+
return df
20+
21+
22+
def encode_categorical(df: pd.DataFrame, columns: list[str]) -> pd.DataFrame:
23+
"""Convert categorical columns into one-hot encoded columns."""
24+
return pd.get_dummies(df, columns=columns, drop_first=True)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import json
2+
from typing import Any
3+
4+
5+
def load_config(file_name: str) -> dict[str, Any]:
6+
"""Load configuration from the specified file."""
7+
with open(file_name, "r") as f:
8+
return json.load(f)
9+
10+
11+
def main() -> None:
12+
config = load_config("config.json")
13+
print(f"Training with config: {config}")
14+
15+
16+
if __name__ == "__main__":
17+
main()
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import json
2+
from functools import wraps
3+
from typing import Any, Callable
4+
5+
6+
def load_config(file_name: str) -> dict[str, Any]:
7+
"""Load configuration from the specified file."""
8+
with open(file_name, "r") as f:
9+
return json.load(f)
10+
11+
12+
def inject_config(file_name: str) -> Callable[[Callable[..., Any]], Callable[..., Any]]:
13+
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
14+
@wraps(func)
15+
def wrapper(*args: Any, **kwargs: Any) -> Any:
16+
config: dict[str, Any] = load_config(file_name)
17+
return func(config, *args, **kwargs)
18+
19+
return wrapper
20+
21+
return decorator
22+
23+
24+
@inject_config("config.json")
25+
def main(config: dict[str, Any]) -> None:
26+
print(f"Training with config: {config}")
27+
28+
29+
if __name__ == "__main__":
30+
main()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import random
2+
import time
3+
4+
5+
# Simulated API call that randomly succeeds or times out
6+
def fetch_from_primary_api(city: str) -> dict[str, str]:
7+
if simulate_timeout():
8+
return {"status": "error", "message": "Primary API timed out."}
9+
return {"status": "success", "data": f"Weather in {city} is sunny."}
10+
11+
12+
def fetch_from_backup_api(city: str) -> dict[str, str]:
13+
if simulate_timeout():
14+
return {"status": "error", "message": "Backup API timed out."}
15+
return {"status": "success", "data": f"Backup: Weather in {city} is cloudy."}
16+
17+
18+
def simulate_timeout() -> bool:
19+
time.sleep(0.2) # network delay
20+
return random.random() < 0.5
21+
22+
23+
# Good: using return values for expected control flow
24+
def get_weather_forecast(city: str) -> dict[str, str]:
25+
result = fetch_from_primary_api(city)
26+
if result["status"] == "error":
27+
result = fetch_from_backup_api(city)
28+
if result["status"] == "error":
29+
return {"status": "error", "message": "Both APIs timed out."}
30+
return result
31+
32+
33+
def main():
34+
result = get_weather_forecast("New York")
35+
print(result)
36+
37+
38+
if __name__ == "__main__":
39+
main()
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import random
2+
import time
3+
4+
5+
# Simulated API call that randomly succeeds or times out
6+
def fetch_from_primary_api(city: str) -> dict[str, str]:
7+
if simulate_timeout():
8+
raise TimeoutError("Primary API timed out.")
9+
return {"status": "success", "data": f"Weather in {city} is sunny."}
10+
11+
12+
def fetch_from_backup_api(city: str) -> dict[str, str]:
13+
if simulate_timeout():
14+
raise TimeoutError("Backup API timed out.")
15+
return {"status": "success", "data": f"Backup: Weather in {city} is cloudy."}
16+
17+
18+
def simulate_timeout() -> bool:
19+
time.sleep(0.2) # network delay
20+
return random.random() < 0.5
21+
22+
23+
# Bad: using exceptions for expected control flow
24+
def get_weather_forecast(city: str) -> dict[str, str]:
25+
try:
26+
return fetch_from_primary_api(city)
27+
except TimeoutError:
28+
try:
29+
return fetch_from_backup_api(city)
30+
except TimeoutError:
31+
return {"status": "error", "message": "Both APIs timed out."}
32+
33+
34+
def main():
35+
result = get_weather_forecast("New York")
36+
print(result)
37+
38+
39+
if __name__ == "__main__":
40+
main()

2025/anti/hardcoded_after.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import io
2+
import json
3+
import os
4+
import zipfile
5+
6+
import lokalise
7+
import numpy as np
8+
import pandas as pd
9+
import requests
10+
import streamlit as st
11+
from dotenv import load_dotenv
12+
13+
load_dotenv()
14+
LOKALISE_API_KEY = os.getenv("LOKALISE_API_KEY")
15+
LOKALISE_PROJECT_ID = os.getenv("LOKALISE_PROJECT_ID")
16+
LANGUAGE = "nl"
17+
18+
19+
class LokaliseTranslator:
20+
def __init__(self, api_key: str, project_id: str, language: str) -> None:
21+
self.client = lokalise.Client(api_key)
22+
self.project_id = project_id
23+
self.language = language
24+
self.translations = self.get_translations()
25+
26+
def get_translations(self) -> dict[str, str]:
27+
response = self.client.download_files(
28+
self.project_id,
29+
{"format": "json", "original_filenames": True, "replace_breaks": False},
30+
)
31+
translations_url = response["bundle_url"]
32+
33+
# Download and extract the ZIP file
34+
zip_response = requests.get(translations_url)
35+
zip_file = zipfile.ZipFile(io.BytesIO(zip_response.content))
36+
37+
# Find the JSON file corresponding to the selected language
38+
json_filename = f"{self.language}/no_filename.json"
39+
with zip_file.open(json_filename) as json_file:
40+
return json.load(json_file)
41+
42+
def __call__(self, key: str) -> str:
43+
return self.translations.get(key, key)
44+
45+
46+
translator = LokaliseTranslator(LOKALISE_API_KEY, LOKALISE_PROJECT_ID, LANGUAGE)
47+
48+
st.title(translator("dashboard_title"))
49+
50+
DATE_COLUMN = "date/time"
51+
DATA_URL = (
52+
"https://s3-us-west-2.amazonaws.com/streamlit-demo-data/uber-raw-data-sep14.csv.gz"
53+
)
54+
55+
56+
@st.cache_data
57+
def load_data(nrows):
58+
data = pd.read_csv(DATA_URL, nrows=nrows)
59+
data.rename(lambda x: str(x).lower(), axis="columns", inplace=True)
60+
data[DATE_COLUMN] = pd.to_datetime(data[DATE_COLUMN])
61+
return data
62+
63+
64+
data_load_state = st.text(translator("loading_data"))
65+
data = load_data(10000)
66+
data_load_state.text(translator("done"))
67+
68+
if st.checkbox(translator("show_raw_data")):
69+
st.subheader(translator("raw_data"))
70+
st.write(data)
71+
72+
st.subheader(translator("nb_pickups_hour"))
73+
hist_values = np.histogram(data[DATE_COLUMN].dt.hour, bins=24, range=(0, 24))[0]
74+
st.bar_chart(hist_values)
75+
76+
# Some number in the range 0-23
77+
hour_to_filter = st.slider("hour", 0, 23, 17)
78+
filtered_data = data[data[DATE_COLUMN].dt.hour == hour_to_filter]
79+
80+
st.subheader(translator("map_all_pickups") % hour_to_filter)
81+
st.map(filtered_data)

2025/anti/hardcoded_before.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import numpy as np
2+
import pandas as pd
3+
import streamlit as st
4+
5+
# Hardcoded constants
6+
DATE_COLUMN = "date/time"
7+
DATA_URL = (
8+
"https://s3-us-west-2.amazonaws.com/streamlit-demo-data/uber-raw-data-sep14.csv.gz"
9+
)
10+
11+
12+
@st.cache_data
13+
def load_data(nrows: int) -> pd.DataFrame:
14+
data = pd.read_csv(DATA_URL, nrows=nrows)
15+
data.rename(lambda x: str(x).lower(), axis="columns", inplace=True)
16+
data[DATE_COLUMN] = pd.to_datetime(data[DATE_COLUMN])
17+
return data
18+
19+
20+
# 🧨 All UI strings are hardcoded — even small changes are tedious
21+
st.title("Uber pickups in NYC")
22+
23+
data_load_state = st.text("Loading data...")
24+
data = load_data(10000)
25+
data_load_state.text("Loading complete!")
26+
27+
if st.checkbox("Show raw data"):
28+
st.subheader("Raw data")
29+
st.write(data)
30+
31+
st.subheader("Number of pickups by hour")
32+
hist_values = np.histogram(data[DATE_COLUMN].dt.hour, bins=24, range=(0, 24))[0]
33+
st.bar_chart(hist_values)
34+
35+
hour_to_filter = st.slider("Hour", 0, 23, 17)
36+
filtered_data = data[data[DATE_COLUMN].dt.hour == hour_to_filter]
37+
38+
st.subheader(f"Map of all pickups at {hour_to_filter}:00")
39+
st.map(filtered_data)

0 commit comments

Comments
 (0)