WIP: status component working; result page still being patched

This commit is contained in:
Francis Secada 2025-01-22 11:53:19 -05:00
parent 05d8baffb1
commit f2363ff81d
9 changed files with 76 additions and 70 deletions

View File

@ -10,7 +10,7 @@ annotated-types==0.7.0
# via # via
# pydantic # pydantic
# sqlmodel-crud-utilities # sqlmodel-crud-utilities
anthropic==0.43.1 anthropic==0.44.0
# via pydantic-ai-slim # via pydantic-ai-slim
anyio==4.8.0 anyio==4.8.0
# via # via
@ -31,7 +31,7 @@ beautifulsoup4==4.12.3
# via httpx-html # via httpx-html
billiard==4.2.1 billiard==4.2.1
# via celery # via celery
cachetools==5.5.0 cachetools==5.5.1
# via google-auth # via google-auth
celery==5.4.0 celery==5.4.0
# via # via
@ -195,7 +195,7 @@ mistralai==1.4.0
# via pydantic-ai-slim # via pydantic-ai-slim
mypy-extensions==1.0.0 mypy-extensions==1.0.0
# via typing-inspect # via typing-inspect
openai==1.59.9 openai==1.60.0
# via # via
# -r core_requirements.in # -r core_requirements.in
# pydantic-ai-slim # pydantic-ai-slim
@ -413,7 +413,7 @@ typing-inspect==0.9.0
# via # via
# -r core_requirements.in # -r core_requirements.in
# mistralai # mistralai
tzdata==2024.2 tzdata==2025.1
# via # via
# celery # celery
# kombu # kombu

View File

@ -84,7 +84,7 @@ fastapi-debug-toolbar @ git+https://github.com/fsecada01/fastapi-debug-toolbar.g
# via -r dev_requirements.in # via -r dev_requirements.in
fastjsonschema==2.21.1 fastjsonschema==2.21.1
# via nbformat # via nbformat
filelock==3.16.1 filelock==3.17.0
# via virtualenv # via virtualenv
fqdn==1.5.1 fqdn==1.5.1
# via jsonschema # via jsonschema

View File

