Spaces:
Sleeping
Sleeping
Commit
·
5cf54dc
1
Parent(s):
99b4bd2
It works!
Browse files- app/config.yaml +8 -0
- app/main.py +82 -15
- app/static/script.js +65 -6
- app/static/style.css +8 -0
- pyproject.toml +14 -5
- requirements.txt +3 -0
- uv.lock +117 -0
app/config.yaml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
llm:
|
| 2 |
+
model_name: "claude-3-5-sonnet-20240620"
|
| 3 |
+
system_prompt: |
|
| 4 |
+
You are an AI assistant that writes essays in the style of Paul Graham.
|
| 5 |
+
Focus on insights about startups, technology, programming, and contrarian thinking.
|
| 6 |
+
Be concise and clear.
|
| 7 |
+
max_tokens: 3500
|
| 8 |
+
prompt_template: "Write a Paul Graham essay about {short_description}"
|
app/main.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
import os
|
| 2 |
import asyncio
|
| 3 |
import logging
|
| 4 |
-
from typing import Optional # Import
|
| 5 |
from fastapi import FastAPI, Request, HTTPException, Form
|
| 6 |
from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
|
| 7 |
from fastapi.staticfiles import StaticFiles
|
|
@@ -12,6 +12,7 @@ from dotenv import load_dotenv
|
|
| 12 |
import json
|
| 13 |
from pydantic import BaseModel
|
| 14 |
from datetime import datetime # Add datetime import
|
|
|
|
| 15 |
|
| 16 |
# --- Import Anthropic ---
|
| 17 |
from anthropic import AsyncAnthropic, APIError
|
|
@@ -63,6 +64,32 @@ except Exception as e:
|
|
| 63 |
# --- Initialize FastAPI App ---
|
| 64 |
app = FastAPI()
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
# --- Mount Static Files & Templates ---
|
| 67 |
script_dir = os.path.dirname(__file__)
|
| 68 |
static_dir = os.path.join(script_dir, "static")
|
|
@@ -383,30 +410,69 @@ async def ask_paul_graham(request: Request, prompt: str = Form(...)):
|
|
| 383 |
)
|
| 384 |
# ---------------------------
|
| 385 |
|
| 386 |
-
# --- Determine Model Parameters --- #
|
| 387 |
-
|
| 388 |
-
model_name = "
|
| 389 |
-
system_prompt =
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 394 |
messages = [
|
| 395 |
{
|
| 396 |
"role": "user",
|
| 397 |
-
"content": prompt_text, # Use
|
| 398 |
}
|
| 399 |
]
|
| 400 |
-
#
|
| 401 |
|
| 402 |
try:
|
| 403 |
# --- Get or Create Model Params ID --- #
|
|
|
|
| 404 |
params_id = await get_or_create_model_params(
|
| 405 |
-
model_name, system_prompt, max_tokens
|
| 406 |
)
|
| 407 |
# ------------------------------------- #
|
| 408 |
|
| 409 |
-
# --- Find existing prompt based on
|
| 410 |
existing_prompt_result = (
|
| 411 |
supabase.table("prompts")
|
| 412 |
.select("prompt_id, created_at")
|
|
@@ -485,10 +551,11 @@ async def ask_paul_graham(request: Request, prompt: str = Form(...)):
|
|
| 485 |
|
| 486 |
# Stream the cached/latest response
|
| 487 |
async def stream_latest_cached():
|
| 488 |
-
chunk_size = 20
|
|
|
|
| 489 |
for i in range(0, len(latest_response_text), chunk_size):
|
| 490 |
chunk = latest_response_text[i : i + chunk_size]
|
| 491 |
-
yield f"data: {json.dumps({'text': chunk})}
|
| 492 |
await asyncio.sleep(0.01)
|
| 493 |
yield f"data: {json.dumps({'end': True})}\n\n"
|
| 494 |
|
|
|
|
| 1 |
import os
|
| 2 |
import asyncio
|
| 3 |
import logging
|
| 4 |
+
from typing import Optional, Dict, Any # Import Dict and Any
|
| 5 |
from fastapi import FastAPI, Request, HTTPException, Form
|
| 6 |
from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
|
| 7 |
from fastapi.staticfiles import StaticFiles
|
|
|
|
| 12 |
import json
|
| 13 |
from pydantic import BaseModel
|
| 14 |
from datetime import datetime # Add datetime import
|
| 15 |
+
import yaml # Import YAML
|
| 16 |
|
| 17 |
# --- Import Anthropic ---
|
| 18 |
from anthropic import AsyncAnthropic, APIError
|
|
|
|
| 64 |
# --- Initialize FastAPI App ---
|
| 65 |
app = FastAPI()
|
| 66 |
|
| 67 |
+
# --- Load Configuration ---
|
| 68 |
+
config: Dict[str, Any] = {} # Global config dictionary
|
| 69 |
+
script_dir = os.path.dirname(__file__)
|
| 70 |
+
config_path = os.path.join(script_dir, "config.yaml")
|
| 71 |
+
try:
|
| 72 |
+
with open(config_path, "r") as f:
|
| 73 |
+
config = yaml.safe_load(f)
|
| 74 |
+
if not config or "llm" not in config:
|
| 75 |
+
raise ValueError("Invalid config structure: 'llm' section missing.")
|
| 76 |
+
logger.info(f"Successfully loaded configuration from {config_path}")
|
| 77 |
+
except FileNotFoundError:
|
| 78 |
+
logger.error(
|
| 79 |
+
f"Configuration file not found at {config_path}. LLM functionality may be limited."
|
| 80 |
+
)
|
| 81 |
+
# Optionally set default values or exit
|
| 82 |
+
config = {"llm": {}} # Ensure config['llm'] exists
|
| 83 |
+
except yaml.YAMLError as e:
|
| 84 |
+
logger.error(f"Error parsing configuration file {config_path}: {e}")
|
| 85 |
+
config = {"llm": {}} # Ensure config['llm'] exists
|
| 86 |
+
except Exception as e:
|
| 87 |
+
logger.exception(
|
| 88 |
+
f"An unexpected error occurred while loading configuration from {config_path}",
|
| 89 |
+
exc_info=e,
|
| 90 |
+
)
|
| 91 |
+
config = {"llm": {}} # Ensure config['llm'] exists
|
| 92 |
+
|
| 93 |
# --- Mount Static Files & Templates ---
|
| 94 |
script_dir = os.path.dirname(__file__)
|
| 95 |
static_dir = os.path.join(script_dir, "static")
|
|
|
|
| 410 |
)
|
| 411 |
# ---------------------------
|
| 412 |
|
| 413 |
+
# --- Determine Model Parameters from Config --- #
|
| 414 |
+
llm_config = config.get("llm", {})
|
| 415 |
+
model_name = llm_config.get("model_name")
|
| 416 |
+
system_prompt = llm_config.get("system_prompt")
|
| 417 |
+
max_tokens_str = llm_config.get("max_tokens", "1000") # Default if missing
|
| 418 |
+
prompt_template = llm_config.get(
|
| 419 |
+
"prompt_template", "Write an essay about {short_description}"
|
| 420 |
+
) # Default template
|
| 421 |
+
|
| 422 |
+
# Validate required parameters
|
| 423 |
+
if not model_name:
|
| 424 |
+
logger.error("LLM model_name is missing in the configuration.")
|
| 425 |
+
raise HTTPException(
|
| 426 |
+
status_code=500, detail="LLM configuration error: model_name missing."
|
| 427 |
+
)
|
| 428 |
+
if not system_prompt:
|
| 429 |
+
logger.warning(
|
| 430 |
+
"LLM system_prompt is missing in the configuration. Using empty system prompt."
|
| 431 |
+
)
|
| 432 |
+
system_prompt = ""
|
| 433 |
+
|
| 434 |
+
# Safely convert max_tokens to int
|
| 435 |
+
try:
|
| 436 |
+
max_tokens = int(max_tokens_str)
|
| 437 |
+
except (ValueError, TypeError):
|
| 438 |
+
logger.warning(
|
| 439 |
+
f"Invalid max_tokens value '{max_tokens_str}' in config. Using default 1000."
|
| 440 |
+
)
|
| 441 |
+
max_tokens = 1000
|
| 442 |
+
|
| 443 |
+
# Construct prompt_text using the template
|
| 444 |
+
try:
|
| 445 |
+
prompt_text = prompt_template.format(short_description=short_description)
|
| 446 |
+
except KeyError:
|
| 447 |
+
logger.warning(
|
| 448 |
+
f"Prompt template '{prompt_template}' is missing '{{short_description}}'. Using default template."
|
| 449 |
+
)
|
| 450 |
+
prompt_text = f"Write an essay about {short_description}"
|
| 451 |
+
except Exception as e:
|
| 452 |
+
logger.error(f"Error formatting prompt template: {e}. Using basic prompt.")
|
| 453 |
+
prompt_text = f"Write an essay about {short_description}"
|
| 454 |
+
|
| 455 |
+
logger.info(f"Using LLM Parameters: model='{model_name}', max_tokens={max_tokens}")
|
| 456 |
+
# logger.debug(f"System Prompt: '{system_prompt[:100]}...'") # Log truncated system prompt
|
| 457 |
+
# logger.debug(f"Generated Prompt Text: '{prompt_text[:100]}...'") # Log truncated final prompt
|
| 458 |
+
|
| 459 |
messages = [
|
| 460 |
{
|
| 461 |
"role": "user",
|
| 462 |
+
"content": prompt_text, # Use the formatted prompt text
|
| 463 |
}
|
| 464 |
]
|
| 465 |
+
# ------------------------------------------ #
|
| 466 |
|
| 467 |
try:
|
| 468 |
# --- Get or Create Model Params ID --- #
|
| 469 |
+
# Pass the actual values retrieved from config
|
| 470 |
params_id = await get_or_create_model_params(
|
| 471 |
+
model_name=model_name, system_prompt=system_prompt, max_tokens=max_tokens
|
| 472 |
)
|
| 473 |
# ------------------------------------- #
|
| 474 |
|
| 475 |
+
# --- Find existing prompt based on prompt_text (generated from template) --- #
|
| 476 |
existing_prompt_result = (
|
| 477 |
supabase.table("prompts")
|
| 478 |
.select("prompt_id, created_at")
|
|
|
|
| 551 |
|
| 552 |
# Stream the cached/latest response
|
| 553 |
async def stream_latest_cached():
|
| 554 |
+
# chunk_size = 20
|
| 555 |
+
chunk_size = len(latest_response_text)
|
| 556 |
for i in range(0, len(latest_response_text), chunk_size):
|
| 557 |
chunk = latest_response_text[i : i + chunk_size]
|
| 558 |
+
yield f"data: {json.dumps({'text': chunk})}\n\n"
|
| 559 |
await asyncio.sleep(0.01)
|
| 560 |
yield f"data: {json.dumps({'end': True})}\n\n"
|
| 561 |
|
app/static/script.js
CHANGED
|
@@ -13,6 +13,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 13 |
|
| 14 |
let currentSort = { field: 'time', order: 'desc' }; // Default sort
|
| 15 |
let eventSource = null; // To hold the EventSource connection
|
|
|
|
| 16 |
|
| 17 |
// --- Character Counter ---
|
| 18 |
promptInput.addEventListener('input', () => {
|
|
@@ -22,6 +23,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 22 |
|
| 23 |
// --- Fetch and Render Essays ---
|
| 24 |
async function fetchEssays(sortBy = 'time', order = 'desc') {
|
|
|
|
| 25 |
essaysList.innerHTML = '<li class="text-gray-400">Loading essays...</li>'; // Show loading state
|
| 26 |
try {
|
| 27 |
const response = await fetch(`/essays?sort_by=${sortBy}&order=${order}`);
|
|
@@ -29,20 +31,43 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 29 |
throw new Error(`HTTP error! status: ${response.status}`);
|
| 30 |
}
|
| 31 |
const essays = await response.json();
|
|
|
|
| 32 |
|
| 33 |
essaysList.innerHTML = ''; // Clear list
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
if (essays.length === 0) {
|
| 36 |
essaysList.innerHTML = '<li class="text-gray-500">No essays found yet.</li>';
|
| 37 |
} else {
|
| 38 |
-
|
|
|
|
|
|
|
| 39 |
const li = document.createElement('li');
|
| 40 |
-
//
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
essaysList.appendChild(li);
|
|
|
|
| 45 |
});
|
|
|
|
| 46 |
}
|
| 47 |
} catch (error) {
|
| 48 |
console.error('Error fetching essays:', error);
|
|
@@ -69,6 +94,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 69 |
loadingIndicator.classList.remove('hidden');
|
| 70 |
responseOutput.innerHTML = ''; // Clear previous output
|
| 71 |
errorMessage.classList.add('hidden'); // Hide previous errors
|
|
|
|
| 72 |
|
| 73 |
try {
|
| 74 |
// Use EventSource for Server-Sent Events
|
|
@@ -114,7 +140,40 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
| 114 |
try {
|
| 115 |
const data = JSON.parse(line.substring(5).trim());
|
| 116 |
if (data.text) {
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
} else if (data.error) {
|
| 119 |
console.error("SSE Error:", data.error);
|
| 120 |
errorMessage.textContent = `Error: ${data.error}`;
|
|
|
|
| 13 |
|
| 14 |
let currentSort = { field: 'time', order: 'desc' }; // Default sort
|
| 15 |
let eventSource = null; // To hold the EventSource connection
|
| 16 |
+
let isFirstChunk = false; // Flag to track the first text chunk
|
| 17 |
|
| 18 |
// --- Character Counter ---
|
| 19 |
promptInput.addEventListener('input', () => {
|
|
|
|
| 23 |
|
| 24 |
// --- Fetch and Render Essays ---
|
| 25 |
async function fetchEssays(sortBy = 'time', order = 'desc') {
|
| 26 |
+
console.log(`Fetching essays: sort=${sortBy}, order=${order}`); // Log fetch start
|
| 27 |
essaysList.innerHTML = '<li class="text-gray-400">Loading essays...</li>'; // Show loading state
|
| 28 |
try {
|
| 29 |
const response = await fetch(`/essays?sort_by=${sortBy}&order=${order}`);
|
|
|
|
| 31 |
throw new Error(`HTTP error! status: ${response.status}`);
|
| 32 |
}
|
| 33 |
const essays = await response.json();
|
| 34 |
+
console.log("Fetched essays data:", essays); // Log the raw fetched data
|
| 35 |
|
| 36 |
essaysList.innerHTML = ''; // Clear list
|
| 37 |
|
| 38 |
+
if (!Array.isArray(essays)) {
|
| 39 |
+
console.error("Invalid data received from /essays endpoint. Expected an array.", essays);
|
| 40 |
+
throw new Error("Received invalid data from server.");
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
if (essays.length === 0) {
|
| 44 |
essaysList.innerHTML = '<li class="text-gray-500">No essays found yet.</li>';
|
| 45 |
} else {
|
| 46 |
+
console.log("Rendering essays..."); // Log before starting loop
|
| 47 |
+
essays.forEach((essay, index) => {
|
| 48 |
+
console.log(`Rendering essay ${index + 1}:`, essay); // Log each essay object
|
| 49 |
const li = document.createElement('li');
|
| 50 |
+
// Use essay.prompt, provide fallback if null/undefined
|
| 51 |
+
const promptText = essay.prompt ? essay.prompt : '[No prompt text]';
|
| 52 |
+
li.textContent = promptText;
|
| 53 |
+
// Optional: Add view count or date (add checks for null values)
|
| 54 |
+
// const createdAt = essay.created_at ? new Date(essay.created_at).toLocaleDateString() : 'N/A';
|
| 55 |
+
// const viewCount = essay.view_count !== undefined ? essay.view_count : 'N/A';
|
| 56 |
+
// li.textContent += ` (Views: ${viewCount}, Created: ${createdAt})`;
|
| 57 |
+
|
| 58 |
+
// Make the list item clickable to load the prompt
|
| 59 |
+
li.classList.add('cursor-pointer', 'hover:text-orange-700');
|
| 60 |
+
li.addEventListener('click', () => {
|
| 61 |
+
// Set the input value and simulate submission
|
| 62 |
+
promptInput.value = essay.prompt; // Use the original prompt text
|
| 63 |
+
promptInput.dispatchEvent(new Event('input')); // Update char count
|
| 64 |
+
promptForm.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
| 65 |
+
});
|
| 66 |
+
|
| 67 |
essaysList.appendChild(li);
|
| 68 |
+
console.log(`Appended item ${index + 1} to the list.`); // Confirm appendChild worked
|
| 69 |
});
|
| 70 |
+
console.log("Finished rendering essays."); // Log after loop completes
|
| 71 |
}
|
| 72 |
} catch (error) {
|
| 73 |
console.error('Error fetching essays:', error);
|
|
|
|
| 94 |
loadingIndicator.classList.remove('hidden');
|
| 95 |
responseOutput.innerHTML = ''; // Clear previous output
|
| 96 |
errorMessage.classList.add('hidden'); // Hide previous errors
|
| 97 |
+
isFirstChunk = true; // Reset flag for new request
|
| 98 |
|
| 99 |
try {
|
| 100 |
// Use EventSource for Server-Sent Events
|
|
|
|
| 140 |
try {
|
| 141 |
const data = JSON.parse(line.substring(5).trim());
|
| 142 |
if (data.text) {
|
| 143 |
+
// Escape HTML to prevent injection
|
| 144 |
+
const escapedText = data.text
|
| 145 |
+
.replace(/&/g, '&')
|
| 146 |
+
.replace(/</g, '<')
|
| 147 |
+
.replace(/>/g, '>')
|
| 148 |
+
.replace(/"/g, '"')
|
| 149 |
+
.replace(/'/g, ''');
|
| 150 |
+
|
| 151 |
+
let formattedOutput = "";
|
| 152 |
+
if (isFirstChunk) {
|
| 153 |
+
// Find the index of the first occurrence of one or more newlines
|
| 154 |
+
const firstParaBreakIndex = escapedText.search(/\n+/);
|
| 155 |
+
|
| 156 |
+
if (firstParaBreakIndex !== -1) {
|
| 157 |
+
// Extract title (before the first paragraph break)
|
| 158 |
+
const title = escapedText.substring(0, firstParaBreakIndex);
|
| 159 |
+
// Extract the rest of the text (including the newlines that caused the break)
|
| 160 |
+
const restOfText = escapedText.substring(firstParaBreakIndex);
|
| 161 |
+
|
| 162 |
+
// Format: Title span + replaced newlines in the rest
|
| 163 |
+
formattedOutput = `<span class="essay-title">${title}</span>${restOfText.replace(/\n+/g, '<br><br>')}`;
|
| 164 |
+
isFirstChunk = false; // Only process the first chunk this way
|
| 165 |
+
} else {
|
| 166 |
+
// If the first chunk has no newline sequence, treat the whole chunk as the title
|
| 167 |
+
const title = escapedText; // No need to replace newlines here if none exist
|
| 168 |
+
formattedOutput = `<span class="essay-title">${title}</span>`;
|
| 169 |
+
// Subsequent chunks will add <br> if needed
|
| 170 |
+
}
|
| 171 |
+
} else {
|
| 172 |
+
// For subsequent chunks, just escape and replace newlines
|
| 173 |
+
formattedOutput = escapedText.replace(/\n+/g, '<br><br>');
|
| 174 |
+
}
|
| 175 |
+
responseOutput.innerHTML += formattedOutput; // Append safely formatted text chunk
|
| 176 |
+
|
| 177 |
} else if (data.error) {
|
| 178 |
console.error("SSE Error:", data.error);
|
| 179 |
errorMessage.textContent = `Error: ${data.error}`;
|
app/static/style.css
CHANGED
|
@@ -2,6 +2,14 @@
|
|
| 2 |
|
| 3 |
/* Add any custom styles here if needed, complementing Tailwind */
|
| 4 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
/* Example: Style for the active sort button */
|
| 6 |
.sort-button.active {
|
| 7 |
font-weight: bold;
|
|
|
|
| 2 |
|
| 3 |
/* Add any custom styles here if needed, complementing Tailwind */
|
| 4 |
|
| 5 |
+
/* Style for the essay title */
|
| 6 |
+
.essay-title {
|
| 7 |
+
color: #841814;
|
| 8 |
+
font-variant: small-caps;
|
| 9 |
+
font-weight: bold;
|
| 10 |
+
/* Optional: make it bolder */
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
/* Example: Style for the active sort button */
|
| 14 |
.sort-button.active {
|
| 15 |
font-weight: bold;
|
pyproject.toml
CHANGED
|
@@ -12,7 +12,7 @@ version = "0.1.0"
|
|
| 12 |
description = "A web app to generate Paul Graham style essays using an LLM, with response storage."
|
| 13 |
readme = "README.md" # Assumes you have or will create a README file
|
| 14 |
requires-python = ">=3.9" # Based on the Python version used in Dockerfile/dev environment
|
| 15 |
-
license =
|
| 16 |
authors = [
|
| 17 |
{ name = "Jason Gross", email = "jasongross9@gmail.com" }, # Optional: Add your details
|
| 18 |
]
|
|
@@ -30,6 +30,8 @@ dependencies = [
|
|
| 30 |
"anthropic>=0.49.0",
|
| 31 |
"httpx>=0.28.1",
|
| 32 |
"supabase>=2.15.0",
|
|
|
|
|
|
|
| 33 |
]
|
| 34 |
|
| 35 |
# --- Optional: Project URLs ---
|
|
@@ -43,10 +45,17 @@ dependencies = [
|
|
| 43 |
# ask-pg = "app.main:app" # Example, depends on how you structure/run
|
| 44 |
|
| 45 |
# --- Optional: Development Dependencies ---
|
| 46 |
-
#
|
| 47 |
-
|
| 48 |
-
|
|
|
|
| 49 |
# "pytest",
|
| 50 |
# "ruff",
|
| 51 |
# "mypy",
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
description = "A web app to generate Paul Graham style essays using an LLM, with response storage."
|
| 13 |
readme = "README.md" # Assumes you have or will create a README file
|
| 14 |
requires-python = ">=3.9" # Based on the Python version used in Dockerfile/dev environment
|
| 15 |
+
license = "MIT" # Use SPDX identifier string
|
| 16 |
authors = [
|
| 17 |
{ name = "Jason Gross", email = "jasongross9@gmail.com" }, # Optional: Add your details
|
| 18 |
]
|
|
|
|
| 30 |
"anthropic>=0.49.0",
|
| 31 |
"httpx>=0.28.1",
|
| 32 |
"supabase>=2.15.0",
|
| 33 |
+
"pyyaml>=6.0.2",
|
| 34 |
+
"requests>=2.32.3",
|
| 35 |
]
|
| 36 |
|
| 37 |
# --- Optional: Project URLs ---
|
|
|
|
| 45 |
# ask-pg = "app.main:app" # Example, depends on how you structure/run
|
| 46 |
|
| 47 |
# --- Optional: Development Dependencies ---
|
| 48 |
+
# Add linters, formatters, testing tools here
|
| 49 |
+
[project.optional-dependencies]
|
| 50 |
+
dev = [
|
| 51 |
+
"types-PyYAML", # Stubs for PyYAML
|
| 52 |
# "pytest",
|
| 53 |
# "ruff",
|
| 54 |
# "mypy",
|
| 55 |
+
]
|
| 56 |
+
|
| 57 |
+
# --- Setuptools Configuration ---
|
| 58 |
+
[tool.setuptools.packages.find]
|
| 59 |
+
where = ["app"] # Look for packages in the app directory
|
| 60 |
+
# exclude = [] # Optional: exclude specific modules/packages
|
| 61 |
+
# include = ["app*"] # Optional: Be more specific if needed
|
requirements.txt
CHANGED
|
@@ -6,3 +6,6 @@ httpx>=0.24.0
|
|
| 6 |
python-dotenv>=1.0.0
|
| 7 |
jinja2>=3.0.0 # For templating
|
| 8 |
anthropic>=0.20.0 # Added Anthropic client library
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
python-dotenv>=1.0.0
|
| 7 |
jinja2>=3.0.0 # For templating
|
| 8 |
anthropic>=0.20.0 # Added Anthropic client library
|
| 9 |
+
psycopg2-binary
|
| 10 |
+
requests
|
| 11 |
+
PyYAML # Added for config file parsing
|
uv.lock
CHANGED
|
@@ -173,10 +173,17 @@ dependencies = [
|
|
| 173 |
{ name = "jinja2" },
|
| 174 |
{ name = "python-dotenv" },
|
| 175 |
{ name = "python-multipart" },
|
|
|
|
|
|
|
| 176 |
{ name = "supabase" },
|
| 177 |
{ name = "uvicorn", extra = ["standard"] },
|
| 178 |
]
|
| 179 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
[package.metadata]
|
| 181 |
requires-dist = [
|
| 182 |
{ name = "anthropic", specifier = ">=0.49.0" },
|
|
@@ -185,7 +192,10 @@ requires-dist = [
|
|
| 185 |
{ name = "jinja2", specifier = ">=3.0.0" },
|
| 186 |
{ name = "python-dotenv", specifier = ">=1.0.0" },
|
| 187 |
{ name = "python-multipart", specifier = ">=0.0.20" },
|
|
|
|
|
|
|
| 188 |
{ name = "supabase", specifier = ">=2.15.0" },
|
|
|
|
| 189 |
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.20.0" },
|
| 190 |
]
|
| 191 |
|
|
@@ -216,6 +226,80 @@ wheels = [
|
|
| 216 |
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
|
| 217 |
]
|
| 218 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
[[package]]
|
| 220 |
name = "click"
|
| 221 |
version = "8.1.8"
|
|
@@ -1177,6 +1261,21 @@ wheels = [
|
|
| 1177 |
{ url = "https://files.pythonhosted.org/packages/1d/b7/1b7651f353e14543c60cdfe40e3ea4dea412cfb2e93ab6384e72be813f05/realtime-2.4.2-py3-none-any.whl", hash = "sha256:0cc1b4a097acf9c0bd3a2f1998170de47744574c606617285113ddb3021e54ca", size = 22025 },
|
| 1178 |
]
|
| 1179 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1180 |
[[package]]
|
| 1181 |
name = "six"
|
| 1182 |
version = "1.17.0"
|
|
@@ -1299,6 +1398,15 @@ wheels = [
|
|
| 1299 |
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
|
| 1300 |
]
|
| 1301 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1302 |
[[package]]
|
| 1303 |
name = "typing-extensions"
|
| 1304 |
version = "4.13.2"
|
|
@@ -1320,6 +1428,15 @@ wheels = [
|
|
| 1320 |
{ url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
|
| 1321 |
]
|
| 1322 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1323 |
[[package]]
|
| 1324 |
name = "uvicorn"
|
| 1325 |
version = "0.34.1"
|
|
|
|
| 173 |
{ name = "jinja2" },
|
| 174 |
{ name = "python-dotenv" },
|
| 175 |
{ name = "python-multipart" },
|
| 176 |
+
{ name = "pyyaml" },
|
| 177 |
+
{ name = "requests" },
|
| 178 |
{ name = "supabase" },
|
| 179 |
{ name = "uvicorn", extra = ["standard"] },
|
| 180 |
]
|
| 181 |
|
| 182 |
+
[package.optional-dependencies]
|
| 183 |
+
dev = [
|
| 184 |
+
{ name = "types-pyyaml" },
|
| 185 |
+
]
|
| 186 |
+
|
| 187 |
[package.metadata]
|
| 188 |
requires-dist = [
|
| 189 |
{ name = "anthropic", specifier = ">=0.49.0" },
|
|
|
|
| 192 |
{ name = "jinja2", specifier = ">=3.0.0" },
|
| 193 |
{ name = "python-dotenv", specifier = ">=1.0.0" },
|
| 194 |
{ name = "python-multipart", specifier = ">=0.0.20" },
|
| 195 |
+
{ name = "pyyaml", specifier = ">=6.0.2" },
|
| 196 |
+
{ name = "requests", specifier = ">=2.32.3" },
|
| 197 |
{ name = "supabase", specifier = ">=2.15.0" },
|
| 198 |
+
{ name = "types-pyyaml", marker = "extra == 'dev'" },
|
| 199 |
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.20.0" },
|
| 200 |
]
|
| 201 |
|
|
|
|
| 226 |
{ url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
|
| 227 |
]
|
| 228 |
|
| 229 |
+
[[package]]
|
| 230 |
+
name = "charset-normalizer"
|
| 231 |
+
version = "3.4.1"
|
| 232 |
+
source = { registry = "https://pypi.org/simple" }
|
| 233 |
+
sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
|
| 234 |
+
wheels = [
|
| 235 |
+
{ url = "https://files.pythonhosted.org/packages/0d/58/5580c1716040bc89206c77d8f74418caf82ce519aae06450393ca73475d1/charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", size = 198013 },
|
| 236 |
+
{ url = "https://files.pythonhosted.org/packages/d0/11/00341177ae71c6f5159a08168bcb98c6e6d196d372c94511f9f6c9afe0c6/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", size = 141285 },
|
| 237 |
+
{ url = "https://files.pythonhosted.org/packages/01/09/11d684ea5819e5a8f5100fb0b38cf8d02b514746607934134d31233e02c8/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", size = 151449 },
|
| 238 |
+
{ url = "https://files.pythonhosted.org/packages/08/06/9f5a12939db324d905dc1f70591ae7d7898d030d7662f0d426e2286f68c9/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", size = 143892 },
|
| 239 |
+
{ url = "https://files.pythonhosted.org/packages/93/62/5e89cdfe04584cb7f4d36003ffa2936681b03ecc0754f8e969c2becb7e24/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", size = 146123 },
|
| 240 |
+
{ url = "https://files.pythonhosted.org/packages/a9/ac/ab729a15c516da2ab70a05f8722ecfccc3f04ed7a18e45c75bbbaa347d61/charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", size = 147943 },
|
| 241 |
+
{ url = "https://files.pythonhosted.org/packages/03/d2/3f392f23f042615689456e9a274640c1d2e5dd1d52de36ab8f7955f8f050/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", size = 142063 },
|
| 242 |
+
{ url = "https://files.pythonhosted.org/packages/f2/e3/e20aae5e1039a2cd9b08d9205f52142329f887f8cf70da3650326670bddf/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", size = 150578 },
|
| 243 |
+
{ url = "https://files.pythonhosted.org/packages/8d/af/779ad72a4da0aed925e1139d458adc486e61076d7ecdcc09e610ea8678db/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", size = 153629 },
|
| 244 |
+
{ url = "https://files.pythonhosted.org/packages/c2/b6/7aa450b278e7aa92cf7732140bfd8be21f5f29d5bf334ae987c945276639/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", size = 150778 },
|
| 245 |
+
{ url = "https://files.pythonhosted.org/packages/39/f4/d9f4f712d0951dcbfd42920d3db81b00dd23b6ab520419626f4023334056/charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", size = 146453 },
|
| 246 |
+
{ url = "https://files.pythonhosted.org/packages/49/2b/999d0314e4ee0cff3cb83e6bc9aeddd397eeed693edb4facb901eb8fbb69/charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", size = 95479 },
|
| 247 |
+
{ url = "https://files.pythonhosted.org/packages/2d/ce/3cbed41cff67e455a386fb5e5dd8906cdda2ed92fbc6297921f2e4419309/charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", size = 102790 },
|
| 248 |
+
{ url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 },
|
| 249 |
+
{ url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 },
|
| 250 |
+
{ url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 },
|
| 251 |
+
{ url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 },
|
| 252 |
+
{ url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 },
|
| 253 |
+
{ url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 },
|
| 254 |
+
{ url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 },
|
| 255 |
+
{ url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 },
|
| 256 |
+
{ url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 },
|
| 257 |
+
{ url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 },
|
| 258 |
+
{ url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 },
|
| 259 |
+
{ url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 },
|
| 260 |
+
{ url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 },
|
| 261 |
+
{ url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 },
|
| 262 |
+
{ url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 },
|
| 263 |
+
{ url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 },
|
| 264 |
+
{ url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 },
|
| 265 |
+
{ url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 },
|
| 266 |
+
{ url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 },
|
| 267 |
+
{ url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 },
|
| 268 |
+
{ url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 },
|
| 269 |
+
{ url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 },
|
| 270 |
+
{ url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 },
|
| 271 |
+
{ url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 },
|
| 272 |
+
{ url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 },
|
| 273 |
+
{ url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 },
|
| 274 |
+
{ url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 },
|
| 275 |
+
{ url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 },
|
| 276 |
+
{ url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 },
|
| 277 |
+
{ url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 },
|
| 278 |
+
{ url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 },
|
| 279 |
+
{ url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 },
|
| 280 |
+
{ url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 },
|
| 281 |
+
{ url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 },
|
| 282 |
+
{ url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 },
|
| 283 |
+
{ url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 },
|
| 284 |
+
{ url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 },
|
| 285 |
+
{ url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 },
|
| 286 |
+
{ url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 },
|
| 287 |
+
{ url = "https://files.pythonhosted.org/packages/7f/c0/b913f8f02836ed9ab32ea643c6fe4d3325c3d8627cf6e78098671cafff86/charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", size = 197867 },
|
| 288 |
+
{ url = "https://files.pythonhosted.org/packages/0f/6c/2bee440303d705b6fb1e2ec789543edec83d32d258299b16eed28aad48e0/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", size = 141385 },
|
| 289 |
+
{ url = "https://files.pythonhosted.org/packages/3d/04/cb42585f07f6f9fd3219ffb6f37d5a39b4fd2db2355b23683060029c35f7/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", size = 151367 },
|
| 290 |
+
{ url = "https://files.pythonhosted.org/packages/54/54/2412a5b093acb17f0222de007cc129ec0e0df198b5ad2ce5699355269dfe/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", size = 143928 },
|
| 291 |
+
{ url = "https://files.pythonhosted.org/packages/5a/6d/e2773862b043dcf8a221342954f375392bb2ce6487bcd9f2c1b34e1d6781/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", size = 146203 },
|
| 292 |
+
{ url = "https://files.pythonhosted.org/packages/b9/f8/ca440ef60d8f8916022859885f231abb07ada3c347c03d63f283bec32ef5/charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", size = 148082 },
|
| 293 |
+
{ url = "https://files.pythonhosted.org/packages/04/d2/42fd330901aaa4b805a1097856c2edf5095e260a597f65def493f4b8c833/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", size = 142053 },
|
| 294 |
+
{ url = "https://files.pythonhosted.org/packages/9e/af/3a97a4fa3c53586f1910dadfc916e9c4f35eeada36de4108f5096cb7215f/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", size = 150625 },
|
| 295 |
+
{ url = "https://files.pythonhosted.org/packages/26/ae/23d6041322a3556e4da139663d02fb1b3c59a23ab2e2b56432bd2ad63ded/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", size = 153549 },
|
| 296 |
+
{ url = "https://files.pythonhosted.org/packages/94/22/b8f2081c6a77cb20d97e57e0b385b481887aa08019d2459dc2858ed64871/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", size = 150945 },
|
| 297 |
+
{ url = "https://files.pythonhosted.org/packages/c7/0b/c5ec5092747f801b8b093cdf5610e732b809d6cb11f4c51e35fc28d1d389/charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", size = 146595 },
|
| 298 |
+
{ url = "https://files.pythonhosted.org/packages/0c/5a/0b59704c38470df6768aa154cc87b1ac7c9bb687990a1559dc8765e8627e/charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", size = 95453 },
|
| 299 |
+
{ url = "https://files.pythonhosted.org/packages/85/2d/a9790237cb4d01a6d57afadc8573c8b73c609ade20b80f4cda30802009ee/charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", size = 102811 },
|
| 300 |
+
{ url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 },
|
| 301 |
+
]
|
| 302 |
+
|
| 303 |
[[package]]
|
| 304 |
name = "click"
|
| 305 |
version = "8.1.8"
|
|
|
|
| 1261 |
{ url = "https://files.pythonhosted.org/packages/1d/b7/1b7651f353e14543c60cdfe40e3ea4dea412cfb2e93ab6384e72be813f05/realtime-2.4.2-py3-none-any.whl", hash = "sha256:0cc1b4a097acf9c0bd3a2f1998170de47744574c606617285113ddb3021e54ca", size = 22025 },
|
| 1262 |
]
|
| 1263 |
|
| 1264 |
+
[[package]]
|
| 1265 |
+
name = "requests"
|
| 1266 |
+
version = "2.32.3"
|
| 1267 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1268 |
+
dependencies = [
|
| 1269 |
+
{ name = "certifi" },
|
| 1270 |
+
{ name = "charset-normalizer" },
|
| 1271 |
+
{ name = "idna" },
|
| 1272 |
+
{ name = "urllib3" },
|
| 1273 |
+
]
|
| 1274 |
+
sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
|
| 1275 |
+
wheels = [
|
| 1276 |
+
{ url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
|
| 1277 |
+
]
|
| 1278 |
+
|
| 1279 |
[[package]]
|
| 1280 |
name = "six"
|
| 1281 |
version = "1.17.0"
|
|
|
|
| 1398 |
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 },
|
| 1399 |
]
|
| 1400 |
|
| 1401 |
+
[[package]]
|
| 1402 |
+
name = "types-pyyaml"
|
| 1403 |
+
version = "6.0.12.20250402"
|
| 1404 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1405 |
+
sdist = { url = "https://files.pythonhosted.org/packages/2d/68/609eed7402f87c9874af39d35942744e39646d1ea9011765ec87b01b2a3c/types_pyyaml-6.0.12.20250402.tar.gz", hash = "sha256:d7c13c3e6d335b6af4b0122a01ff1d270aba84ab96d1a1a1063ecba3e13ec075", size = 17282 }
|
| 1406 |
+
wheels = [
|
| 1407 |
+
{ url = "https://files.pythonhosted.org/packages/ed/56/1fe61db05685fbb512c07ea9323f06ea727125951f1eb4dff110b3311da3/types_pyyaml-6.0.12.20250402-py3-none-any.whl", hash = "sha256:652348fa9e7a203d4b0d21066dfb00760d3cbd5a15ebb7cf8d33c88a49546681", size = 20329 },
|
| 1408 |
+
]
|
| 1409 |
+
|
| 1410 |
[[package]]
|
| 1411 |
name = "typing-extensions"
|
| 1412 |
version = "4.13.2"
|
|
|
|
| 1428 |
{ url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
|
| 1429 |
]
|
| 1430 |
|
| 1431 |
+
[[package]]
|
| 1432 |
+
name = "urllib3"
|
| 1433 |
+
version = "2.4.0"
|
| 1434 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1435 |
+
sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 }
|
| 1436 |
+
wheels = [
|
| 1437 |
+
{ url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 },
|
| 1438 |
+
]
|
| 1439 |
+
|
| 1440 |
[[package]]
|
| 1441 |
name = "uvicorn"
|
| 1442 |
version = "0.34.1"
|