codejedi's picture
Add Pydantic validation for option_id and issue_id parameters
8b1dacf
import gradio as gr
from textblob import TextBlob
from pydantic import BaseModel, Field, validator
from typing import Union
import gradio as gr
from textblob import TextBlob
import requests
from bs4 import BeautifulSoup
nation_states_metrics = {
0: "civil rights",
1: "economy",
2: "political freedoms",
3: "population",
4: "wealth gaps",
5: "death rate (née unexpected death rate)",
6: "compassion",
7: "eco-friendliness",
8: "social conservatism",
9: "nudity",
10: "industry: automobile manufacturing",
11: "industry: cheese exports",
12: "industry: basket weaving",
13: "industry: information technology",
14: "industry: pizza delivery",
15: "industry: trout fishing",
16: "industry: arms manufacturing",
17: "sector: agriculture",
18: "industry: beverage sales",
19: "industry: timber woodchipping",
20: "industry: mining",
21: "industry: insurance",
22: "industry: furniture restoration",
23: "industry: retail",
24: "industry: book publishing",
25: "industry: gambling",
26: "sector: manufacturing",
27: "government size",
28: "welfare",
29: "public healthcare",
30: "law enforcement",
31: "business subsidization",
32: "religiousness",
33: "income equality",
34: "niceness",
35: "rudeness",
36: "intelligence",
37: "ignorance (née stupidity)",
38: "political apathy",
39: "health",
40: "cheerfulness (née happiness)",
41: "weather",
42: "compliance (née safety from crime)",
43: "safety",
44: "lifespan",
45: "ideological radicality",
46: "defense forces",
47: "pacifism",
48: "economic freedom (née most pro-market)",
49: "taxation",
50: "freedom from taxation",
51: "corruption",
52: "integrity (née freedom from corruption)",
53: "authoritarianism",
54: "youth rebelliousness",
55: "culture",
56: "employment",
57: "public transport",
58: "tourism",
59: "weaponization",
60: "recreational drug use",
61: "obesity",
62: "secularism (née godlessness)",
63: "environmental beauty",
64: "charmlessness (née toxicity)",
65: "influence",
66: "world assembly endorsements",
67: "averageness",
68: "human development index",
69: "primitiveness",
70: "scientific advancement",
71: "inclusiveness",
72: "average income",
73: "average income of poor",
74: "average income of rich",
75: "public education",
76: "economic output",
77: "crime",
78: "foreign aid",
79: "black market",
80: "residency",
81: "survivors",
82: "zombies",
83: "dead",
84: "percentage zombies",
85: "average disposable income",
86: "international artwork",
87: "patriotism",
88: "food quality"
}
class GetNationIssuesRequest(BaseModel):
"""Pydantic model for get_nation_issues request parameters."""
nation: str = Field(..., description="The nation name")
password: str = Field(..., description="The API password")
class AddressNationIssuesRequest(BaseModel):
"""Pydantic model for address_nation_issues request parameters."""
nation: str = Field(..., description="The nation name")
password: str = Field(..., description="The API password")
issue_id: Union[str, int] = Field(..., description="The issue ID to address")
option_id: Union[str, int] = Field(..., description="The chosen option ID for the issue")
@validator('issue_id', 'option_id', pre=True)
def convert_to_string(cls, v):
"""Convert issue_id and option_id to strings for consistent processing."""
return str(v)
def get_nation_issues(nation: str, password: str):
"""
Fetch nation issues using validated parameters.
Args:
nation (str): The nation name
password (str): The API password
Returns:
dict: Formatted nation issues data
"""
# Validate inputs using Pydantic
request_data = GetNationIssuesRequest(nation=nation, password=password)
url = f"https://www.nationstates.net/cgi-bin/api.cgi"
headers = {
"X-Password": request_data.password,
"User-Agent": "Nationstate LLM"
}
params = {
"nation": request_data.nation,
"q": "issues"
}
response = requests.get(url, headers=headers, params=params)
def get_nation_issues_JSON(sml_data):
"""
Fetches and reformats issues from the NationStates API.
Args:
nation (str): The nation name.
password (str): The API password.
Returns:
dict: A dictionary containing the nation ID and a list of issue details.
"""
# Parse the SML/XML
soup = BeautifulSoup(sml_data, "xml")
# Extract nation info
nation_elem = soup.find("NATION")
nation_id = nation_elem.get("id") if nation_elem else "Unknown"
# List to store issues
issues_list = []
# Extract issues
if nation_elem:
for issue in nation_elem.find_all("ISSUE"):
issue_data = {
"issue_id": issue.get("id", "Unknown"),
"title": issue.TITLE.get_text() if issue.TITLE else "No title available",
"text": issue.TEXT.get_text() if issue.TEXT else "No text available",
"author": issue.AUTHOR.get_text() if issue.AUTHOR else "Unknown author",
"editor": issue.EDITOR.get_text() if issue.EDITOR else "Unknown editor",
"pic1": issue.PIC1.get_text() if issue.PIC1 else "No image 1",
"pic2": issue.PIC2.get_text() if issue.PIC2 else "No image 2",
"options": []
}
# Extract options within the issue
for option in issue.find_all("OPTION"):
option_data = {
"option_id": option.get("id", "Unknown"),
"text": option.get_text() if option else "No option text"
}
issue_data["options"].append(option_data)
# Add issue data to list
issues_list.append(issue_data)
return {"nation_id": nation_id, "issues": issues_list}
return get_nation_issues_JSON(response.text)
def address_nation_issues(nation: str, password: str, issue_id: Union[str, int], option_id: Union[str, int]):
"""
Address a specific issue for a nation in NationStates API.
Args:
nation (str): The nation name.
password (str): The API password.
issue_id (Union[str, int]): The issue ID to address.
option_id (Union[str, int]): The chosen option ID for the issue.
Returns:
dict: Formatted nation data response.
"""
# Validate inputs using Pydantic
request_data = AddressNationIssuesRequest(
nation=nation,
password=password,
issue_id=issue_id,
option_id=option_id
)
url = "https://www.nationstates.net/cgi-bin/api.cgi"
headers = {
"X-Password": request_data.password,
"User-Agent": "Nationstate LLM"
}
data = {
"nation": request_data.nation,
"c": "issue",
"issue": request_data.issue_id, # Now guaranteed to be a string
"option": request_data.option_id # Now guaranteed to be a string
}
response = requests.post(url, headers=headers, data=data)
def reformat_nation_data(xml_data):
"""
Parses and reformats NationStates XML data into a structured dictionary.
Args:
xml_data (str): Raw XML data.
Returns:
dict: A dictionary containing nation details, issue info, rankings, reclassifications, and headlines.
"""
soup = BeautifulSoup(xml_data, "xml")
nation_elem = soup.find("NATION")
nation_id = nation_elem.get("id") if nation_elem else "Unknown"
issue_elem = nation_elem.find("ISSUE") if nation_elem else None
issue_data = {
"issue_id": issue_elem.get("id") if issue_elem else "Unknown",
"choice": issue_elem.get("choice") if issue_elem else "Unknown",
"ok": issue_elem.OK.get_text() if issue_elem and issue_elem.OK else "Unknown",
"description": issue_elem.DESC.get_text() if issue_elem and issue_elem.DESC else "No description available",
"rankings": [],
"reclassifications": [],
"headlines": []
}
# Extract rankings
rankings_elem = issue_elem.find("RANKINGS") if issue_elem else None
if rankings_elem:
for rank in rankings_elem.find_all("RANK"):
census_id = rank.get("census_id", "Unknown")
census_id_key = int(census_id) if census_id.isdigit() else -1
assert census_id_key.isdigit(), f"Invalid census_id: {census_id}"
issue_data["rankings"].append({
"census_id": rank.get("id", "Unknown"),
"census": nation_states_metrics.get(census_id_key, "Unknown Metric"),
"score": rank.SCORE.get_text() if rank.SCORE else "Unknown",
"change": rank.CHANGE.get_text() if rank.CHANGE else "Unknown",
"pchange": rank.PCHANGE.get_text() if rank.PCHANGE else "Unknown"
})
# Extract reclassifications
reclassifications_elem = issue_elem.find("RECLASSIFICATIONS") if issue_elem else None
if reclassifications_elem:
for reclassify in reclassifications_elem.find_all("RECLASSIFY"):
issue_data["reclassifications"].append({
"type": reclassify.get("type", "Unknown"),
"from": reclassify.FROM.get_text() if reclassify.FROM else "Unknown",
"to": reclassify.TO.get_text() if reclassify.TO else "Unknown"
})
# Extract headlines
headlines_elem = issue_elem.find("HEADLINES") if issue_elem else None
if headlines_elem:
issue_data["headlines"] = [headline.get_text() for headline in headlines_elem.find_all("HEADLINE")]
return {"nation_id": nation_id, "issue": issue_data}
return reformat_nation_data(response.text)
def get_id_to_name(rank_id):
"""
Converts an ID (rank_id) to a human-readable name based on the NationStates metrics.
Args:
rank_id (int): The ID to convert.
Returns:
str: The human-readable name corresponding to the rank_id.
"""
return nation_states_metrics.get(rank_id, "Unknown Metric")
# Create the Gradio interface
get_nation_issues_interface = gr.Interface(
fn=get_nation_issues,
inputs=[
gr.Textbox(label="Nation Name", placeholder="Enter the nation name"),
gr.Textbox(label="API Password", placeholder="Enter your API password"),
],
outputs=gr.JSON(),
title="Get Nation Issues as JSON",
description="Fetch and reformat issues from the NationStates API",
api_name="get_nation_issues_JSON",
)
address_nation_issues_interface = gr.Interface(
fn=address_nation_issues,
inputs=[
gr.Textbox(label="Nation Name", placeholder="Enter the nation name"),
gr.Textbox(label="API Password", placeholder="Enter your API password"),
gr.Textbox(label="Issue ID", placeholder="Enter the issue ID to address"),
gr.Textbox(label="Option ID", placeholder="Enter the option ID to choose")
],
outputs=gr.JSON(),
title="Address Nation Issues",
description="Address a specific issue for a nation in NationStates API",
api_name="address_nation_issues",
)
# Create a tabbed interface for the two functionalities
'''
id_to_name_interface = gr.Interface(
fn=get_id_to_name,
inputs=gr.Number(label="rank_id", value=0, precision=0),
outputs=gr.Textbox(label="Human-Readable Name"),
title="ID to Name Converter",
description="Convert an ID(rank_id) to a human-readable name based on NationStates metrics",
api_name="get_id_to_name"
)
'''
demo = gr.TabbedInterface(
interface_list=[get_nation_issues_interface, address_nation_issues_interface],
tab_names=["Get Nation Issues JSON", "Address Nation Issues"],
title="NationStates API Tools",
)
# Launch the interface and MCP server
if __name__ == "__main__":
demo.launch(mcp_server=True)