@ -80,7 +80,7 @@ dependencies = [
"aiomysql==0.2.0", "aiomysql==0.2.0",
"amqp==5.3.1", "amqp==5.3.1",
"annotated-types==0.7.0", "annotated-types==0.7.0",
"anthropic==0.43.1", "anthropic==0.44.0",
"anyio==4.8.0", "anyio==4.8.0",
"appdirs==1.4.4", "appdirs==1.4.4",
"asgiref==3.8.1", "asgiref==3.8.1",
@ -88,7 +88,7 @@ dependencies = [
"asyncpg==0.30.0", "asyncpg==0.30.0",
"beautifulsoup4==4.12.3", "beautifulsoup4==4.12.3",
"billiard==4.2.1", "billiard==4.2.1",
"cachetools==5.5.0", "cachetools==5.5.1",
"celery==5.4.0", "celery==5.4.0",
"certifi==2024.12.14", "certifi==2024.12.14",
"charset-normalizer==3.4.1", "charset-normalizer==3.4.1",
@ -141,7 +141,7 @@ dependencies = [
"mdurl==0.1.2", "mdurl==0.1.2",
"mistralai==1.4.0", "mistralai==1.4.0",
"mypy-extensions==1.0.0", "mypy-extensions==1.0.0",
"openai==1.59.9", "openai==1.60.0",
"opentelemetry-api==1.29.0", "opentelemetry-api==1.29.0",
"opentelemetry-exporter-otlp-proto-common==1.29.0", "opentelemetry-exporter-otlp-proto-common==1.29.0",
"opentelemetry-exporter-otlp-proto-http==1.29.0", "opentelemetry-exporter-otlp-proto-http==1.29.0",
@ -203,7 +203,7 @@ dependencies = [
"tqdm==4.67.1", "tqdm==4.67.1",
"typing-extensions==4.12.2", "typing-extensions==4.12.2",
"typing-inspect==0.9.0", "typing-inspect==0.9.0",
"tzdata==2024.2", "tzdata==2025.1",
"update-checker==0.18.0", "update-checker==0.18.0",
"urllib3==1.26.20", "urllib3==1.26.20",
"uvicorn==0.34.0", "uvicorn==0.34.0",
@ -252,7 +252,7 @@ dev = [
"fastapi-debug-toolbar==0.6.3", "fastapi-debug-toolbar==0.6.3",
"fastapi==0.115.6", "fastapi==0.115.6",
"fastjsonschema==2.21.1", "fastjsonschema==2.21.1",
"filelock==3.16.1", "filelock==3.17.0",
"fqdn==1.5.1", "fqdn==1.5.1",
"greenlet==3.1.1", "greenlet==3.1.1",
"h11==0.14.0", "h11==0.14.0",

View File

@ -119,7 +119,7 @@ async def run_agent(
f"Perform a comprehensive SWOT analysis for this product: {url}", f"Perform a comprehensive SWOT analysis for this product: {url}",
deps=deps, deps=deps,
) )
logger.info(f"Agent Result: {pformat(result.data)}") logger.info(f"Agent Result: {pformat(result.data.model_dump())}")
if deps.update_status_func: if deps.update_status_func:
await deps.update_status_func(deps.request, "Analysis Complete") await deps.update_status_func(deps.request, "Analysis Complete")

View File

@ -13,6 +13,7 @@ from backend.site.consts import (
ANALYSIS_COMPLETE_MESSAGE, ANALYSIS_COMPLETE_MESSAGE,
ANALYZING_MESSAGE, ANALYZING_MESSAGE,
result_store, result_store,
running_tasks,
status_store, status_store,
) )
from backend.site.utils import run_agent_with_progress from backend.site.utils import run_agent_with_progress
@ -47,13 +48,13 @@ async def analyze_url(request: Request, url: str = Form(...)) -> HTMLResponse:
:param url: :param url:
:return: :return:
""" """
running_tasks = set()
session_id = str(id(request)) session_id = str(id(request))
request.session["analysis_id"] = session_id request.session["analysis_id"] = session_id
request.session["start_time"] = asyncio.get_event_loop().time() request.session["start_time"] = asyncio.get_event_loop().time()
# Clearing out the status store for the analysis ID session # Clearing out the status store for the analysis ID session
status_store[session_id] = [] status_store[session_id] = []
result_store[session_id] = None
status_store[session_id].append(ANALYZING_MESSAGE) status_store[session_id].append(ANALYZING_MESSAGE)
@ -83,15 +84,18 @@ async def get_status(request: Request):
context = {"request": request, "messages": [], "result": False} context = {"request": request, "messages": [], "result": False}
session_id = request.session.get("analysis_id") session_id = request.session.get("analysis_id")
if session_id: if session_id:
logger.info(f"Found session id! {session_id}")
messages = status_store.get(session_id, []) messages = status_store.get(session_id, [])
result = ANALYSIS_COMPLETE_MESSAGE in messages result = ANALYSIS_COMPLETE_MESSAGE in messages
logger.info( logger.info(
f"Status check - Session ID: {'session_id'}, Messages: " f"Status check - Session ID: {session_id}, Messages: "
f"{messages}", f"{messages}",
) )
context.update({"messages": messages, "result": result}) context.update({"messages": messages, "result": result})
logger.info(context)
return templates.TemplateResponse("status.html", context=context) return templates.TemplateResponse("status.html", context=context)

View File

@ -1,6 +1,6 @@
{% extends "components/main/base.html" %} {% extends "components/main/base.html" %}
{% block content %} {% block content %}
<section class="hero is-large is-info"> <section class="hero is-medium is-info">
<div class="hero-body"> <div class="hero-body">
<p class="title">SWOT ANALYZER</p> <p class="title">SWOT ANALYZER</p>
<p class="subtitle">Strengths, Weaknesses, Opportunities and Threats <p class="subtitle">Strengths, Weaknesses, Opportunities and Threats
@ -31,29 +31,28 @@
</Search> </Search>
</div> </div>
</section> </section>
<section class="section"> <section class="section"
<div class="container" id="analysis-content">
id="status"> <div class="box"
<div class="box" id="status"
id="status" hx-get={{ url_for('get_status') }}
hx-get={{ url_for('get_status') }} hx-trigger="load, every 1s"
hx-trigger='load, every 1s' hx-swap="innerHTML"
hx-swap='innerHTML' style="display: none">
style="display: none"> </div>
</div> <div class="box"
<div class="box" id="result"
id="result" hx-get={{ url_for('get_result') }}
hx-get={{ url_for('get_result') }} hx-trigger="load, every 1s[!this.querySelector('#result-container') || this.style.display === 'none']"
hx-trigger="load, every 1s[!this.querySelector('#result-container') || this.style.display === 'none']" hx-swap="innerHTML"
hx-swap='innerHTML' hx-on:after-request="
hx-on:after-request=" if(this.innerHTML.trim().length > 0) {
if(this.innerHTML.trim().length > 0) { console.log('Going to turn off the status element and load the result element.')
const statusDiv = document.querySelector(#status); const statusDiv = document.querySelector('#status');
if (statusDiv) statusDiv.style.display = 'none'; if (statusDiv) statusDiv.style.display = 'none';
this.style.display = 'block' this.style.display = 'block'
} }
"> ">
</div>
</div> </div>
</section> </section>
{% endblock content %} {% endblock content %}

View File

@ -1,17 +1,19 @@
{% if result %} {% if result %}
<section class="section" <section class="section"
id="result-container"> id="result-container">
<div class="container" <h2 class="title is-2">
id="result-container"> {{ result }}
</h2>
<div class="container">
<h2 class="subtitle is-2">Analysis Complete</h2> <h2 class="subtitle is-2">Analysis Complete</h2>
<div class="fixed-grid"> <div class="fixed-grid">
<div class="grid"> <div class="grid">
{% for cat in result %} {% for cat, val in result.items() %}
<div class="cell"> <div class="cell">
<div class="content"> <div class="content">
<ul> <ul>
{% for result in cat %} {% for value in val %}
<ResultEntry result=result> <ResultEntry result=value>
{% if cat == 'strengths' %} {% if cat == 'strengths' %}
<i class="fas fa-solid fa-arrow-up"></i> <i class="fas fa-solid fa-arrow-up"></i>
{% elif cat == 'weaknesses' %} {% elif cat == 'weaknesses' %}
@ -23,7 +25,6 @@
class="fas fa-solid fa-triangle-exclamation"></i> class="fas fa-solid fa-triangle-exclamation"></i>
{% endif %} {% endif %}
</ResultEntry> </ResultEntry>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -7,18 +7,20 @@
{% set is_loading = loop.last and not result %} {% set is_loading = loop.last and not result %}
{% set is_tool_message = message.startswith('Using tool') %} {% set is_tool_message = message.startswith('Using tool') %}
<div class="box"> <div class="box">
{% set bg_color = 'danger' if is_error else ('dark' if is_loading else
"info") %}
{% if is_error %} {% if is_error %}
{% set bg_color = 'danger' %}
{% set header_content, content = message.split('body:', 1) %} {% set header_content, content = message.split('body:', 1) %}
{% elif is_tool_message %}
{% set header_content, content = message.split(' ', 2)[2].split('...', 1) %}
{% elif is_loading %} {% elif is_loading %}
{% set content = message %} {% set bg_color = 'success' %}
{% set header_content = 'In Progress' %}
{% else %}
{% set content = message %} {% set content = message %}
{% set header_content = 'Complete' %} {% set header_content = 'Complete' %}
{% elif is_tool_message %}
{% set bg_color = 'dark'%}
{% set header_content, content = message.split(' ', 2)[2].split('...', 1) %}
{% else %}
{% set bg_color = 'info' %}
{% set content = message %}
{% set header_content = 'In Progress' %}
{% endif %} {% endif %}
<StatusResult div_class={{ bg_color }} <StatusResult div_class={{ bg_color }}
header_content={{ header_content }}>{{ message }} header_content={{ header_content }}>{{ message }}

40
uv.lock generated
View File

@ -282,7 +282,7 @@ requires-dist = [
{ name = "aiomysql", specifier = "==0.2.0" }, { name = "aiomysql", specifier = "==0.2.0" },
{ name = "amqp", specifier = "==5.3.1" }, { name = "amqp", specifier = "==5.3.1" },
{ name = "annotated-types", specifier = "==0.7.0" }, { name = "annotated-types", specifier = "==0.7.0" },
{ name = "anthropic", specifier = "==0.43.1" }, { name = "anthropic", specifier = "==0.44.0" },
{ name = "anyio", specifier = "==4.8.0" }, { name = "anyio", specifier = "==4.8.0" },
{ name = "appdirs", specifier = "==1.4.4" }, { name = "appdirs", specifier = "==1.4.4" },
{ name = "asgiref", specifier = "==3.8.1" }, { name = "asgiref", specifier = "==3.8.1" },
@ -290,7 +290,7 @@ requires-dist = [
{ name = "asyncpg", specifier = "==0.30.0" }, { name = "asyncpg", specifier = "==0.30.0" },
{ name = "beautifulsoup4", specifier = "==4.12.3" }, { name = "beautifulsoup4", specifier = "==4.12.3" },
{ name = "billiard", specifier = "==4.2.1" }, { name = "billiard", specifier = "==4.2.1" },
{ name = "cachetools", specifier = "==5.5.0" }, { name = "cachetools", specifier = "==5.5.1" },
{ name = "celery", specifier = "==5.4.0" }, { name = "celery", specifier = "==5.4.0" },
{ name = "certifi", specifier = "==2024.12.14" }, { name = "certifi", specifier = "==2024.12.14" },
{ name = "charset-normalizer", specifier = "==3.4.1" }, { name = "charset-normalizer", specifier = "==3.4.1" },
@ -343,7 +343,7 @@ requires-dist = [
{ name = "mdurl", specifier = "==0.1.2" }, { name = "mdurl", specifier = "==0.1.2" },
{ name = "mistralai", specifier = "==1.4.0" }, { name = "mistralai", specifier = "==1.4.0" },
{ name = "mypy-extensions", specifier = "==1.0.0" }, { name = "mypy-extensions", specifier = "==1.0.0" },
{ name = "openai", specifier = "==1.59.9" }, { name = "openai", specifier = "==1.60.0" },
{ name = "opentelemetry-api", specifier = "==1.29.0" }, { name = "opentelemetry-api", specifier = "==1.29.0" },
{ name = "opentelemetry-exporter-otlp-proto-common", specifier = "==1.29.0" }, { name = "opentelemetry-exporter-otlp-proto-common", specifier = "==1.29.0" },
{ name = "opentelemetry-exporter-otlp-proto-http", specifier = "==1.29.0" }, { name = "opentelemetry-exporter-otlp-proto-http", specifier = "==1.29.0" },
@ -405,7 +405,7 @@ requires-dist = [
{ name = "tqdm", specifier = "==4.67.1" }, { name = "tqdm", specifier = "==4.67.1" },
{ name = "typing-extensions", specifier = "==4.12.2" }, { name = "typing-extensions", specifier = "==4.12.2" },
{ name = "typing-inspect", specifier = "==0.9.0" }, { name = "typing-inspect", specifier = "==0.9.0" },
{ name = "tzdata", specifier = "==2024.2" }, { name = "tzdata", specifier = "==2025.1" },
{ name = "update-checker", specifier = "==0.18.0" }, { name = "update-checker", specifier = "==0.18.0" },
{ name = "urllib3", specifier = "==1.26.20" }, { name = "urllib3", specifier = "==1.26.20" },
{ name = "uvicorn", specifier = "==0.34.0" }, { name = "uvicorn", specifier = "==0.34.0" },
@ -454,7 +454,7 @@ dev = [
{ name = "fastapi", specifier = "==0.115.6" }, { name = "fastapi", specifier = "==0.115.6" },
{ name = "fastapi-debug-toolbar", git = "https://github.com/fsecada01/fastapi-debug-toolbar.git?rev=2da9f1e724d1d7ca56990ba7a8e72598fa3e1cf4" }, { name = "fastapi-debug-toolbar", git = "https://github.com/fsecada01/fastapi-debug-toolbar.git?rev=2da9f1e724d1d7ca56990ba7a8e72598fa3e1cf4" },
{ name = "fastjsonschema", specifier = "==2.21.1" }, { name = "fastjsonschema", specifier = "==2.21.1" },
{ name = "filelock", specifier = "==3.16.1" }, { name = "filelock", specifier = "==3.17.0" },
{ name = "fqdn", specifier = "==1.5.1" }, { name = "fqdn", specifier = "==1.5.1" },
{ name = "greenlet", specifier = "==3.1.1" }, { name = "greenlet", specifier = "==3.1.1" },
{ name = "h11", specifier = "==0.14.0" }, { name = "h11", specifier = "==0.14.0" },
@ -606,7 +606,7 @@ wheels = [
[[package]] [[package]]
name = "anthropic" name = "anthropic"
version = "0.43.1" version = "0.44.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "anyio" }, { name = "anyio" },
@ -617,9 +617,9 @@ dependencies = [
{ name = "sniffio" }, { name = "sniffio" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/ca/d8/238c2bc59e41a787e7b62460adfc7b2edd88f28b0a14e292801a72725369/anthropic-0.43.1.tar.gz", hash = "sha256:c7f13e4b7b515ac4a3111142310b214527c0fc561485e5bc9b582e49fe3adba2", size = 195298 } sdist = { url = "https://files.pythonhosted.org/packages/eb/34/ed394012684f7d6e36bb36c8b7c82b438f0ef189d2afd3d0090b85801211/anthropic-0.44.0.tar.gz", hash = "sha256:dc5c91c8b0463f97513d2e79350511ef295a56910bac4fbbe9491016c71f2ef0", size = 196065 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/89/2b/63b167d76401f759c8c4ff0266042e60aac6fd3cc0685b27437ceaaf95eb/anthropic-0.43.1-py3-none-any.whl", hash = "sha256:20759c25cd0f4072eb966b0180a41c061c156473bbb674da6a3f1e92e1ad78f8", size = 208170 }, { url = "https://files.pythonhosted.org/packages/d3/ad/c3c7d199eedc0b6d4621deed8dc1ed252ce399400cd76f1da098f1f50e56/anthropic-0.44.0-py3-none-any.whl", hash = "sha256:7087ccfc8ed7b164f971e094495cd3aeabac1e435fa393480cc146c87946c21c", size = 208634 },
] ]
[[package]] [[package]]
@ -823,11 +823,11 @@ css = [
[[package]] [[package]]
name = "cachetools" name = "cachetools"
version = "5.5.0" version = "5.5.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/38/a0f315319737ecf45b4319a8cd1f3a908e29d9277b46942263292115eee7/cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a", size = 27661 } sdist = { url = "https://files.pythonhosted.org/packages/d9/74/57df1ab0ce6bc5f6fa868e08de20df8ac58f9c44330c7671ad922d2bbeae/cachetools-5.5.1.tar.gz", hash = "sha256:70f238fbba50383ef62e55c6aff6d9673175fe59f7c6782c7a0b9e38f4a9df95", size = 28044 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/07/14f8ad37f2d12a5ce41206c21820d8cb6561b728e51fad4530dff0552a67/cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292", size = 9524 }, { url = "https://files.pythonhosted.org/packages/ec/4e/de4ff18bcf55857ba18d3a4bd48c8a9fde6bb0980c9d20b263f05387fd88/cachetools-5.5.1-py3-none-any.whl", hash = "sha256:b76651fdc3b24ead3c648bbdeeb940c1b04d365b38b4af66788f9ec4a81d42bb", size = 9530 },
] ]
[[package]] [[package]]
@ -1162,11 +1162,11 @@ wheels = [
[[package]] [[package]]
name = "filelock" name = "filelock"
version = "3.16.1" version = "3.17.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } sdist = { url = "https://files.pythonhosted.org/packages/dc/9c/0b15fb47b464e1b663b1acd1253a062aa5feecb07d4e597daea542ebd2b5/filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e", size = 18027 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, { url = "https://files.pythonhosted.org/packages/89/ec/00d68c4ddfedfe64159999e5f8a98fb8442729a63e2077eb9dcd89623d27/filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338", size = 16164 },
] ]
[[package]] [[package]]
@ -2091,7 +2091,7 @@ wheels = [
[[package]] [[package]]
name = "openai" name = "openai"
version = "1.59.9" version = "1.60.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "anyio" }, { name = "anyio" },
@ -2103,9 +2103,9 @@ dependencies = [
{ name = "tqdm" }, { name = "tqdm" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/ec/2d/04faa92bac0341649223398503db4415d2f658a757d9d32bb68f3378ddd0/openai-1.59.9.tar.gz", hash = "sha256:ec1a20b0351b4c3e65c6292db71d8233515437c6065efd4fd50edeb55df5f5d2", size = 347134 } sdist = { url = "https://files.pythonhosted.org/packages/d4/2d/9bdf4435d7669b4d027d6d69b4ac82f6be76153d9e90d3155d4224626a29/openai-1.60.0.tar.gz", hash = "sha256:7fa536cd4b644718645b874d2706e36dbbef38b327e42ca0623275da347ee1a9", size = 347844 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/07/b4/57f1954a4560092ad8c45f07ad183eab9c8e093e0a1db829f9b506b2d5d1/openai-1.59.9-py3-none-any.whl", hash = "sha256:61a0608a1313c08ddf92fe793b6dbd1630675a1fe3866b2f96447ce30050c448", size = 455527 }, { url = "https://files.pythonhosted.org/packages/c0/53/782008d94f5f3141795e65bd7f87afaebb97e7516342299c1b1a08d5aaf8/openai-1.60.0-py3-none-any.whl", hash = "sha256:df06c43be8018274980ac363da07d4b417bd835ead1c66e14396f6f15a0d5dda", size = 456109 },
] ]
[[package]] [[package]]
@ -3309,11 +3309,11 @@ wheels = [
[[package]] [[package]]
name = "tzdata" name = "tzdata"
version = "2024.2" version = "2025.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 } sdist = { url = "https://files.pythonhosted.org/packages/43/0f/fa4723f22942480be4ca9527bbde8d43f6c3f2fe8412f00e7f5f6746bc8b/tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694", size = 194950 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 }, { url = "https://files.pythonhosted.org/packages/0f/dd/84f10e23edd882c6f968c21c2434fe67bd4a528967067515feca9e611e5e/tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639", size = 346762 },
] ]
[[package]] [[package]]