From 35a1f83e129f4837c0c1127e1bbce96dbe6b39d6 Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Sat, 31 Jan 2026 13:26:10 -0800 Subject: [PATCH 01/50] upgrade to python 3.11 and with uv support --- .devcontainer/devcontainer.json | 21 +- .gitignore | 1 + .python-version | 1 + DEVELOPMENT.md | 47 +- local_server.bat | 9 +- local_server.sh | 9 +- public/screenshot-stock-price-live.webp | Bin 0 -> 146062 bytes py-src/data_formulator/agents/client_utils.py | 24 +- py-src/data_formulator/agents/web_utils.py | 7 +- py-src/data_formulator/app.py | 2 +- .../data_loader/athena_data_loader.py | 14 +- .../data_loader/azure_blob_data_loader.py | 12 +- .../data_loader/bigquery_data_loader.py | 12 +- .../data_loader/external_data_loader.py | 12 +- .../data_loader/kusto_data_loader.py | 10 +- .../data_loader/mongodb_data_loader.py | 14 +- .../data_loader/mssql_data_loader.py | 8 +- .../data_loader/mysql_data_loader.py | 12 +- .../data_loader/postgresql_data_loader.py | 10 +- .../data_loader/s3_data_loader.py | 12 +- py-src/data_formulator/db_manager.py | 3 +- py-src/data_formulator/demo_stream_routes.py | 10 +- .../security/query_validator.py | 6 +- py-src/data_formulator/tables_routes.py | 3 +- .../workflows/create_vl_plots.py | 16 +- .../workflows/exploration_flow.py | 19 +- pyproject.toml | 7 +- requirements.txt | 675 ++- uv.lock | 4301 +++++++++++++++++ 29 files changed, 5107 insertions(+), 170 deletions(-) create mode 100644 .python-version create mode 100644 public/screenshot-stock-price-live.webp create mode 100644 uv.lock diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4e9cd336..466ddb34 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,23 +1,24 @@ // For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/python { - "name": "Python 3", + "name": "Data Formulator Dev", // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/python:1-3.12-bullseye", + "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye", // Features to add to the dev container. More info: https://containers.dev/features. - "features": { - "ghcr.io/devcontainers/features/node:1": { - "version": "18" - }, - "ghcr.io/devcontainers/features/azure-cli:1": {} - }, + "features": { + "ghcr.io/devcontainers/features/node:1": { + "version": "18" + }, + "ghcr.io/devcontainers/features/azure-cli:1": {}, + "ghcr.io/astral-sh/uv:1": {} + }, // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], + "forwardPorts": [5000, 5173], // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "cd /workspaces/data-formulator && npm install && npm run build && python3 -m venv /workspaces/data-formulator/venv && . /workspaces/data-formulator/venv/bin/activate && pip install -e /workspaces/data-formulator --verbose && data_formulator" + "postCreateCommand": "cd /workspaces/data-formulator && npm install && npm run build && uv sync && uv run data_formulator" // Configure tool-specific properties. // "customizations": {}, diff --git a/.gitignore b/.gitignore index f3420acd..6b8ca6c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *env +.venv/ *api-keys.env **/*.ipynb_checkpoints/ .DS_Store diff --git a/.python-version b/.python-version new file mode 100644 index 00000000..2c073331 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 6467a874..4b2afe3c 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -2,16 +2,34 @@ How to set up your local machine. ## Prerequisites -* Python > 3.11 +* Python >= 3.11 * Node.js * Yarn +* [uv](https://docs.astral.sh/uv/) (recommended) or pip ## Backend (Python) +### Option 1: With uv (recommended) + +uv is faster and provides reproducible builds via lockfile. + +```bash +uv sync # Creates .venv and installs all dependencies +uv run data_formulator # Run app (opens browser automatically) +uv run data_formulator --dev # Run backend only (for frontend development) +``` + +**Which command to use:** +- **End users / testing the full app**: `uv run data_formulator` - starts server and opens browser to http://localhost:5000 +- **Frontend development**: `uv run data_formulator --dev` - starts backend server only, then run `yarn start` separately for the Vite dev server on http://localhost:5173 + +### Option 2: With pip (fallback) + - **Create a Virtual Environment** ```bash python -m venv venv - .\venv\Scripts\activate + source venv/bin/activate # Unix + # or .\venv\Scripts\activate # Windows ``` - **Install Dependencies** @@ -41,14 +59,16 @@ How to set up your local machine. - **Run the app** - - **Windows** - ```bash - .\local_server.bat - ``` - - - **Unix-based** ```bash + # Unix ./local_server.sh + + # Windows + .\local_server.bat + + # Or directly + data_formulator # Opens browser automatically + data_formulator --dev # Backend only (for frontend development) ``` ## Frontend (TypeScript) @@ -61,7 +81,12 @@ How to set up your local machine. - **Development mode** - Run the front-end in development mode using, allowing real-time edits and previews: + First, start the backend server (in a separate terminal): + ```bash + uv run data_formulator --dev # or ./local_server.sh + ``` + + Then, run the frontend in development mode with hot reloading: ```bash yarn start ``` @@ -81,6 +106,10 @@ How to set up your local machine. Then, build python package: ```bash + # With uv + uv build + + # Or with pip pip install build python -m build ``` diff --git a/local_server.bat b/local_server.bat index b585d712..36026cf9 100644 --- a/local_server.bat +++ b/local_server.bat @@ -7,4 +7,11 @@ :: set https_proxy=http://127.0.0.1:7890 set FLASK_RUN_PORT=5000 -python -m py-src.data_formulator.app --port %FLASK_RUN_PORT% --dev + +:: Use uv if available, otherwise fall back to python +where uv >nul 2>nul +if %ERRORLEVEL% EQU 0 ( + uv run data_formulator --port %FLASK_RUN_PORT% --dev +) else ( + python -m data_formulator.app --port %FLASK_RUN_PORT% --dev +) diff --git a/local_server.sh b/local_server.sh index 0df7db89..fbba1e3b 100644 --- a/local_server.sh +++ b/local_server.sh @@ -5,6 +5,11 @@ # export http_proxy=http://127.0.0.1:7890 # export https_proxy=http://127.0.0.1:7890 -#env FLASK_APP=py-src/data_formulator/app.py FLASK_RUN_PORT=5000 FLASK_RUN_HOST=0.0.0.0 flask run export FLASK_RUN_PORT=5000 -python -m py-src.data_formulator.app --port ${FLASK_RUN_PORT} --dev \ No newline at end of file + +# Use uv if available, otherwise fall back to python +if command -v uv &> /dev/null; then + uv run data_formulator --port ${FLASK_RUN_PORT} --dev +else + python -m data_formulator.app --port ${FLASK_RUN_PORT} --dev +fi \ No newline at end of file diff --git a/public/screenshot-stock-price-live.webp b/public/screenshot-stock-price-live.webp new file mode 100644 index 0000000000000000000000000000000000000000..b0ebe71b6bdcb3b9113f05cfd9704a43e691e205 GIT binary patch literal 146062 zcmcG$1zeR&*EhWB?(PQZPU-G$k#6afPDxQZ1f&}gkQ9)Xl>RR9XxLIi`ey5$Esr{Ibr={4B zb|+WWAM-b}lM?;j-`q*^$FtiygIfRmT}vyOANRUgs!M-wH!=D-e=|FgpJO^}|CpPp znYhIF{$_R>Kc3gYMfAsSIoN#LC*Rh}%tq=*yR)0xk9%D}%=vvB2N&fZYiMmG`@omq z?ghvLBmjy4Qh+jm9l!=~05F1T5C^-MJqPhG03c@X;NfItVd+9D0@`tAq%w9UObn!K zENpB5z{CE1=mP+pOnuvzVA!xf+X99F0KOg2m$^UN6uSU`I#&PyRqA@M0GI$g05O09@aWsjxB!mcZN=2Er!7 z7Q=pm9f941y@5l8qk`juQ-U*v^MHE|mkak1ZV+xA?gkzWo(5h3ULD>VJ^(%$z6`z- zeh&Tw0Re#=ffqpq!3x12AsL|@p&MZt;R+EQkq%J|Q4i4-@fBhLVhiFl;t3KG5;c-A zk}i@9(krAQq;{kQq)TKBWF}-8WK(267uh7*ochBJb5flGiZjBAD)f%_hJ9QPWJ6i)*08D0!tE#5pnBt9L! z3cfr3Tl`M^JpxRE#{?z>5d@V4GlbxTbcCveUW7S>{e)*k#6(g=4n!$L?L>RT*u=ub z*2M9|pNO|fFh~SRtVrTXnn`v@u}DQoZAg#4Z_(h=$k2Gw zl+esQLV6_h$njD3qcK`&S{~YGwCS`%bl`NHbe43fbc6KZ^j!2-^y&1&3{VWb40a6J z43mtAj3SI~j3tcAOxR5FOo2?bO#94~%(~1m%w5d)ESxMhEV(SRtmv$=tbwc_S&!N1 z*i6_`*~ZwB*d^Kh*z4GjIOsXdINowhabj>PaE5ZWao%xpb2)L9acy%`avO4|aZmDK z@+kAXg0XD66EZY^buTdaHJ+VW=6X z6|0@Ai>kj?pVfGz;i}Q5iK3~eS)_TPC9V~#wWQ6g?W5hNgRf(yQ>P1~tEro>d-g=) zNy3wLJx;w4y@{tZPoF>S(Z|=f(Qh(9F)%XtU<9}Wg%b@XR&Q5WSMBWXC-F!#_Gsg+B(Df;+fL3f@eS* zEt~hYu(pP_^>*lX&+IzviS3`;4?EC1gg7iY@;N3r9y-Z6~IF5NZVKRic!Zuh+3gU%z|W7AX8GtUds%fzeAo6I}Fd)Y_CC(9Si*Vwns zkHRn5Z{1(Yzc2tk;90VeQENtH;OeXISTm7{MEo~uGep)p`-1hCt`$R3S-e?J!4nn z6yj>)N#i5pPZJChdJ;JjGm_wwT$7fP<&*1DC{vISBUf=05&)F!N^*Pq}&1%CoIdp4i7IJE4w zTC}dV8MMu|YqyVesB{c;%5`>smi*k_CDPU0EzsTA!`D;a%hOxi$JJNU&)Hx7h4V}G z0M|gxAopP15bw~(VgBJyBSIssqvE5V$7II(#udkhCp0FeCiNzlrc9@{rfsH=X540O zX8q@&<|5`%=MxtQ7qS;=7vC>&Ej2BRFZZvgt<0{PtnRKkuidT(Zy;?XZjx*kZLw`N zZcApAzm`8RKd9yVGyZ-BJvlo&$HOguxq_@L(mFbDfU+cV zIv5=Q#Q|0@Mmj@2FPAzmi#@q~2p!JK?iN<=T(N0ot}B`<9QFh)c0dqC37EAI8ovl1 z`#JVD)rOUT5_sI)ZLfa1c--9T7kBnyZQ-ln=tc8lGqyq3Ve=Aj$HVe^@F=73E+3c! z+`KQivu^>8EZzc#?}2yVek%9dJ92jyUoSqAe9br&V)z8@CkwQ?$vYsrUp#M~U%V8$ zYOVo-H81+@1A%7~Kp@b4so}yI2)suLXx=qA69NL4&v)-OFOBb~fqoY`vnZftJa9m{ z*PjEn2m#M(FAvT#4uqEd`hllXAEqCW${otd;!acay~+i}32;H^i{CsD?ZoeL@B+B%=OXm!TIg!?Q0UOF z$M?c7>I&-i!&UQD##f<@<`ch|E0ybmo8~RQY4)I7s1NDssD&0Iehq&##;l4dMBA)ka0?yvY-7%-TuK|~U2SAIf zqI0-A`dhzy`%{uVA&2|aZ$0|%YWLmifMY@vz^}k3_ZJs1d)(JF7X~+rYeEzE3x1#l zyLbks>ZpB z+suWgUU-r5DWbWgk#F-zj5tK*F5}3wF~tQxrGfqU@6D;xq%kMPBvI;~km=r2&>uev zF<0u8hsQZf`w9nul_}^* zw(=qsc~pI-=t%TSs8zA8`el}R`@<@GD}OMWu};+xe59+K6C;3x^ZN005xwlXOTv(Z zNYwRYxV7l}wldo<{ajM--%-)hAG|xzGIfI^g70^Fb|DjzgMB?frHvC(%dFAOy~Gvh zy`-0W;)-Jf%a=Z^BHX0vvfL8%2YCJdZx-I`cm zxvfl5d$hx#vBIm#LSxY~FGL&FS#)-s#T7 z^tBdaACN0w>%%qUP~7K4Gbd9NfDvK!iobm=9-N-lr=a^WR3~S*k;M+(}$N_K_pNBGIKAesd}0Fl=7JeD6`T;g*SAK5F%uPQs^lzbXqZLN@R_O(v(Dz z;Vf84QC}9;J;E}~p^rm~i<9m+<#J>qkPeqD&toE$xR1%nojtDeGKkSP2akv<{ z)fNmyOnFSaIK$j_(!@i6uO;-LTm)w}7H|n~9I8U)Tf;k7H(f*$SIHdVq8>5a9hg42 zE~|eeFSIgQtsk?yQ_zX<-!Nc4c0nPsjMfaYe*@_a-kf-UMe9WbTFcv*ARlb-7GbUg zH@Qd#1=4NUSuxfg0Wg{a-Vz9D75HI(?l^bY+OTYjn7ABfJa(y;TX?p(%`H;=dSlN0 z6y1`wRpS^Oh!ONLI)3`knPlQAb|Im(JZ0<>U!t1#`Zn2+)}CLpoyz%coKK|<+vTln z8`2M$V$UXs@8|0CA^$dWV51CwFO?M?o;QUtcmV+A$q ze`mFvQKM;tG2t|ZdDb9;s=PBm($4Z9!khr;{ca8Xg1>f_N(;UQl-BY<7nNBA?>=z6?k z2q($FP+G6(^}S?{sngF(f^Unlb!kN#AD;%l!=^7jOHAMTu5oRc@_=yD?}!>o>3fDU z4MoV+0mlr~{2jZ3IdvxMkoI`lPYEtxe>OmUAx8UG&L$)R&frEBG0|wvRqQ>fN4ZBs zS$Oj9#N9Aeffia#;z`Xo`XOAGB)h18E~^~2iQ6d494bGCrv@}W8Waeam?C3Go0|M* z_5b(PFoHu;9pnd_hX1j}!jg@katG15D=Z0q2NNCgQ|<}EwWjr09^!GyVZ2&`2dVoHF) zDb1JWoYU5p+~NqSxtc1wn58iXSEKKbwF{!mE!5!D9+1rwZ+XQ0U!cxko^^)44gT7? zj3$jd)3N!#gGzt7e_TFH=~~I)mGi5Q{)LkHs2XphxV;|VuN5l#d2p_wMYAprE1tt8 zA2i2m$Uv@)Wu-jMV$Uexru1%+na)wLlFHV!9DT;#C=vv|npgeg(o11!*wBa*k%Aac zE!Lv!wc0(Dv7vXia~K*7*0Y|FfV}a&uLLRmJ#079F{mGy7$3TbZzdDM6Wgyu=KW~Oxq1{E5n6_DStysB3eFdh$0HuS{!K3H z%XFRm#=t*Cb4|pd|Nl;M|0b~5$T)+0aLYTo2F&?U6(H>1=IZn2-9gC683b!#?{B~e ztqC8Hd88gfDRKMTXmT|B&?C>ei*Tm8s`PN@Qch7N%Q}Ak_8S~P9JOfjz9`xG_Zo^ox+aIpFq zzi%sN%s7$Z^VfXigs3BjyfXI0aSVvkb$45+`l#$8`*2N7$``_%R^y!Br3!whsth_o zw*#H0cLewwREDl;We@kgdcrSS(EDS5{SPYaw}tqr1L@_u<03JtU-I&A8zG*b4ZU^1 zUMl`mBhG{dKSS2&4y9WSV+(%XG2+$soc93t6vvm{`NQ+$R$bz0S_VNXYW<=;9!f(+CBmPd5^(sa@J|UNv5F%3py3^x%j&+% zQjqnM+kg1LNSTgG+y7|H{B>XaE8`!A)TDbO>|3VL#aAr6eS z>3bg-f0%%u-hGNmkjp{2_eiewytH_St`t{;wDT4+_o*Q=zOWYM4n^)cL*TmE!ml&^1Xt#HR}F={q~UFv2V)=`%T2gmwyk+d^WKv_ z>8o4|l2lq<4aIm9j}(O}mww^CN#W#o*k+5h4EDd*$G^OEV@Tb<>>D!K255Jm-fZg| zQz|_QD4dH!2_(E|GOdAZ6eOGuuX{Gzj~MB?^VNj`e7Bag*jn)B(G<@bSXz%@t2pS- zQKayarMh5bcY~ih)Xc`my1dUXde6mT*RI!KDv*!bO{_bK(e}fH?)d+@7d#;sFq6cO zD1P&E%qLZh(w`&Pn8QV&9k|%ClR~m?`iJBzbBTgbI!wa$v(0`FdqBY(EL1zCmX_O#!~uvBsHv0dK7fC9PkYeJ%Ld9_TPgrU7eWd>7k9?4gnClKz~BzrLP8rct{} z>SfmcckGTG=>5w({ON`GFE{Ry7*c;Qy?MCnv!fWTk7#Qr^_R(G2q-fZEW=Qg>Vmvv zOlU65eAmMyqpXl9Re1+rGFjd|N{pjuaq39(4Y%>Hg`P~G9%#*Mx!AOJ44#h$JhzAc zra9Y-rZ1CWnIjf91U&=odu5VVS=1|5b}9RgBaQ}rRulXuNdb+^y>}JhDd!M#(}xGX z$j-W0n0VLrqqL5QZExRuWcmL98;y-WciI2`5kx za65hIEpJ+C>0 z5c^L|`J#_$f&;fzg&QqW3&$V-Ijd}&-)Wq+y<$aEblMG__)H;d%#eXHzR|2d-AFU! zSVMJ&@Jq(*`D$VWzHK22HF2_KO#iulxzCnQg2}1&ao*A|;MP%h2C2Rz(de%J+PP(J z|GeB{{(SlZkzaUiVs}YO%nB_j;?ZCiQr`RF$p-E*cbvd&JQ3tgzf_iIb_m@Q<_S5D zA7mi=e1vPFZYQQu*Y(1j%6@Xc^%AR7sq&3A`St(J`~BgGt9It(_1bZ|7?U_C>RVJx z`c%Nwx1_czL;?1-!h{{u2l#pJdi#mINBPRI!E}#UZ*NU*PH}S=%$-tqy?g6NT++8| z;b-wF6K1wDU8`CyHiZouxo~O(uNmWlx62Mtg6a3y;;mQ|9M6T9sN-7>W|he#G3#ln zidez*qR?lOKR|QmvEqhHM<3q59}8B5bJR!uuE~DK`Tv!&FDam74f@KKf4uMfQ#NtN zk(5fhABv{-h;*9~9J0jnSsq_meyYE}&9?G<+B=C^@Q%Fq=_3+tD@=?j&X4epgw3hM zG*Vw-S?kMMo?{J>(RxY>PQMss#^>(kc@<2VK)AO%tiO=Ms?=>3xOkDBOL zJ$|b#=zB~Je7($7jdzAi^g9d9E-);&VYU=cB?+U5STwuYGmoopy;sx-r~ui?88}t) zD0Yw0Zzb4_VC3FQYg)g#x=A7q*V%Zj)V1C1a91lI9U%}OlhjUgiiw-m)hR+w(-Hqa zO5R_<%|GrEH3ZQh;V4|<>eg93RyvM&ZOo2YT#i=DXBXMV#Vd$dsv17c%ebFubcoHt ziWskg&B#O⁣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 z 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≧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 O(t4aasGqy0$N3*a&^}ZE literal 0 HcmV?d00001 diff --git a/py-src/data_formulator/agents/client_utils.py b/py-src/data_formulator/agents/client_utils.py index 4ccce9d0..43cf0ee3 100644 --- a/py-src/data_formulator/agents/client_utils.py +++ b/py-src/data_formulator/agents/client_utils.py @@ -1,27 +1,7 @@ import litellm import openai from azure.identity import DefaultAzureCredential, get_bearer_token_provider -from typing import Dict, Optional, Union -class OpenAIClientAdapter(object): - """ - Wrapper around OpenAI or AzureOpenAI client that provides the same interface as Client. - """ - def __init__(self, openai_client: Union[openai.OpenAI, openai.AzureOpenAI], model: str): - self._openai_client = openai_client - self.model = model - self.params = {} - - def get_completion(self, messages): - """ - Returns a completion using the wrapped OpenAI client. - """ - completion_params = { - "model": self.model, - "messages": messages, - } - - return self._openai_client.chat.completions.create(**completion_params) class Client(object): """ @@ -69,7 +49,7 @@ def __init__(self, endpoint, model, api_key=None, api_base=None, api_version=No self.model = f"ollama/{model}" @classmethod - def from_config(cls, model_config: Dict[str, str]): + def from_config(cls, model_config: dict[str, str]): """ Create a client instance from model configuration. @@ -132,7 +112,7 @@ def get_completion(self, messages, stream=False): ) - def get_response(self, messages: list[dict], tools: Optional[list] = None): + def get_response(self, messages: list[dict], tools: list | None = None): """ Returns a response using OpenAI's Response API approach. """ diff --git a/py-src/data_formulator/agents/web_utils.py b/py-src/data_formulator/agents/web_utils.py index 1fd3aaea..a04f6f48 100644 --- a/py-src/data_formulator/agents/web_utils.py +++ b/py-src/data_formulator/agents/web_utils.py @@ -3,7 +3,6 @@ import requests from bs4 import BeautifulSoup -from typing import Optional, Union import logging from urllib.parse import urlparse import tempfile @@ -111,7 +110,7 @@ def _validate_url_for_ssrf(url: str) -> str: return url -def download_html_content(url: str, timeout: int = 30, headers: Optional[dict] = None) -> str: +def download_html_content(url: str, timeout: int = 30, headers: dict | None = None) -> str: """ Download HTML content from a given URL with SSRF protection. @@ -254,7 +253,7 @@ def html_to_text(html_content: str, remove_scripts: bool = True, remove_styles: # Fallback: return the raw content if parsing fails return html_content -def get_html_title(html_content: str) -> Optional[str]: +def get_html_title(html_content: str) -> str | None: """ Extract the title from HTML content. @@ -276,7 +275,7 @@ def get_html_title(html_content: str) -> Optional[str]: return None -def get_html_meta_description(html_content: str) -> Optional[str]: +def get_html_meta_description(html_content: str) -> str | None: """ Extract the meta description from HTML content. diff --git a/py-src/data_formulator/app.py b/py-src/data_formulator/app.py index a767d277..634f0870 100644 --- a/py-src/data_formulator/app.py +++ b/py-src/data_formulator/app.py @@ -40,7 +40,7 @@ from data_formulator.example_datasets_config import EXAMPLE_DATASETS import queue -from typing import Dict, Any +from typing import Any app = Flask(__name__, static_url_path='', static_folder=os.path.join(APP_ROOT, "dist")) app.secret_key = secrets.token_hex(16) # Generate a random secret key for sessions diff --git a/py-src/data_formulator/data_loader/athena_data_loader.py b/py-src/data_formulator/data_loader/athena_data_loader.py index 8ba617fe..bb50ad26 100644 --- a/py-src/data_formulator/data_loader/athena_data_loader.py +++ b/py-src/data_formulator/data_loader/athena_data_loader.py @@ -5,7 +5,7 @@ import duckdb from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name -from typing import Dict, Any, List, Optional +from typing import Any from data_formulator.security import validate_sql_query try: @@ -54,7 +54,7 @@ def _validate_s3_url(url: str) -> None: raise ValueError(f"Invalid S3 URL format: '{url}'. Expected format: 's3://bucket/path'") -def _escape_sql_string(value: Optional[str]) -> str: +def _escape_sql_string(value: str | None) -> str: """Escape single quotes in SQL string values.""" if value is None: return "" @@ -69,7 +69,7 @@ class AthenaDataLoader(ExternalDataLoader): """ @staticmethod - def list_params() -> List[Dict[str, Any]]: + def list_params() -> list[dict[str, Any]]: params_list = [ {"name": "aws_profile", "type": "string", "required": False, "default": "", "description": "AWS profile name from ~/.aws/credentials (if set, access key and secret are not required)"}, {"name": "aws_access_key_id", "type": "string", "required": False, "default": "", "description": "AWS access key ID (not required if using aws_profile)"}, @@ -160,7 +160,7 @@ def auth_instructions() -> str: **Security:** Never share secret keys, rotate regularly, use least privilege permissions. """ - def __init__(self, params: Dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): + def __init__(self, params: dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): if not BOTO3_AVAILABLE: raise ImportError( "boto3 is required for Athena connections. " @@ -398,7 +398,7 @@ def _execute_query(self, query: str) -> str: wait_time = min(2 ** (elapsed // 10), 10) time.sleep(wait_time) - def list_tables(self, table_filter: str = None) -> List[Dict[str, Any]]: + def list_tables(self, table_filter: str | None = None) -> list[dict[str, Any]]: """List tables from Athena catalog (Glue Data Catalog).""" results = [] @@ -469,7 +469,7 @@ def list_tables(self, table_filter: str = None) -> List[Dict[str, Any]]: log.info(f"Returning {len(results)} tables") return results - def ingest_data(self, table_name: str, name_as: str = None, size: int = 1000000, sort_columns: List[str] = None, sort_order: str = 'asc'): + def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1000000, sort_columns: list[str] | None = None, sort_order: str = 'asc'): """Ingest data from an Athena table by executing a SELECT query.""" # Validate table name to prevent SQL injection _validate_athena_table_name(table_name) @@ -512,7 +512,7 @@ def ingest_data(self, table_name: str, name_as: str = None, size: int = 1000000, log.info(f"Successfully ingested data into table '{name_as}'") - def view_query_sample(self, query: str) -> List[Dict[str, Any]]: + def view_query_sample(self, query: str) -> list[dict[str, Any]]: """Execute query and return sample results.""" result, error_message = validate_sql_query(query) if not result: diff --git a/py-src/data_formulator/data_loader/azure_blob_data_loader.py b/py-src/data_formulator/data_loader/azure_blob_data_loader.py index 1206f4e0..663c8b95 100644 --- a/py-src/data_formulator/data_loader/azure_blob_data_loader.py +++ b/py-src/data_formulator/data_loader/azure_blob_data_loader.py @@ -4,7 +4,7 @@ import os from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name -from typing import Dict, Any, List +from typing import Any from data_formulator.security import validate_sql_query try: @@ -17,7 +17,7 @@ class AzureBlobDataLoader(ExternalDataLoader): @staticmethod - def list_params() -> List[Dict[str, Any]]: + def list_params() -> list[dict[str, Any]]: params_list = [ {"name": "account_name", "type": "string", "required": True, "default": "", "description": "Azure storage account name"}, {"name": "container_name", "type": "string", "required": True, "default": "", "description": "Azure blob container name"}, @@ -65,7 +65,7 @@ def auth_instructions() -> str: - JSON files (.json, .jsonl) """ - def __init__(self, params: Dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): + def __init__(self, params: dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): if not AZURE_BLOB_AVAILABLE: raise ImportError( "Azure storage libraries are required for Azure Blob connections. " @@ -130,7 +130,7 @@ def _setup_azure_authentication(self): ) """) - def list_tables(self, table_filter: str = None) -> List[Dict[str, Any]]: + def list_tables(self, table_filter: str | None = None) -> list[dict[str, Any]]: # Use Azure SDK to list blobs in the container from azure.storage.blob import BlobServiceClient @@ -348,7 +348,7 @@ def _estimate_by_row_sampling(self, azure_url: str, file_extension: str) -> int: print(f"Error in row sampling for {azure_url}: {e}") return 0 - def ingest_data(self, table_name: str, name_as: str = None, size: int = 1000000, sort_columns: List[str] = None, sort_order: str = 'asc'): + def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1000000, sort_columns: list[str] | None = None, sort_order: str = 'asc'): if name_as is None: name_as = table_name.split('/')[-1].split('.')[0] @@ -386,7 +386,7 @@ def ingest_data(self, table_name: str, name_as: str = None, size: int = 1000000, else: raise ValueError(f"Unsupported file type: {table_name}") - def view_query_sample(self, query: str) -> List[Dict[str, Any]]: + def view_query_sample(self, query: str) -> list[dict[str, Any]]: result, error_message = validate_sql_query(query) if not result: raise ValueError(error_message) diff --git a/py-src/data_formulator/data_loader/bigquery_data_loader.py b/py-src/data_formulator/data_loader/bigquery_data_loader.py index cd3c1cf6..25819cb5 100644 --- a/py-src/data_formulator/data_loader/bigquery_data_loader.py +++ b/py-src/data_formulator/data_loader/bigquery_data_loader.py @@ -1,7 +1,7 @@ import json import logging import re -from typing import Dict, Any, List, Optional +from typing import Any import pandas as pd import duckdb @@ -21,7 +21,7 @@ class BigQueryDataLoader(ExternalDataLoader): """BigQuery data loader implementation""" @staticmethod - def list_params() -> List[Dict[str, Any]]: + def list_params() -> list[dict[str, Any]]: return [ {"name": "project_id", "type": "text", "required": True, "description": "Google Cloud Project ID", "default": ""}, {"name": "dataset_id", "type": "text", "required": False, "description": "Dataset ID(s) - leave empty for all, or specify one (e.g., 'billing') or multiple separated by commas (e.g., 'billing,enterprise_collected,ga_api')", "default": ""}, @@ -68,7 +68,7 @@ def auth_instructions() -> str: - Execute custom SQL queries """ - def __init__(self, params: Dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): + def __init__(self, params: dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): if not BIGQUERY_AVAILABLE: raise ImportError( "google-cloud-bigquery is required for BigQuery connections. " @@ -96,7 +96,7 @@ def __init__(self, params: Dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnecti location=self.location ) - def list_tables(self, table_filter: str = None) -> List[Dict[str, Any]]: + def list_tables(self, table_filter: str | None = None) -> list[dict[str, Any]]: """List tables from BigQuery datasets""" results = [] @@ -204,7 +204,7 @@ def safe_convert(x): return df - def ingest_data(self, table_name: str, name_as: Optional[str] = None, size: int = 1000000, sort_columns: List[str] = None, sort_order: str = 'asc'): + def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1000000, sort_columns: list[str] | None = None, sort_order: str = 'asc'): """Ingest data from BigQuery table into DuckDB with stable, de-duplicated column aliases.""" if name_as is None: name_as = table_name.split('.')[-1] @@ -296,7 +296,7 @@ def process_field(field, parent_path: str = ""): self.ingest_df_to_duckdb(df, name_as) - def view_query_sample(self, query: str) -> List[Dict[str, Any]]: + def view_query_sample(self, query: str) -> list[dict[str, Any]]: """Execute query and return sample results as a list of dictionaries""" result, error_message = validate_sql_query(query) if not result: diff --git a/py-src/data_formulator/data_loader/external_data_loader.py b/py-src/data_formulator/data_loader/external_data_loader.py index 41060d87..552e5d41 100644 --- a/py-src/data_formulator/data_loader/external_data_loader.py +++ b/py-src/data_formulator/data_loader/external_data_loader.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Dict, Any, List +from typing import Any import pandas as pd import json import duckdb @@ -76,7 +76,7 @@ def ingest_df_to_duckdb(self, df: pd.DataFrame, table_name: str): @staticmethod @abstractmethod - def list_params() -> List[Dict[str, Any]]: + def list_params() -> list[dict[str, Any]]: pass @staticmethod @@ -84,16 +84,16 @@ def list_params() -> List[Dict[str, Any]]: def auth_instructions() -> str: pass @abstractmethod - def __init__(self, params: Dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): + def __init__(self, params: dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): pass @abstractmethod - def list_tables(self, table_filter: str = None) -> List[Dict[str, Any]]: + def list_tables(self, table_filter: str = None) -> list[dict[str, Any]]: # should include: table_name, column_names, column_types, sample_data pass @abstractmethod - def ingest_data(self, table_name: str, name_as: str = None, size: int = 1000000, sort_columns: List[str] = None, sort_order: str = 'asc'): + def ingest_data(self, table_name: str, name_as: str = None, size: int = 1000000, sort_columns: list[str] = None, sort_order: str = 'asc'): """Ingest data from a table into DuckDB. Args: @@ -106,7 +106,7 @@ def ingest_data(self, table_name: str, name_as: str = None, size: int = 1000000, pass @abstractmethod - def view_query_sample(self, query: str) -> List[Dict[str, Any]]: + def view_query_sample(self, query: str) -> list[dict[str, Any]]: pass @abstractmethod diff --git a/py-src/data_formulator/data_loader/kusto_data_loader.py b/py-src/data_formulator/data_loader/kusto_data_loader.py index 6ed6d602..97788247 100644 --- a/py-src/data_formulator/data_loader/kusto_data_loader.py +++ b/py-src/data_formulator/data_loader/kusto_data_loader.py @@ -1,6 +1,6 @@ import logging import sys -from typing import Dict, Any, List +from typing import Any import pandas as pd import json import duckdb @@ -60,7 +60,7 @@ def auth_instructions() -> str: - kusto_database: Name of the database you want to access """ - def __init__(self, params: Dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): + def __init__(self, params: dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): if not KUSTO_AVAILABLE: raise ImportError( "azure-kusto-data is required for Kusto/Azure Data Explorer connections. " @@ -156,7 +156,7 @@ def query(self, kql: str) -> pd.DataFrame: return df - def list_tables(self, table_filter: str = None) -> List[Dict[str, Any]]: + def list_tables(self, table_filter: str | None = None) -> list[dict[str, Any]]: query = ".show tables" tables_df = self.query(query) @@ -197,7 +197,7 @@ def list_tables(self, table_filter: str = None) -> List[Dict[str, Any]]: return tables - def ingest_data(self, table_name: str, name_as: str = None, size: int = 5000000, sort_columns: List[str] = None, sort_order: str = 'asc') -> pd.DataFrame: + def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 5000000, sort_columns: list[str] | None = None, sort_order: str = 'asc') -> pd.DataFrame: if name_as is None: name_as = table_name @@ -254,7 +254,7 @@ def ingest_data(self, table_name: str, name_as: str = None, size: int = 5000000, total_rows_ingested += len(chunk_df) - def view_query_sample(self, query: str) -> List[Dict[str, Any]]: + def view_query_sample(self, query: str) -> list[dict[str, Any]]: df = self.query(query).head(10) return json.loads(df.to_json(orient="records", date_format='iso')) diff --git a/py-src/data_formulator/data_loader/mongodb_data_loader.py b/py-src/data_formulator/data_loader/mongodb_data_loader.py index 6b460354..e8434834 100644 --- a/py-src/data_formulator/data_loader/mongodb_data_loader.py +++ b/py-src/data_formulator/data_loader/mongodb_data_loader.py @@ -11,7 +11,7 @@ from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name from data_formulator.security import validate_sql_query -from typing import Dict, Any, Optional, List +from typing import Any class MongoDBDataLoader(ExternalDataLoader): @@ -56,7 +56,7 @@ def auth_instructions() -> str: - Test connection: `mongosh --host [host] --port [port]` """ - def __init__(self, params: Dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): + def __init__(self, params: dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): self.params = params self.duck_db_conn = duck_db_conn @@ -113,7 +113,7 @@ def __del__(self): self.close() @staticmethod - def _flatten_document(doc: Dict[str, Any], parent_key: str = '', sep: str = '_') -> Dict[str, Any]: + def _flatten_document(doc: dict[str, Any], parent_key: str = '', sep: str = '_') -> dict[str, Any]: """ Use recursion to flatten nested MongoDB documents """ @@ -139,7 +139,7 @@ def _flatten_document(doc: Dict[str, Any], parent_key: str = '', sep: str = '_') return dict(items) @staticmethod - def _convert_special_types(doc: Dict[str, Any]) -> Dict[str, Any]: + def _convert_special_types(doc: dict[str, Any]) -> dict[str, Any]: """ Convert MongoDB special types (ObjectId, datetime, etc.) to serializable types """ @@ -165,7 +165,7 @@ def _convert_special_types(doc: Dict[str, Any]) -> Dict[str, Any]: result[key] = value return result - def _process_documents(self, documents: List[Dict[str, Any]]) -> pd.DataFrame: + def _process_documents(self, documents: list[dict[str, Any]]) -> pd.DataFrame: """ Process MongoDB documents list, flatten and convert to DataFrame """ @@ -240,7 +240,7 @@ def list_tables(self, table_filter: str = None): return results - def ingest_data(self, table_name: str, name_as: Optional[str] = None, size: int = 100000, sort_columns: List[str] = None, sort_order: str = 'asc'): + def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 100000, sort_columns: list[str] | None = None, sort_order: str = 'asc'): """ Import MongoDB collection data into DuckDB """ @@ -277,7 +277,7 @@ def ingest_data(self, table_name: str, name_as: Optional[str] = None, size: int return - def view_query_sample(self, query: str) -> List[Dict[str, Any]]: + def view_query_sample(self, query: str) -> list[dict[str, Any]]: self._existed_collections_in_duckdb() self._difference_collections() diff --git a/py-src/data_formulator/data_loader/mssql_data_loader.py b/py-src/data_formulator/data_loader/mssql_data_loader.py index 1f18a794..875358b2 100644 --- a/py-src/data_formulator/data_loader/mssql_data_loader.py +++ b/py-src/data_formulator/data_loader/mssql_data_loader.py @@ -1,6 +1,6 @@ import json import logging -from typing import Dict, Any, Optional, List +from typing import Any import duckdb import pandas as pd @@ -144,7 +144,7 @@ def auth_instructions() -> str: - Windows: Install SQL Server or download ODBC driver separately """ - def __init__(self, params: Dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): + def __init__(self, params: dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): log.info("Initializing MSSQL DataLoader with parameters: %s", params) if not PYODBC_AVAILABLE: @@ -387,7 +387,7 @@ def list_tables(self): return results - def ingest_data(self, table_name: str, name_as: Optional[str] = None, size: int = 1000000, sort_columns: List[str] = None, sort_order: str = 'asc'): + def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1000000, sort_columns: list[str] | None = None, sort_order: str = 'asc'): """Ingest data from SQL Server table into DuckDB""" # Parse table name (assuming format: schema.table) if "." in table_name: @@ -421,7 +421,7 @@ def ingest_data(self, table_name: str, name_as: Optional[str] = None, size: int log.error(f"Failed to ingest data from {table_name}: {e}") raise - def view_query_sample(self, query: str) -> List[Dict[str, Any]]: + def view_query_sample(self, query: str) -> list[dict[str, Any]]: """Execute a custom query and return sample results""" try: # Add TOP 10 if not already present for SELECT queries diff --git a/py-src/data_formulator/data_loader/mysql_data_loader.py b/py-src/data_formulator/data_loader/mysql_data_loader.py index 0430a57a..f12a192c 100644 --- a/py-src/data_formulator/data_loader/mysql_data_loader.py +++ b/py-src/data_formulator/data_loader/mysql_data_loader.py @@ -7,7 +7,7 @@ from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name from data_formulator.security import validate_sql_query -from typing import Dict, Any, Optional, List +from typing import Any try: import pymysql @@ -21,7 +21,7 @@ class MySQLDataLoader(ExternalDataLoader): @staticmethod - def list_params() -> List[Dict[str, Any]]: + def list_params() -> list[dict[str, Any]]: params_list = [ {"name": "user", "type": "string", "required": True, "default": "root", "description": ""}, {"name": "password", "type": "string", "required": False, "default": "", "description": "leave blank for no password"}, @@ -58,7 +58,7 @@ def auth_instructions() -> str: - Test connection: `mysql -u [username] -p -h [host] -P [port] [database]` """ - def __init__(self, params: Dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): + def __init__(self, params: dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): if not PYMYSQL_AVAILABLE: raise ImportError( "pymysql is required for MySQL connections. " @@ -154,7 +154,7 @@ def _reconnect_if_needed(self): charset='utf8mb4' ) - def list_tables(self, table_filter: str = None) -> List[Dict[str, Any]]: + def list_tables(self, table_filter: str | None = None) -> list[dict[str, Any]]: # Get list of tables from the connected database # Filter by the specific database we're connected to for better performance tables_query = """ @@ -220,7 +220,7 @@ def list_tables(self, table_filter: str = None) -> List[Dict[str, Any]]: return results - def ingest_data(self, table_name: str, name_as: Optional[str] = None, size: int = 1000000, sort_columns: List[str] = None, sort_order: str = 'asc'): + def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1000000, sort_columns: list[str] | None = None, sort_order: str = 'asc'): """Fetch data from MySQL and ingest into DuckDB.""" if name_as is None: name_as = table_name.split('.')[-1] @@ -264,7 +264,7 @@ def ingest_data(self, table_name: str, name_as: Optional[str] = None, size: int self.ingest_df_to_duckdb(df, name_as) logger.info(f"Successfully ingested {len(df)} rows from {table_name} into DuckDB table {name_as}") - def view_query_sample(self, query: str) -> List[Dict[str, Any]]: + def view_query_sample(self, query: str) -> list[dict[str, Any]]: result, error_message = validate_sql_query(query) if not result: raise ValueError(error_message) diff --git a/py-src/data_formulator/data_loader/postgresql_data_loader.py b/py-src/data_formulator/data_loader/postgresql_data_loader.py index b327a737..47ce41e3 100644 --- a/py-src/data_formulator/data_loader/postgresql_data_loader.py +++ b/py-src/data_formulator/data_loader/postgresql_data_loader.py @@ -5,13 +5,13 @@ from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name -from typing import Dict, Any, List, Optional +from typing import Any from data_formulator.security import validate_sql_query class PostgreSQLDataLoader(ExternalDataLoader): @staticmethod - def list_params() -> List[Dict[str, Any]]: + def list_params() -> list[dict[str, Any]]: params_list = [ {"name": "user", "type": "string", "required": True, "default": "postgres", "description": "PostgreSQL username"}, {"name": "password", "type": "string", "required": False, "default": "", "description": "leave blank for no password"}, @@ -25,7 +25,7 @@ def list_params() -> List[Dict[str, Any]]: def auth_instructions() -> str: return "Provide your PostgreSQL connection details. The user must have SELECT permissions on the tables you want to access." - def __init__(self, params: Dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): + def __init__(self, params: dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): self.params = params self.duck_db_conn = duck_db_conn @@ -130,7 +130,7 @@ def list_tables(self): print(f"Error listing tables: {e}") return [] - def ingest_data(self, table_name: str, name_as: Optional[str] = None, size: int = 1000000, sort_columns: List[str] = None, sort_order: str = 'asc'): + def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1000000, sort_columns: list[str] | None = None, sort_order: str = 'asc'): # Create table in the main DuckDB database from Postgres data if name_as is None: name_as = table_name.split('.')[-1] @@ -152,7 +152,7 @@ def ingest_data(self, table_name: str, name_as: Optional[str] = None, size: int LIMIT {size} """) - def view_query_sample(self, query: str) -> List[Dict[str, Any]]: + def view_query_sample(self, query: str) -> list[dict[str, Any]]: result, error_message = validate_sql_query(query) if not result: raise ValueError(error_message) diff --git a/py-src/data_formulator/data_loader/s3_data_loader.py b/py-src/data_formulator/data_loader/s3_data_loader.py index d92b7c41..c318da2d 100644 --- a/py-src/data_formulator/data_loader/s3_data_loader.py +++ b/py-src/data_formulator/data_loader/s3_data_loader.py @@ -4,7 +4,7 @@ import os from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name -from typing import Dict, Any, List +from typing import Any from data_formulator.security import validate_sql_query try: @@ -16,7 +16,7 @@ class S3DataLoader(ExternalDataLoader): @staticmethod - def list_params() -> List[Dict[str, Any]]: + def list_params() -> list[dict[str, Any]]: params_list = [ {"name": "aws_access_key_id", "type": "string", "required": True, "default": "", "description": "AWS access key ID"}, {"name": "aws_secret_access_key", "type": "string", "required": True, "default": "", "description": "AWS secret access key"}, @@ -63,7 +63,7 @@ def auth_instructions() -> str: **Security:** Never share secret keys, rotate regularly, use least privilege permissions. """ - def __init__(self, params: Dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): + def __init__(self, params: dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnection): if not BOTO3_AVAILABLE: raise ImportError( "boto3 is required for S3 connections. " @@ -91,7 +91,7 @@ def __init__(self, params: Dict[str, Any], duck_db_conn: duckdb.DuckDBPyConnecti if self.aws_session_token: # Add this block self.duck_db_conn.execute(f"SET s3_session_token='{self.aws_session_token}'") - def list_tables(self, table_filter: str = None) -> List[Dict[str, Any]]: + def list_tables(self, table_filter: str | None = None) -> list[dict[str, Any]]: # Use boto3 to list objects in the bucket import boto3 @@ -181,7 +181,7 @@ def _estimate_row_count(self, s3_url: str) -> int: print(f"Error estimating row count for {s3_url}: {e}") return 0 - def ingest_data(self, table_name: str, name_as: str = None, size: int = 1000000, sort_columns: List[str] = None, sort_order: str = 'asc'): + def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1000000, sort_columns: list[str] | None = None, sort_order: str = 'asc'): if name_as is None: name_as = table_name.split('/')[-1].split('.')[0] @@ -219,7 +219,7 @@ def ingest_data(self, table_name: str, name_as: str = None, size: int = 1000000, else: raise ValueError(f"Unsupported file type: {table_name}") - def view_query_sample(self, query: str) -> List[Dict[str, Any]]: + def view_query_sample(self, query: str) -> list[dict[str, Any]]: result, error_message = validate_sql_query(query) if not result: raise ValueError(error_message) diff --git a/py-src/data_formulator/db_manager.py b/py-src/data_formulator/db_manager.py index 66bf2c8c..5212dbaa 100644 --- a/py-src/data_formulator/db_manager.py +++ b/py-src/data_formulator/db_manager.py @@ -3,7 +3,6 @@ import duckdb import pandas as pd -from typing import Dict import tempfile import os from contextlib import contextmanager @@ -15,7 +14,7 @@ class DuckDBManager: def __init__(self, local_db_dir: str, disabled: bool = False): # Store session db file paths - self._db_files: Dict[str, str] = {} + self._db_files: dict[str, str] = {} self._local_db_dir: str = local_db_dir self._disabled: bool = disabled diff --git a/py-src/data_formulator/demo_stream_routes.py b/py-src/data_formulator/demo_stream_routes.py index fd1229a4..42bb96e5 100644 --- a/py-src/data_formulator/demo_stream_routes.py +++ b/py-src/data_formulator/demo_stream_routes.py @@ -30,7 +30,7 @@ import math from datetime import datetime, timedelta from flask import Blueprint, Response, request, jsonify -from typing import List, Dict, Any, Optional +from typing import Any from collections import deque import threading @@ -107,9 +107,9 @@ def make_csv_response(rows: list, filename: str = "data.csv") -> Response: # Thread-safe storage for ISS position history _iss_track_lock = threading.Lock() _iss_track_history: deque = deque(maxlen=10000) # Keep last 10000 positions (~20000 min at 5s intervals) -_iss_last_fetch: Optional[datetime] = None +_iss_last_fetch: datetime | None = None -def _fetch_iss_position() -> Optional[Dict[str, Any]]: +def _fetch_iss_position() -> dict[str, Any] | None: """Fetch current ISS position from API""" try: response = requests.get("http://api.open-notify.org/iss-now.json", timeout=10) @@ -1074,7 +1074,7 @@ def get_yfinance_financials(): # Thread-safe storage for sales transaction history _sales_lock = threading.Lock() _sales_history: deque = deque(maxlen=1000) # Keep last 1000 transactions -_sales_last_update: Optional[datetime] = None +_sales_last_update: datetime | None = None # Products with realistic pricing and popularity _SALES_PRODUCTS = [ @@ -1097,7 +1097,7 @@ def get_yfinance_financials(): _SALES_CHANNEL_WEIGHTS = [0.40, 0.35, 0.15, 0.10] -def _generate_sale_transaction(timestamp: datetime) -> Dict[str, Any]: +def _generate_sale_transaction(timestamp: datetime) -> dict[str, Any]: """Generate a single sale transaction""" product = random.choices(_SALES_PRODUCTS, weights=[p["popularity"] for p in _SALES_PRODUCTS])[0] region = random.choices(_SALES_REGIONS, weights=_SALES_REGION_WEIGHTS)[0] diff --git a/py-src/data_formulator/security/query_validator.py b/py-src/data_formulator/security/query_validator.py index 8aa03db9..7e0e5e0a 100644 --- a/py-src/data_formulator/security/query_validator.py +++ b/py-src/data_formulator/security/query_validator.py @@ -3,7 +3,7 @@ import re import logging -from typing import Tuple, Dict, Any +from typing import Any logger = logging.getLogger(__name__) @@ -21,7 +21,7 @@ def normalize_query(query: str) -> str: query_normalized = re.sub(r'/\*.*?\*/', '', query_normalized, flags=re.DOTALL) # Multi-line comments return query_normalized.strip().lower() -def validate_sql_query(query: str) -> Tuple[bool, str]: +def validate_sql_query(query: str) -> tuple[bool, str]: """ Simple regex-based SQL query validation for dangerous operations. @@ -140,7 +140,7 @@ def validate_sql_query(query: str) -> Tuple[bool, str]: return False, f"Query validation error: {str(e)}" -def validate_sql_query_strict(query: str) -> Tuple[bool, str]: +def validate_sql_query_strict(query: str) -> tuple[bool, str]: """ Strict validation that only allows SELECT queries and basic operations. diff --git a/py-src/data_formulator/tables_routes.py b/py-src/data_formulator/tables_routes.py index 80bdeba9..01da3aff 100644 --- a/py-src/data_formulator/tables_routes.py +++ b/py-src/data_formulator/tables_routes.py @@ -20,7 +20,6 @@ from data_formulator.data_loader import DATA_LOADERS import re -from typing import Tuple # Get logger for this module (logging config done in app.py) logger = logging.getLogger(__name__) @@ -662,7 +661,7 @@ def sanitize_table_name(table_name: str) -> str: return f'table_{uuid.uuid4()}' return sanitized_table_name -def sanitize_db_error_message(error: Exception) -> Tuple[str, int]: +def sanitize_db_error_message(error: Exception) -> tuple[str, int]: """ Sanitize error messages before sending to client. Returns a tuple of (sanitized_message, status_code) diff --git a/py-src/data_formulator/workflows/create_vl_plots.py b/py-src/data_formulator/workflows/create_vl_plots.py index 41776fec..74b38006 100644 --- a/py-src/data_formulator/workflows/create_vl_plots.py +++ b/py-src/data_formulator/workflows/create_vl_plots.py @@ -1,6 +1,6 @@ import pandas as pd import numpy as np -from typing import Dict, List, Any, Optional +from typing import Any import vl_convert as vlc import base64 @@ -68,7 +68,7 @@ def detect_field_type(series: pd.Series) -> str: ] -def get_chart_template(chart_type: str) -> Optional[Dict]: +def get_chart_template(chart_type: str) -> dict | None: """ Find a chart template by chart type name. """ @@ -77,7 +77,7 @@ def get_chart_template(chart_type: str) -> Optional[Dict]: return template return None -def create_chart_spec(df: pd.DataFrame, fields: List[str], chart_type: str) -> Dict[str, Dict[str, str]]: +def create_chart_spec(df: pd.DataFrame, fields: list[str], chart_type: str) -> dict[str, dict[str, str]]: """ Assign fields to appropriate visualization channels based on their data types and chart type. """ @@ -85,7 +85,7 @@ def create_chart_spec(df: pd.DataFrame, fields: List[str], chart_type: str) -> D return assemble_vegailte_chart(df, chart_type, encodings) -def fields_to_encodings(df, chart_type: str, fields: List[str]) -> Dict[str, Dict[str, str]]: +def fields_to_encodings(df, chart_type: str, fields: list[str]) -> dict[str, dict[str, str]]: """ Assign fields to appropriate visualization channels based on their data types and chart type. @@ -389,9 +389,9 @@ def assign_faceting_channels(): def assemble_vegailte_chart( df: pd.DataFrame, chart_type: str, - encodings: Dict[str, Dict[str, str]], + encodings: dict[str, dict[str, str]], max_nominal_values: int = 68 -) -> Dict: +) -> dict: """ Assemble a Vega-Lite chart specification from a dataframe, chart type, and encodings. @@ -574,7 +574,7 @@ def _get_top_values(df: pd.DataFrame, field_name: str, unique_values: list, return unique_values[:max_values] -def vl_spec_to_png(spec: Dict, output_path: str = None, scale: float = 1.0) -> bytes: +def vl_spec_to_png(spec: dict, output_path: str | None = None, scale: float = 1.0) -> bytes: """ Convert a Vega-Lite specification to a PNG image. @@ -600,7 +600,7 @@ def vl_spec_to_png(spec: Dict, output_path: str = None, scale: float = 1.0) -> b return png_data -def spec_to_base64(spec: Dict, scale: float = 1.0) -> str: +def spec_to_base64(spec: dict, scale: float = 1.0) -> str: """ Convert a Vega-Lite specification to a base64 encoded PNG string. diff --git a/py-src/data_formulator/workflows/exploration_flow.py b/py-src/data_formulator/workflows/exploration_flow.py index dc241a8b..578e06fc 100644 --- a/py-src/data_formulator/workflows/exploration_flow.py +++ b/py-src/data_formulator/workflows/exploration_flow.py @@ -1,11 +1,9 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. -import json import logging -from this import d import pandas as pd -from typing import Dict, List, Any, Optional, Tuple, Generator +from typing import Any, Generator from data_formulator.agents.agent_exploration import ExplorationAgent from data_formulator.agents.agent_py_data_rec import PythonDataRecAgent @@ -13,14 +11,13 @@ from data_formulator.agents.client_utils import Client from data_formulator.db_manager import db_manager from data_formulator.workflows.create_vl_plots import assemble_vegailte_chart, spec_to_base64, detect_field_type -from data_formulator.agents.agent_utils import extract_json_objects logger = logging.getLogger(__name__) def create_chart_spec_from_data( - transformed_data: Dict[str, Any], + transformed_data: dict[str, Any], chart_type: str, - chart_encodings: Dict[str, str] + chart_encodings: dict[str, str] ) -> str: """ Create a chart from transformed data using Vega-Lite. @@ -59,17 +56,17 @@ def create_chart_spec_from_data( return None def run_exploration_flow_streaming( - model_config: Dict[str, str], - input_tables: List[Dict[str, Any]], - initial_plan: List[str], + model_config: dict[str, str], + input_tables: list[dict[str, Any]], + initial_plan: list[str], language: str = "python", - session_id: Optional[str] = None, + session_id: str | None = None, exec_python_in_subprocess: bool = False, max_iterations: int = 5, max_repair_attempts: int = 1, agent_exploration_rules: str = "", agent_coding_rules: str = "" -) -> Generator[Dict[str, Any], None, None]: +) -> Generator[dict[str, Any], None, None]: """ Run the complete exploration flow from high-level question to final insights as a streaming generator. diff --git a/pyproject.toml b/pyproject.toml index 49ed802a..c68e2c75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" name = "data_formulator" version = "0.6" -requires-python = ">=3.9" +requires-python = ">=3.11" authors = [ {name = "Chenglong Wang", email = "chenglong.wang@microsoft.com"}, {name = "Dan Marshall", email = "danmar@microsoft.com"}, @@ -62,3 +62,8 @@ include-package-data = true [project.scripts] data_formulator = "data_formulator:run_app" + +[tool.uv] +dev-dependencies = [ + "build", +] diff --git a/requirements.txt b/requirements.txt index 0fe4db15..aa8d271c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,33 +1,646 @@ -# Core dependencies (always required) -jupyter -pandas -numpy -flask -flask-cors -flask-limiter -openai -python-dotenv -vega_datasets -litellm -duckdb -vl-convert-python -backoff -beautifulsoup4 -scikit-learn -yfinance # for demo stream routes - -# External data loaders (Azure, BigQuery, AWS S3, MySQL, MSSQL) -azure-identity -azure-kusto-data -azure-keyvault-secrets -azure-storage-blob -google-cloud-bigquery -google-auth -db-dtypes -boto3 -pymysql -pyodbc -pymongo +# This file was autogenerated by uv via the following command: +# uv export --frozen --no-hashes +aiohappyeyeballs==2.6.1 + # via aiohttp +aiohttp==3.13.3 + # via litellm +aiosignal==1.4.0 + # via aiohttp +annotated-types==0.7.0 + # via pydantic +anyio==4.12.1 + # via + # httpx + # jupyter-server + # openai +appnope==0.1.4 ; sys_platform == 'darwin' + # via ipykernel +argon2-cffi==25.1.0 + # via jupyter-server +argon2-cffi-bindings==25.1.0 + # via argon2-cffi +arrow==1.4.0 + # via isoduration +asttokens==3.0.1 + # via stack-data +async-lru==2.1.0 + # via jupyterlab +attrs==25.4.0 + # via + # aiohttp + # jsonschema + # referencing +azure-core==1.38.0 + # via + # azure-identity + # azure-keyvault-secrets + # azure-kusto-data + # azure-storage-blob +azure-identity==1.25.1 + # via + # azure-kusto-data + # data-formulator +azure-keyvault-secrets==4.10.0 + # via data-formulator +azure-kusto-data==6.0.1 + # via data-formulator +azure-storage-blob==12.28.0 + # via data-formulator +babel==2.17.0 + # via jupyterlab-server +backoff==2.2.1 + # via data-formulator +beautifulsoup4==4.14.3 + # via + # data-formulator + # nbconvert + # yfinance +bleach==6.3.0 + # via nbconvert +blinker==1.9.0 + # via flask +boto3==1.42.39 + # via data-formulator +botocore==1.42.39 + # via + # boto3 + # s3transfer +build==1.4.0 +certifi==2026.1.4 + # via + # curl-cffi + # httpcore + # httpx + # requests +cffi==2.0.0 + # via + # argon2-cffi-bindings + # cryptography + # curl-cffi + # pyzmq +charset-normalizer==3.4.4 + # via requests +click==8.3.1 + # via + # flask + # litellm + # typer-slim +colorama==0.4.6 ; os_name == 'nt' or sys_platform == 'win32' + # via + # build + # click + # ipython + # tqdm +comm==0.2.3 + # via + # ipykernel + # ipywidgets +cryptography==46.0.4 + # via + # azure-identity + # azure-storage-blob + # google-auth + # msal + # pyjwt +curl-cffi==0.13.0 + # via yfinance +db-dtypes==1.5.0 + # via data-formulator +debugpy==1.8.20 + # via ipykernel +decorator==5.2.1 + # via ipython +defusedxml==0.7.1 + # via nbconvert +deprecated==1.3.1 + # via limits +distro==1.9.0 + # via openai +dnspython==2.8.0 + # via pymongo +duckdb==1.4.4 + # via data-formulator +executing==2.2.1 + # via stack-data +fastjsonschema==2.21.2 + # via nbformat +fastuuid==0.14.0 + # via litellm +filelock==3.20.3 + # via huggingface-hub +flask==3.1.2 + # via + # data-formulator + # flask-cors + # flask-limiter +flask-cors==6.0.2 + # via data-formulator +flask-limiter==4.1.1 + # via data-formulator +fqdn==1.5.1 + # via jsonschema +frozendict==2.4.7 + # via yfinance +frozenlist==1.8.0 + # via + # aiohttp + # aiosignal +fsspec==2026.1.0 + # via huggingface-hub +google-api-core==2.29.0 + # via + # google-cloud-bigquery + # google-cloud-core +google-auth==2.48.0 + # via + # data-formulator + # google-api-core + # google-cloud-bigquery + # google-cloud-core +google-cloud-bigquery==3.40.0 + # via data-formulator +google-cloud-core==2.5.0 + # via google-cloud-bigquery +google-crc32c==1.8.0 + # via google-resumable-media +google-resumable-media==2.8.0 + # via google-cloud-bigquery +googleapis-common-protos==1.72.0 + # via + # google-api-core + # grpcio-status +grpcio==1.76.0 + # via + # google-api-core + # grpcio-status +grpcio-status==1.76.0 + # via google-api-core +h11==0.16.0 + # via httpcore +hf-xet==1.2.0 ; platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64' + # via huggingface-hub +httpcore==1.0.9 + # via httpx +httpx==0.28.1 + # via + # huggingface-hub + # jupyterlab + # litellm + # openai +huggingface-hub==1.3.5 + # via tokenizers +idna==3.11 + # via + # anyio + # httpx + # jsonschema + # requests + # yarl +ijson==3.4.0.post0 + # via azure-kusto-data +importlib-metadata==8.7.1 + # via litellm +ipykernel==7.1.0 + # via + # jupyter + # jupyter-console + # jupyterlab +ipython==8.38.0 + # via + # ipykernel + # ipywidgets + # jupyter-console +ipywidgets==8.1.8 + # via jupyter +isodate==0.7.2 + # via + # azure-keyvault-secrets + # azure-storage-blob +isoduration==20.11.0 + # via jsonschema +itsdangerous==2.2.0 + # via flask +jedi==0.19.2 + # via ipython +jinja2==3.1.6 + # via + # flask + # jupyter-server + # jupyterlab + # jupyterlab-server + # litellm + # nbconvert +jiter==0.12.0 + # via openai +jmespath==1.1.0 + # via + # boto3 + # botocore +joblib==1.5.3 + # via scikit-learn +json5==0.13.0 + # via jupyterlab-server +jsonpointer==3.0.0 + # via jsonschema +jsonschema==4.26.0 + # via + # jupyter-events + # jupyterlab-server + # litellm + # nbformat +jsonschema-specifications==2025.9.1 + # via jsonschema +jupyter==1.1.1 + # via data-formulator +jupyter-client==8.8.0 + # via + # ipykernel + # jupyter-console + # jupyter-server + # nbclient +jupyter-console==6.6.3 + # via jupyter +jupyter-core==5.9.1 + # via + # ipykernel + # jupyter-client + # jupyter-console + # jupyter-server + # jupyterlab + # nbclient + # nbconvert + # nbformat +jupyter-events==0.12.0 + # via jupyter-server +jupyter-lsp==2.3.0 + # via jupyterlab +jupyter-server==2.17.0 + # via + # jupyter-lsp + # jupyterlab + # jupyterlab-server + # notebook + # notebook-shim +jupyter-server-terminals==0.5.4 + # via jupyter-server +jupyterlab==4.5.3 + # via + # jupyter + # notebook +jupyterlab-pygments==0.3.0 + # via nbconvert +jupyterlab-server==2.28.0 + # via + # jupyterlab + # notebook +jupyterlab-widgets==3.0.16 + # via ipywidgets +lark==1.3.1 + # via rfc3987-syntax +limits==5.6.0 + # via flask-limiter +litellm==1.81.5 + # via data-formulator +markupsafe==3.0.3 + # via + # flask + # jinja2 + # nbconvert + # werkzeug +matplotlib-inline==0.2.1 + # via + # ipykernel + # ipython +mistune==3.2.0 + # via nbconvert +msal==1.34.0 + # via + # azure-identity + # azure-kusto-data + # msal-extensions +msal-extensions==1.3.1 + # via azure-identity +multidict==6.7.1 + # via + # aiohttp + # yarl +multitasking==0.0.12 + # via yfinance +nbclient==0.10.4 + # via nbconvert +nbconvert==7.17.0 + # via + # jupyter + # jupyter-server +nbformat==5.10.4 + # via + # jupyter-server + # nbclient + # nbconvert +nest-asyncio==1.6.0 + # via ipykernel +notebook==7.5.3 + # via jupyter +notebook-shim==0.2.4 + # via + # jupyterlab + # notebook +numpy==2.2.6 + # via + # data-formulator + # db-dtypes + # pandas + # scikit-learn + # scipy + # yfinance +openai==2.16.0 + # via + # data-formulator + # litellm +ordered-set==4.1.0 + # via flask-limiter +overrides==7.7.0 ; python_full_version < '3.12' + # via jupyter-server +packaging==26.0 + # via + # build + # db-dtypes + # google-cloud-bigquery + # huggingface-hub + # ipykernel + # jupyter-events + # jupyter-server + # jupyterlab + # jupyterlab-server + # limits + # nbconvert +pandas==2.3.3 + # via + # data-formulator + # db-dtypes + # vega-datasets + # yfinance +pandocfilters==1.5.1 + # via nbconvert +parso==0.8.5 + # via jedi +peewee==3.19.0 + # via yfinance +pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' + # via ipython +platformdirs==4.5.1 + # via + # jupyter-core + # yfinance +prometheus-client==0.24.1 + # via jupyter-server +prompt-toolkit==3.0.52 + # via + # ipython + # jupyter-console +propcache==0.4.1 + # via + # aiohttp + # yarl +proto-plus==1.27.0 + # via google-api-core +protobuf==6.33.5 + # via + # google-api-core + # googleapis-common-protos + # grpcio-status + # proto-plus + # yfinance +psutil==7.2.2 + # via ipykernel +ptyprocess==0.7.0 ; os_name != 'nt' or (sys_platform != 'emscripten' and sys_platform != 'win32') + # via + # pexpect + # terminado +pure-eval==0.2.3 + # via stack-data +pyarrow==23.0.0 + # via db-dtypes +pyasn1==0.6.2 + # via + # pyasn1-modules + # rsa +pyasn1-modules==0.4.2 + # via google-auth +pycparser==3.0 ; implementation_name != 'PyPy' + # via cffi +pydantic==2.12.5 + # via + # litellm + # openai +pydantic-core==2.41.5 + # via pydantic +pygments==2.19.2 + # via + # ipython + # jupyter-console + # nbconvert +pyjwt==2.11.0 + # via msal +pymongo==4.16.0 + # via data-formulator +pymysql==1.1.2 + # via data-formulator +pyodbc==5.3.0 + # via data-formulator +pyproject-hooks==1.2.0 + # via build +python-dateutil==2.9.0.post0 + # via + # arrow + # azure-kusto-data + # botocore + # google-cloud-bigquery + # jupyter-client + # pandas +python-dotenv==1.2.1 + # via + # data-formulator + # litellm +python-json-logger==4.0.0 + # via jupyter-events +pytz==2025.2 + # via + # pandas + # yfinance +pywinpty==3.0.2 ; os_name == 'nt' + # via + # jupyter-server + # jupyter-server-terminals + # terminado +pyyaml==6.0.3 + # via + # huggingface-hub + # jupyter-events +pyzmq==27.1.0 + # via + # ipykernel + # jupyter-client + # jupyter-console + # jupyter-server +referencing==0.37.0 + # via + # jsonschema + # jsonschema-specifications + # jupyter-events +regex==2026.1.15 + # via tiktoken +requests==2.32.5 + # via + # azure-core + # azure-kusto-data + # google-api-core + # google-cloud-bigquery + # jupyterlab-server + # msal + # tiktoken + # yfinance +rfc3339-validator==0.1.4 + # via + # jsonschema + # jupyter-events +rfc3986-validator==0.1.1 + # via + # jsonschema + # jupyter-events +rfc3987-syntax==1.1.0 + # via jsonschema +rpds-py==0.30.0 + # via + # jsonschema + # referencing +rsa==4.9.1 + # via google-auth +s3transfer==0.16.0 + # via boto3 +scikit-learn==1.7.2 + # via data-formulator +scipy==1.15.3 + # via scikit-learn +send2trash==2.1.0 + # via jupyter-server +setuptools==80.10.2 + # via jupyterlab +shellingham==1.5.4 + # via huggingface-hub +six==1.17.0 + # via + # python-dateutil + # rfc3339-validator +sniffio==1.3.1 + # via openai +soupsieve==2.8.3 + # via beautifulsoup4 +stack-data==0.6.3 + # via ipython +terminado==0.18.1 + # via + # jupyter-server + # jupyter-server-terminals +threadpoolctl==3.6.0 + # via scikit-learn +tiktoken==0.12.0 + # via litellm +tinycss2==1.4.0 + # via bleach +tokenizers==0.22.2 + # via litellm +tornado==6.5.4 + # via + # ipykernel + # jupyter-client + # jupyter-server + # jupyterlab + # notebook + # terminado +tqdm==4.67.2 + # via + # huggingface-hub + # openai +traitlets==5.14.3 + # via + # ipykernel + # ipython + # ipywidgets + # jupyter-client + # jupyter-console + # jupyter-core + # jupyter-events + # jupyter-server + # jupyterlab + # matplotlib-inline + # nbclient + # nbconvert + # nbformat +typer-slim==0.21.1 + # via huggingface-hub +typing-extensions==4.15.0 + # via + # aiosignal + # anyio + # azure-core + # azure-identity + # azure-keyvault-secrets + # azure-storage-blob + # beautifulsoup4 + # flask-limiter + # grpcio + # huggingface-hub + # ipython + # limits + # openai + # pydantic + # pydantic-core + # referencing + # typer-slim + # typing-inspection +typing-inspection==0.4.2 + # via pydantic +tzdata==2025.3 + # via + # arrow + # pandas +uri-template==1.3.0 + # via jsonschema +urllib3==2.6.3 + # via + # botocore + # requests +vega-datasets==0.9.0 + # via data-formulator +vl-convert-python==1.9.0.post1 + # via data-formulator +wcwidth==0.5.2 + # via prompt-toolkit +webcolors==25.10.0 + # via jsonschema +webencodings==0.5.1 + # via + # bleach + # tinycss2 +websocket-client==1.9.0 + # via jupyter-server +websockets==16.0 + # via yfinance +werkzeug==3.1.5 + # via + # flask + # flask-cors +widgetsnbextension==4.0.15 + # via ipywidgets +wrapt==2.0.1 + # via deprecated +yarl==1.22.0 + # via aiohttp +yfinance==1.1.0 + # via data-formulator +zipp==3.23.0 + # via importlib-metadata # Install data_formulator itself in editable mode --e . \ No newline at end of file +-e . diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..d2373c89 --- /dev/null +++ b/uv.lock @@ -0,0 +1,4301 @@ +version = 1 +revision = 2 +requires-python = ">=3.11" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version == '3.13.*'", + "python_full_version == '3.12.*'", + "python_full_version < '3.12'", +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b", size = 746051, upload-time = "2026-01-03T17:29:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64", size = 499234, upload-time = "2026-01-03T17:29:44.822Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea", size = 494979, upload-time = "2026-01-03T17:29:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a", size = 1748297, upload-time = "2026-01-03T17:29:48.083Z" }, + { url = "https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540", size = 1707172, upload-time = "2026-01-03T17:29:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b", size = 1805405, upload-time = "2026-01-03T17:29:51.244Z" }, + { url = "https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3", size = 1899449, upload-time = "2026-01-03T17:29:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1", size = 1748444, upload-time = "2026-01-03T17:29:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3", size = 1606038, upload-time = "2026-01-03T17:29:57.179Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440", size = 1724156, upload-time = "2026-01-03T17:29:58.914Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7", size = 1722340, upload-time = "2026-01-03T17:30:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c", size = 1783041, upload-time = "2026-01-03T17:30:03.609Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51", size = 1596024, upload-time = "2026-01-03T17:30:05.132Z" }, + { url = "https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4", size = 1804590, upload-time = "2026-01-03T17:30:07.135Z" }, + { url = "https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29", size = 1740355, upload-time = "2026-01-03T17:30:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239", size = 433701, upload-time = "2026-01-03T17:30:10.869Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f", size = 457678, upload-time = "2026-01-03T17:30:12.719Z" }, + { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, + { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, + { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, + { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, + { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, + { url = "https://files.pythonhosted.org/packages/97/8a/12ca489246ca1faaf5432844adbfce7ff2cc4997733e0af120869345643a/aiohttp-3.13.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dff64413671b0d3e7d5918ea490bdccb97a4ad29b3f311ed423200b2203e01c", size = 734190, upload-time = "2026-01-03T17:30:45.832Z" }, + { url = "https://files.pythonhosted.org/packages/32/08/de43984c74ed1fca5c014808963cc83cb00d7bb06af228f132d33862ca76/aiohttp-3.13.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:87b9aab6d6ed88235aa2970294f496ff1a1f9adcd724d800e9b952395a80ffd9", size = 491783, upload-time = "2026-01-03T17:30:47.466Z" }, + { url = "https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3", size = 490704, upload-time = "2026-01-03T17:30:49.373Z" }, + { url = "https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf", size = 1720652, upload-time = "2026-01-03T17:30:50.974Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7e/917fe18e3607af92657e4285498f500dca797ff8c918bd7d90b05abf6c2a/aiohttp-3.13.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:697753042d57f4bf7122cab985bf15d0cef23c770864580f5af4f52023a56bd6", size = 1692014, upload-time = "2026-01-03T17:30:52.729Z" }, + { url = "https://files.pythonhosted.org/packages/71/b6/cefa4cbc00d315d68973b671cf105b21a609c12b82d52e5d0c9ae61d2a09/aiohttp-3.13.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6de499a1a44e7de70735d0b39f67c8f25eb3d91eb3103be99ca0fa882cdd987d", size = 1759777, upload-time = "2026-01-03T17:30:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e3/e06ee07b45e59e6d81498b591fc589629be1553abb2a82ce33efe2a7b068/aiohttp-3.13.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:37239e9f9a7ea9ac5bf6b92b0260b01f8a22281996da609206a84df860bc1261", size = 1861276, upload-time = "2026-01-03T17:30:56.512Z" }, + { url = "https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0", size = 1743131, upload-time = "2026-01-03T17:30:58.256Z" }, + { url = "https://files.pythonhosted.org/packages/04/98/3d21dde21889b17ca2eea54fdcff21b27b93f45b7bb94ca029c31ab59dc3/aiohttp-3.13.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fc290605db2a917f6e81b0e1e0796469871f5af381ce15c604a3c5c7e51cb730", size = 1556863, upload-time = "2026-01-03T17:31:00.445Z" }, + { url = "https://files.pythonhosted.org/packages/9e/84/da0c3ab1192eaf64782b03971ab4055b475d0db07b17eff925e8c93b3aa5/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4021b51936308aeea0367b8f006dc999ca02bc118a0cc78c303f50a2ff6afb91", size = 1682793, upload-time = "2026-01-03T17:31:03.024Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0f/5802ada182f575afa02cbd0ec5180d7e13a402afb7c2c03a9aa5e5d49060/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:49a03727c1bba9a97d3e93c9f93ca03a57300f484b6e935463099841261195d3", size = 1716676, upload-time = "2026-01-03T17:31:04.842Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8c/714d53bd8b5a4560667f7bbbb06b20c2382f9c7847d198370ec6526af39c/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3d9908a48eb7416dc1f4524e69f1d32e5d90e3981e4e37eb0aa1cd18f9cfa2a4", size = 1733217, upload-time = "2026-01-03T17:31:06.868Z" }, + { url = "https://files.pythonhosted.org/packages/7d/79/e2176f46d2e963facea939f5be2d26368ce543622be6f00a12844d3c991f/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2712039939ec963c237286113c68dbad80a82a4281543f3abf766d9d73228998", size = 1552303, upload-time = "2026-01-03T17:31:08.958Z" }, + { url = "https://files.pythonhosted.org/packages/ab/6a/28ed4dea1759916090587d1fe57087b03e6c784a642b85ef48217b0277ae/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7bfdc049127717581866fa4708791220970ce291c23e28ccf3922c700740fdc0", size = 1763673, upload-time = "2026-01-03T17:31:10.676Z" }, + { url = "https://files.pythonhosted.org/packages/e8/35/4a3daeb8b9fab49240d21c04d50732313295e4bd813a465d840236dd0ce1/aiohttp-3.13.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8057c98e0c8472d8846b9c79f56766bcc57e3e8ac7bfd510482332366c56c591", size = 1721120, upload-time = "2026-01-03T17:31:12.575Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9f/d643bb3c5fb99547323e635e251c609fbbc660d983144cfebec529e09264/aiohttp-3.13.3-cp313-cp313-win32.whl", hash = "sha256:1449ceddcdbcf2e0446957863af03ebaaa03f94c090f945411b61269e2cb5daf", size = 427383, upload-time = "2026-01-03T17:31:14.382Z" }, + { url = "https://files.pythonhosted.org/packages/4e/f1/ab0395f8a79933577cdd996dd2f9aa6014af9535f65dddcf88204682fe62/aiohttp-3.13.3-cp313-cp313-win_amd64.whl", hash = "sha256:693781c45a4033d31d4187d2436f5ac701e7bbfe5df40d917736108c1cc7436e", size = 453899, upload-time = "2026-01-03T17:31:15.958Z" }, + { url = "https://files.pythonhosted.org/packages/99/36/5b6514a9f5d66f4e2597e40dea2e3db271e023eb7a5d22defe96ba560996/aiohttp-3.13.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:ea37047c6b367fd4bd632bff8077449b8fa034b69e812a18e0132a00fae6e808", size = 737238, upload-time = "2026-01-03T17:31:17.909Z" }, + { url = "https://files.pythonhosted.org/packages/f7/49/459327f0d5bcd8c6c9ca69e60fdeebc3622861e696490d8674a6d0cb90a6/aiohttp-3.13.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6fc0e2337d1a4c3e6acafda6a78a39d4c14caea625124817420abceed36e2415", size = 492292, upload-time = "2026-01-03T17:31:19.919Z" }, + { url = "https://files.pythonhosted.org/packages/e8/0b/b97660c5fd05d3495b4eb27f2d0ef18dc1dc4eff7511a9bf371397ff0264/aiohttp-3.13.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c685f2d80bb67ca8c3837823ad76196b3694b0159d232206d1e461d3d434666f", size = 493021, upload-time = "2026-01-03T17:31:21.636Z" }, + { url = "https://files.pythonhosted.org/packages/54/d4/438efabdf74e30aeceb890c3290bbaa449780583b1270b00661126b8aae4/aiohttp-3.13.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:48e377758516d262bde50c2584fc6c578af272559c409eecbdd2bae1601184d6", size = 1717263, upload-time = "2026-01-03T17:31:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/71/f2/7bddc7fd612367d1459c5bcf598a9e8f7092d6580d98de0e057eb42697ad/aiohttp-3.13.3-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:34749271508078b261c4abb1767d42b8d0c0cc9449c73a4df494777dc55f0687", size = 1669107, upload-time = "2026-01-03T17:31:25.334Z" }, + { url = "https://files.pythonhosted.org/packages/00/5a/1aeaecca40e22560f97610a329e0e5efef5e0b5afdf9f857f0d93839ab2e/aiohttp-3.13.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:82611aeec80eb144416956ec85b6ca45a64d76429c1ed46ae1b5f86c6e0c9a26", size = 1760196, upload-time = "2026-01-03T17:31:27.394Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f8/0ff6992bea7bd560fc510ea1c815f87eedd745fe035589c71ce05612a19a/aiohttp-3.13.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fff83cfc93f18f215896e3a190e8e5cb413ce01553901aca925176e7568963a", size = 1843591, upload-time = "2026-01-03T17:31:29.238Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d1/e30e537a15f53485b61f5be525f2157da719819e8377298502aebac45536/aiohttp-3.13.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bbe7d4cecacb439e2e2a8a1a7b935c25b812af7a5fd26503a66dadf428e79ec1", size = 1720277, upload-time = "2026-01-03T17:31:31.053Z" }, + { url = "https://files.pythonhosted.org/packages/84/45/23f4c451d8192f553d38d838831ebbc156907ea6e05557f39563101b7717/aiohttp-3.13.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b928f30fe49574253644b1ca44b1b8adbd903aa0da4b9054a6c20fc7f4092a25", size = 1548575, upload-time = "2026-01-03T17:31:32.87Z" }, + { url = "https://files.pythonhosted.org/packages/6a/ed/0a42b127a43712eda7807e7892c083eadfaf8429ca8fb619662a530a3aab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7b5e8fe4de30df199155baaf64f2fcd604f4c678ed20910db8e2c66dc4b11603", size = 1679455, upload-time = "2026-01-03T17:31:34.76Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b5/c05f0c2b4b4fe2c9d55e73b6d3ed4fd6c9dc2684b1d81cbdf77e7fad9adb/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:8542f41a62bcc58fc7f11cf7c90e0ec324ce44950003feb70640fc2a9092c32a", size = 1687417, upload-time = "2026-01-03T17:31:36.699Z" }, + { url = "https://files.pythonhosted.org/packages/c9/6b/915bc5dad66aef602b9e459b5a973529304d4e89ca86999d9d75d80cbd0b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5e1d8c8b8f1d91cd08d8f4a3c2b067bfca6ec043d3ff36de0f3a715feeedf926", size = 1729968, upload-time = "2026-01-03T17:31:38.622Z" }, + { url = "https://files.pythonhosted.org/packages/11/3b/e84581290a9520024a08640b63d07673057aec5ca548177a82026187ba73/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:90455115e5da1c3c51ab619ac57f877da8fd6d73c05aacd125c5ae9819582aba", size = 1545690, upload-time = "2026-01-03T17:31:40.57Z" }, + { url = "https://files.pythonhosted.org/packages/f5/04/0c3655a566c43fd647c81b895dfe361b9f9ad6d58c19309d45cff52d6c3b/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:042e9e0bcb5fba81886c8b4fbb9a09d6b8a00245fd8d88e4d989c1f96c74164c", size = 1746390, upload-time = "2026-01-03T17:31:42.857Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/71165b26978f719c3419381514c9690bd5980e764a09440a10bb816ea4ab/aiohttp-3.13.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2eb752b102b12a76ca02dff751a801f028b4ffbbc478840b473597fc91a9ed43", size = 1702188, upload-time = "2026-01-03T17:31:44.984Z" }, + { url = "https://files.pythonhosted.org/packages/29/a7/cbe6c9e8e136314fa1980da388a59d2f35f35395948a08b6747baebb6aa6/aiohttp-3.13.3-cp314-cp314-win32.whl", hash = "sha256:b556c85915d8efaed322bf1bdae9486aa0f3f764195a0fb6ee962e5c71ef5ce1", size = 433126, upload-time = "2026-01-03T17:31:47.463Z" }, + { url = "https://files.pythonhosted.org/packages/de/56/982704adea7d3b16614fc5936014e9af85c0e34b58f9046655817f04306e/aiohttp-3.13.3-cp314-cp314-win_amd64.whl", hash = "sha256:9bf9f7a65e7aa20dd764151fb3d616c81088f91f8df39c3893a536e279b4b984", size = 459128, upload-time = "2026-01-03T17:31:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2a/3c79b638a9c3d4658d345339d22070241ea341ed4e07b5ac60fb0f418003/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:05861afbbec40650d8a07ea324367cb93e9e8cc7762e04dd4405df99fa65159c", size = 769512, upload-time = "2026-01-03T17:31:51.134Z" }, + { url = "https://files.pythonhosted.org/packages/29/b9/3e5014d46c0ab0db8707e0ac2711ed28c4da0218c358a4e7c17bae0d8722/aiohttp-3.13.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2fc82186fadc4a8316768d61f3722c230e2c1dcab4200d52d2ebdf2482e47592", size = 506444, upload-time = "2026-01-03T17:31:52.85Z" }, + { url = "https://files.pythonhosted.org/packages/90/03/c1d4ef9a054e151cd7839cdc497f2638f00b93cbe8043983986630d7a80c/aiohttp-3.13.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0add0900ff220d1d5c5ebbf99ed88b0c1bbf87aa7e4262300ed1376a6b13414f", size = 510798, upload-time = "2026-01-03T17:31:54.91Z" }, + { url = "https://files.pythonhosted.org/packages/ea/76/8c1e5abbfe8e127c893fe7ead569148a4d5a799f7cf958d8c09f3eedf097/aiohttp-3.13.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:568f416a4072fbfae453dcf9a99194bbb8bdeab718e08ee13dfa2ba0e4bebf29", size = 1868835, upload-time = "2026-01-03T17:31:56.733Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/984c5a6f74c363b01ff97adc96a3976d9c98940b8969a1881575b279ac5d/aiohttp-3.13.3-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:add1da70de90a2569c5e15249ff76a631ccacfe198375eead4aadf3b8dc849dc", size = 1720486, upload-time = "2026-01-03T17:31:58.65Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9a/b7039c5f099c4eb632138728828b33428585031a1e658d693d41d07d89d1/aiohttp-3.13.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:10b47b7ba335d2e9b1239fa571131a87e2d8ec96b333e68b2a305e7a98b0bae2", size = 1847951, upload-time = "2026-01-03T17:32:00.989Z" }, + { url = "https://files.pythonhosted.org/packages/3c/02/3bec2b9a1ba3c19ff89a43a19324202b8eb187ca1e928d8bdac9bbdddebd/aiohttp-3.13.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3dd4dce1c718e38081c8f35f323209d4c1df7d4db4bab1b5c88a6b4d12b74587", size = 1941001, upload-time = "2026-01-03T17:32:03.122Z" }, + { url = "https://files.pythonhosted.org/packages/37/df/d879401cedeef27ac4717f6426c8c36c3091c6e9f08a9178cc87549c537f/aiohttp-3.13.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34bac00a67a812570d4a460447e1e9e06fae622946955f939051e7cc895cfab8", size = 1797246, upload-time = "2026-01-03T17:32:05.255Z" }, + { url = "https://files.pythonhosted.org/packages/8d/15/be122de1f67e6953add23335c8ece6d314ab67c8bebb3f181063010795a7/aiohttp-3.13.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a19884d2ee70b06d9204b2727a7b9f983d0c684c650254679e716b0b77920632", size = 1627131, upload-time = "2026-01-03T17:32:07.607Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/70eedcac9134cfa3219ab7af31ea56bc877395b1ac30d65b1bc4b27d0438/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ca7f2bb6ba8348a3614c7918cc4bb73268c5ac2a207576b7afea19d3d9f64", size = 1795196, upload-time = "2026-01-03T17:32:09.59Z" }, + { url = "https://files.pythonhosted.org/packages/32/11/b30e1b1cd1f3054af86ebe60df96989c6a414dd87e27ad16950eee420bea/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:b0d95340658b9d2f11d9697f59b3814a9d3bb4b7a7c20b131df4bcef464037c0", size = 1782841, upload-time = "2026-01-03T17:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/88/0d/d98a9367b38912384a17e287850f5695c528cff0f14f791ce8ee2e4f7796/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1e53262fd202e4b40b70c3aff944a8155059beedc8a89bba9dc1f9ef06a1b56", size = 1795193, upload-time = "2026-01-03T17:32:13.705Z" }, + { url = "https://files.pythonhosted.org/packages/43/a5/a2dfd1f5ff5581632c7f6a30e1744deda03808974f94f6534241ef60c751/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:d60ac9663f44168038586cab2157e122e46bdef09e9368b37f2d82d354c23f72", size = 1621979, upload-time = "2026-01-03T17:32:15.965Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f0/12973c382ae7c1cccbc4417e129c5bf54c374dfb85af70893646e1f0e749/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:90751b8eed69435bac9ff4e3d2f6b3af1f57e37ecb0fbeee59c0174c9e2d41df", size = 1822193, upload-time = "2026-01-03T17:32:18.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5f/24155e30ba7f8c96918af1350eb0663e2430aad9e001c0489d89cd708ab1/aiohttp-3.13.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fc353029f176fd2b3ec6cfc71be166aba1936fe5d73dd1992ce289ca6647a9aa", size = 1769801, upload-time = "2026-01-03T17:32:20.25Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f8/7314031ff5c10e6ece114da79b338ec17eeff3a079e53151f7e9f43c4723/aiohttp-3.13.3-cp314-cp314t-win32.whl", hash = "sha256:2e41b18a58da1e474a057b3d35248d8320029f61d70a37629535b16a0c8f3767", size = 466523, upload-time = "2026-01-03T17:32:22.215Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/278a98c715ae467624eafe375542d8ba9b4383a016df8fdefe0ae28382a7/aiohttp-3.13.3-cp314-cp314t-win_amd64.whl", hash = "sha256:44531a36aa2264a1860089ffd4dce7baf875ee5a6079d5fb42e261c704ef7344", size = 499694, upload-time = "2026-01-03T17:32:24.546Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170, upload-time = "2024-02-06T09:43:11.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" }, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/3c0a35f46e52108d4707c44b95cfe2afcafc50800b5450c197454569b776/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f", size = 54393, upload-time = "2025-07-30T10:01:40.97Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f4/98bbd6ee89febd4f212696f13c03ca302b8552e7dbf9c8efa11ea4a388c3/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b", size = 29328, upload-time = "2025-07-30T10:01:41.916Z" }, + { url = "https://files.pythonhosted.org/packages/43/24/90a01c0ef12ac91a6be05969f29944643bc1e5e461155ae6559befa8f00b/argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a", size = 31269, upload-time = "2025-07-30T10:01:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d3/942aa10782b2697eee7af5e12eeff5ebb325ccfb86dd8abda54174e377e4/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44", size = 86558, upload-time = "2025-07-30T10:01:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/0d/82/b484f702fec5536e71836fc2dbc8c5267b3f6e78d2d539b4eaa6f0db8bf8/argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb", size = 92364, upload-time = "2025-07-30T10:01:44.887Z" }, + { url = "https://files.pythonhosted.org/packages/c9/c1/a606ff83b3f1735f3759ad0f2cd9e038a0ad11a3de3b6c673aa41c24bb7b/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92", size = 85637, upload-time = "2025-07-30T10:01:46.225Z" }, + { url = "https://files.pythonhosted.org/packages/44/b4/678503f12aceb0262f84fa201f6027ed77d71c5019ae03b399b97caa2f19/argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85", size = 91934, upload-time = "2025-07-30T10:01:47.203Z" }, + { url = "https://files.pythonhosted.org/packages/f0/c7/f36bd08ef9bd9f0a9cff9428406651f5937ce27b6c5b07b92d41f91ae541/argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f", size = 28158, upload-time = "2025-07-30T10:01:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/b3/80/0106a7448abb24a2c467bf7d527fe5413b7fdfa4ad6d6a96a43a62ef3988/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6", size = 32597, upload-time = "2025-07-30T10:01:49.112Z" }, + { url = "https://files.pythonhosted.org/packages/05/b8/d663c9caea07e9180b2cb662772865230715cbd573ba3b5e81793d580316/argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623", size = 28231, upload-time = "2025-07-30T10:01:49.92Z" }, + { url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" }, + { url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" }, + { url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" }, + { url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" }, + { url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" }, + { url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" }, +] + +[[package]] +name = "arrow" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/a5/8e3f9b6771b0b408517c82d97aed8f2036509bc247d46114925e32fe33f0/asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7", size = 62308, upload-time = "2025-11-15T16:43:48.578Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a", size = 27047, upload-time = "2025-11-15T16:43:16.109Z" }, +] + +[[package]] +name = "async-lru" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/c3/bbf34f15ea88dfb649ab2c40f9d75081784a50573a9ea431563cab64adb8/async_lru-2.1.0.tar.gz", hash = "sha256:9eeb2fecd3fe42cc8a787fc32ead53a3a7158cc43d039c3c55ab3e4e5b2a80ed", size = 12041, upload-time = "2026-01-17T22:52:18.931Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/e9/eb6a5db5ac505d5d45715388e92bced7a5bb556facc4d0865d192823f2d2/async_lru-2.1.0-py3-none-any.whl", hash = "sha256:fa12dcf99a42ac1280bc16c634bbaf06883809790f6304d85cdab3f666f33a7e", size = 6933, upload-time = "2026-01-17T22:52:17.389Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "azure-core" +version = "1.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/1b/e503e08e755ea94e7d3419c9242315f888fc664211c90d032e40479022bf/azure_core-1.38.0.tar.gz", hash = "sha256:8194d2682245a3e4e3151a667c686464c3786fed7918b394d035bdcd61bb5993", size = 363033, upload-time = "2026-01-12T17:03:05.535Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/d8/b8fcba9464f02b121f39de2db2bf57f0b216fe11d014513d666e8634380d/azure_core-1.38.0-py3-none-any.whl", hash = "sha256:ab0c9b2cd71fecb1842d52c965c95285d3cfb38902f6766e4a471f1cd8905335", size = 217825, upload-time = "2026-01-12T17:03:07.291Z" }, +] + +[[package]] +name = "azure-identity" +version = "1.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "msal" }, + { name = "msal-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/8d/1a6c41c28a37eab26dc85ab6c86992c700cd3f4a597d9ed174b0e9c69489/azure_identity-1.25.1.tar.gz", hash = "sha256:87ca8328883de6036443e1c37b40e8dc8fb74898240f61071e09d2e369361456", size = 279826, upload-time = "2025-10-06T20:30:02.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/7b/5652771e24fff12da9dde4c20ecf4682e606b104f26419d139758cc935a6/azure_identity-1.25.1-py3-none-any.whl", hash = "sha256:e9edd720af03dff020223cd269fa3a61e8f345ea75443858273bcb44844ab651", size = 191317, upload-time = "2025-10-06T20:30:04.251Z" }, +] + +[[package]] +name = "azure-keyvault-secrets" +version = "4.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "isodate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/e5/3074e581b6e8923c4a1f2e42192ea6f390bb52de3600c68baaaed529ef05/azure_keyvault_secrets-4.10.0.tar.gz", hash = "sha256:666fa42892f9cee749563e551a90f060435ab878977c95265173a8246d546a36", size = 129695, upload-time = "2025-06-16T22:52:20.986Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/94/7c902e966b28e7cb5080a8e0dd6bffc22ba44bc907f09c4c633d2b7c4f6a/azure_keyvault_secrets-4.10.0-py3-none-any.whl", hash = "sha256:9dbde256077a4ee1a847646671580692e3f9bea36bcfc189c3cf2b9a94eb38b9", size = 125237, upload-time = "2025-06-16T22:52:22.489Z" }, +] + +[[package]] +name = "azure-kusto-data" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "azure-identity" }, + { name = "ijson" }, + { name = "msal" }, + { name = "python-dateutil" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6b/96/6a851a4bd84402d6de2d4ab7fc5ca9f199001e51def3f754d4d79f1f8d26/azure_kusto_data-6.0.1.tar.gz", hash = "sha256:1d5e04d273376330b58d6d11b055aeadda748cd1ecee1117fdc1b8329e76cb75", size = 39891, upload-time = "2025-12-28T07:21:10.183Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/35/919cc35773a28950c5d2df9e07260eebb43d9a993aea9b28d2921fc7dfd2/azure_kusto_data-6.0.1-py3-none-any.whl", hash = "sha256:8d4e7adbe122ea08d5f0053ec37f294171ff734e8d77f36983a29694485bc347", size = 51836, upload-time = "2025-12-28T07:21:07.459Z" }, +] + +[[package]] +name = "azure-storage-blob" +version = "12.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "isodate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/24/072ba8e27b0e2d8fec401e9969b429d4f5fc4c8d4f0f05f4661e11f7234a/azure_storage_blob-12.28.0.tar.gz", hash = "sha256:e7d98ea108258d29aa0efbfd591b2e2075fa1722a2fae8699f0b3c9de11eff41", size = 604225, upload-time = "2026-01-06T23:48:57.282Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d8/3a/6ef2047a072e54e1142718d433d50e9514c999a58f51abfff7902f3a72f8/azure_storage_blob-12.28.0-py3-none-any.whl", hash = "sha256:00fb1db28bf6a7b7ecaa48e3b1d5c83bfadacc5a678b77826081304bd87d6461", size = 431499, upload-time = "2026-01-06T23:48:58.995Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b0/1c6a16426d389813b48d95e26898aff79abbde42ad353958ad95cc8c9b21/beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86", size = 627737, upload-time = "2025-11-30T15:08:26.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb", size = 107721, upload-time = "2025-11-30T15:08:24.087Z" }, +] + +[[package]] +name = "bleach" +version = "6.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + +[[package]] +name = "boto3" +version = "1.42.39" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/ea/b96c77da49fed28744ee0347374d8223994a2b8570e76e8380a4064a8c4a/boto3-1.42.39.tar.gz", hash = "sha256:d03f82363314759eff7f84a27b9e6428125f89d8119e4588e8c2c1d79892c956", size = 112783, upload-time = "2026-01-30T20:38:31.226Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/c4/3493b5c86e32d6dd558b30d16b55503e24a6e6cd7115714bc102b247d26e/boto3-1.42.39-py3-none-any.whl", hash = "sha256:d9d6ce11df309707b490d2f5f785b761cfddfd6d1f665385b78c9d8ed097184b", size = 140606, upload-time = "2026-01-30T20:38:28.635Z" }, +] + +[[package]] +name = "botocore" +version = "1.42.39" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/a6/3a34d1b74effc0f759f5ff4e91c77729d932bc34dd3207905e9ecbba1103/botocore-1.42.39.tar.gz", hash = "sha256:0f00355050821e91a5fe6d932f7bf220f337249b752899e3e4cf6ed54326249e", size = 14914927, upload-time = "2026-01-30T20:38:19.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/71/9a2c88abb5fe47b46168b262254d5b5d635de371eba4bd01ea5c8c109575/botocore-1.42.39-py3-none-any.whl", hash = "sha256:9e0d0fed9226449cc26fcf2bbffc0392ac698dd8378e8395ce54f3ec13f81d58", size = 14591958, upload-time = "2026-01-30T20:38:14.814Z" }, +] + +[[package]] +name = "build" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "os_name == 'nt'" }, + { name = "packaging" }, + { name = "pyproject-hooks" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/18/94eaffda7b329535d91f00fe605ab1f1e5cd68b2074d03f255c7d250687d/build-1.4.0.tar.gz", hash = "sha256:f1b91b925aa322be454f8330c6fb48b465da993d1e7e7e6fa35027ec49f3c936", size = 50054, upload-time = "2026-01-08T16:41:47.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl", hash = "sha256:6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596", size = 24141, upload-time = "2026-01-08T16:41:46.453Z" }, +] + +[[package]] +name = "certifi" +version = "2026.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "comm" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/19/f748958276519adf6a0c1e79e7b8860b4830dda55ccdf29f2719b5fc499c/cryptography-46.0.4.tar.gz", hash = "sha256:bfd019f60f8abc2ed1b9be4ddc21cfef059c841d86d710bb69909a688cbb8f59", size = 749301, upload-time = "2026-01-28T00:24:37.379Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/99/157aae7949a5f30d51fcb1a9851e8ebd5c74bf99b5285d8bb4b8b9ee641e/cryptography-46.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:281526e865ed4166009e235afadf3a4c4cba6056f99336a99efba65336fd5485", size = 7173686, upload-time = "2026-01-28T00:23:07.515Z" }, + { url = "https://files.pythonhosted.org/packages/87/91/874b8910903159043b5c6a123b7e79c4559ddd1896e38967567942635778/cryptography-46.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f14fba5bf6f4390d7ff8f086c566454bff0411f6d8aa7af79c88b6f9267aecc", size = 4275871, upload-time = "2026-01-28T00:23:09.439Z" }, + { url = "https://files.pythonhosted.org/packages/c0/35/690e809be77896111f5b195ede56e4b4ed0435b428c2f2b6d35046fbb5e8/cryptography-46.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47bcd19517e6389132f76e2d5303ded6cf3f78903da2158a671be8de024f4cd0", size = 4423124, upload-time = "2026-01-28T00:23:11.529Z" }, + { url = "https://files.pythonhosted.org/packages/1a/5b/a26407d4f79d61ca4bebaa9213feafdd8806dc69d3d290ce24996d3cfe43/cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:01df4f50f314fbe7009f54046e908d1754f19d0c6d3070df1e6268c5a4af09fa", size = 4277090, upload-time = "2026-01-28T00:23:13.123Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d8/4bb7aec442a9049827aa34cee1aa83803e528fa55da9a9d45d01d1bb933e/cryptography-46.0.4-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5aa3e463596b0087b3da0dbe2b2487e9fc261d25da85754e30e3b40637d61f81", size = 4947652, upload-time = "2026-01-28T00:23:14.554Z" }, + { url = "https://files.pythonhosted.org/packages/2b/08/f83e2e0814248b844265802d081f2fac2f1cbe6cd258e72ba14ff006823a/cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0a9ad24359fee86f131836a9ac3bffc9329e956624a2d379b613f8f8abaf5255", size = 4455157, upload-time = "2026-01-28T00:23:16.443Z" }, + { url = "https://files.pythonhosted.org/packages/0a/05/19d849cf4096448779d2dcc9bb27d097457dac36f7273ffa875a93b5884c/cryptography-46.0.4-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:dc1272e25ef673efe72f2096e92ae39dea1a1a450dd44918b15351f72c5a168e", size = 3981078, upload-time = "2026-01-28T00:23:17.838Z" }, + { url = "https://files.pythonhosted.org/packages/e6/89/f7bac81d66ba7cde867a743ea5b37537b32b5c633c473002b26a226f703f/cryptography-46.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:de0f5f4ec8711ebc555f54735d4c673fc34b65c44283895f1a08c2b49d2fd99c", size = 4276213, upload-time = "2026-01-28T00:23:19.257Z" }, + { url = "https://files.pythonhosted.org/packages/da/9f/7133e41f24edd827020ad21b068736e792bc68eecf66d93c924ad4719fb3/cryptography-46.0.4-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:eeeb2e33d8dbcccc34d64651f00a98cb41b2dc69cef866771a5717e6734dfa32", size = 4912190, upload-time = "2026-01-28T00:23:21.244Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f7/6d43cbaddf6f65b24816e4af187d211f0bc536a29961f69faedc48501d8e/cryptography-46.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3d425eacbc9aceafd2cb429e42f4e5d5633c6f873f5e567077043ef1b9bbf616", size = 4454641, upload-time = "2026-01-28T00:23:22.866Z" }, + { url = "https://files.pythonhosted.org/packages/9e/4f/ebd0473ad656a0ac912a16bd07db0f5d85184924e14fc88feecae2492834/cryptography-46.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91627ebf691d1ea3976a031b61fb7bac1ccd745afa03602275dda443e11c8de0", size = 4405159, upload-time = "2026-01-28T00:23:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d1/f7/7923886f32dc47e27adeff8246e976d77258fd2aa3efdd1754e4e323bf49/cryptography-46.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2d08bc22efd73e8854b0b7caff402d735b354862f1145d7be3b9c0f740fef6a0", size = 4666059, upload-time = "2026-01-28T00:23:26.766Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a7/0fca0fd3591dffc297278a61813d7f661a14243dd60f499a7a5b48acb52a/cryptography-46.0.4-cp311-abi3-win32.whl", hash = "sha256:82a62483daf20b8134f6e92898da70d04d0ef9a75829d732ea1018678185f4f5", size = 3026378, upload-time = "2026-01-28T00:23:28.317Z" }, + { url = "https://files.pythonhosted.org/packages/2d/12/652c84b6f9873f0909374864a57b003686c642ea48c84d6c7e2c515e6da5/cryptography-46.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:6225d3ebe26a55dbc8ead5ad1265c0403552a63336499564675b29eb3184c09b", size = 3478614, upload-time = "2026-01-28T00:23:30.275Z" }, + { url = "https://files.pythonhosted.org/packages/b9/27/542b029f293a5cce59349d799d4d8484b3b1654a7b9a0585c266e974a488/cryptography-46.0.4-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:485e2b65d25ec0d901bca7bcae0f53b00133bf3173916d8e421f6fddde103908", size = 7116417, upload-time = "2026-01-28T00:23:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f5/559c25b77f40b6bf828eabaf988efb8b0e17b573545edb503368ca0a2a03/cryptography-46.0.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:078e5f06bd2fa5aea5a324f2a09f914b1484f1d0c2a4d6a8a28c74e72f65f2da", size = 4264508, upload-time = "2026-01-28T00:23:34.264Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/551fa162d33074b660dc35c9bc3616fefa21a0e8c1edd27b92559902e408/cryptography-46.0.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dce1e4f068f03008da7fa51cc7abc6ddc5e5de3e3d1550334eaf8393982a5829", size = 4409080, upload-time = "2026-01-28T00:23:35.793Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6a/4d8d129a755f5d6df1bbee69ea2f35ebfa954fa1847690d1db2e8bca46a5/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:2067461c80271f422ee7bdbe79b9b4be54a5162e90345f86a23445a0cf3fd8a2", size = 4270039, upload-time = "2026-01-28T00:23:37.263Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f5/ed3fcddd0a5e39321e595e144615399e47e7c153a1fb8c4862aec3151ff9/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:c92010b58a51196a5f41c3795190203ac52edfd5dc3ff99149b4659eba9d2085", size = 4926748, upload-time = "2026-01-28T00:23:38.884Z" }, + { url = "https://files.pythonhosted.org/packages/43/ae/9f03d5f0c0c00e85ecb34f06d3b79599f20630e4db91b8a6e56e8f83d410/cryptography-46.0.4-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:829c2b12bbc5428ab02d6b7f7e9bbfd53e33efd6672d21341f2177470171ad8b", size = 4442307, upload-time = "2026-01-28T00:23:40.56Z" }, + { url = "https://files.pythonhosted.org/packages/8b/22/e0f9f2dae8040695103369cf2283ef9ac8abe4d51f68710bec2afd232609/cryptography-46.0.4-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:62217ba44bf81b30abaeda1488686a04a702a261e26f87db51ff61d9d3510abd", size = 3959253, upload-time = "2026-01-28T00:23:42.827Z" }, + { url = "https://files.pythonhosted.org/packages/01/5b/6a43fcccc51dae4d101ac7d378a8724d1ba3de628a24e11bf2f4f43cba4d/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:9c2da296c8d3415b93e6053f5a728649a87a48ce084a9aaf51d6e46c87c7f2d2", size = 4269372, upload-time = "2026-01-28T00:23:44.655Z" }, + { url = "https://files.pythonhosted.org/packages/17/b7/0f6b8c1dd0779df2b526e78978ff00462355e31c0a6f6cff8a3e99889c90/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:9b34d8ba84454641a6bf4d6762d15847ecbd85c1316c0a7984e6e4e9f748ec2e", size = 4891908, upload-time = "2026-01-28T00:23:46.48Z" }, + { url = "https://files.pythonhosted.org/packages/83/17/259409b8349aa10535358807a472c6a695cf84f106022268d31cea2b6c97/cryptography-46.0.4-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:df4a817fa7138dd0c96c8c8c20f04b8aaa1fac3bbf610913dcad8ea82e1bfd3f", size = 4441254, upload-time = "2026-01-28T00:23:48.403Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fe/e4a1b0c989b00cee5ffa0764401767e2d1cf59f45530963b894129fd5dce/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b1de0ebf7587f28f9190b9cb526e901bf448c9e6a99655d2b07fff60e8212a82", size = 4396520, upload-time = "2026-01-28T00:23:50.26Z" }, + { url = "https://files.pythonhosted.org/packages/b3/81/ba8fd9657d27076eb40d6a2f941b23429a3c3d2f56f5a921d6b936a27bc9/cryptography-46.0.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9b4d17bc7bd7cdd98e3af40b441feaea4c68225e2eb2341026c84511ad246c0c", size = 4651479, upload-time = "2026-01-28T00:23:51.674Z" }, + { url = "https://files.pythonhosted.org/packages/00/03/0de4ed43c71c31e4fe954edd50b9d28d658fef56555eba7641696370a8e2/cryptography-46.0.4-cp314-cp314t-win32.whl", hash = "sha256:c411f16275b0dea722d76544a61d6421e2cc829ad76eec79280dbdc9ddf50061", size = 3001986, upload-time = "2026-01-28T00:23:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/5c/70/81830b59df7682917d7a10f833c4dab2a5574cd664e86d18139f2b421329/cryptography-46.0.4-cp314-cp314t-win_amd64.whl", hash = "sha256:728fedc529efc1439eb6107b677f7f7558adab4553ef8669f0d02d42d7b959a7", size = 3468288, upload-time = "2026-01-28T00:23:55.09Z" }, + { url = "https://files.pythonhosted.org/packages/56/f7/f648fdbb61d0d45902d3f374217451385edc7e7768d1b03ff1d0e5ffc17b/cryptography-46.0.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a9556ba711f7c23f77b151d5798f3ac44a13455cc68db7697a1096e6d0563cab", size = 7169583, upload-time = "2026-01-28T00:23:56.558Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cc/8f3224cbb2a928de7298d6ed4790f5ebc48114e02bdc9559196bfb12435d/cryptography-46.0.4-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8bf75b0259e87fa70bddc0b8b4078b76e7fd512fd9afae6c1193bcf440a4dbef", size = 4275419, upload-time = "2026-01-28T00:23:58.364Z" }, + { url = "https://files.pythonhosted.org/packages/17/43/4a18faa7a872d00e4264855134ba82d23546c850a70ff209e04ee200e76f/cryptography-46.0.4-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c268a3490df22270955966ba236d6bc4a8f9b6e4ffddb78aac535f1a5ea471d", size = 4419058, upload-time = "2026-01-28T00:23:59.867Z" }, + { url = "https://files.pythonhosted.org/packages/ee/64/6651969409821d791ba12346a124f55e1b76f66a819254ae840a965d4b9c/cryptography-46.0.4-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:812815182f6a0c1d49a37893a303b44eaac827d7f0d582cecfc81b6427f22973", size = 4278151, upload-time = "2026-01-28T00:24:01.731Z" }, + { url = "https://files.pythonhosted.org/packages/20/0b/a7fce65ee08c3c02f7a8310cc090a732344066b990ac63a9dfd0a655d321/cryptography-46.0.4-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a90e43e3ef65e6dcf969dfe3bb40cbf5aef0d523dff95bfa24256be172a845f4", size = 4939441, upload-time = "2026-01-28T00:24:03.175Z" }, + { url = "https://files.pythonhosted.org/packages/db/a7/20c5701e2cd3e1dfd7a19d2290c522a5f435dd30957d431dcb531d0f1413/cryptography-46.0.4-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a05177ff6296644ef2876fce50518dffb5bcdf903c85250974fc8bc85d54c0af", size = 4451617, upload-time = "2026-01-28T00:24:05.403Z" }, + { url = "https://files.pythonhosted.org/packages/00/dc/3e16030ea9aa47b63af6524c354933b4fb0e352257c792c4deeb0edae367/cryptography-46.0.4-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:daa392191f626d50f1b136c9b4cf08af69ca8279d110ea24f5c2700054d2e263", size = 3977774, upload-time = "2026-01-28T00:24:06.851Z" }, + { url = "https://files.pythonhosted.org/packages/42/c8/ad93f14118252717b465880368721c963975ac4b941b7ef88f3c56bf2897/cryptography-46.0.4-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e07ea39c5b048e085f15923511d8121e4a9dc45cee4e3b970ca4f0d338f23095", size = 4277008, upload-time = "2026-01-28T00:24:08.926Z" }, + { url = "https://files.pythonhosted.org/packages/00/cf/89c99698151c00a4631fbfcfcf459d308213ac29e321b0ff44ceeeac82f1/cryptography-46.0.4-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d5a45ddc256f492ce42a4e35879c5e5528c09cd9ad12420828c972951d8e016b", size = 4903339, upload-time = "2026-01-28T00:24:12.009Z" }, + { url = "https://files.pythonhosted.org/packages/03/c3/c90a2cb358de4ac9309b26acf49b2a100957e1ff5cc1e98e6c4996576710/cryptography-46.0.4-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:6bb5157bf6a350e5b28aee23beb2d84ae6f5be390b2f8ee7ea179cda077e1019", size = 4451216, upload-time = "2026-01-28T00:24:13.975Z" }, + { url = "https://files.pythonhosted.org/packages/96/2c/8d7f4171388a10208671e181ca43cdc0e596d8259ebacbbcfbd16de593da/cryptography-46.0.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd5aba870a2c40f87a3af043e0dee7d9eb02d4aff88a797b48f2b43eff8c3ab4", size = 4404299, upload-time = "2026-01-28T00:24:16.169Z" }, + { url = "https://files.pythonhosted.org/packages/e9/23/cbb2036e450980f65c6e0a173b73a56ff3bccd8998965dea5cc9ddd424a5/cryptography-46.0.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:93d8291da8d71024379ab2cb0b5c57915300155ad42e07f76bea6ad838d7e59b", size = 4664837, upload-time = "2026-01-28T00:24:17.629Z" }, + { url = "https://files.pythonhosted.org/packages/0a/21/f7433d18fe6d5845329cbdc597e30caf983229c7a245bcf54afecc555938/cryptography-46.0.4-cp38-abi3-win32.whl", hash = "sha256:0563655cb3c6d05fb2afe693340bc050c30f9f34e15763361cf08e94749401fc", size = 3009779, upload-time = "2026-01-28T00:24:20.198Z" }, + { url = "https://files.pythonhosted.org/packages/3a/6a/bd2e7caa2facffedf172a45c1a02e551e6d7d4828658c9a245516a598d94/cryptography-46.0.4-cp38-abi3-win_amd64.whl", hash = "sha256:fa0900b9ef9c49728887d1576fd8d9e7e3ea872fa9b25ef9b64888adc434e976", size = 3466633, upload-time = "2026-01-28T00:24:21.851Z" }, + { url = "https://files.pythonhosted.org/packages/59/e0/f9c6c53e1f2a1c2507f00f2faba00f01d2f334b35b0fbfe5286715da2184/cryptography-46.0.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:766330cce7416c92b5e90c3bb71b1b79521760cdcfc3a6a1a182d4c9fab23d2b", size = 3476316, upload-time = "2026-01-28T00:24:24.144Z" }, + { url = "https://files.pythonhosted.org/packages/27/7a/f8d2d13227a9a1a9fe9c7442b057efecffa41f1e3c51d8622f26b9edbe8f/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c236a44acfb610e70f6b3e1c3ca20ff24459659231ef2f8c48e879e2d32b73da", size = 4216693, upload-time = "2026-01-28T00:24:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/c5/de/3787054e8f7972658370198753835d9d680f6cd4a39df9f877b57f0dd69c/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8a15fb869670efa8f83cbffbc8753c1abf236883225aed74cd179b720ac9ec80", size = 4382765, upload-time = "2026-01-28T00:24:27.577Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5f/60e0afb019973ba6a0b322e86b3d61edf487a4f5597618a430a2a15f2d22/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:fdc3daab53b212472f1524d070735b2f0c214239df131903bae1d598016fa822", size = 4216066, upload-time = "2026-01-28T00:24:29.056Z" }, + { url = "https://files.pythonhosted.org/packages/81/8e/bf4a0de294f147fee66f879d9bae6f8e8d61515558e3d12785dd90eca0be/cryptography-46.0.4-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:44cc0675b27cadb71bdbb96099cca1fa051cd11d2ade09e5cd3a2edb929ed947", size = 4382025, upload-time = "2026-01-28T00:24:30.681Z" }, + { url = "https://files.pythonhosted.org/packages/79/f4/9ceb90cfd6a3847069b0b0b353fd3075dc69b49defc70182d8af0c4ca390/cryptography-46.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be8c01a7d5a55f9a47d1888162b76c8f49d62b234d88f0ff91a9fbebe32ffbc3", size = 3406043, upload-time = "2026-01-28T00:24:32.236Z" }, +] + +[[package]] +name = "curl-cffi" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/3d/f39ca1f8fdf14408888e7c25e15eed63eac5f47926e206fb93300d28378c/curl_cffi-0.13.0.tar.gz", hash = "sha256:62ecd90a382bd5023750e3606e0aa7cb1a3a8ba41c14270b8e5e149ebf72c5ca", size = 151303, upload-time = "2025-08-06T13:05:42.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/d1/acabfd460f1de26cad882e5ef344d9adde1507034528cb6f5698a2e6a2f1/curl_cffi-0.13.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:434cadbe8df2f08b2fc2c16dff2779fb40b984af99c06aa700af898e185bb9db", size = 5686337, upload-time = "2025-08-06T13:05:28.985Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1c/cdb4fb2d16a0e9de068e0e5bc02094e105ce58a687ff30b4c6f88e25a057/curl_cffi-0.13.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:59afa877a9ae09efa04646a7d068eeea48915a95d9add0a29854e7781679fcd7", size = 2994613, upload-time = "2025-08-06T13:05:31.027Z" }, + { url = "https://files.pythonhosted.org/packages/04/3e/fdf617c1ec18c3038b77065d484d7517bb30f8fb8847224eb1f601a4e8bc/curl_cffi-0.13.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d06ed389e45a7ca97b17c275dbedd3d6524560270e675c720e93a2018a766076", size = 7931353, upload-time = "2025-08-06T13:05:32.273Z" }, + { url = "https://files.pythonhosted.org/packages/3d/10/6f30c05d251cf03ddc2b9fd19880f3cab8c193255e733444a2df03b18944/curl_cffi-0.13.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4e0de45ab3b7a835c72bd53640c2347415111b43421b5c7a1a0b18deae2e541", size = 7486378, upload-time = "2025-08-06T13:05:33.672Z" }, + { url = "https://files.pythonhosted.org/packages/77/81/5bdb7dd0d669a817397b2e92193559bf66c3807f5848a48ad10cf02bf6c7/curl_cffi-0.13.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8eb4083371bbb94e9470d782de235fb5268bf43520de020c9e5e6be8f395443f", size = 8328585, upload-time = "2025-08-06T13:05:35.28Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c1/df5c6b4cfad41c08442e0f727e449f4fb5a05f8aa564d1acac29062e9e8e/curl_cffi-0.13.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:28911b526e8cd4aa0e5e38401bfe6887e8093907272f1f67ca22e6beb2933a51", size = 8739831, upload-time = "2025-08-06T13:05:37.078Z" }, + { url = "https://files.pythonhosted.org/packages/1a/91/6dd1910a212f2e8eafe57877bcf97748eb24849e1511a266687546066b8a/curl_cffi-0.13.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6d433ffcb455ab01dd0d7bde47109083aa38b59863aa183d29c668ae4c96bf8e", size = 8711908, upload-time = "2025-08-06T13:05:38.741Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e4/15a253f9b4bf8d008c31e176c162d2704a7e0c5e24d35942f759df107b68/curl_cffi-0.13.0-cp39-abi3-win_amd64.whl", hash = "sha256:66a6b75ce971de9af64f1b6812e275f60b88880577bac47ef1fa19694fa21cd3", size = 1614510, upload-time = "2025-08-06T13:05:40.451Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0f/9c5275f17ad6ff5be70edb8e0120fdc184a658c9577ca426d4230f654beb/curl_cffi-0.13.0-cp39-abi3-win_arm64.whl", hash = "sha256:d438a3b45244e874794bc4081dc1e356d2bb926dcc7021e5a8fef2e2105ef1d8", size = 1365753, upload-time = "2025-08-06T13:05:41.879Z" }, +] + +[[package]] +name = "data-formulator" +version = "0.6" +source = { editable = "." } +dependencies = [ + { name = "azure-identity" }, + { name = "azure-keyvault-secrets" }, + { name = "azure-kusto-data" }, + { name = "azure-storage-blob" }, + { name = "backoff" }, + { name = "beautifulsoup4" }, + { name = "boto3" }, + { name = "db-dtypes" }, + { name = "duckdb" }, + { name = "flask" }, + { name = "flask-cors" }, + { name = "flask-limiter" }, + { name = "google-auth" }, + { name = "google-cloud-bigquery" }, + { name = "jupyter" }, + { name = "litellm" }, + { name = "numpy" }, + { name = "openai" }, + { name = "pandas" }, + { name = "pymongo" }, + { name = "pymysql" }, + { name = "pyodbc" }, + { name = "python-dotenv" }, + { name = "scikit-learn" }, + { name = "vega-datasets" }, + { name = "vl-convert-python" }, + { name = "yfinance" }, +] + +[package.dev-dependencies] +dev = [ + { name = "build" }, +] + +[package.metadata] +requires-dist = [ + { name = "azure-identity" }, + { name = "azure-keyvault-secrets" }, + { name = "azure-kusto-data" }, + { name = "azure-storage-blob" }, + { name = "backoff" }, + { name = "beautifulsoup4" }, + { name = "boto3" }, + { name = "db-dtypes" }, + { name = "duckdb" }, + { name = "flask" }, + { name = "flask-cors" }, + { name = "flask-limiter" }, + { name = "google-auth" }, + { name = "google-cloud-bigquery" }, + { name = "jupyter" }, + { name = "litellm" }, + { name = "numpy" }, + { name = "openai" }, + { name = "pandas" }, + { name = "pymongo" }, + { name = "pymysql" }, + { name = "pyodbc" }, + { name = "python-dotenv" }, + { name = "scikit-learn" }, + { name = "vega-datasets" }, + { name = "vl-convert-python" }, + { name = "yfinance" }, +] + +[package.metadata.requires-dev] +dev = [{ name = "build" }] + +[[package]] +name = "db-dtypes" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/3d/8c021df7796bde3bc4a9f8129865676a45d95ba8c5fd14b1de82997ff0b4/db_dtypes-1.5.0.tar.gz", hash = "sha256:ad9e94243f53e104bc77dbf9ae44b580d83a770d3694483aba59c9767966daa5", size = 34800, upload-time = "2025-12-15T21:47:21.874Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/80/171c7c5b78e60ab25d6f11e3d38675fe7ef843ddc79a7fd26916d3a6ca05/db_dtypes-1.5.0-py3-none-any.whl", hash = "sha256:abdbb2e4eb965800ed6f98af0c5c1cafff9063ace09114be2d26a7f046be2c8a", size = 18267, upload-time = "2025-12-15T21:47:21.026Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/b7/cd8080344452e4874aae67c40d8940e2b4d47b01601a8fd9f44786c757c7/debugpy-1.8.20.tar.gz", hash = "sha256:55bc8701714969f1ab89a6d5f2f3d40c36f91b2cbe2f65d98bf8196f6a6a2c33", size = 1645207, upload-time = "2026-01-29T23:03:28.199Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/56/c3baf5cbe4dd77427fd9aef99fcdade259ad128feeb8a786c246adb838e5/debugpy-1.8.20-cp311-cp311-macosx_15_0_universal2.whl", hash = "sha256:eada6042ad88fa1571b74bd5402ee8b86eded7a8f7b827849761700aff171f1b", size = 2208318, upload-time = "2026-01-29T23:03:36.481Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/4fa79a57a8e69fe0d9763e98d1110320f9ecd7f1f362572e3aafd7417c9d/debugpy-1.8.20-cp311-cp311-manylinux_2_34_x86_64.whl", hash = "sha256:7de0b7dfeedc504421032afba845ae2a7bcc32ddfb07dae2c3ca5442f821c344", size = 3171493, upload-time = "2026-01-29T23:03:37.775Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f2/1e8f8affe51e12a26f3a8a8a4277d6e60aa89d0a66512f63b1e799d424a4/debugpy-1.8.20-cp311-cp311-win32.whl", hash = "sha256:773e839380cf459caf73cc533ea45ec2737a5cc184cf1b3b796cd4fd98504fec", size = 5209240, upload-time = "2026-01-29T23:03:39.109Z" }, + { url = "https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl", hash = "sha256:1f7650546e0eded1902d0f6af28f787fa1f1dbdbc97ddabaf1cd963a405930cb", size = 5233481, upload-time = "2026-01-29T23:03:40.659Z" }, + { url = "https://files.pythonhosted.org/packages/14/57/7f34f4736bfb6e00f2e4c96351b07805d83c9a7b33d28580ae01374430f7/debugpy-1.8.20-cp312-cp312-macosx_15_0_universal2.whl", hash = "sha256:4ae3135e2089905a916909ef31922b2d733d756f66d87345b3e5e52b7a55f13d", size = 2550686, upload-time = "2026-01-29T23:03:42.023Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/b193a3975ca34458f6f0e24aaf5c3e3da72f5401f6054c0dfd004b41726f/debugpy-1.8.20-cp312-cp312-manylinux_2_34_x86_64.whl", hash = "sha256:88f47850a4284b88bd2bfee1f26132147d5d504e4e86c22485dfa44b97e19b4b", size = 4310588, upload-time = "2026-01-29T23:03:43.314Z" }, + { url = "https://files.pythonhosted.org/packages/c1/55/f14deb95eaf4f30f07ef4b90a8590fc05d9e04df85ee379712f6fb6736d7/debugpy-1.8.20-cp312-cp312-win32.whl", hash = "sha256:4057ac68f892064e5f98209ab582abfee3b543fb55d2e87610ddc133a954d390", size = 5331372, upload-time = "2026-01-29T23:03:45.526Z" }, + { url = "https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl", hash = "sha256:a1a8f851e7cf171330679ef6997e9c579ef6dd33c9098458bd9986a0f4ca52e3", size = 5372835, upload-time = "2026-01-29T23:03:47.245Z" }, + { url = "https://files.pythonhosted.org/packages/15/e2/fc500524cc6f104a9d049abc85a0a8b3f0d14c0a39b9c140511c61e5b40b/debugpy-1.8.20-cp313-cp313-macosx_15_0_universal2.whl", hash = "sha256:5dff4bb27027821fdfcc9e8f87309a28988231165147c31730128b1c983e282a", size = 2539560, upload-time = "2026-01-29T23:03:48.738Z" }, + { url = "https://files.pythonhosted.org/packages/90/83/fb33dcea789ed6018f8da20c5a9bc9d82adc65c0c990faed43f7c955da46/debugpy-1.8.20-cp313-cp313-manylinux_2_34_x86_64.whl", hash = "sha256:84562982dd7cf5ebebfdea667ca20a064e096099997b175fe204e86817f64eaf", size = 4293272, upload-time = "2026-01-29T23:03:50.169Z" }, + { url = "https://files.pythonhosted.org/packages/a6/25/b1e4a01bfb824d79a6af24b99ef291e24189080c93576dfd9b1a2815cd0f/debugpy-1.8.20-cp313-cp313-win32.whl", hash = "sha256:da11dea6447b2cadbf8ce2bec59ecea87cc18d2c574980f643f2d2dfe4862393", size = 5331208, upload-time = "2026-01-29T23:03:51.547Z" }, + { url = "https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl", hash = "sha256:eb506e45943cab2efb7c6eafdd65b842f3ae779f020c82221f55aca9de135ed7", size = 5372930, upload-time = "2026-01-29T23:03:53.585Z" }, + { url = "https://files.pythonhosted.org/packages/33/2e/f6cb9a8a13f5058f0a20fe09711a7b726232cd5a78c6a7c05b2ec726cff9/debugpy-1.8.20-cp314-cp314-macosx_15_0_universal2.whl", hash = "sha256:9c74df62fc064cd5e5eaca1353a3ef5a5d50da5eb8058fcef63106f7bebe6173", size = 2538066, upload-time = "2026-01-29T23:03:54.999Z" }, + { url = "https://files.pythonhosted.org/packages/c5/56/6ddca50b53624e1ca3ce1d1e49ff22db46c47ea5fb4c0cc5c9b90a616364/debugpy-1.8.20-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:077a7447589ee9bc1ff0cdf443566d0ecf540ac8aa7333b775ebcb8ce9f4ecad", size = 4269425, upload-time = "2026-01-29T23:03:56.518Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d9/d64199c14a0d4c476df46c82470a3ce45c8d183a6796cfb5e66533b3663c/debugpy-1.8.20-cp314-cp314-win32.whl", hash = "sha256:352036a99dd35053b37b7803f748efc456076f929c6a895556932eaf2d23b07f", size = 5331407, upload-time = "2026-01-29T23:03:58.481Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl", hash = "sha256:a98eec61135465b062846112e5ecf2eebb855305acc1dfbae43b72903b8ab5be", size = 5372521, upload-time = "2026-01-29T23:03:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl", hash = "sha256:5be9bed9ae3be00665a06acaa48f8329d2b9632f15fd09f6a9a8c8d9907e54d7", size = 5337658, upload-time = "2026-01-29T23:04:17.404Z" }, +] + +[[package]] +name = "decorator" +version = "5.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/fa/6d96a0978d19e17b68d634497769987b16c8f4cd0a7a05048bec693caa6b/decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360", size = 56711, upload-time = "2025-02-24T04:41:34.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "deprecated" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/85/12f0a49a7c4ffb70572b6c2ef13c90c88fd190debda93b23f026b25f9634/deprecated-1.3.1.tar.gz", hash = "sha256:b1b50e0ff0c1fddaa5708a2c6b0a6588bb09b892825ab2b214ac9ea9d92a5223", size = 2932523, upload-time = "2025-10-30T08:19:02.757Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/d0/205d54408c08b13550c733c4b85429e7ead111c7f0014309637425520a9a/deprecated-1.3.1-py2.py3-none-any.whl", hash = "sha256:597bfef186b6f60181535a29fbe44865ce137a5079f295b479886c82729d5f3f", size = 11298, upload-time = "2025-10-30T08:19:00.758Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "dnspython" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, +] + +[[package]] +name = "duckdb" +version = "1.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/9d/ab66a06e416d71b7bdcb9904cdf8d4db3379ef632bb8e9495646702d9718/duckdb-1.4.4.tar.gz", hash = "sha256:8bba52fd2acb67668a4615ee17ee51814124223de836d9e2fdcbc4c9021b3d3c", size = 18419763, upload-time = "2026-01-26T11:50:37.68Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/68/19233412033a2bc5a144a3f531f64e3548d4487251e3f16b56c31411a06f/duckdb-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5ba684f498d4e924c7e8f30dd157da8da34c8479746c5011b6c0e037e9c60ad2", size = 28883816, upload-time = "2026-01-26T11:49:01.009Z" }, + { url = "https://files.pythonhosted.org/packages/b3/3e/cec70e546c298ab76d80b990109e111068d82cca67942c42328eaa7d6fdb/duckdb-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5536eb952a8aa6ae56469362e344d4e6403cc945a80bc8c5c2ebdd85d85eb64b", size = 15339662, upload-time = "2026-01-26T11:49:04.058Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f0/cf4241a040ec4f571859a738007ec773b642fbc27df4cbcf34b0c32ea559/duckdb-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:47dd4162da6a2be59a0aef640eb08d6360df1cf83c317dcc127836daaf3b7f7c", size = 13670044, upload-time = "2026-01-26T11:49:06.627Z" }, + { url = "https://files.pythonhosted.org/packages/11/64/de2bb4ec1e35ec9ebf6090a95b930fc56934a0ad6f34a24c5972a14a77ef/duckdb-1.4.4-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6cb357cfa3403910e79e2eb46c8e445bb1ee2fd62e9e9588c6b999df4256abc1", size = 18409951, upload-time = "2026-01-26T11:49:09.808Z" }, + { url = "https://files.pythonhosted.org/packages/79/a2/ac0f5ee16df890d141304bcd48733516b7202c0de34cd3555634d6eb4551/duckdb-1.4.4-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c25d5b0febda02b7944e94fdae95aecf952797afc8cb920f677b46a7c251955", size = 20411739, upload-time = "2026-01-26T11:49:12.652Z" }, + { url = "https://files.pythonhosted.org/packages/37/a2/9a3402edeedaecf72de05fe9ff7f0303d701b8dfc136aea4a4be1a5f7eee/duckdb-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:6703dd1bb650025b3771552333d305d62ddd7ff182de121483d4e042ea6e2e00", size = 12256972, upload-time = "2026-01-26T11:49:15.468Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e6/052ea6dcdf35b259fd182eff3efd8d75a071de4010c9807556098df137b9/duckdb-1.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:bf138201f56e5d6fc276a25138341b3523e2f84733613fc43f02c54465619a95", size = 13006696, upload-time = "2026-01-26T11:49:18.054Z" }, + { url = "https://files.pythonhosted.org/packages/58/33/beadaa69f8458afe466126f2c5ee48c4759cc9d5d784f8703d44e0b52c3c/duckdb-1.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ddcfd9c6ff234da603a1edd5fd8ae6107f4d042f74951b65f91bc5e2643856b3", size = 28896535, upload-time = "2026-01-26T11:49:21.232Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/82413f386df10467affc87f65bac095b7c88dbd9c767584164d5f4dc4cb8/duckdb-1.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6792ca647216bd5c4ff16396e4591cfa9b4a72e5ad7cdd312cec6d67e8431a7c", size = 15349716, upload-time = "2026-01-26T11:49:23.989Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8c/c13d396fd4e9bf970916dc5b4fea410c1b10fe531069aea65f1dcf849a71/duckdb-1.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1f8d55843cc940e36261689054f7dfb6ce35b1f5b0953b0d355b6adb654b0d52", size = 13672403, upload-time = "2026-01-26T11:49:26.741Z" }, + { url = "https://files.pythonhosted.org/packages/db/77/2446a0b44226bb95217748d911c7ca66a66ca10f6481d5178d9370819631/duckdb-1.4.4-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c65d15c440c31e06baaebfd2c06d71ce877e132779d309f1edf0a85d23c07e92", size = 18419001, upload-time = "2026-01-26T11:49:29.353Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a3/97715bba30040572fb15d02c26f36be988d48bc00501e7ac02b1d65ef9d0/duckdb-1.4.4-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b297eff642503fd435a9de5a9cb7db4eccb6f61d61a55b30d2636023f149855f", size = 20437385, upload-time = "2026-01-26T11:49:32.302Z" }, + { url = "https://files.pythonhosted.org/packages/8b/0a/18b9167adf528cbe3867ef8a84a5f19f37bedccb606a8a9e59cfea1880c8/duckdb-1.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d525de5f282b03aa8be6db86b1abffdceae5f1055113a03d5b50cd2fb8cf2ef8", size = 12267343, upload-time = "2026-01-26T11:49:34.985Z" }, + { url = "https://files.pythonhosted.org/packages/f8/15/37af97f5717818f3d82d57414299c293b321ac83e048c0a90bb8b6a09072/duckdb-1.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:50f2eb173c573811b44aba51176da7a4e5c487113982be6a6a1c37337ec5fa57", size = 13007490, upload-time = "2026-01-26T11:49:37.413Z" }, + { url = "https://files.pythonhosted.org/packages/7f/fe/64810fee20030f2bf96ce28b527060564864ce5b934b50888eda2cbf99dd/duckdb-1.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:337f8b24e89bc2e12dadcfe87b4eb1c00fd920f68ab07bc9b70960d6523b8bc3", size = 28899349, upload-time = "2026-01-26T11:49:40.294Z" }, + { url = "https://files.pythonhosted.org/packages/9c/9b/3c7c5e48456b69365d952ac201666053de2700f5b0144a699a4dc6854507/duckdb-1.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0509b39ea7af8cff0198a99d206dca753c62844adab54e545984c2e2c1381616", size = 15350691, upload-time = "2026-01-26T11:49:43.242Z" }, + { url = "https://files.pythonhosted.org/packages/a6/7b/64e68a7b857ed0340045501535a0da99ea5d9d5ea3708fec0afb8663eb27/duckdb-1.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fb94de6d023de9d79b7edc1ae07ee1d0b4f5fa8a9dcec799650b5befdf7aafec", size = 13672311, upload-time = "2026-01-26T11:49:46.069Z" }, + { url = "https://files.pythonhosted.org/packages/09/5b/3e7aa490841784d223de61beb2ae64e82331501bf5a415dc87a0e27b4663/duckdb-1.4.4-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d636ceda422e7babd5e2f7275f6a0d1a3405e6a01873f00d38b72118d30c10b", size = 18422740, upload-time = "2026-01-26T11:49:49.034Z" }, + { url = "https://files.pythonhosted.org/packages/53/32/256df3dbaa198c58539ad94f9a41e98c2c8ff23f126b8f5f52c7dcd0a738/duckdb-1.4.4-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7df7351328ffb812a4a289732f500d621e7de9942a3a2c9b6d4afcf4c0e72526", size = 20435578, upload-time = "2026-01-26T11:49:51.946Z" }, + { url = "https://files.pythonhosted.org/packages/a4/f0/620323fd87062ea43e527a2d5ed9e55b525e0847c17d3b307094ddab98a2/duckdb-1.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:6fb1225a9ea5877421481d59a6c556a9532c32c16c7ae6ca8d127e2b878c9389", size = 12268083, upload-time = "2026-01-26T11:49:54.615Z" }, + { url = "https://files.pythonhosted.org/packages/e5/07/a397fdb7c95388ba9c055b9a3d38dfee92093f4427bc6946cf9543b1d216/duckdb-1.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:f28a18cc790217e5b347bb91b2cab27aafc557c58d3d8382e04b4fe55d0c3f66", size = 13006123, upload-time = "2026-01-26T11:49:57.092Z" }, + { url = "https://files.pythonhosted.org/packages/97/a6/f19e2864e651b0bd8e4db2b0c455e7e0d71e0d4cd2cd9cc052f518e43eb3/duckdb-1.4.4-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:25874f8b1355e96178079e37312c3ba6d61a2354f51319dae860cf21335c3a20", size = 28909554, upload-time = "2026-01-26T11:50:00.107Z" }, + { url = "https://files.pythonhosted.org/packages/0e/93/8a24e932c67414fd2c45bed83218e62b73348996bf859eda020c224774b2/duckdb-1.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:452c5b5d6c349dc5d1154eb2062ee547296fcbd0c20e9df1ed00b5e1809089da", size = 15353804, upload-time = "2026-01-26T11:50:03.382Z" }, + { url = "https://files.pythonhosted.org/packages/62/13/e5378ff5bb1d4397655d840b34b642b1b23cdd82ae19599e62dc4b9461c9/duckdb-1.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8e5c2d8a0452df55e092959c0bfc8ab8897ac3ea0f754cb3b0ab3e165cd79aff", size = 13676157, upload-time = "2026-01-26T11:50:06.232Z" }, + { url = "https://files.pythonhosted.org/packages/2d/94/24364da564b27aeebe44481f15bd0197a0b535ec93f188a6b1b98c22f082/duckdb-1.4.4-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1af6e76fe8bd24875dc56dd8e38300d64dc708cd2e772f67b9fbc635cc3066a3", size = 18426882, upload-time = "2026-01-26T11:50:08.97Z" }, + { url = "https://files.pythonhosted.org/packages/26/0a/6ae31b2914b4dc34243279b2301554bcbc5f1a09ccc82600486c49ab71d1/duckdb-1.4.4-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0440f59e0cd9936a9ebfcf7a13312eda480c79214ffed3878d75947fc3b7d6d", size = 20435641, upload-time = "2026-01-26T11:50:12.188Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b1/fd5c37c53d45efe979f67e9bd49aaceef640147bb18f0699a19edd1874d6/duckdb-1.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:59c8d76016dde854beab844935b1ec31de358d4053e792988108e995b18c08e7", size = 12762360, upload-time = "2026-01-26T11:50:14.76Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2d/13e6024e613679d8a489dd922f199ef4b1d08a456a58eadd96dc2f05171f/duckdb-1.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:53cd6423136ab44383ec9955aefe7599b3fb3dd1fe006161e6396d8167e0e0d4", size = 13458633, upload-time = "2026-01-26T11:50:17.657Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/b5/23b216d9d985a956623b6bd12d4086b60f0059b27799f23016af04a74ea1/fastjsonschema-2.21.2.tar.gz", hash = "sha256:b1eb43748041c880796cd077f1a07c3d94e93ae84bba5ed36800a33554ae05de", size = 374130, upload-time = "2025-08-14T18:49:36.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/a8/20d0723294217e47de6d9e2e40fd4a9d2f7c4b6ef974babd482a59743694/fastjsonschema-2.21.2-py3-none-any.whl", hash = "sha256:1c797122d0a86c5cace2e54bf4e819c36223b552017172f32c5c024a6b77e463", size = 24024, upload-time = "2025-08-14T18:49:34.776Z" }, +] + +[[package]] +name = "fastuuid" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/7d/d9daedf0f2ebcacd20d599928f8913e9d2aea1d56d2d355a93bfa2b611d7/fastuuid-0.14.0.tar.gz", hash = "sha256:178947fc2f995b38497a74172adee64fdeb8b7ec18f2a5934d037641ba265d26", size = 18232, upload-time = "2025-10-19T22:19:22.402Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/f3/12481bda4e5b6d3e698fbf525df4443cc7dce746f246b86b6fcb2fba1844/fastuuid-0.14.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:73946cb950c8caf65127d4e9a325e2b6be0442a224fd51ba3b6ac44e1912ce34", size = 516386, upload-time = "2025-10-19T22:42:40.176Z" }, + { url = "https://files.pythonhosted.org/packages/59/19/2fc58a1446e4d72b655648eb0879b04e88ed6fa70d474efcf550f640f6ec/fastuuid-0.14.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:12ac85024637586a5b69645e7ed986f7535106ed3013640a393a03e461740cb7", size = 264569, upload-time = "2025-10-19T22:25:50.977Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/3c74756e5b02c40cfcc8b1d8b5bac4edbd532b55917a6bcc9113550e99d1/fastuuid-0.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:05a8dde1f395e0c9b4be515b7a521403d1e8349443e7641761af07c7ad1624b1", size = 254366, upload-time = "2025-10-19T22:29:49.166Z" }, + { url = "https://files.pythonhosted.org/packages/52/96/d761da3fccfa84f0f353ce6e3eb8b7f76b3aa21fd25e1b00a19f9c80a063/fastuuid-0.14.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09378a05020e3e4883dfdab438926f31fea15fd17604908f3d39cbeb22a0b4dc", size = 278978, upload-time = "2025-10-19T22:35:41.306Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c2/f84c90167cc7765cb82b3ff7808057608b21c14a38531845d933a4637307/fastuuid-0.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbb0c4b15d66b435d2538f3827f05e44e2baafcc003dd7d8472dc67807ab8fd8", size = 279692, upload-time = "2025-10-19T22:25:36.997Z" }, + { url = "https://files.pythonhosted.org/packages/af/7b/4bacd03897b88c12348e7bd77943bac32ccf80ff98100598fcff74f75f2e/fastuuid-0.14.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cd5a7f648d4365b41dbf0e38fe8da4884e57bed4e77c83598e076ac0c93995e7", size = 303384, upload-time = "2025-10-19T22:29:46.578Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a2/584f2c29641df8bd810d00c1f21d408c12e9ad0c0dafdb8b7b29e5ddf787/fastuuid-0.14.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c0a94245afae4d7af8c43b3159d5e3934c53f47140be0be624b96acd672ceb73", size = 460921, upload-time = "2025-10-19T22:36:42.006Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/c6b77443bb7764c760e211002c8638c0c7cce11cb584927e723215ba1398/fastuuid-0.14.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:2b29e23c97e77c3a9514d70ce343571e469098ac7f5a269320a0f0b3e193ab36", size = 480575, upload-time = "2025-10-19T22:28:18.975Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/93f553111b33f9bb83145be12868c3c475bf8ea87c107063d01377cc0e8e/fastuuid-0.14.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1e690d48f923c253f28151b3a6b4e335f2b06bf669c68a02665bc150b7839e94", size = 452317, upload-time = "2025-10-19T22:25:32.75Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8c/a04d486ca55b5abb7eaa65b39df8d891b7b1635b22db2163734dc273579a/fastuuid-0.14.0-cp311-cp311-win32.whl", hash = "sha256:a6f46790d59ab38c6aa0e35c681c0484b50dc0acf9e2679c005d61e019313c24", size = 154804, upload-time = "2025-10-19T22:24:15.615Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b2/2d40bf00820de94b9280366a122cbaa60090c8cf59e89ac3938cf5d75895/fastuuid-0.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:e150eab56c95dc9e3fefc234a0eedb342fac433dacc273cd4d150a5b0871e1fa", size = 156099, upload-time = "2025-10-19T22:24:31.646Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/e78fcc5df65467f0d207661b7ef86c5b7ac62eea337c0c0fcedbeee6fb13/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77e94728324b63660ebf8adb27055e92d2e4611645bf12ed9d88d30486471d0a", size = 510164, upload-time = "2025-10-19T22:31:45.635Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b3/c846f933f22f581f558ee63f81f29fa924acd971ce903dab1a9b6701816e/fastuuid-0.14.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:caa1f14d2102cb8d353096bc6ef6c13b2c81f347e6ab9d6fbd48b9dea41c153d", size = 261837, upload-time = "2025-10-19T22:38:38.53Z" }, + { url = "https://files.pythonhosted.org/packages/54/ea/682551030f8c4fa9a769d9825570ad28c0c71e30cf34020b85c1f7ee7382/fastuuid-0.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d23ef06f9e67163be38cece704170486715b177f6baae338110983f99a72c070", size = 251370, upload-time = "2025-10-19T22:40:26.07Z" }, + { url = "https://files.pythonhosted.org/packages/14/dd/5927f0a523d8e6a76b70968e6004966ee7df30322f5fc9b6cdfb0276646a/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c9ec605ace243b6dbe3bd27ebdd5d33b00d8d1d3f580b39fdd15cd96fd71796", size = 277766, upload-time = "2025-10-19T22:37:23.779Z" }, + { url = "https://files.pythonhosted.org/packages/16/6e/c0fb547eef61293153348f12e0f75a06abb322664b34a1573a7760501336/fastuuid-0.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:808527f2407f58a76c916d6aa15d58692a4a019fdf8d4c32ac7ff303b7d7af09", size = 278105, upload-time = "2025-10-19T22:26:56.821Z" }, + { url = "https://files.pythonhosted.org/packages/2d/b1/b9c75e03b768f61cf2e84ee193dc18601aeaf89a4684b20f2f0e9f52b62c/fastuuid-0.14.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2fb3c0d7fef6674bbeacdd6dbd386924a7b60b26de849266d1ff6602937675c8", size = 301564, upload-time = "2025-10-19T22:30:31.604Z" }, + { url = "https://files.pythonhosted.org/packages/fc/fa/f7395fdac07c7a54f18f801744573707321ca0cee082e638e36452355a9d/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab3f5d36e4393e628a4df337c2c039069344db5f4b9d2a3c9cea48284f1dd741", size = 459659, upload-time = "2025-10-19T22:31:32.341Z" }, + { url = "https://files.pythonhosted.org/packages/66/49/c9fd06a4a0b1f0f048aacb6599e7d96e5d6bc6fa680ed0d46bf111929d1b/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b9a0ca4f03b7e0b01425281ffd44e99d360e15c895f1907ca105854ed85e2057", size = 478430, upload-time = "2025-10-19T22:26:22.962Z" }, + { url = "https://files.pythonhosted.org/packages/be/9c/909e8c95b494e8e140e8be6165d5fc3f61fdc46198c1554df7b3e1764471/fastuuid-0.14.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3acdf655684cc09e60fb7e4cf524e8f42ea760031945aa8086c7eae2eeeabeb8", size = 450894, upload-time = "2025-10-19T22:27:01.647Z" }, + { url = "https://files.pythonhosted.org/packages/90/eb/d29d17521976e673c55ef7f210d4cdd72091a9ec6755d0fd4710d9b3c871/fastuuid-0.14.0-cp312-cp312-win32.whl", hash = "sha256:9579618be6280700ae36ac42c3efd157049fe4dd40ca49b021280481c78c3176", size = 154374, upload-time = "2025-10-19T22:29:19.879Z" }, + { url = "https://files.pythonhosted.org/packages/cc/fc/f5c799a6ea6d877faec0472d0b27c079b47c86b1cdc577720a5386483b36/fastuuid-0.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:d9e4332dc4ba054434a9594cbfaf7823b57993d7d8e7267831c3e059857cf397", size = 156550, upload-time = "2025-10-19T22:27:49.658Z" }, + { url = "https://files.pythonhosted.org/packages/a5/83/ae12dd39b9a39b55d7f90abb8971f1a5f3c321fd72d5aa83f90dc67fe9ed/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:77a09cb7427e7af74c594e409f7731a0cf887221de2f698e1ca0ebf0f3139021", size = 510720, upload-time = "2025-10-19T22:42:34.633Z" }, + { url = "https://files.pythonhosted.org/packages/53/b0/a4b03ff5d00f563cc7546b933c28cb3f2a07344b2aec5834e874f7d44143/fastuuid-0.14.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:9bd57289daf7b153bfa3e8013446aa144ce5e8c825e9e366d455155ede5ea2dc", size = 262024, upload-time = "2025-10-19T22:30:25.482Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6d/64aee0a0f6a58eeabadd582e55d0d7d70258ffdd01d093b30c53d668303b/fastuuid-0.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ac60fc860cdf3c3f327374db87ab8e064c86566ca8c49d2e30df15eda1b0c2d5", size = 251679, upload-time = "2025-10-19T22:36:14.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/a7e9cda8369e4f7919d36552db9b2ae21db7915083bc6336f1b0082c8b2e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab32f74bd56565b186f036e33129da77db8be09178cd2f5206a5d4035fb2a23f", size = 277862, upload-time = "2025-10-19T22:36:23.302Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d3/8ce11827c783affffd5bd4d6378b28eb6cc6d2ddf41474006b8d62e7448e/fastuuid-0.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e678459cf4addaedd9936bbb038e35b3f6b2061330fd8f2f6a1d80414c0f87", size = 278278, upload-time = "2025-10-19T22:29:43.809Z" }, + { url = "https://files.pythonhosted.org/packages/a2/51/680fb6352d0bbade04036da46264a8001f74b7484e2fd1f4da9e3db1c666/fastuuid-0.14.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e3cc56742f76cd25ecb98e4b82a25f978ccffba02e4bdce8aba857b6d85d87b", size = 301788, upload-time = "2025-10-19T22:36:06.825Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7c/2014b5785bd8ebdab04ec857635ebd84d5ee4950186a577db9eff0fb8ff6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cb9a030f609194b679e1660f7e32733b7a0f332d519c5d5a6a0a580991290022", size = 459819, upload-time = "2025-10-19T22:35:31.623Z" }, + { url = "https://files.pythonhosted.org/packages/01/d2/524d4ceeba9160e7a9bc2ea3e8f4ccf1ad78f3bde34090ca0c51f09a5e91/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:09098762aad4f8da3a888eb9ae01c84430c907a297b97166b8abc07b640f2995", size = 478546, upload-time = "2025-10-19T22:26:03.023Z" }, + { url = "https://files.pythonhosted.org/packages/bc/17/354d04951ce114bf4afc78e27a18cfbd6ee319ab1829c2d5fb5e94063ac6/fastuuid-0.14.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1383fff584fa249b16329a059c68ad45d030d5a4b70fb7c73a08d98fd53bcdab", size = 450921, upload-time = "2025-10-19T22:31:02.151Z" }, + { url = "https://files.pythonhosted.org/packages/fb/be/d7be8670151d16d88f15bb121c5b66cdb5ea6a0c2a362d0dcf30276ade53/fastuuid-0.14.0-cp313-cp313-win32.whl", hash = "sha256:a0809f8cc5731c066c909047f9a314d5f536c871a7a22e815cc4967c110ac9ad", size = 154559, upload-time = "2025-10-19T22:36:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/22/1d/5573ef3624ceb7abf4a46073d3554e37191c868abc3aecd5289a72f9810a/fastuuid-0.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:0df14e92e7ad3276327631c9e7cec09e32572ce82089c55cb1bb8df71cf394ed", size = 156539, upload-time = "2025-10-19T22:33:35.898Z" }, + { url = "https://files.pythonhosted.org/packages/16/c9/8c7660d1fe3862e3f8acabd9be7fc9ad71eb270f1c65cce9a2b7a31329ab/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b852a870a61cfc26c884af205d502881a2e59cc07076b60ab4a951cc0c94d1ad", size = 510600, upload-time = "2025-10-19T22:43:44.17Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f4/a989c82f9a90d0ad995aa957b3e572ebef163c5299823b4027986f133dfb/fastuuid-0.14.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c7502d6f54cd08024c3ea9b3514e2d6f190feb2f46e6dbcd3747882264bb5f7b", size = 262069, upload-time = "2025-10-19T22:43:38.38Z" }, + { url = "https://files.pythonhosted.org/packages/da/6c/a1a24f73574ac995482b1326cf7ab41301af0fabaa3e37eeb6b3df00e6e2/fastuuid-0.14.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ca61b592120cf314cfd66e662a5b54a578c5a15b26305e1b8b618a6f22df714", size = 251543, upload-time = "2025-10-19T22:32:22.537Z" }, + { url = "https://files.pythonhosted.org/packages/1a/20/2a9b59185ba7a6c7b37808431477c2d739fcbdabbf63e00243e37bd6bf49/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa75b6657ec129d0abded3bec745e6f7ab642e6dba3a5272a68247e85f5f316f", size = 277798, upload-time = "2025-10-19T22:33:53.821Z" }, + { url = "https://files.pythonhosted.org/packages/ef/33/4105ca574f6ded0af6a797d39add041bcfb468a1255fbbe82fcb6f592da2/fastuuid-0.14.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8a0dfea3972200f72d4c7df02c8ac70bad1bb4c58d7e0ec1e6f341679073a7f", size = 278283, upload-time = "2025-10-19T22:29:02.812Z" }, + { url = "https://files.pythonhosted.org/packages/fe/8c/fca59f8e21c4deb013f574eae05723737ddb1d2937ce87cb2a5d20992dc3/fastuuid-0.14.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1bf539a7a95f35b419f9ad105d5a8a35036df35fdafae48fb2fd2e5f318f0d75", size = 301627, upload-time = "2025-10-19T22:35:54.985Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e2/f78c271b909c034d429218f2798ca4e89eeda7983f4257d7865976ddbb6c/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:9a133bf9cc78fdbd1179cb58a59ad0100aa32d8675508150f3658814aeefeaa4", size = 459778, upload-time = "2025-10-19T22:28:00.999Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f0/5ff209d865897667a2ff3e7a572267a9ced8f7313919f6d6043aed8b1caa/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_i686.whl", hash = "sha256:f54d5b36c56a2d5e1a31e73b950b28a0d83eb0c37b91d10408875a5a29494bad", size = 478605, upload-time = "2025-10-19T22:36:21.764Z" }, + { url = "https://files.pythonhosted.org/packages/e0/c8/2ce1c78f983a2c4987ea865d9516dbdfb141a120fd3abb977ae6f02ba7ca/fastuuid-0.14.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:ec27778c6ca3393ef662e2762dba8af13f4ec1aaa32d08d77f71f2a70ae9feb8", size = 450837, upload-time = "2025-10-19T22:34:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/df/60/dad662ec9a33b4a5fe44f60699258da64172c39bd041da2994422cdc40fe/fastuuid-0.14.0-cp314-cp314-win32.whl", hash = "sha256:e23fc6a83f112de4be0cc1990e5b127c27663ae43f866353166f87df58e73d06", size = 154532, upload-time = "2025-10-19T22:35:18.217Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/da4db31001e854025ffd26bc9ba0740a9cbba2c3259695f7c5834908b336/fastuuid-0.14.0-cp314-cp314-win_amd64.whl", hash = "sha256:df61342889d0f5e7a32f7284e55ef95103f2110fee433c2ae7c2c0956d76ac8a", size = 156457, upload-time = "2025-10-19T22:33:44.579Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, +] + +[[package]] +name = "flask" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "blinker" }, + { name = "click" }, + { name = "itsdangerous" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/6d/cfe3c0fcc5e477df242b98bfe186a4c34357b4847e87ecaef04507332dab/flask-3.1.2.tar.gz", hash = "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", size = 720160, upload-time = "2025-08-19T21:03:21.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/f9/7f9263c5695f4bd0023734af91bedb2ff8209e8de6ead162f35d8dc762fd/flask-3.1.2-py3-none-any.whl", hash = "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c", size = 103308, upload-time = "2025-08-19T21:03:19.499Z" }, +] + +[[package]] +name = "flask-cors" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "werkzeug" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/74/0fc0fa68d62f21daef41017dafab19ef4b36551521260987eb3a5394c7ba/flask_cors-6.0.2.tar.gz", hash = "sha256:6e118f3698249ae33e429760db98ce032a8bf9913638d085ca0f4c5534ad2423", size = 13472, upload-time = "2025-12-12T20:31:42.861Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4f/af/72ad54402e599152de6d067324c46fe6a4f531c7c65baf7e96c63db55eaf/flask_cors-6.0.2-py3-none-any.whl", hash = "sha256:e57544d415dfd7da89a9564e1e3a9e515042df76e12130641ca6f3f2f03b699a", size = 13257, upload-time = "2025-12-12T20:31:41.3Z" }, +] + +[[package]] +name = "flask-limiter" +version = "4.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "flask" }, + { name = "limits" }, + { name = "ordered-set" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/98/71780be5d1afb941219c4b48d241d4e246e3062b017caa4e79c4dc71314c/flask_limiter-4.1.1.tar.gz", hash = "sha256:ca11608fc7eec43dcea606964ca07c3bd4ec1ae89043a0f67f717899a4f48106", size = 403198, upload-time = "2025-12-06T17:39:00.575Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/7c/9fe9ffc83be199011bb0c6deb82cdcbc5a355601e380581de9dbc30490dd/flask_limiter-4.1.1-py3-none-any.whl", hash = "sha256:e1ae13e06e6b3e39a4902e7d240b901586b25932c2add7bd5f5eeb4bdc11111b", size = 30554, upload-time = "2025-12-06T17:38:59.162Z" }, +] + +[[package]] +name = "fqdn" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" }, +] + +[[package]] +name = "frozendict" +version = "2.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/b2/2a3d1374b7780999d3184e171e25439a8358c47b481f68be883c14086b4c/frozendict-2.4.7.tar.gz", hash = "sha256:e478fb2a1391a56c8a6e10cc97c4a9002b410ecd1ac28c18d780661762e271bd", size = 317082, upload-time = "2025-11-11T22:40:14.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/74/f94141b38a51a553efef7f510fc213894161ae49b88bffd037f8d2a7cb2f/frozendict-2.4.7-py3-none-any.whl", hash = "sha256:972af65924ea25cf5b4d9326d549e69a9a4918d8a76a9d3a7cd174d98b237550", size = 16264, upload-time = "2025-11-11T22:40:12.836Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2026.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/7d/5df2650c57d47c57232af5ef4b4fdbff182070421e405e0d62c6cdbfaa87/fsspec-2026.1.0.tar.gz", hash = "sha256:e987cb0496a0d81bba3a9d1cee62922fb395e7d4c3b575e57f547953334fe07b", size = 310496, upload-time = "2026-01-09T15:21:35.562Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/c9/97cc5aae1648dcb851958a3ddf73ccd7dbe5650d95203ecb4d7720b4cdbf/fsspec-2026.1.0-py3-none-any.whl", hash = "sha256:cb76aa913c2285a3b49bdd5fc55b1d7c708d7208126b60f2eb8194fe1b4cbdcc", size = 201838, upload-time = "2026-01-09T15:21:34.041Z" }, +] + +[[package]] +name = "google-api-core" +version = "2.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/10/05572d33273292bac49c2d1785925f7bc3ff2fe50e3044cf1062c1dde32e/google_api_core-2.29.0.tar.gz", hash = "sha256:84181be0f8e6b04006df75ddfe728f24489f0af57c96a529ff7cf45bc28797f7", size = 177828, upload-time = "2026-01-08T22:21:39.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/b6/85c4d21067220b9a78cfb81f516f9725ea6befc1544ec9bd2c1acd97c324/google_api_core-2.29.0-py3-none-any.whl", hash = "sha256:d30bc60980daa36e314b5d5a3e5958b0200cb44ca8fa1be2b614e932b75a3ea9", size = 173906, upload-time = "2026-01-08T22:21:36.093Z" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-auth" +version = "2.48.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/41/242044323fbd746615884b1c16639749e73665b718209946ebad7ba8a813/google_auth-2.48.0.tar.gz", hash = "sha256:4f7e706b0cd3208a3d940a19a822c37a476ddba5450156c3e6624a71f7c841ce", size = 326522, upload-time = "2026-01-26T19:22:47.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/1d/d6466de3a5249d35e832a52834115ca9d1d0de6abc22065f049707516d47/google_auth-2.48.0-py3-none-any.whl", hash = "sha256:2e2a537873d449434252a9632c28bfc268b0adb1e53f9fb62afc5333a975903f", size = 236499, upload-time = "2026-01-26T19:22:45.099Z" }, +] + +[[package]] +name = "google-cloud-bigquery" +version = "3.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-resumable-media" }, + { name = "packaging" }, + { name = "python-dateutil" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/0a/62438ca138a095945468968696d9cca75a4cfd059e810402e70b0236d8ba/google_cloud_bigquery-3.40.0.tar.gz", hash = "sha256:b3ccb11caf0029f15b29569518f667553fe08f6f1459b959020c83fbbd8f2e68", size = 509287, upload-time = "2026-01-08T01:07:26.065Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/6a/90a04270dd60cc70259b73744f6e610ae9a158b21ab50fb695cca0056a3d/google_cloud_bigquery-3.40.0-py3-none-any.whl", hash = "sha256:0469bcf9e3dad3cab65b67cce98180c8c0aacf3253d47f0f8e976f299b49b5ab", size = 261335, upload-time = "2026-01-08T01:07:23.761Z" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/03/ef0bc99d0e0faf4fdbe67ac445e18cdaa74824fd93cd069e7bb6548cb52d/google_cloud_core-2.5.0.tar.gz", hash = "sha256:7c1b7ef5c92311717bd05301aa1a91ffbc565673d3b0b4163a52d8413a186963", size = 36027, upload-time = "2025-10-29T23:17:39.513Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/20/bfa472e327c8edee00f04beecc80baeddd2ab33ee0e86fd7654da49d45e9/google_cloud_core-2.5.0-py3-none-any.whl", hash = "sha256:67d977b41ae6c7211ee830c7912e41003ea8194bff15ae7d72fd6f51e57acabc", size = 29469, upload-time = "2025-10-29T23:17:38.548Z" }, +] + +[[package]] +name = "google-crc32c" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298, upload-time = "2025-12-16T00:20:32.241Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872, upload-time = "2025-12-16T00:33:58.785Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243, upload-time = "2025-12-16T00:40:21.46Z" }, + { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608, upload-time = "2025-12-16T00:40:22.204Z" }, + { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439, upload-time = "2025-12-16T00:35:20.458Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, + { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, + { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, + { url = "https://files.pythonhosted.org/packages/d1/db/000f15b41724589b0e7bc24bc7a8967898d8d3bc8caf64c513d91ef1f6c0/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:3ebb04528e83b2634857f43f9bb8ef5b2bbe7f10f140daeb01b58f972d04736b", size = 31297, upload-time = "2025-12-16T00:23:20.709Z" }, + { url = "https://files.pythonhosted.org/packages/d7/0d/8ebed0c39c53a7e838e2a486da8abb0e52de135f1b376ae2f0b160eb4c1a/google_crc32c-1.8.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:450dc98429d3e33ed2926fc99ee81001928d63460f8538f21a5d6060912a8e27", size = 30867, upload-time = "2025-12-16T00:43:14.628Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/b468aec74a0354b34c8cbf748db20d6e350a68a2b0912e128cabee49806c/google_crc32c-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3b9776774b24ba76831609ffbabce8cdf6fa2bd5e9df37b594221c7e333a81fa", size = 33344, upload-time = "2025-12-16T00:40:24.742Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e8/b33784d6fc77fb5062a8a7854e43e1e618b87d5ddf610a88025e4de6226e/google_crc32c-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89c17d53d75562edfff86679244830599ee0a48efc216200691de8b02ab6b2b8", size = 33694, upload-time = "2025-12-16T00:40:25.505Z" }, + { url = "https://files.pythonhosted.org/packages/92/b1/d3cbd4d988afb3d8e4db94ca953df429ed6db7282ed0e700d25e6c7bfc8d/google_crc32c-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:57a50a9035b75643996fbf224d6661e386c7162d1dfdab9bc4ca790947d1007f", size = 34435, upload-time = "2025-12-16T00:35:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/21/88/8ecf3c2b864a490b9e7010c84fd203ec8cf3b280651106a3a74dd1b0ca72/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:e6584b12cb06796d285d09e33f63309a09368b9d806a551d8036a4207ea43697", size = 31301, upload-time = "2025-12-16T00:24:48.527Z" }, + { url = "https://files.pythonhosted.org/packages/36/c6/f7ff6c11f5ca215d9f43d3629163727a272eabc356e5c9b2853df2bfe965/google_crc32c-1.8.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:f4b51844ef67d6cf2e9425983274da75f18b1597bb2c998e1c0a0e8d46f8f651", size = 30868, upload-time = "2025-12-16T00:48:12.163Z" }, + { url = "https://files.pythonhosted.org/packages/56/15/c25671c7aad70f8179d858c55a6ae8404902abe0cdcf32a29d581792b491/google_crc32c-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b0d1a7afc6e8e4635564ba8aa5c0548e3173e41b6384d7711a9123165f582de2", size = 33381, upload-time = "2025-12-16T00:40:26.268Z" }, + { url = "https://files.pythonhosted.org/packages/42/fa/f50f51260d7b0ef5d4898af122d8a7ec5a84e2984f676f746445f783705f/google_crc32c-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3f68782f3cbd1bce027e48768293072813469af6a61a86f6bb4977a4380f21", size = 33734, upload-time = "2025-12-16T00:40:27.028Z" }, + { url = "https://files.pythonhosted.org/packages/08/a5/7b059810934a09fb3ccb657e0843813c1fee1183d3bc2c8041800374aa2c/google_crc32c-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:d511b3153e7011a27ab6ee6bb3a5404a55b994dc1a7322c0b87b29606d9790e2", size = 34878, upload-time = "2025-12-16T00:35:23.142Z" }, + { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615, upload-time = "2025-12-16T00:40:29.298Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, +] + +[[package]] +name = "google-resumable-media" +version = "2.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-crc32c" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/d7/520b62a35b23038ff005e334dba3ffc75fcf583bee26723f1fd8fd4b6919/google_resumable_media-2.8.0.tar.gz", hash = "sha256:f1157ed8b46994d60a1bc432544db62352043113684d4e030ee02e77ebe9a1ae", size = 2163265, upload-time = "2025-11-17T15:38:06.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/0b/93afde9cfe012260e9fe1522f35c9b72d6ee222f316586b1f23ecf44d518/google_resumable_media-2.8.0-py3-none-any.whl", hash = "sha256:dd14a116af303845a8d932ddae161a26e86cc229645bc98b39f026f9b1717582", size = 81340, upload-time = "2025-11-17T15:38:05.594Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.72.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/7b/adfd75544c415c487b33061fe7ae526165241c1ea133f9a9125a56b39fd8/googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5", size = 147433, upload-time = "2025-11-06T18:29:24.087Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/ab/09169d5a4612a5f92490806649ac8d41e3ec9129c636754575b3553f4ea4/googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038", size = 297515, upload-time = "2025-11-06T18:29:13.14Z" }, +] + +[[package]] +name = "grpcio" +version = "1.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/00/8163a1beeb6971f66b4bbe6ac9457b97948beba8dd2fc8e1281dce7f79ec/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a", size = 5843567, upload-time = "2025-10-21T16:20:52.829Z" }, + { url = "https://files.pythonhosted.org/packages/10/c1/934202f5cf335e6d852530ce14ddb0fef21be612ba9ecbbcbd4d748ca32d/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c", size = 11848017, upload-time = "2025-10-21T16:20:56.705Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/8dec16b1863d74af6eb3543928600ec2195af49ca58b16334972f6775663/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465", size = 6412027, upload-time = "2025-10-21T16:20:59.3Z" }, + { url = "https://files.pythonhosted.org/packages/d7/64/7b9e6e7ab910bea9d46f2c090380bab274a0b91fb0a2fe9b0cd399fffa12/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48", size = 7075913, upload-time = "2025-10-21T16:21:01.645Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/093c46e9546073cefa789bd76d44c5cb2abc824ca62af0c18be590ff13ba/grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da", size = 6615417, upload-time = "2025-10-21T16:21:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b6/5709a3a68500a9c03da6fb71740dcdd5ef245e39266461a03f31a57036d8/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397", size = 7199683, upload-time = "2025-10-21T16:21:06.195Z" }, + { url = "https://files.pythonhosted.org/packages/91/d3/4b1f2bf16ed52ce0b508161df3a2d186e4935379a159a834cb4a7d687429/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749", size = 8163109, upload-time = "2025-10-21T16:21:08.498Z" }, + { url = "https://files.pythonhosted.org/packages/5c/61/d9043f95f5f4cf085ac5dd6137b469d41befb04bd80280952ffa2a4c3f12/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00", size = 7626676, upload-time = "2025-10-21T16:21:10.693Z" }, + { url = "https://files.pythonhosted.org/packages/36/95/fd9a5152ca02d8881e4dd419cdd790e11805979f499a2e5b96488b85cf27/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054", size = 3997688, upload-time = "2025-10-21T16:21:12.746Z" }, + { url = "https://files.pythonhosted.org/packages/60/9c/5c359c8d4c9176cfa3c61ecd4efe5affe1f38d9bae81e81ac7186b4c9cc8/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d", size = 4709315, upload-time = "2025-10-21T16:21:15.26Z" }, + { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718, upload-time = "2025-10-21T16:21:17.939Z" }, + { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627, upload-time = "2025-10-21T16:21:20.466Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167, upload-time = "2025-10-21T16:21:23.122Z" }, + { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267, upload-time = "2025-10-21T16:21:25.995Z" }, + { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963, upload-time = "2025-10-21T16:21:28.631Z" }, + { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484, upload-time = "2025-10-21T16:21:30.837Z" }, + { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777, upload-time = "2025-10-21T16:21:33.577Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014, upload-time = "2025-10-21T16:21:41.882Z" }, + { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750, upload-time = "2025-10-21T16:21:44.006Z" }, + { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003, upload-time = "2025-10-21T16:21:46.244Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716, upload-time = "2025-10-21T16:21:48.475Z" }, + { url = "https://files.pythonhosted.org/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", size = 11821522, upload-time = "2025-10-21T16:21:51.142Z" }, + { url = "https://files.pythonhosted.org/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", size = 6362558, upload-time = "2025-10-21T16:21:54.213Z" }, + { url = "https://files.pythonhosted.org/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", size = 7049990, upload-time = "2025-10-21T16:21:56.476Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", size = 6575387, upload-time = "2025-10-21T16:21:59.051Z" }, + { url = "https://files.pythonhosted.org/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", size = 7166668, upload-time = "2025-10-21T16:22:02.049Z" }, + { url = "https://files.pythonhosted.org/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", size = 8124928, upload-time = "2025-10-21T16:22:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", size = 7589983, upload-time = "2025-10-21T16:22:07.881Z" }, + { url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727, upload-time = "2025-10-21T16:22:10.032Z" }, + { url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799, upload-time = "2025-10-21T16:22:12.709Z" }, + { url = "https://files.pythonhosted.org/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62", size = 5808417, upload-time = "2025-10-21T16:22:15.02Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f5/a4531f7fb8b4e2a60b94e39d5d924469b7a6988176b3422487be61fe2998/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd", size = 11828219, upload-time = "2025-10-21T16:22:17.954Z" }, + { url = "https://files.pythonhosted.org/packages/4b/1c/de55d868ed7a8bd6acc6b1d6ddc4aa36d07a9f31d33c912c804adb1b971b/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc", size = 6367826, upload-time = "2025-10-21T16:22:20.721Z" }, + { url = "https://files.pythonhosted.org/packages/59/64/99e44c02b5adb0ad13ab3adc89cb33cb54bfa90c74770f2607eea629b86f/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a", size = 7049550, upload-time = "2025-10-21T16:22:23.637Z" }, + { url = "https://files.pythonhosted.org/packages/43/28/40a5be3f9a86949b83e7d6a2ad6011d993cbe9b6bd27bea881f61c7788b6/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba", size = 6575564, upload-time = "2025-10-21T16:22:26.016Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a9/1be18e6055b64467440208a8559afac243c66a8b904213af6f392dc2212f/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09", size = 7176236, upload-time = "2025-10-21T16:22:28.362Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/dba05d3fcc151ce6e81327541d2cc8394f442f6b350fead67401661bf041/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc", size = 8125795, upload-time = "2025-10-21T16:22:31.075Z" }, + { url = "https://files.pythonhosted.org/packages/4a/45/122df922d05655f63930cf42c9e3f72ba20aadb26c100ee105cad4ce4257/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc", size = 7592214, upload-time = "2025-10-21T16:22:33.831Z" }, + { url = "https://files.pythonhosted.org/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", size = 4062961, upload-time = "2025-10-21T16:22:36.468Z" }, + { url = "https://files.pythonhosted.org/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", size = 4834462, upload-time = "2025-10-21T16:22:39.772Z" }, +] + +[[package]] +name = "grpcio-status" +version = "1.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/46/e9f19d5be65e8423f886813a2a9d0056ba94757b0c5007aa59aed1a961fa/grpcio_status-1.76.0.tar.gz", hash = "sha256:25fcbfec74c15d1a1cb5da3fab8ee9672852dc16a5a9eeb5baf7d7a9952943cd", size = 13679, upload-time = "2025-10-21T16:28:52.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/cc/27ba60ad5a5f2067963e6a858743500df408eb5855e98be778eaef8c9b02/grpcio_status-1.76.0-py3-none-any.whl", hash = "sha256:380568794055a8efbbd8871162df92012e0228a5f6dffaf57f2a00c534103b18", size = 14425, upload-time = "2025-10-21T16:28:40.853Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f", size = 506020, upload-time = "2025-10-24T19:04:32.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/a5/85ef910a0aa034a2abcfadc360ab5ac6f6bc4e9112349bd40ca97551cff0/hf_xet-1.2.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:ceeefcd1b7aed4956ae8499e2199607765fbd1c60510752003b6cc0b8413b649", size = 2861870, upload-time = "2025-10-24T19:04:11.422Z" }, + { url = "https://files.pythonhosted.org/packages/ea/40/e2e0a7eb9a51fe8828ba2d47fe22a7e74914ea8a0db68a18c3aa7449c767/hf_xet-1.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b70218dd548e9840224df5638fdc94bd033552963cfa97f9170829381179c813", size = 2717584, upload-time = "2025-10-24T19:04:09.586Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7d/daf7f8bc4594fdd59a8a596f9e3886133fdc68e675292218a5e4c1b7e834/hf_xet-1.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d40b18769bb9a8bc82a9ede575ce1a44c75eb80e7375a01d76259089529b5dc", size = 3315004, upload-time = "2025-10-24T19:04:00.314Z" }, + { url = "https://files.pythonhosted.org/packages/b1/ba/45ea2f605fbf6d81c8b21e4d970b168b18a53515923010c312c06cd83164/hf_xet-1.2.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:cd3a6027d59cfb60177c12d6424e31f4b5ff13d8e3a1247b3a584bf8977e6df5", size = 3222636, upload-time = "2025-10-24T19:03:58.111Z" }, + { url = "https://files.pythonhosted.org/packages/4a/1d/04513e3cab8f29ab8c109d309ddd21a2705afab9d52f2ba1151e0c14f086/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6de1fc44f58f6dd937956c8d304d8c2dea264c80680bcfa61ca4a15e7b76780f", size = 3408448, upload-time = "2025-10-24T19:04:20.951Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7c/60a2756d7feec7387db3a1176c632357632fbe7849fce576c5559d4520c7/hf_xet-1.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f182f264ed2acd566c514e45da9f2119110e48a87a327ca271027904c70c5832", size = 3503401, upload-time = "2025-10-24T19:04:22.549Z" }, + { url = "https://files.pythonhosted.org/packages/4e/64/48fffbd67fb418ab07451e4ce641a70de1c40c10a13e25325e24858ebe5a/hf_xet-1.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:293a7a3787e5c95d7be1857358a9130694a9c6021de3f27fa233f37267174382", size = 2900866, upload-time = "2025-10-24T19:04:33.461Z" }, + { url = "https://files.pythonhosted.org/packages/e2/51/f7e2caae42f80af886db414d4e9885fac959330509089f97cccb339c6b87/hf_xet-1.2.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:10bfab528b968c70e062607f663e21e34e2bba349e8038db546646875495179e", size = 2861861, upload-time = "2025-10-24T19:04:19.01Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1d/a641a88b69994f9371bd347f1dd35e5d1e2e2460a2e350c8d5165fc62005/hf_xet-1.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a212e842647b02eb6a911187dc878e79c4aa0aa397e88dd3b26761676e8c1f8", size = 2717699, upload-time = "2025-10-24T19:04:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/df/e0/e5e9bba7d15f0318955f7ec3f4af13f92e773fbb368c0b8008a5acbcb12f/hf_xet-1.2.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30e06daccb3a7d4c065f34fc26c14c74f4653069bb2b194e7f18f17cbe9939c0", size = 3314885, upload-time = "2025-10-24T19:04:07.642Z" }, + { url = "https://files.pythonhosted.org/packages/21/90/b7fe5ff6f2b7b8cbdf1bd56145f863c90a5807d9758a549bf3d916aa4dec/hf_xet-1.2.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:29c8fc913a529ec0a91867ce3d119ac1aac966e098cf49501800c870328cc090", size = 3221550, upload-time = "2025-10-24T19:04:05.55Z" }, + { url = "https://files.pythonhosted.org/packages/6f/cb/73f276f0a7ce46cc6a6ec7d6c7d61cbfe5f2e107123d9bbd0193c355f106/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e159cbfcfbb29f920db2c09ed8b660eb894640d284f102ada929b6e3dc410a", size = 3408010, upload-time = "2025-10-24T19:04:28.598Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1e/d642a12caa78171f4be64f7cd9c40e3ca5279d055d0873188a58c0f5fbb9/hf_xet-1.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9c91d5ae931510107f148874e9e2de8a16052b6f1b3ca3c1b12f15ccb491390f", size = 3503264, upload-time = "2025-10-24T19:04:30.397Z" }, + { url = "https://files.pythonhosted.org/packages/17/b5/33764714923fa1ff922770f7ed18c2daae034d21ae6e10dbf4347c854154/hf_xet-1.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:210d577732b519ac6ede149d2f2f34049d44e8622bf14eb3d63bbcd2d4b332dc", size = 2901071, upload-time = "2025-10-24T19:04:37.463Z" }, + { url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848", size = 2866099, upload-time = "2025-10-24T19:04:15.366Z" }, + { url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4", size = 2722178, upload-time = "2025-10-24T19:04:13.695Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd", size = 3320214, upload-time = "2025-10-24T19:04:03.596Z" }, + { url = "https://files.pythonhosted.org/packages/46/92/3f7ec4a1b6a65bf45b059b6d4a5d38988f63e193056de2f420137e3c3244/hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c", size = 3229054, upload-time = "2025-10-24T19:04:01.949Z" }, + { url = "https://files.pythonhosted.org/packages/0b/dd/7ac658d54b9fb7999a0ccb07ad863b413cbaf5cf172f48ebcd9497ec7263/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737", size = 3413812, upload-time = "2025-10-24T19:04:24.585Z" }, + { url = "https://files.pythonhosted.org/packages/92/68/89ac4e5b12a9ff6286a12174c8538a5930e2ed662091dd2572bbe0a18c8a/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865", size = 3508920, upload-time = "2025-10-24T19:04:26.927Z" }, + { url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "1.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "httpx" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "shellingham" }, + { name = "tqdm" }, + { name = "typer-slim" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/e9/2658cb9bc4c72a67b7f87650e827266139befaf499095883d30dabc4d49f/huggingface_hub-1.3.5.tar.gz", hash = "sha256:8045aca8ddab35d937138f3c386c6d43a275f53437c5c64cdc9aa8408653b4ed", size = 627456, upload-time = "2026-01-29T10:34:19.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/84/a579b95c46fe8e319f89dc700c087596f665141575f4dcf136aaa97d856f/huggingface_hub-1.3.5-py3-none-any.whl", hash = "sha256:fe332d7f86a8af874768452295c22cd3f37730fb2463cf6cc3295e26036f8ef9", size = 536675, upload-time = "2026-01-29T10:34:17.713Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "ijson" +version = "3.4.0.post0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/30/7ab4b9e88e7946f6beef419f74edcc541df3ea562c7882257b4eaa82417d/ijson-3.4.0.post0.tar.gz", hash = "sha256:9aa02dc70bb245670a6ca7fba737b992aeeb4895360980622f7e568dbf23e41e", size = 67216, upload-time = "2025-10-10T05:29:25.62Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/ac/3d57249d4acba66a33eaef794edb5b2a2222ca449ae08800f8abe9286645/ijson-3.4.0.post0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b473112e72c0c506da425da3278367b6680f340ecc093084693a1e819d28435", size = 88278, upload-time = "2025-10-10T05:27:55.403Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/2d068d23d1a665f500282ceb6f2473952a95fc7107d739fd629b4ab41959/ijson-3.4.0.post0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:043f9b7cf9cc744263a78175e769947733710d2412d25180df44b1086b23ebd5", size = 59898, upload-time = "2025-10-10T05:27:56.361Z" }, + { url = "https://files.pythonhosted.org/packages/26/3d/8b14589dfb0e5dbb7bcf9063e53d3617c041cf315ff3dfa60945382237ce/ijson-3.4.0.post0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b55e49045f4c8031f3673f56662fd828dc9e8d65bd3b03a9420dda0d370e64ba", size = 59945, upload-time = "2025-10-10T05:27:57.581Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/086a75094397d4b7584698a540a279689e12905271af78cdfc903bf9eaf8/ijson-3.4.0.post0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11f13b73194ea2a5a8b4a2863f25b0b4624311f10db3a75747b510c4958179b0", size = 131318, upload-time = "2025-10-10T05:27:58.453Z" }, + { url = "https://files.pythonhosted.org/packages/df/35/7f61e9ce4a9ff1306ec581eb851f8a660439126d92ee595c6dc8084aac97/ijson-3.4.0.post0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:659acb2843433e080c271ecedf7d19c71adde1ee5274fc7faa2fec0a793f9f1c", size = 137990, upload-time = "2025-10-10T05:27:59.328Z" }, + { url = "https://files.pythonhosted.org/packages/59/bf/590bbc3c3566adce5e2f43ba5894520cbaf19a3e7f38c1250926ba67eee4/ijson-3.4.0.post0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:deda4cfcaafa72ca3fa845350045b1d0fef9364ec9f413241bb46988afbe6ee6", size = 134416, upload-time = "2025-10-10T05:28:00.317Z" }, + { url = "https://files.pythonhosted.org/packages/24/c1/fb719049851979df71f3e039d6f1a565d349c9cb1b29c0f8775d9db141b4/ijson-3.4.0.post0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47352563e8c594360bacee2e0753e97025f0861234722d02faace62b1b6d2b2a", size = 138034, upload-time = "2025-10-10T05:28:01.627Z" }, + { url = "https://files.pythonhosted.org/packages/10/ce/ccda891f572876aaf2c43f0b2079e31d5b476c3ae53196187eab1a788eff/ijson-3.4.0.post0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5a48b9486242d1295abe7fd0fbb6308867da5ca3f69b55c77922a93c2b6847aa", size = 132510, upload-time = "2025-10-10T05:28:03.141Z" }, + { url = "https://files.pythonhosted.org/packages/11/b5/ca8e64ab7cf5252f358e467be767630f085b5bbcd3c04333a3a5f36c3dd3/ijson-3.4.0.post0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9c0886234d1fae15cf4581a430bdba03d79251c1ab3b07e30aa31b13ef28d01c", size = 134907, upload-time = "2025-10-10T05:28:04.438Z" }, + { url = "https://files.pythonhosted.org/packages/93/14/63a4d5dc548690f29f0c2fc9cabd5ecbb37532547439c05f5b3b9ce73021/ijson-3.4.0.post0-cp311-cp311-win32.whl", hash = "sha256:fecae19b5187d92900c73debb3a979b0b3290a53f85df1f8f3c5ba7d1e9fb9cb", size = 52006, upload-time = "2025-10-10T05:28:05.424Z" }, + { url = "https://files.pythonhosted.org/packages/fa/bf/932740899e572a97f9be0c6cd64ebda557eae7701ac216fc284aba21786d/ijson-3.4.0.post0-cp311-cp311-win_amd64.whl", hash = "sha256:b39dbf87071f23a23c8077eea2ae7cfeeca9ff9ffec722dfc8b5f352e4dd729c", size = 54410, upload-time = "2025-10-10T05:28:06.264Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fe/3b6af0025288e769dbfa30485dae1b3bd3f33f00390f3ee532cbb1c33e9b/ijson-3.4.0.post0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b607a500fca26101be47d2baf7cddb457b819ab60a75ce51ed1092a40da8b2f9", size = 87847, upload-time = "2025-10-10T05:28:07.229Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/95ee2ca82f3b1a57892452f6e5087607d56c620beb8ce625475194568698/ijson-3.4.0.post0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4827d9874a6a81625412c59f7ca979a84d01f7f6bfb3c6d4dc4c46d0382b14e0", size = 59815, upload-time = "2025-10-10T05:28:08.448Z" }, + { url = "https://files.pythonhosted.org/packages/51/8d/5a704ab3c17c55c21c86423458db8610626ca99cc9086a74dfeb7ee9054c/ijson-3.4.0.post0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4d4afec780881edb2a0d2dd40b1cdbe246e630022d5192f266172a0307986a7", size = 59648, upload-time = "2025-10-10T05:28:09.307Z" }, + { url = "https://files.pythonhosted.org/packages/25/56/ca5d6ca145d007f30b44e747f3c163bc08710ce004af0deaad4a2301339b/ijson-3.4.0.post0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432fb60ffb952926f9438e0539011e2dfcd108f8426ee826ccc6173308c3ff2c", size = 138279, upload-time = "2025-10-10T05:28:10.489Z" }, + { url = "https://files.pythonhosted.org/packages/c3/d3/22e3cc806fcdda7ad4c8482ed74db7a017d4a1d49b4300c7bc07052fb561/ijson-3.4.0.post0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:54a0e3e05d9a0c95ecba73d9579f146cf6d5c5874116c849dba2d39a5f30380e", size = 149110, upload-time = "2025-10-10T05:28:12.263Z" }, + { url = "https://files.pythonhosted.org/packages/3e/04/efb30f413648b9267f5a33920ac124d7ebef3bc4063af8f6ffc8ca11ddcb/ijson-3.4.0.post0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05807edc0bcbd222dc6ea32a2b897f0c81dc7f12c8580148bc82f6d7f5e7ec7b", size = 149026, upload-time = "2025-10-10T05:28:13.557Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cf/481165f7046ade32488719300a3994a437020bc41cfbb54334356348f513/ijson-3.4.0.post0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5269af16f715855d9864937f9dd5c348ca1ac49cee6a2c7a1b7091c159e874f", size = 150012, upload-time = "2025-10-10T05:28:14.859Z" }, + { url = "https://files.pythonhosted.org/packages/0f/24/642e3289917ecf860386e26dfde775f9962d26ab7f6c2e364ed3ca3c25d8/ijson-3.4.0.post0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b200df83c901f5bfa416d069ac71077aa1608f854a4c50df1b84ced560e9c9ec", size = 142193, upload-time = "2025-10-10T05:28:16.131Z" }, + { url = "https://files.pythonhosted.org/packages/0f/f5/fd2f038abe95e553e1c3ee207cda19db9196eb416e63c7c89699a8cf0db7/ijson-3.4.0.post0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6458bd8e679cdff459a0a5e555b107c3bbacb1f382da3fe0f40e392871eb518d", size = 150904, upload-time = "2025-10-10T05:28:17.401Z" }, + { url = "https://files.pythonhosted.org/packages/49/35/24259d22519987928164e6cb8fe3486e1df0899b2999ada4b0498639b463/ijson-3.4.0.post0-cp312-cp312-win32.whl", hash = "sha256:55f7f656b5986326c978cbb3a9eea9e33f3ef6ecc4535b38f1d452c731da39ab", size = 52358, upload-time = "2025-10-10T05:28:18.315Z" }, + { url = "https://files.pythonhosted.org/packages/a1/2b/6f7ade27a8ff5758fc41006dadd2de01730def84fe3e60553b329c59e0d4/ijson-3.4.0.post0-cp312-cp312-win_amd64.whl", hash = "sha256:e15833dcf6f6d188fdc624a31cd0520c3ba21b6855dc304bc7c1a8aeca02d4ac", size = 54789, upload-time = "2025-10-10T05:28:19.552Z" }, + { url = "https://files.pythonhosted.org/packages/1b/20/aaec6977f9d538bbadd760c7fa0f6a0937742abdcc920ec6478a8576e55f/ijson-3.4.0.post0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:114ed248166ac06377e87a245a158d6b98019d2bdd3bb93995718e0bd996154f", size = 87863, upload-time = "2025-10-10T05:28:20.786Z" }, + { url = "https://files.pythonhosted.org/packages/5b/29/06bf56a866e2fe21453a1ad8f3a5d7bca3c723f73d96329656dfee969783/ijson-3.4.0.post0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ffb21203736b08fe27cb30df6a4f802fafb9ef7646c5ff7ef79569b63ea76c57", size = 59806, upload-time = "2025-10-10T05:28:21.596Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ae/e1d0fda91ba7a444b75f0d60cb845fdb1f55d3111351529dcbf4b1c276fe/ijson-3.4.0.post0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:07f20ecd748602ac7f18c617637e53bd73ded7f3b22260bba3abe401a7fc284e", size = 59643, upload-time = "2025-10-10T05:28:22.45Z" }, + { url = "https://files.pythonhosted.org/packages/4d/24/5a24533be2726396cc1724dc237bada09b19715b5bfb0e7b9400db0901ad/ijson-3.4.0.post0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:27aa193d47ffc6bc4e45453896ad98fb089a367e8283b973f1fe5c0198b60b4e", size = 138082, upload-time = "2025-10-10T05:28:23.319Z" }, + { url = "https://files.pythonhosted.org/packages/05/60/026c3efcec23c329657e878cbc0a9a25b42e7eb3971e8c2377cb3284e2b7/ijson-3.4.0.post0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ccddb2894eb7af162ba43b9475ac5825d15d568832f82eb8783036e5d2aebd42", size = 149145, upload-time = "2025-10-10T05:28:24.279Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c2/036499909b7a1bc0bcd85305e4348ad171aeb9df57581287533bdb3497e9/ijson-3.4.0.post0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61ab0b8c5bf707201dc67e02c116f4b6545c4afd7feb2264b989d242d9c4348a", size = 149046, upload-time = "2025-10-10T05:28:25.186Z" }, + { url = "https://files.pythonhosted.org/packages/ba/75/e7736073ad96867c129f9e799e3e65086badd89dbf3911f76d9b3bf8a115/ijson-3.4.0.post0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:254cfb8c124af68327a0e7a49b50bbdacafd87c4690a3d62c96eb01020a685ef", size = 150356, upload-time = "2025-10-10T05:28:26.135Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1b/1c1575d2cda136985561fcf774fe6c54412cd0fa08005342015af0403193/ijson-3.4.0.post0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04ac9ca54db20f82aeda6379b5f4f6112fdb150d09ebce04affeab98a17b4ed3", size = 142322, upload-time = "2025-10-10T05:28:27.125Z" }, + { url = "https://files.pythonhosted.org/packages/28/4d/aba9871feb624df8494435d1a9ddc7b6a4f782c6044bfc0d770a4b59f145/ijson-3.4.0.post0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a603d7474bf35e7b3a8e49c8dabfc4751841931301adff3f3318171c4e407f32", size = 151386, upload-time = "2025-10-10T05:28:28.274Z" }, + { url = "https://files.pythonhosted.org/packages/3f/9a/791baa83895fb6e492bce2c7a0ea6427b6a41fe854349e62a37d0c9deaf0/ijson-3.4.0.post0-cp313-cp313-win32.whl", hash = "sha256:ec5bb1520cb212ebead7dba048bb9b70552c3440584f83b01b0abc96862e2a09", size = 52352, upload-time = "2025-10-10T05:28:29.191Z" }, + { url = "https://files.pythonhosted.org/packages/a9/0c/061f51493e1da21116d74ee8f6a6b9ae06ca5fa2eb53c3b38b64f9a9a5ae/ijson-3.4.0.post0-cp313-cp313-win_amd64.whl", hash = "sha256:3505dff18bdeb8b171eb28af6df34857e2be80dc01e2e3b624e77215ad58897f", size = 54783, upload-time = "2025-10-10T05:28:30.048Z" }, + { url = "https://files.pythonhosted.org/packages/c7/89/4344e176f2c5f5ef3251c9bfa4ddd5b4cf3f9601fd6ec3f677a3ba0b9c71/ijson-3.4.0.post0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:45a0b1c833ed2620eaf8da958f06ac8351c59e5e470e078400d23814670ed708", size = 92342, upload-time = "2025-10-10T05:28:31.389Z" }, + { url = "https://files.pythonhosted.org/packages/d4/b1/85012c586a6645f9fb8bfa3ef62ed2f303c8d73fc7c2f705111582925980/ijson-3.4.0.post0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7809ec8c8f40228edaaa089f33e811dff4c5b8509702652870d3f286c9682e27", size = 62028, upload-time = "2025-10-10T05:28:32.849Z" }, + { url = "https://files.pythonhosted.org/packages/65/ea/7b7e2815c101d78b33e74d64ddb70cccc377afccd5dda76e566ed3fcb56f/ijson-3.4.0.post0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cf4a34c2cfe852aee75c89c05b0a4531c49dc0be27eeed221afd6fbf9c3e149c", size = 61773, upload-time = "2025-10-10T05:28:34.016Z" }, + { url = "https://files.pythonhosted.org/packages/59/7d/2175e599cb77a64f528629bad3ce95dfdf2aa6171d313c1fc00bbfaf0d22/ijson-3.4.0.post0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a39d5d36067604b26b78de70b8951c90e9272450642661fe531a8f7a6936a7fa", size = 198562, upload-time = "2025-10-10T05:28:34.878Z" }, + { url = "https://files.pythonhosted.org/packages/13/97/82247c501c92405bb2fc44ab5efb497335bcb9cf0f5d3a0b04a800737bd8/ijson-3.4.0.post0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83fc738d81c9ea686b452996110b8a6678296c481e0546857db24785bff8da92", size = 216212, upload-time = "2025-10-10T05:28:36.208Z" }, + { url = "https://files.pythonhosted.org/packages/95/ca/b956f507bb02e05ce109fd11ab6a2c054f8b686cc5affe41afe50630984d/ijson-3.4.0.post0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b2a81aee91633868f5b40280e2523f7c5392e920a5082f47c5e991e516b483f6", size = 206618, upload-time = "2025-10-10T05:28:37.243Z" }, + { url = "https://files.pythonhosted.org/packages/3e/12/e827840ab81d86a9882e499097934df53294f05155f1acfcb9a211ac1142/ijson-3.4.0.post0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:56169e298c5a2e7196aaa55da78ddc2415876a74fe6304f81b1eb0d3273346f7", size = 210689, upload-time = "2025-10-10T05:28:38.252Z" }, + { url = "https://files.pythonhosted.org/packages/1b/3b/59238d9422c31a4aefa22ebeb8e599e706158a0ab03669ef623be77a499a/ijson-3.4.0.post0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eeb9540f0b1a575cbb5968166706946458f98c16e7accc6f2fe71efa29864241", size = 199927, upload-time = "2025-10-10T05:28:39.233Z" }, + { url = "https://files.pythonhosted.org/packages/b6/0f/ec01c36c128c37edb8a5ae8f3de3256009f886338d459210dfe121ee4ba9/ijson-3.4.0.post0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ba3478ff0bb49d7ba88783f491a99b6e3fa929c930ab062d2bb7837e6a38fe88", size = 204455, upload-time = "2025-10-10T05:28:40.644Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cf/5560e1db96c6d10a5313be76bf5a1754266cbfb5cc13ff64d107829e07b1/ijson-3.4.0.post0-cp313-cp313t-win32.whl", hash = "sha256:b005ce84e82f28b00bf777a464833465dfe3efa43a0a26c77b5ac40723e1a728", size = 54566, upload-time = "2025-10-10T05:28:41.663Z" }, + { url = "https://files.pythonhosted.org/packages/22/5a/cbb69144c3b25dd56f5421ff7dc0cf3051355579062024772518e4f4b3c5/ijson-3.4.0.post0-cp313-cp313t-win_amd64.whl", hash = "sha256:fe9c84c9b1c8798afa407be1cea1603401d99bfc7c34497e19f4f5e5ddc9b441", size = 57298, upload-time = "2025-10-10T05:28:42.881Z" }, + { url = "https://files.pythonhosted.org/packages/af/0b/a4ce8524fd850302bbf5d9f38d07c0fa981fdbe44951d2fcd036935b67dd/ijson-3.4.0.post0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da6a21b88cbf5ecbc53371283988d22c9643aa71ae2873bbeaefd2dea3b6160b", size = 88361, upload-time = "2025-10-10T05:28:43.73Z" }, + { url = "https://files.pythonhosted.org/packages/be/90/a5e5f33e46f28174a9c8142d12dcb3d26ce358d9a2230b9b15f5c987b3a5/ijson-3.4.0.post0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cf24a48a1c3ca9d44a04feb59ccefeb9aa52bb49b9cb70ad30518c25cce74bb7", size = 59960, upload-time = "2025-10-10T05:28:44.585Z" }, + { url = "https://files.pythonhosted.org/packages/83/e2/551dd7037dda759aa0ce53f0d3d7be03b03c6b05c0b0a5d5ab7a47e6b4b1/ijson-3.4.0.post0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d14427d366f95f21adcb97d0ed1f6d30f6fdc04d0aa1e4de839152c50c2b8d65", size = 59957, upload-time = "2025-10-10T05:28:45.748Z" }, + { url = "https://files.pythonhosted.org/packages/ac/b9/3006384f85cc26cf83dbbd542d362cc336f1e1ddd491e32147cfa46ea8ae/ijson-3.4.0.post0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:339d49f6c5d24051c85d9226be96d2d56e633cb8b7d09dd8099de8d8b51a97e2", size = 139967, upload-time = "2025-10-10T05:28:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/77/3b/b5234add8115cbfe8635b6c152fb527327f45e4c0f0bf2e93844b36b5217/ijson-3.4.0.post0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7206afcb396aaef66c2b066997b4e9d9042c4b7d777f4d994e9cec6d322c2fe6", size = 149196, upload-time = "2025-10-10T05:28:48.226Z" }, + { url = "https://files.pythonhosted.org/packages/a2/d2/c4ae543e37d7a9fba09740c221976a63705dbad23a9cda9022fc9fa0f3de/ijson-3.4.0.post0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c8dd327da225887194fe8b93f2b3c9c256353e14a6b9eefc940ed17fde38f5b8", size = 148516, upload-time = "2025-10-10T05:28:49.237Z" }, + { url = "https://files.pythonhosted.org/packages/0d/a1/914b5fb1c26af2474cd04841626e0e95576499a4ca940661fb105ee12dd2/ijson-3.4.0.post0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4810546e66128af51fd4a0c9a640e84e8508e9c15c4f247d8a3e3253b20e1465", size = 149770, upload-time = "2025-10-10T05:28:50.501Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c1/51c3584102d0d85d4aa10cc88dbbe431ecb9fe98160a9e2fad62a4456aed/ijson-3.4.0.post0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:103a0838061297d063bca81d724b0958b616f372bd893bbc278320152252c652", size = 143688, upload-time = "2025-10-10T05:28:51.823Z" }, + { url = "https://files.pythonhosted.org/packages/47/3d/a54f13d766332620bded8ee76bcdd274509ecc53cf99573450f95b3ad910/ijson-3.4.0.post0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:40007c977e230e04118b27322f25a72ae342a3d61464b2057fcd9b21eeb7427a", size = 150688, upload-time = "2025-10-10T05:28:52.757Z" }, + { url = "https://files.pythonhosted.org/packages/72/49/43d97cccf3266da7c044bd42e5083340ad1fd97fbb16d1bcd6791fd8918f/ijson-3.4.0.post0-cp314-cp314-win32.whl", hash = "sha256:f932969fc1fd4449ca141cf5f47ff357656a154a361f28d9ebca0badc5b02297", size = 52882, upload-time = "2025-10-10T05:28:53.708Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f0/008f1ed4e0fc6f6dc7a5a82ecf08a59bb212514e158954374d440d700e6c/ijson-3.4.0.post0-cp314-cp314-win_amd64.whl", hash = "sha256:3ed19b1e4349240773a8ce4a4bfa450892d4a57949c02c515cd6be5a46b7696a", size = 55568, upload-time = "2025-10-10T05:28:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/69/1c/8a199fded709e762aced89bb7086973c837e432dd714bbad78a6ac789c23/ijson-3.4.0.post0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:226447e40ca9340a39ed07d68ea02ee14b52cb4fe649425b256c1f0073531c83", size = 92345, upload-time = "2025-10-10T05:28:55.657Z" }, + { url = "https://files.pythonhosted.org/packages/be/60/04e97f6a403203bd2eb8849570bdce5719d696b5fb96aa2a62566fe7a1d9/ijson-3.4.0.post0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c88f0669d45d4b1aa017c9b68d378e7cd15d188dfb6f0209adc78b7f45590a7", size = 62029, upload-time = "2025-10-10T05:28:56.561Z" }, + { url = "https://files.pythonhosted.org/packages/2a/97/e88295f9456ba939d90d4603af28fcabda3b443ef55e709e9381df3daa58/ijson-3.4.0.post0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:56b3089dc28c12492d92cc4896d2be585a89ecae34e25d08c1df88f21815cb50", size = 61776, upload-time = "2025-10-10T05:28:57.401Z" }, + { url = "https://files.pythonhosted.org/packages/1b/9f/0e9c236e720c2de887ab0d7cad8a15d2aa55fb449f792437fc99899957a9/ijson-3.4.0.post0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c117321cfa7b749cc1213f9b4c80dc958f0a206df98ec038ae4bcbbdb8463a15", size = 199808, upload-time = "2025-10-10T05:28:58.62Z" }, + { url = "https://files.pythonhosted.org/packages/0e/70/c21de30e7013e074924cd82057acfc5760e7b2cc41180f80770621b0ad36/ijson-3.4.0.post0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8311f48db6a33116db5c81682f08b6e2405501a4b4e460193ae69fec3cd1f87a", size = 217152, upload-time = "2025-10-10T05:28:59.656Z" }, + { url = "https://files.pythonhosted.org/packages/64/78/63a0bcc0707037df4e22bb836451279d850592258c859685a402c27f5d6d/ijson-3.4.0.post0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91c61a3e63e04da648737e6b4abd537df1b46fb8cdf3219b072e790bb3c1a46b", size = 207663, upload-time = "2025-10-10T05:29:00.73Z" }, + { url = "https://files.pythonhosted.org/packages/7d/85/834e9838d69893cb7567e1210be044444213c78f7414aaf1cd241df16078/ijson-3.4.0.post0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1709171023ce82651b2f132575c2e6282e47f64ad67bd3260da476418d0e7895", size = 211157, upload-time = "2025-10-10T05:29:01.87Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9b/9fda503799ebc30397710552e5dedc1d98d9ea6a694e5717415892623a94/ijson-3.4.0.post0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5f0a72b1e3c0f78551670c12b2fdc1bf05f2796254d9c2055ba319bec2216020", size = 200231, upload-time = "2025-10-10T05:29:02.883Z" }, + { url = "https://files.pythonhosted.org/packages/15/f3/6419d1d5795a16591233d3aa3747b084e82c0c1d7184bdad9be638174560/ijson-3.4.0.post0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b982a3597b0439ce9c8f4cfc929d86c6ed43907908be1e8463a34dc35fe5b258", size = 204825, upload-time = "2025-10-10T05:29:04.242Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8d/a520e6902129c55fa94428ea0a22e8547540d5e7ca30f18b39594a5feea2/ijson-3.4.0.post0-cp314-cp314t-win32.whl", hash = "sha256:4e39bfdc36b0b460ef15a06550a6a385c64c81f7ac205ccff39bd45147918912", size = 55559, upload-time = "2025-10-10T05:29:05.681Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/0ac6dd0045957ba1270b7b1860864f7d8cea4062e70b1083134c587e5768/ijson-3.4.0.post0-cp314-cp314t-win_amd64.whl", hash = "sha256:17e45262a5ddef39894013fb1548ee7094e444c8389eb1a97f86708b19bea03e", size = 58238, upload-time = "2025-10-10T05:29:06.656Z" }, + { url = "https://files.pythonhosted.org/packages/43/66/27cfcea16e85b95e33814eae2052dab187206b8820cdd90aa39d32ffb441/ijson-3.4.0.post0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:add9242f886eae844a7410b84aee2bbb8bdc83c624f227cb1fdb2d0476a96cb1", size = 57029, upload-time = "2025-10-10T05:29:19.733Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1b/df3f1561c6629241fb2f8bd7ea1da14e3c2dd16fe9d7cbc97120870ed09c/ijson-3.4.0.post0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:69718ed41710dfcaa7564b0af42abc05875d4f7aaa24627c808867ef32634bc7", size = 56523, upload-time = "2025-10-10T05:29:20.641Z" }, + { url = "https://files.pythonhosted.org/packages/39/0a/6c6a3221ddecf62b696fde0e864415237e05b9a36ab6685a606b8fb3b5a2/ijson-3.4.0.post0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:636b6eca96c6c43c04629c6b37fad0181662eaacf9877c71c698485637f752f9", size = 70546, upload-time = "2025-10-10T05:29:21.526Z" }, + { url = "https://files.pythonhosted.org/packages/42/cb/edf69755e86a3a9f8b418efd60239cb308af46c7c8e12f869423f51c9851/ijson-3.4.0.post0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb5e73028f6e63d27b3d286069fe350ed80a4ccc493b022b590fea4bb086710d", size = 70532, upload-time = "2025-10-10T05:29:22.718Z" }, + { url = "https://files.pythonhosted.org/packages/96/7e/c8730ea39b8712622cd5a1bdff676098208400e37bb92052ba52f93e2aa1/ijson-3.4.0.post0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:461acf4320219459dabe5ed90a45cb86c9ba8cc6d6db9dad0d9427d42f57794c", size = 67927, upload-time = "2025-10-10T05:29:23.596Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f2/53b6e9bdd2a91202066764eaa74b572ba4dede0fe47a5a26f4de34b7541a/ijson-3.4.0.post0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a0fedf09c0f6ffa2a99e7e7fd9c5f3caf74e655c1ee015a0797383e99382ebc3", size = 54657, upload-time = "2025-10-10T05:29:24.482Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, +] + +[[package]] +name = "ipykernel" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "sys_platform == 'darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/a4/4948be6eb88628505b83a1f2f40d90254cab66abf2043b3c40fa07dfce0f/ipykernel-7.1.0.tar.gz", hash = "sha256:58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db", size = 174579, upload-time = "2025-10-27T09:46:39.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/17/20c2552266728ceba271967b87919664ecc0e33efca29c3efc6baf88c5f9/ipykernel-7.1.0-py3-none-any.whl", hash = "sha256:763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c", size = 117968, upload-time = "2025-10-27T09:46:37.805Z" }, +] + +[[package]] +name = "ipython" +version = "8.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/61/1810830e8b93c72dcd3c0f150c80a00c3deb229562d9423807ec92c3a539/ipython-8.38.0.tar.gz", hash = "sha256:9cfea8c903ce0867cc2f23199ed8545eb741f3a69420bfcf3743ad1cec856d39", size = 5513996, upload-time = "2026-01-05T10:59:06.901Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/df/db59624f4c71b39717c423409950ac3f2c8b2ce4b0aac843112c7fb3f721/ipython-8.38.0-py3-none-any.whl", hash = "sha256:750162629d800ac65bb3b543a14e7a74b0e88063eac9b92124d4b2aa3f6d8e86", size = 831813, upload-time = "2026-01-05T10:59:04.239Z" }, +] + +[[package]] +name = "ipywidgets" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "comm" }, + { name = "ipython" }, + { name = "jupyterlab-widgets" }, + { name = "traitlets" }, + { name = "widgetsnbextension" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/ae/c5ce1edc1afe042eadb445e95b0671b03cee61895264357956e61c0d2ac0/ipywidgets-8.1.8.tar.gz", hash = "sha256:61f969306b95f85fba6b6986b7fe45d73124d1d9e3023a8068710d47a22ea668", size = 116739, upload-time = "2025-11-01T21:18:12.393Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808, upload-time = "2025-11-01T21:18:10.956Z" }, +] + +[[package]] +name = "isodate" +version = "0.7.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, +] + +[[package]] +name = "isoduration" +version = "20.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "arrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" }, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jiter" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/9d/e0660989c1370e25848bb4c52d061c71837239738ad937e83edca174c273/jiter-0.12.0.tar.gz", hash = "sha256:64dfcd7d5c168b38d3f9f8bba7fc639edb3418abcc74f22fdbe6b8938293f30b", size = 168294, upload-time = "2025-11-09T20:49:23.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/f9/eaca4633486b527ebe7e681c431f529b63fe2709e7c5242fc0f43f77ce63/jiter-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8f8a7e317190b2c2d60eb2e8aa835270b008139562d70fe732e1c0020ec53c9", size = 316435, upload-time = "2025-11-09T20:47:02.087Z" }, + { url = "https://files.pythonhosted.org/packages/10/c1/40c9f7c22f5e6ff715f28113ebaba27ab85f9af2660ad6e1dd6425d14c19/jiter-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2218228a077e784c6c8f1a8e5d6b8cb1dea62ce25811c356364848554b2056cd", size = 320548, upload-time = "2025-11-09T20:47:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/6b/1b/efbb68fe87e7711b00d2cfd1f26bb4bfc25a10539aefeaa7727329ffb9cb/jiter-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9354ccaa2982bf2188fd5f57f79f800ef622ec67beb8329903abf6b10da7d423", size = 351915, upload-time = "2025-11-09T20:47:05.171Z" }, + { url = "https://files.pythonhosted.org/packages/15/2d/c06e659888c128ad1e838123d0638f0efad90cc30860cb5f74dd3f2fc0b3/jiter-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2607185ea89b4af9a604d4c7ec40e45d3ad03ee66998b031134bc510232bb7", size = 368966, upload-time = "2025-11-09T20:47:06.508Z" }, + { url = "https://files.pythonhosted.org/packages/6b/20/058db4ae5fb07cf6a4ab2e9b9294416f606d8e467fb74c2184b2a1eeacba/jiter-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a585a5e42d25f2e71db5f10b171f5e5ea641d3aa44f7df745aa965606111cc2", size = 482047, upload-time = "2025-11-09T20:47:08.382Z" }, + { url = "https://files.pythonhosted.org/packages/49/bb/dc2b1c122275e1de2eb12905015d61e8316b2f888bdaac34221c301495d6/jiter-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd9e21d34edff5a663c631f850edcb786719c960ce887a5661e9c828a53a95d9", size = 380835, upload-time = "2025-11-09T20:47:09.81Z" }, + { url = "https://files.pythonhosted.org/packages/23/7d/38f9cd337575349de16da575ee57ddb2d5a64d425c9367f5ef9e4612e32e/jiter-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a612534770470686cd5431478dc5a1b660eceb410abade6b1b74e320ca98de6", size = 364587, upload-time = "2025-11-09T20:47:11.529Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a3/b13e8e61e70f0bb06085099c4e2462647f53cc2ca97614f7fedcaa2bb9f3/jiter-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3985aea37d40a908f887b34d05111e0aae822943796ebf8338877fee2ab67725", size = 390492, upload-time = "2025-11-09T20:47:12.993Z" }, + { url = "https://files.pythonhosted.org/packages/07/71/e0d11422ed027e21422f7bc1883c61deba2d9752b720538430c1deadfbca/jiter-0.12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b1207af186495f48f72529f8d86671903c8c10127cac6381b11dddc4aaa52df6", size = 522046, upload-time = "2025-11-09T20:47:14.6Z" }, + { url = "https://files.pythonhosted.org/packages/9f/59/b968a9aa7102a8375dbbdfbd2aeebe563c7e5dddf0f47c9ef1588a97e224/jiter-0.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef2fb241de583934c9915a33120ecc06d94aa3381a134570f59eed784e87001e", size = 513392, upload-time = "2025-11-09T20:47:16.011Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e4/7df62002499080dbd61b505c5cb351aa09e9959d176cac2aa8da6f93b13b/jiter-0.12.0-cp311-cp311-win32.whl", hash = "sha256:453b6035672fecce8007465896a25b28a6b59cfe8fbc974b2563a92f5a92a67c", size = 206096, upload-time = "2025-11-09T20:47:17.344Z" }, + { url = "https://files.pythonhosted.org/packages/bb/60/1032b30ae0572196b0de0e87dce3b6c26a1eff71aad5fe43dee3082d32e0/jiter-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca264b9603973c2ad9435c71a8ec8b49f8f715ab5ba421c85a51cde9887e421f", size = 204899, upload-time = "2025-11-09T20:47:19.365Z" }, + { url = "https://files.pythonhosted.org/packages/49/d5/c145e526fccdb834063fb45c071df78b0cc426bbaf6de38b0781f45d956f/jiter-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:cb00ef392e7d684f2754598c02c409f376ddcef857aae796d559e6cacc2d78a5", size = 188070, upload-time = "2025-11-09T20:47:20.75Z" }, + { url = "https://files.pythonhosted.org/packages/92/c9/5b9f7b4983f1b542c64e84165075335e8a236fa9e2ea03a0c79780062be8/jiter-0.12.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:305e061fa82f4680607a775b2e8e0bcb071cd2205ac38e6ef48c8dd5ebe1cf37", size = 314449, upload-time = "2025-11-09T20:47:22.999Z" }, + { url = "https://files.pythonhosted.org/packages/98/6e/e8efa0e78de00db0aee82c0cf9e8b3f2027efd7f8a71f859d8f4be8e98ef/jiter-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1860627048e302a528333c9307c818c547f214d8659b0705d2195e1a94b274", size = 319855, upload-time = "2025-11-09T20:47:24.779Z" }, + { url = "https://files.pythonhosted.org/packages/20/26/894cd88e60b5d58af53bec5c6759d1292bd0b37a8b5f60f07abf7a63ae5f/jiter-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df37577a4f8408f7e0ec3205d2a8f87672af8f17008358063a4d6425b6081ce3", size = 350171, upload-time = "2025-11-09T20:47:26.469Z" }, + { url = "https://files.pythonhosted.org/packages/f5/27/a7b818b9979ac31b3763d25f3653ec3a954044d5e9f5d87f2f247d679fd1/jiter-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fdd787356c1c13a4f40b43c2156276ef7a71eb487d98472476476d803fb2cf", size = 365590, upload-time = "2025-11-09T20:47:27.918Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7e/e46195801a97673a83746170b17984aa8ac4a455746354516d02ca5541b4/jiter-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1eb5db8d9c65b112aacf14fcd0faae9913d07a8afea5ed06ccdd12b724e966a1", size = 479462, upload-time = "2025-11-09T20:47:29.654Z" }, + { url = "https://files.pythonhosted.org/packages/ca/75/f833bfb009ab4bd11b1c9406d333e3b4357709ed0570bb48c7c06d78c7dd/jiter-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73c568cc27c473f82480abc15d1301adf333a7ea4f2e813d6a2c7d8b6ba8d0df", size = 378983, upload-time = "2025-11-09T20:47:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/71/b3/7a69d77943cc837d30165643db753471aff5df39692d598da880a6e51c24/jiter-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4321e8a3d868919bcb1abb1db550d41f2b5b326f72df29e53b2df8b006eb9403", size = 361328, upload-time = "2025-11-09T20:47:33.286Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ac/a78f90caf48d65ba70d8c6efc6f23150bc39dc3389d65bbec2a95c7bc628/jiter-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a51bad79f8cc9cac2b4b705039f814049142e0050f30d91695a2d9a6611f126", size = 386740, upload-time = "2025-11-09T20:47:34.703Z" }, + { url = "https://files.pythonhosted.org/packages/39/b6/5d31c2cc8e1b6a6bcf3c5721e4ca0a3633d1ab4754b09bc7084f6c4f5327/jiter-0.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a67b678f6a5f1dd6c36d642d7db83e456bc8b104788262aaefc11a22339f5a9", size = 520875, upload-time = "2025-11-09T20:47:36.058Z" }, + { url = "https://files.pythonhosted.org/packages/30/b5/4df540fae4e9f68c54b8dab004bd8c943a752f0b00efd6e7d64aa3850339/jiter-0.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efe1a211fe1fd14762adea941e3cfd6c611a136e28da6c39272dbb7a1bbe6a86", size = 511457, upload-time = "2025-11-09T20:47:37.932Z" }, + { url = "https://files.pythonhosted.org/packages/07/65/86b74010e450a1a77b2c1aabb91d4a91dd3cd5afce99f34d75fd1ac64b19/jiter-0.12.0-cp312-cp312-win32.whl", hash = "sha256:d779d97c834b4278276ec703dc3fc1735fca50af63eb7262f05bdb4e62203d44", size = 204546, upload-time = "2025-11-09T20:47:40.47Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c7/6659f537f9562d963488e3e55573498a442503ced01f7e169e96a6110383/jiter-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8269062060212b373316fe69236096aaf4c49022d267c6736eebd66bbbc60bb", size = 205196, upload-time = "2025-11-09T20:47:41.794Z" }, + { url = "https://files.pythonhosted.org/packages/21/f4/935304f5169edadfec7f9c01eacbce4c90bb9a82035ac1de1f3bd2d40be6/jiter-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:06cb970936c65de926d648af0ed3d21857f026b1cf5525cb2947aa5e01e05789", size = 186100, upload-time = "2025-11-09T20:47:43.007Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a6/97209693b177716e22576ee1161674d1d58029eb178e01866a0422b69224/jiter-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6cc49d5130a14b732e0612bc76ae8db3b49898732223ef8b7599aa8d9810683e", size = 313658, upload-time = "2025-11-09T20:47:44.424Z" }, + { url = "https://files.pythonhosted.org/packages/06/4d/125c5c1537c7d8ee73ad3d530a442d6c619714b95027143f1b61c0b4dfe0/jiter-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37f27a32ce36364d2fa4f7fdc507279db604d27d239ea2e044c8f148410defe1", size = 318605, upload-time = "2025-11-09T20:47:45.973Z" }, + { url = "https://files.pythonhosted.org/packages/99/bf/a840b89847885064c41a5f52de6e312e91fa84a520848ee56c97e4fa0205/jiter-0.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc0944aa3d4b4773e348cda635252824a78f4ba44328e042ef1ff3f6080d1cf", size = 349803, upload-time = "2025-11-09T20:47:47.535Z" }, + { url = "https://files.pythonhosted.org/packages/8a/88/e63441c28e0db50e305ae23e19c1d8fae012d78ed55365da392c1f34b09c/jiter-0.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da25c62d4ee1ffbacb97fac6dfe4dcd6759ebdc9015991e92a6eae5816287f44", size = 365120, upload-time = "2025-11-09T20:47:49.284Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7c/49b02714af4343970eb8aca63396bc1c82fa01197dbb1e9b0d274b550d4e/jiter-0.12.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:048485c654b838140b007390b8182ba9774621103bd4d77c9c3f6f117474ba45", size = 479918, upload-time = "2025-11-09T20:47:50.807Z" }, + { url = "https://files.pythonhosted.org/packages/69/ba/0a809817fdd5a1db80490b9150645f3aae16afad166960bcd562be194f3b/jiter-0.12.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:635e737fbb7315bef0037c19b88b799143d2d7d3507e61a76751025226b3ac87", size = 379008, upload-time = "2025-11-09T20:47:52.211Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c3/c9fc0232e736c8877d9e6d83d6eeb0ba4e90c6c073835cc2e8f73fdeef51/jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e017c417b1ebda911bd13b1e40612704b1f5420e30695112efdbed8a4b389ed", size = 361785, upload-time = "2025-11-09T20:47:53.512Z" }, + { url = "https://files.pythonhosted.org/packages/96/61/61f69b7e442e97ca6cd53086ddc1cf59fb830549bc72c0a293713a60c525/jiter-0.12.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:89b0bfb8b2bf2351fba36bb211ef8bfceba73ef58e7f0c68fb67b5a2795ca2f9", size = 386108, upload-time = "2025-11-09T20:47:54.893Z" }, + { url = "https://files.pythonhosted.org/packages/e9/2e/76bb3332f28550c8f1eba3bf6e5efe211efda0ddbbaf24976bc7078d42a5/jiter-0.12.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f5aa5427a629a824a543672778c9ce0c5e556550d1569bb6ea28a85015287626", size = 519937, upload-time = "2025-11-09T20:47:56.253Z" }, + { url = "https://files.pythonhosted.org/packages/84/d6/fa96efa87dc8bff2094fb947f51f66368fa56d8d4fc9e77b25d7fbb23375/jiter-0.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed53b3d6acbcb0fd0b90f20c7cb3b24c357fe82a3518934d4edfa8c6898e498c", size = 510853, upload-time = "2025-11-09T20:47:58.32Z" }, + { url = "https://files.pythonhosted.org/packages/8a/28/93f67fdb4d5904a708119a6ab58a8f1ec226ff10a94a282e0215402a8462/jiter-0.12.0-cp313-cp313-win32.whl", hash = "sha256:4747de73d6b8c78f2e253a2787930f4fffc68da7fa319739f57437f95963c4de", size = 204699, upload-time = "2025-11-09T20:47:59.686Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1f/30b0eb087045a0abe2a5c9c0c0c8da110875a1d3be83afd4a9a4e548be3c/jiter-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:e25012eb0c456fcc13354255d0338cd5397cce26c77b2832b3c4e2e255ea5d9a", size = 204258, upload-time = "2025-11-09T20:48:01.01Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f4/2b4daf99b96bce6fc47971890b14b2a36aef88d7beb9f057fafa032c6141/jiter-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:c97b92c54fe6110138c872add030a1f99aea2401ddcdaa21edf74705a646dd60", size = 185503, upload-time = "2025-11-09T20:48:02.35Z" }, + { url = "https://files.pythonhosted.org/packages/39/ca/67bb15a7061d6fe20b9b2a2fd783e296a1e0f93468252c093481a2f00efa/jiter-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:53839b35a38f56b8be26a7851a48b89bc47e5d88e900929df10ed93b95fea3d6", size = 317965, upload-time = "2025-11-09T20:48:03.783Z" }, + { url = "https://files.pythonhosted.org/packages/18/af/1788031cd22e29c3b14bc6ca80b16a39a0b10e611367ffd480c06a259831/jiter-0.12.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f669548e55c91ab47fef8bddd9c954dab1938644e715ea49d7e117015110a4", size = 345831, upload-time = "2025-11-09T20:48:05.55Z" }, + { url = "https://files.pythonhosted.org/packages/05/17/710bf8472d1dff0d3caf4ced6031060091c1320f84ee7d5dcbed1f352417/jiter-0.12.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351d54f2b09a41600ffea43d081522d792e81dcfb915f6d2d242744c1cc48beb", size = 361272, upload-time = "2025-11-09T20:48:06.951Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f1/1dcc4618b59761fef92d10bcbb0b038b5160be653b003651566a185f1a5c/jiter-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2a5e90604620f94bf62264e7c2c038704d38217b7465b863896c6d7c902b06c7", size = 204604, upload-time = "2025-11-09T20:48:08.328Z" }, + { url = "https://files.pythonhosted.org/packages/d9/32/63cb1d9f1c5c6632a783c0052cde9ef7ba82688f7065e2f0d5f10a7e3edb/jiter-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:88ef757017e78d2860f96250f9393b7b577b06a956ad102c29c8237554380db3", size = 185628, upload-time = "2025-11-09T20:48:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/a8/99/45c9f0dbe4a1416b2b9a8a6d1236459540f43d7fb8883cff769a8db0612d/jiter-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c46d927acd09c67a9fb1416df45c5a04c27e83aae969267e98fba35b74e99525", size = 312478, upload-time = "2025-11-09T20:48:10.898Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a7/54ae75613ba9e0f55fcb0bc5d1f807823b5167cc944e9333ff322e9f07dd/jiter-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:774ff60b27a84a85b27b88cd5583899c59940bcc126caca97eb2a9df6aa00c49", size = 318706, upload-time = "2025-11-09T20:48:12.266Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/2aa241ad2c10774baf6c37f8b8e1f39c07db358f1329f4eb40eba179c2a2/jiter-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5433fab222fb072237df3f637d01b81f040a07dcac1cb4a5c75c7aa9ed0bef1", size = 351894, upload-time = "2025-11-09T20:48:13.673Z" }, + { url = "https://files.pythonhosted.org/packages/54/4f/0f2759522719133a9042781b18cc94e335b6d290f5e2d3e6899d6af933e3/jiter-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8c593c6e71c07866ec6bfb790e202a833eeec885022296aff6b9e0b92d6a70e", size = 365714, upload-time = "2025-11-09T20:48:15.083Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6f/806b895f476582c62a2f52c453151edd8a0fde5411b0497baaa41018e878/jiter-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90d32894d4c6877a87ae00c6b915b609406819dce8bc0d4e962e4de2784e567e", size = 478989, upload-time = "2025-11-09T20:48:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/86/6c/012d894dc6e1033acd8db2b8346add33e413ec1c7c002598915278a37f79/jiter-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:798e46eed9eb10c3adbbacbd3bdb5ecd4cf7064e453d00dbef08802dae6937ff", size = 378615, upload-time = "2025-11-09T20:48:18.614Z" }, + { url = "https://files.pythonhosted.org/packages/87/30/d718d599f6700163e28e2c71c0bbaf6dace692e7df2592fd793ac9276717/jiter-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3f1368f0a6719ea80013a4eb90ba72e75d7ea67cfc7846db2ca504f3df0169a", size = 364745, upload-time = "2025-11-09T20:48:20.117Z" }, + { url = "https://files.pythonhosted.org/packages/8f/85/315b45ce4b6ddc7d7fceca24068543b02bdc8782942f4ee49d652e2cc89f/jiter-0.12.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f04a9d0b4406f7e51279710b27484af411896246200e461d80d3ba0caa901a", size = 386502, upload-time = "2025-11-09T20:48:21.543Z" }, + { url = "https://files.pythonhosted.org/packages/74/0b/ce0434fb40c5b24b368fe81b17074d2840748b4952256bab451b72290a49/jiter-0.12.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fd990541982a24281d12b67a335e44f117e4c6cbad3c3b75c7dea68bf4ce3a67", size = 519845, upload-time = "2025-11-09T20:48:22.964Z" }, + { url = "https://files.pythonhosted.org/packages/e8/a3/7a7a4488ba052767846b9c916d208b3ed114e3eb670ee984e4c565b9cf0d/jiter-0.12.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b111b0e9152fa7df870ecaebb0bd30240d9f7fff1f2003bcb4ed0f519941820b", size = 510701, upload-time = "2025-11-09T20:48:24.483Z" }, + { url = "https://files.pythonhosted.org/packages/c3/16/052ffbf9d0467b70af24e30f91e0579e13ded0c17bb4a8eb2aed3cb60131/jiter-0.12.0-cp314-cp314-win32.whl", hash = "sha256:a78befb9cc0a45b5a5a0d537b06f8544c2ebb60d19d02c41ff15da28a9e22d42", size = 205029, upload-time = "2025-11-09T20:48:25.749Z" }, + { url = "https://files.pythonhosted.org/packages/e4/18/3cf1f3f0ccc789f76b9a754bdb7a6977e5d1d671ee97a9e14f7eb728d80e/jiter-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:e1fe01c082f6aafbe5c8faf0ff074f38dfb911d53f07ec333ca03f8f6226debf", size = 204960, upload-time = "2025-11-09T20:48:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/736821e52ecfdeeb0f024b8ab01b5a229f6b9293bbdb444c27efade50b0f/jiter-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:d72f3b5a432a4c546ea4bedc84cce0c3404874f1d1676260b9c7f048a9855451", size = 185529, upload-time = "2025-11-09T20:48:29.125Z" }, + { url = "https://files.pythonhosted.org/packages/30/61/12ed8ee7a643cce29ac97c2281f9ce3956eb76b037e88d290f4ed0d41480/jiter-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6ded41aeba3603f9728ed2b6196e4df875348ab97b28fc8afff115ed42ba7a7", size = 318974, upload-time = "2025-11-09T20:48:30.87Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c6/f3041ede6d0ed5e0e79ff0de4c8f14f401bbf196f2ef3971cdbe5fd08d1d/jiter-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a947920902420a6ada6ad51892082521978e9dd44a802663b001436e4b771684", size = 345932, upload-time = "2025-11-09T20:48:32.658Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5d/4d94835889edd01ad0e2dbfc05f7bdfaed46292e7b504a6ac7839aa00edb/jiter-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add5e227e0554d3a52cf390a7635edaffdf4f8fce4fdbcef3cc2055bb396a30c", size = 367243, upload-time = "2025-11-09T20:48:34.093Z" }, + { url = "https://files.pythonhosted.org/packages/fd/76/0051b0ac2816253a99d27baf3dda198663aff882fa6ea7deeb94046da24e/jiter-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9b1cda8fcb736250d7e8711d4580ebf004a46771432be0ae4796944b5dfa5d", size = 479315, upload-time = "2025-11-09T20:48:35.507Z" }, + { url = "https://files.pythonhosted.org/packages/70/ae/83f793acd68e5cb24e483f44f482a1a15601848b9b6f199dacb970098f77/jiter-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb12a2223fe0135c7ff1356a143d57f95bbf1f4a66584f1fc74df21d86b993", size = 380714, upload-time = "2025-11-09T20:48:40.014Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/4808a88338ad2c228b1126b93fcd8ba145e919e886fe910d578230dabe3b/jiter-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c596cc0f4cb574877550ce4ecd51f8037469146addd676d7c1a30ebe6391923f", size = 365168, upload-time = "2025-11-09T20:48:41.462Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d4/04619a9e8095b42aef436b5aeb4c0282b4ff1b27d1db1508df9f5dc82750/jiter-0.12.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ab4c823b216a4aeab3fdbf579c5843165756bd9ad87cc6b1c65919c4715f783", size = 387893, upload-time = "2025-11-09T20:48:42.921Z" }, + { url = "https://files.pythonhosted.org/packages/17/ea/d3c7e62e4546fdc39197fa4a4315a563a89b95b6d54c0d25373842a59cbe/jiter-0.12.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e427eee51149edf962203ff8db75a7514ab89be5cb623fb9cea1f20b54f1107b", size = 520828, upload-time = "2025-11-09T20:48:44.278Z" }, + { url = "https://files.pythonhosted.org/packages/cc/0b/c6d3562a03fd767e31cb119d9041ea7958c3c80cb3d753eafb19b3b18349/jiter-0.12.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:edb868841f84c111255ba5e80339d386d937ec1fdce419518ce1bd9370fac5b6", size = 511009, upload-time = "2025-11-09T20:48:45.726Z" }, + { url = "https://files.pythonhosted.org/packages/aa/51/2cb4468b3448a8385ebcd15059d325c9ce67df4e2758d133ab9442b19834/jiter-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8bbcfe2791dfdb7c5e48baf646d37a6a3dcb5a97a032017741dea9f817dca183", size = 205110, upload-time = "2025-11-09T20:48:47.033Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c5/ae5ec83dec9c2d1af805fd5fe8f74ebded9c8670c5210ec7820ce0dbeb1e/jiter-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fa940963bf02e1d8226027ef461e36af472dea85d36054ff835aeed944dd873", size = 205223, upload-time = "2025-11-09T20:48:49.076Z" }, + { url = "https://files.pythonhosted.org/packages/97/9a/3c5391907277f0e55195550cf3fa8e293ae9ee0c00fb402fec1e38c0c82f/jiter-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:506c9708dd29b27288f9f8f1140c3cb0e3d8ddb045956d7757b1fa0e0f39a473", size = 185564, upload-time = "2025-11-09T20:48:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/5339ef1ecaa881c6948669956567a64d2670941925f245c434f494ffb0e5/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:4739a4657179ebf08f85914ce50332495811004cc1747852e8b2041ed2aab9b8", size = 311144, upload-time = "2025-11-09T20:49:10.503Z" }, + { url = "https://files.pythonhosted.org/packages/27/74/3446c652bffbd5e81ab354e388b1b5fc1d20daac34ee0ed11ff096b1b01a/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:41da8def934bf7bec16cb24bd33c0ca62126d2d45d81d17b864bd5ad721393c3", size = 305877, upload-time = "2025-11-09T20:49:12.269Z" }, + { url = "https://files.pythonhosted.org/packages/a1/f4/ed76ef9043450f57aac2d4fbeb27175aa0eb9c38f833be6ef6379b3b9a86/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c44ee814f499c082e69872d426b624987dbc5943ab06e9bbaa4f81989fdb79e", size = 340419, upload-time = "2025-11-09T20:49:13.803Z" }, + { url = "https://files.pythonhosted.org/packages/21/01/857d4608f5edb0664aa791a3d45702e1a5bcfff9934da74035e7b9803846/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2097de91cf03eaa27b3cbdb969addf83f0179c6afc41bbc4513705e013c65d", size = 347212, upload-time = "2025-11-09T20:49:15.643Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f5/12efb8ada5f5c9edc1d4555fe383c1fb2eac05ac5859258a72d61981d999/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e8547883d7b96ef2e5fe22b88f8a4c8725a56e7f4abafff20fd5272d634c7ecb", size = 309974, upload-time = "2025-11-09T20:49:17.187Z" }, + { url = "https://files.pythonhosted.org/packages/85/15/d6eb3b770f6a0d332675141ab3962fd4a7c270ede3515d9f3583e1d28276/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:89163163c0934854a668ed783a2546a0617f71706a2551a4a0666d91ab365d6b", size = 304233, upload-time = "2025-11-09T20:49:18.734Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3e/e7e06743294eea2cf02ced6aa0ff2ad237367394e37a0e2b4a1108c67a36/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96b264ab7d34bbb2312dedc47ce07cd53f06835eacbc16dde3761f47c3a9e7f", size = 338537, upload-time = "2025-11-09T20:49:20.317Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9c/6753e6522b8d0ef07d3a3d239426669e984fb0eba15a315cdbc1253904e4/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24e864cb30ab82311c6425655b0cdab0a98c5d973b065c66a3f020740c2324c", size = 346110, upload-time = "2025-11-09T20:49:21.817Z" }, +] + +[[package]] +name = "jmespath" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/59/322338183ecda247fb5d1763a6cbe46eff7222eaeebafd9fa65d4bf5cb11/jmespath-1.1.0.tar.gz", hash = "sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d", size = 27377, upload-time = "2026-01-22T16:35:26.279Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl", hash = "sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64", size = 20419, upload-time = "2026-01-22T16:35:24.919Z" }, +] + +[[package]] +name = "joblib" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, +] + +[[package]] +name = "json5" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/77/e8/a3f261a66e4663f22700bc8a17c08cb83e91fbf086726e7a228398968981/json5-0.13.0.tar.gz", hash = "sha256:b1edf8d487721c0bf64d83c28e91280781f6e21f4a797d3261c7c828d4c165bf", size = 52441, upload-time = "2026-01-01T19:42:14.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl", hash = "sha256:9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc", size = 36163, upload-time = "2026-01-01T19:42:13.962Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[package.optional-dependencies] +format-nongpl = [ + { name = "fqdn" }, + { name = "idna" }, + { name = "isoduration" }, + { name = "jsonpointer" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "rfc3987-syntax" }, + { name = "uri-template" }, + { name = "webcolors" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "jupyter" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "ipywidgets" }, + { name = "jupyter-console" }, + { name = "jupyterlab" }, + { name = "nbconvert" }, + { name = "notebook" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/f3/af28ea964ab8bc1e472dba2e82627d36d470c51f5cd38c37502eeffaa25e/jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a", size = 5714959, upload-time = "2024-08-30T07:15:48.299Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83", size = 2657, upload-time = "2024-08-30T07:15:47.045Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.8.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/e4/ba649102a3bc3fbca54e7239fb924fd434c766f855693d86de0b1f2bec81/jupyter_client-8.8.0.tar.gz", hash = "sha256:d556811419a4f2d96c869af34e854e3f059b7cc2d6d01a9cd9c85c267691be3e", size = 348020, upload-time = "2026-01-08T13:55:47.938Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/0b/ceb7694d864abc0a047649aec263878acb9f792e1fec3e676f22dc9015e3/jupyter_client-8.8.0-py3-none-any.whl", hash = "sha256:f93a5b99c5e23a507b773d3a1136bd6e16c67883ccdbd9a829b0bbdb98cd7d7a", size = 107371, upload-time = "2026-01-08T13:55:45.562Z" }, +] + +[[package]] +name = "jupyter-console" +version = "6.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "pyzmq" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/2d/e2fd31e2fc41c14e2bcb6c976ab732597e907523f6b2420305f9fc7fdbdb/jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539", size = 34363, upload-time = "2023-03-06T14:13:31.02Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/77/71d78d58f15c22db16328a476426f7ac4a60d3a5a7ba3b9627ee2f7903d4/jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485", size = 24510, upload-time = "2023-03-06T14:13:28.229Z" }, +] + +[[package]] +name = "jupyter-core" +version = "5.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/02/49/9d1284d0dc65e2c757b74c6687b6d319b02f822ad039e5c512df9194d9dd/jupyter_core-5.9.1.tar.gz", hash = "sha256:4d09aaff303b9566c3ce657f580bd089ff5c91f5f89cf7d8846c3cdf465b5508", size = 89814, upload-time = "2025-10-16T19:19:18.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" }, +] + +[[package]] +name = "jupyter-events" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonschema", extra = ["format-nongpl"] }, + { name = "packaging" }, + { name = "python-json-logger" }, + { name = "pyyaml" }, + { name = "referencing" }, + { name = "rfc3339-validator" }, + { name = "rfc3986-validator" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" }, +] + +[[package]] +name = "jupyter-lsp" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/5a/9066c9f8e94ee517133cd98dba393459a16cd48bba71a82f16a65415206c/jupyter_lsp-2.3.0.tar.gz", hash = "sha256:458aa59339dc868fb784d73364f17dbce8836e906cd75fd471a325cba02e0245", size = 54823, upload-time = "2025-08-27T17:47:34.671Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/60/1f6cee0c46263de1173894f0fafcb3475ded276c472c14d25e0280c18d6d/jupyter_lsp-2.3.0-py3-none-any.whl", hash = "sha256:e914a3cb2addf48b1c7710914771aaf1819d46b2e5a79b0f917b5478ec93f34f", size = 76687, upload-time = "2025-08-27T17:47:33.15Z" }, +] + +[[package]] +name = "jupyter-server" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "argon2-cffi" }, + { name = "jinja2" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "jupyter-events" }, + { name = "jupyter-server-terminals" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "overrides", marker = "python_full_version < '3.12'" }, + { name = "packaging" }, + { name = "prometheus-client" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, + { name = "websocket-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" }, +] + +[[package]] +name = "jupyter-server-terminals" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "terminado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/a7/bcd0a9b0cbba88986fe944aaaf91bfda603e5a50bda8ed15123f381a3b2f/jupyter_server_terminals-0.5.4.tar.gz", hash = "sha256:bbda128ed41d0be9020349f9f1f2a4ab9952a73ed5f5ac9f1419794761fb87f5", size = 31770, upload-time = "2026-01-14T16:53:20.213Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/2d/6674563f71c6320841fc300911a55143925112a72a883e2ca71fba4c618d/jupyter_server_terminals-0.5.4-py3-none-any.whl", hash = "sha256:55be353fc74a80bc7f3b20e6be50a55a61cd525626f578dcb66a5708e2007d14", size = 13704, upload-time = "2026-01-14T16:53:18.738Z" }, +] + +[[package]] +name = "jupyterlab" +version = "4.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-lru" }, + { name = "httpx" }, + { name = "ipykernel" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyter-lsp" }, + { name = "jupyter-server" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "packaging" }, + { name = "setuptools" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/76/393eae3349f9a39bf21f8f5406e5244d36e2bfc932049b6070c271f92764/jupyterlab-4.5.3.tar.gz", hash = "sha256:4a159f71067cb38e4a82e86a42de8e7e926f384d7f2291964f282282096d27e8", size = 23939231, upload-time = "2026-01-23T15:04:25.768Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/9a/0bf9a7a45f0006d7ff4fdc4fc313de4255acab02bf4db1887c65f0472c01/jupyterlab-4.5.3-py3-none-any.whl", hash = "sha256:63c9f3a48de72ba00df766ad6eed416394f5bb883829f11eeff0872302520ba7", size = 12391761, upload-time = "2026-01-23T15:04:21.214Z" }, +] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/90/51/9187be60d989df97f5f0aba133fa54e7300f17616e065d1ada7d7646b6d6/jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d", size = 512900, upload-time = "2023-11-23T09:26:37.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" }, +] + +[[package]] +name = "jupyterlab-server" +version = "2.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "jinja2" }, + { name = "json5" }, + { name = "jsonschema" }, + { name = "jupyter-server" }, + { name = "packaging" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/90153f189e421e93c4bb4f9e3f59802a1f01abd2ac5cf40b152d7f735232/jupyterlab_server-2.28.0.tar.gz", hash = "sha256:35baa81898b15f93573e2deca50d11ac0ae407ebb688299d3a5213265033712c", size = 76996, upload-time = "2025-10-22T13:59:18.37Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/07/a000fe835f76b7e1143242ab1122e6362ef1c03f23f83a045c38859c2ae0/jupyterlab_server-2.28.0-py3-none-any.whl", hash = "sha256:e4355b148fdcf34d312bbbc80f22467d6d20460e8b8736bf235577dd18506968", size = 59830, upload-time = "2025-10-22T13:59:16.767Z" }, +] + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.16" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423, upload-time = "2025-11-01T21:11:29.724Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" }, +] + +[[package]] +name = "lark" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" }, +] + +[[package]] +name = "limits" +version = "5.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "deprecated" }, + { name = "packaging" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/e5/c968d43a65128cd54fb685f257aafb90cd5e4e1c67d084a58f0e4cbed557/limits-5.6.0.tar.gz", hash = "sha256:807fac75755e73912e894fdd61e2838de574c5721876a19f7ab454ae1fffb4b5", size = 182984, upload-time = "2025-09-29T17:15:22.689Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/96/4fcd44aed47b8fcc457653b12915fcad192cd646510ef3f29fd216f4b0ab/limits-5.6.0-py3-none-any.whl", hash = "sha256:b585c2104274528536a5b68864ec3835602b3c4a802cd6aa0b07419798394021", size = 60604, upload-time = "2025-09-29T17:15:18.419Z" }, +] + +[[package]] +name = "litellm" +version = "1.81.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "click" }, + { name = "fastuuid" }, + { name = "httpx" }, + { name = "importlib-metadata" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "openai" }, + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "tiktoken" }, + { name = "tokenizers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/f4/c109bc5504520baa7b96a910b619d1b1b5af6cb5c28053e53adfed83e3ab/litellm-1.81.5.tar.gz", hash = "sha256:599994651cbb64b8ee7cd3b4979275139afc6e426bdd4aa840a61121bb3b04c9", size = 13615436, upload-time = "2026-01-29T01:37:54.817Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/0f/5312b944208efeec5dcbf8e0ed956f8f7c430b0c6458301d206380c90b56/litellm-1.81.5-py3-none-any.whl", hash = "sha256:206505c5a0c6503e465154b9c979772be3ede3f5bf746d15b37dca5ae54d239f", size = 11950016, upload-time = "2026-01-29T01:37:52.6Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/74/97e72a36efd4ae2bccb3463284300f8953f199b5ffbc04cbbb0ec78f74b1/matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe", size = 8110, upload-time = "2025-10-23T09:00:22.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76", size = 9516, upload-time = "2025-10-23T09:00:20.675Z" }, +] + +[[package]] +name = "mistune" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, +] + +[[package]] +name = "msal" +version = "1.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" }, +] + +[[package]] +name = "msal-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/f2/22/929c141d6c0dba87d3e1d38fbdf1ba8baba86b7776469f2bc2d3227a1e67/multidict-6.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2b41f5fed0ed563624f1c17630cb9941cf2309d4df00e494b551b5f3e3d67a23", size = 76174, upload-time = "2026-01-26T02:44:18.509Z" }, + { url = "https://files.pythonhosted.org/packages/c7/75/bc704ae15fee974f8fccd871305e254754167dce5f9e42d88a2def741a1d/multidict-6.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84e61e3af5463c19b67ced91f6c634effb89ef8bfc5ca0267f954451ed4bb6a2", size = 45116, upload-time = "2026-01-26T02:44:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445", size = 43524, upload-time = "2026-01-26T02:44:21.571Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3c/414842ef8d5a1628d68edee29ba0e5bcf235dbfb3ccd3ea303a7fe8c72ff/multidict-6.7.1-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:432feb25a1cb67fe82a9680b4d65fb542e4635cb3166cd9c01560651ad60f177", size = 249368, upload-time = "2026-01-26T02:44:22.803Z" }, + { url = "https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23", size = 256952, upload-time = "2026-01-26T02:44:24.306Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/c878a44ba877f366630c860fdf74bfb203c33778f12b6ac274936853c451/multidict-6.7.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4cfb48c6ea66c83bcaaf7e4dfa7ec1b6bbcf751b7db85a328902796dfde4c060", size = 240317, upload-time = "2026-01-26T02:44:25.772Z" }, + { url = "https://files.pythonhosted.org/packages/68/49/57421b4d7ad2e9e60e25922b08ceb37e077b90444bde6ead629095327a6f/multidict-6.7.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d540e51b7e8e170174555edecddbd5538105443754539193e3e1061864d444d", size = 267132, upload-time = "2026-01-26T02:44:27.648Z" }, + { url = "https://files.pythonhosted.org/packages/b7/fe/ec0edd52ddbcea2a2e89e174f0206444a61440b40f39704e64dc807a70bd/multidict-6.7.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:273d23f4b40f3dce4d6c8a821c741a86dec62cded82e1175ba3d99be128147ed", size = 268140, upload-time = "2026-01-26T02:44:29.588Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429", size = 254277, upload-time = "2026-01-26T02:44:30.902Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b2/5fb8c124d7561a4974c342bc8c778b471ebbeb3cc17df696f034a7e9afe7/multidict-6.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:12fad252f8b267cc75b66e8fc51b3079604e8d43a75428ffe193cd9e2195dfd6", size = 252291, upload-time = "2026-01-26T02:44:32.31Z" }, + { url = "https://files.pythonhosted.org/packages/5a/96/51d4e4e06bcce92577fcd488e22600bd38e4fd59c20cb49434d054903bd2/multidict-6.7.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:03ede2a6ffbe8ef936b92cb4529f27f42be7f56afcdab5ab739cd5f27fb1cbf9", size = 250156, upload-time = "2026-01-26T02:44:33.734Z" }, + { url = "https://files.pythonhosted.org/packages/db/6b/420e173eec5fba721a50e2a9f89eda89d9c98fded1124f8d5c675f7a0c0f/multidict-6.7.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:90efbcf47dbe33dcf643a1e400d67d59abeac5db07dc3f27d6bdeae497a2198c", size = 249742, upload-time = "2026-01-26T02:44:35.222Z" }, + { url = "https://files.pythonhosted.org/packages/44/a3/ec5b5bd98f306bc2aa297b8c6f11a46714a56b1e6ef5ebda50a4f5d7c5fb/multidict-6.7.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c4b9bfc148f5a91be9244d6264c53035c8a0dcd2f51f1c3c6e30e30ebaa1c84", size = 262221, upload-time = "2026-01-26T02:44:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/cd/f7/e8c0d0da0cd1e28d10e624604e1a36bcc3353aaebdfdc3a43c72bc683a12/multidict-6.7.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:401c5a650f3add2472d1d288c26deebc540f99e2fb83e9525007a74cd2116f1d", size = 258664, upload-time = "2026-01-26T02:44:38.008Z" }, + { url = "https://files.pythonhosted.org/packages/52/da/151a44e8016dd33feed44f730bd856a66257c1ee7aed4f44b649fb7edeb3/multidict-6.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:97891f3b1b3ffbded884e2916cacf3c6fc87b66bb0dde46f7357404750559f33", size = 249490, upload-time = "2026-01-26T02:44:39.386Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/a3b86bf9630b732897f6fc3f4c4714b90aa4361983ccbdcd6c0339b21b0c/multidict-6.7.1-cp313-cp313-win32.whl", hash = "sha256:e1c5988359516095535c4301af38d8a8838534158f649c05dd1050222321bcb3", size = 41695, upload-time = "2026-01-26T02:44:41.318Z" }, + { url = "https://files.pythonhosted.org/packages/b2/35/e994121b0e90e46134673422dd564623f93304614f5d11886b1b3e06f503/multidict-6.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:960c83bf01a95b12b08fd54324a4eb1d5b52c88932b5cba5d6e712bb3ed12eb5", size = 45884, upload-time = "2026-01-26T02:44:42.488Z" }, + { url = "https://files.pythonhosted.org/packages/ca/61/42d3e5dbf661242a69c97ea363f2d7b46c567da8eadef8890022be6e2ab0/multidict-6.7.1-cp313-cp313-win_arm64.whl", hash = "sha256:563fe25c678aaba333d5399408f5ec3c383ca5b663e7f774dd179a520b8144df", size = 43122, upload-time = "2026-01-26T02:44:43.664Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b3/e6b21c6c4f314bb956016b0b3ef2162590a529b84cb831c257519e7fde44/multidict-6.7.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c76c4bec1538375dad9d452d246ca5368ad6e1c9039dadcf007ae59c70619ea1", size = 83175, upload-time = "2026-01-26T02:44:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/23ecd2abfe0957b234f6c960f4ade497f55f2c16aeb684d4ecdbf1c95791/multidict-6.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:57b46b24b5d5ebcc978da4ec23a819a9402b4228b8a90d9c656422b4bdd8a963", size = 48460, upload-time = "2026-01-26T02:44:46.106Z" }, + { url = "https://files.pythonhosted.org/packages/c4/57/a0ed92b23f3a042c36bc4227b72b97eca803f5f1801c1ab77c8a212d455e/multidict-6.7.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e954b24433c768ce78ab7929e84ccf3422e46deb45a4dc9f93438f8217fa2d34", size = 46930, upload-time = "2026-01-26T02:44:47.278Z" }, + { url = "https://files.pythonhosted.org/packages/b5/66/02ec7ace29162e447f6382c495dc95826bf931d3818799bbef11e8f7df1a/multidict-6.7.1-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3bd231490fa7217cc832528e1cd8752a96f0125ddd2b5749390f7c3ec8721b65", size = 242582, upload-time = "2026-01-26T02:44:48.604Z" }, + { url = "https://files.pythonhosted.org/packages/58/18/64f5a795e7677670e872673aca234162514696274597b3708b2c0d276cce/multidict-6.7.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:253282d70d67885a15c8a7716f3a73edf2d635793ceda8173b9ecc21f2fb8292", size = 250031, upload-time = "2026-01-26T02:44:50.544Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ed/e192291dbbe51a8290c5686f482084d31bcd9d09af24f63358c3d42fd284/multidict-6.7.1-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b4c48648d7649c9335cf1927a8b87fa692de3dcb15faa676c6a6f1f1aabda43", size = 228596, upload-time = "2026-01-26T02:44:51.951Z" }, + { url = "https://files.pythonhosted.org/packages/1e/7e/3562a15a60cf747397e7f2180b0a11dc0c38d9175a650e75fa1b4d325e15/multidict-6.7.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98bc624954ec4d2c7cb074b8eefc2b5d0ce7d482e410df446414355d158fe4ca", size = 257492, upload-time = "2026-01-26T02:44:53.902Z" }, + { url = "https://files.pythonhosted.org/packages/24/02/7d0f9eae92b5249bb50ac1595b295f10e263dd0078ebb55115c31e0eaccd/multidict-6.7.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1b99af4d9eec0b49927b4402bcbb58dea89d3e0db8806a4086117019939ad3dd", size = 255899, upload-time = "2026-01-26T02:44:55.316Z" }, + { url = "https://files.pythonhosted.org/packages/00/e3/9b60ed9e23e64c73a5cde95269ef1330678e9c6e34dd4eb6b431b85b5a10/multidict-6.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aac4f16b472d5b7dc6f66a0d49dd57b0e0902090be16594dc9ebfd3d17c47e7", size = 247970, upload-time = "2026-01-26T02:44:56.783Z" }, + { url = "https://files.pythonhosted.org/packages/3e/06/538e58a63ed5cfb0bd4517e346b91da32fde409d839720f664e9a4ae4f9d/multidict-6.7.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:21f830fe223215dffd51f538e78c172ed7c7f60c9b96a2bf05c4848ad49921c3", size = 245060, upload-time = "2026-01-26T02:44:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/b2/2f/d743a3045a97c895d401e9bd29aaa09b94f5cbdf1bd561609e5a6c431c70/multidict-6.7.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f5dd81c45b05518b9aa4da4aa74e1c93d715efa234fd3e8a179df611cc85e5f4", size = 235888, upload-time = "2026-01-26T02:44:59.57Z" }, + { url = "https://files.pythonhosted.org/packages/38/83/5a325cac191ab28b63c52f14f1131f3b0a55ba3b9aa65a6d0bf2a9b921a0/multidict-6.7.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:eb304767bca2bb92fb9c5bd33cedc95baee5bb5f6c88e63706533a1c06ad08c8", size = 243554, upload-time = "2026-01-26T02:45:01.054Z" }, + { url = "https://files.pythonhosted.org/packages/20/1f/9d2327086bd15da2725ef6aae624208e2ef828ed99892b17f60c344e57ed/multidict-6.7.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c9035dde0f916702850ef66460bc4239d89d08df4d02023a5926e7446724212c", size = 252341, upload-time = "2026-01-26T02:45:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/e8/2c/2a1aa0280cf579d0f6eed8ee5211c4f1730bd7e06c636ba2ee6aafda302e/multidict-6.7.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:af959b9beeb66c822380f222f0e0a1889331597e81f1ded7f374f3ecb0fd6c52", size = 246391, upload-time = "2026-01-26T02:45:03.862Z" }, + { url = "https://files.pythonhosted.org/packages/e5/03/7ca022ffc36c5a3f6e03b179a5ceb829be9da5783e6fe395f347c0794680/multidict-6.7.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:41f2952231456154ee479651491e94118229844dd7226541788be783be2b5108", size = 243422, upload-time = "2026-01-26T02:45:05.296Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1d/b31650eab6c5778aceed46ba735bd97f7c7d2f54b319fa916c0f96e7805b/multidict-6.7.1-cp313-cp313t-win32.whl", hash = "sha256:df9f19c28adcb40b6aae30bbaa1478c389efd50c28d541d76760199fc1037c32", size = 47770, upload-time = "2026-01-26T02:45:06.754Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5b/2d2d1d522e51285bd61b1e20df8f47ae1a9d80839db0b24ea783b3832832/multidict-6.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:d54ecf9f301853f2c5e802da559604b3e95bb7a3b01a9c295c6ee591b9882de8", size = 53109, upload-time = "2026-01-26T02:45:08.044Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a3/cc409ba012c83ca024a308516703cf339bdc4b696195644a7215a5164a24/multidict-6.7.1-cp313-cp313t-win_arm64.whl", hash = "sha256:5a37ca18e360377cfda1d62f5f382ff41f2b8c4ccb329ed974cc2e1643440118", size = 45573, upload-time = "2026-01-26T02:45:09.349Z" }, + { url = "https://files.pythonhosted.org/packages/91/cc/db74228a8be41884a567e88a62fd589a913708fcf180d029898c17a9a371/multidict-6.7.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f333ec9c5eb1b7105e3b84b53141e66ca05a19a605368c55450b6ba208cb9ee", size = 75190, upload-time = "2026-01-26T02:45:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/d5/22/492f2246bb5b534abd44804292e81eeaf835388901f0c574bac4eeec73c5/multidict-6.7.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a407f13c188f804c759fc6a9f88286a565c242a76b27626594c133b82883b5c2", size = 44486, upload-time = "2026-01-26T02:45:11.938Z" }, + { url = "https://files.pythonhosted.org/packages/f1/4f/733c48f270565d78b4544f2baddc2fb2a245e5a8640254b12c36ac7ac68e/multidict-6.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0e161ddf326db5577c3a4cc2d8648f81456e8a20d40415541587a71620d7a7d1", size = 43219, upload-time = "2026-01-26T02:45:14.346Z" }, + { url = "https://files.pythonhosted.org/packages/24/bb/2c0c2287963f4259c85e8bcbba9182ced8d7fca65c780c38e99e61629d11/multidict-6.7.1-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1e3a8bb24342a8201d178c3b4984c26ba81a577c80d4d525727427460a50c22d", size = 245132, upload-time = "2026-01-26T02:45:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f9/44d4b3064c65079d2467888794dea218d1601898ac50222ab8a9a8094460/multidict-6.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97231140a50f5d447d3164f994b86a0bed7cd016e2682f8650d6a9158e14fd31", size = 252420, upload-time = "2026-01-26T02:45:17.293Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/78f7275e73fa17b24c9a51b0bd9d73ba64bb32d0ed51b02a746eb876abe7/multidict-6.7.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b10359683bd8806a200fd2909e7c8ca3a7b24ec1d8132e483d58e791d881048", size = 233510, upload-time = "2026-01-26T02:45:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/4b/25/8167187f62ae3cbd52da7893f58cb036b47ea3fb67138787c76800158982/multidict-6.7.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:283ddac99f7ac25a4acadbf004cb5ae34480bbeb063520f70ce397b281859362", size = 264094, upload-time = "2026-01-26T02:45:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e7/69a3a83b7b030cf283fb06ce074a05a02322359783424d7edf0f15fe5022/multidict-6.7.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:538cec1e18c067d0e6103aa9a74f9e832904c957adc260e61cd9d8cf0c3b3d37", size = 260786, upload-time = "2026-01-26T02:45:22.818Z" }, + { url = "https://files.pythonhosted.org/packages/fe/3b/8ec5074bcfc450fe84273713b4b0a0dd47c0249358f5d82eb8104ffe2520/multidict-6.7.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eee46ccb30ff48a1e35bb818cc90846c6be2b68240e42a78599166722cea709", size = 248483, upload-time = "2026-01-26T02:45:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/d5a99e3acbca0e29c5d9cba8f92ceb15dce78bab963b308ae692981e3a5d/multidict-6.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa263a02f4f2dd2d11a7b1bb4362aa7cb1049f84a9235d31adf63f30143469a0", size = 248403, upload-time = "2026-01-26T02:45:25.982Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/e58cd31f6c7d5102f2a4bf89f96b9cf7e00b6c6f3d04ecc44417c00a5a3c/multidict-6.7.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:2e1425e2f99ec5bd36c15a01b690a1a2456209c5deed58f95469ffb46039ccbb", size = 240315, upload-time = "2026-01-26T02:45:27.487Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/1cd210229559cb90b6786c30676bb0c58249ff42f942765f88793b41fdce/multidict-6.7.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:497394b3239fc6f0e13a78a3e1b61296e72bf1c5f94b4c4eb80b265c37a131cd", size = 245528, upload-time = "2026-01-26T02:45:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6e1107d226278c876c783056b7db43d800bb64c6131cec9c8dfb6903698e/multidict-6.7.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:233b398c29d3f1b9676b4b6f75c518a06fcb2ea0b925119fb2c1bc35c05e1601", size = 258784, upload-time = "2026-01-26T02:45:30.503Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c1/11f664f14d525e4a1b5327a82d4de61a1db604ab34c6603bb3c2cc63ad34/multidict-6.7.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:93b1818e4a6e0930454f0f2af7dfce69307ca03cdcfb3739bf4d91241967b6c1", size = 251980, upload-time = "2026-01-26T02:45:32.603Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9f/75a9ac888121d0c5bbd4ecf4eead45668b1766f6baabfb3b7f66a410e231/multidict-6.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f33dc2a3abe9249ea5d8360f969ec7f4142e7ac45ee7014d8f8d5acddf178b7b", size = 243602, upload-time = "2026-01-26T02:45:34.043Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e7/50bf7b004cc8525d80dbbbedfdc7aed3e4c323810890be4413e589074032/multidict-6.7.1-cp314-cp314-win32.whl", hash = "sha256:3ab8b9d8b75aef9df299595d5388b14530839f6422333357af1339443cff777d", size = 40930, upload-time = "2026-01-26T02:45:36.278Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bf/52f25716bbe93745595800f36fb17b73711f14da59ed0bb2eba141bc9f0f/multidict-6.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:5e01429a929600e7dab7b166062d9bb54a5eed752384c7384c968c2afab8f50f", size = 45074, upload-time = "2026-01-26T02:45:37.546Z" }, + { url = "https://files.pythonhosted.org/packages/97/ab/22803b03285fa3a525f48217963da3a65ae40f6a1b6f6cf2768879e208f9/multidict-6.7.1-cp314-cp314-win_arm64.whl", hash = "sha256:4885cb0e817aef5d00a2e8451d4665c1808378dc27c2705f1bf4ef8505c0d2e5", size = 42471, upload-time = "2026-01-26T02:45:38.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/6d/f9293baa6146ba9507e360ea0292b6422b016907c393e2f63fc40ab7b7b5/multidict-6.7.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0458c978acd8e6ea53c81eefaddbbee9c6c5e591f41b3f5e8e194780fe026581", size = 82401, upload-time = "2026-01-26T02:45:40.254Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/53b5494738d83558d87c3c71a486504d8373421c3e0dbb6d0db48ad42ee0/multidict-6.7.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:c0abd12629b0af3cf590982c0b413b1e7395cd4ec026f30986818ab95bfaa94a", size = 48143, upload-time = "2026-01-26T02:45:41.635Z" }, + { url = "https://files.pythonhosted.org/packages/37/e8/5284c53310dcdc99ce5d66563f6e5773531a9b9fe9ec7a615e9bc306b05f/multidict-6.7.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:14525a5f61d7d0c94b368a42cff4c9a4e7ba2d52e2672a7b23d84dc86fb02b0c", size = 46507, upload-time = "2026-01-26T02:45:42.99Z" }, + { url = "https://files.pythonhosted.org/packages/e4/fc/6800d0e5b3875568b4083ecf5f310dcf91d86d52573160834fb4bfcf5e4f/multidict-6.7.1-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:17307b22c217b4cf05033dabefe68255a534d637c6c9b0cc8382718f87be4262", size = 239358, upload-time = "2026-01-26T02:45:44.376Z" }, + { url = "https://files.pythonhosted.org/packages/41/75/4ad0973179361cdf3a113905e6e088173198349131be2b390f9fa4da5fc6/multidict-6.7.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a7e590ff876a3eaf1c02a4dfe0724b6e69a9e9de6d8f556816f29c496046e59", size = 246884, upload-time = "2026-01-26T02:45:47.167Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9c/095bb28b5da139bd41fb9a5d5caff412584f377914bd8787c2aa98717130/multidict-6.7.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5fa6a95dfee63893d80a34758cd0e0c118a30b8dcb46372bf75106c591b77889", size = 225878, upload-time = "2026-01-26T02:45:48.698Z" }, + { url = "https://files.pythonhosted.org/packages/07/d0/c0a72000243756e8f5a277b6b514fa005f2c73d481b7d9e47cd4568aa2e4/multidict-6.7.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0543217a6a017692aa6ae5cc39adb75e587af0f3a82288b1492eb73dd6cc2a4", size = 253542, upload-time = "2026-01-26T02:45:50.164Z" }, + { url = "https://files.pythonhosted.org/packages/c0/6b/f69da15289e384ecf2a68837ec8b5ad8c33e973aa18b266f50fe55f24b8c/multidict-6.7.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f99fe611c312b3c1c0ace793f92464d8cd263cc3b26b5721950d977b006b6c4d", size = 252403, upload-time = "2026-01-26T02:45:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/a2/76/b9669547afa5a1a25cd93eaca91c0da1c095b06b6d2d8ec25b713588d3a1/multidict-6.7.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9004d8386d133b7e6135679424c91b0b854d2d164af6ea3f289f8f2761064609", size = 244889, upload-time = "2026-01-26T02:45:53.27Z" }, + { url = "https://files.pythonhosted.org/packages/7e/a9/a50d2669e506dad33cfc45b5d574a205587b7b8a5f426f2fbb2e90882588/multidict-6.7.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e628ef0e6859ffd8273c69412a2465c4be4a9517d07261b33334b5ec6f3c7489", size = 241982, upload-time = "2026-01-26T02:45:54.919Z" }, + { url = "https://files.pythonhosted.org/packages/c5/bb/1609558ad8b456b4827d3c5a5b775c93b87878fd3117ed3db3423dfbce1b/multidict-6.7.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:841189848ba629c3552035a6a7f5bf3b02eb304e9fea7492ca220a8eda6b0e5c", size = 232415, upload-time = "2026-01-26T02:45:56.981Z" }, + { url = "https://files.pythonhosted.org/packages/d8/59/6f61039d2aa9261871e03ab9dc058a550d240f25859b05b67fd70f80d4b3/multidict-6.7.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce1bbd7d780bb5a0da032e095c951f7014d6b0a205f8318308140f1a6aba159e", size = 240337, upload-time = "2026-01-26T02:45:58.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/29/fdc6a43c203890dc2ae9249971ecd0c41deaedfe00d25cb6564b2edd99eb/multidict-6.7.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b26684587228afed0d50cf804cc71062cc9c1cdf55051c4c6345d372947b268c", size = 248788, upload-time = "2026-01-26T02:46:00.862Z" }, + { url = "https://files.pythonhosted.org/packages/a9/14/a153a06101323e4cf086ecee3faadba52ff71633d471f9685c42e3736163/multidict-6.7.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9f9af11306994335398293f9958071019e3ab95e9a707dc1383a35613f6abcb9", size = 242842, upload-time = "2026-01-26T02:46:02.824Z" }, + { url = "https://files.pythonhosted.org/packages/41/5f/604ae839e64a4a6efc80db94465348d3b328ee955e37acb24badbcd24d83/multidict-6.7.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b4938326284c4f1224178a560987b6cf8b4d38458b113d9b8c1db1a836e640a2", size = 240237, upload-time = "2026-01-26T02:46:05.898Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/c3a5187bf66f6fb546ff4ab8fb5a077cbdd832d7b1908d4365c7f74a1917/multidict-6.7.1-cp314-cp314t-win32.whl", hash = "sha256:98655c737850c064a65e006a3df7c997cd3b220be4ec8fe26215760b9697d4d7", size = 48008, upload-time = "2026-01-26T02:46:07.468Z" }, + { url = "https://files.pythonhosted.org/packages/0c/f7/addf1087b860ac60e6f382240f64fb99f8bfb532bb06f7c542b83c29ca61/multidict-6.7.1-cp314-cp314t-win_amd64.whl", hash = "sha256:497bde6223c212ba11d462853cfa4f0ae6ef97465033e7dc9940cdb3ab5b48e5", size = 53542, upload-time = "2026-01-26T02:46:08.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/81/4629d0aa32302ef7b2ec65c75a728cc5ff4fa410c50096174c1632e70b3e/multidict-6.7.1-cp314-cp314t-win_arm64.whl", hash = "sha256:2bbd113e0d4af5db41d5ebfe9ccaff89de2120578164f86a5d17d5a576d1e5b2", size = 44719, upload-time = "2026-01-26T02:46:11.146Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, +] + +[[package]] +name = "multitasking" +version = "0.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/17/0d/74f0293dfd7dcc3837746d0138cbedd60b31701ecc75caec7d3f281feba0/multitasking-0.0.12.tar.gz", hash = "sha256:2fba2fa8ed8c4b85e227c5dd7dc41c7d658de3b6f247927316175a57349b84d1", size = 19984, upload-time = "2025-07-20T21:27:51.636Z" } + +[[package]] +name = "nbclient" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/91/1c1d5a4b9a9ebba2b4e32b8c852c2975c872aec1fe42ab5e516b2cecd193/nbclient-0.10.4.tar.gz", hash = "sha256:1e54091b16e6da39e297b0ece3e10f6f29f4ac4e8ee515d29f8a7099bd6553c9", size = 62554, upload-time = "2025-12-23T07:45:46.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl", hash = "sha256:9162df5a7373d70d606527300a95a975a47c137776cd942e52d9c7e29ff83440", size = 25465, upload-time = "2025-12-23T07:45:44.51Z" }, +] + +[[package]] +name = "nbconvert" +version = "7.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "bleach", extra = ["css"] }, + { name = "defusedxml" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyterlab-pygments" }, + { name = "markupsafe" }, + { name = "mistune" }, + { name = "nbclient" }, + { name = "nbformat" }, + { name = "packaging" }, + { name = "pandocfilters" }, + { name = "pygments" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/47/81f886b699450d0569f7bc551df2b1673d18df7ff25cc0c21ca36ed8a5ff/nbconvert-7.17.0.tar.gz", hash = "sha256:1b2696f1b5be12309f6c7d707c24af604b87dfaf6d950794c7b07acab96dda78", size = 862855, upload-time = "2026-01-29T16:37:48.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl", hash = "sha256:4f99a63b337b9a23504347afdab24a11faa7d86b405e5c8f9881cd313336d518", size = 261510, upload-time = "2026-01-29T16:37:46.322Z" }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749, upload-time = "2024-04-04T11:20:37.371Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454, upload-time = "2024-04-04T11:20:34.895Z" }, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418, upload-time = "2024-01-21T14:25:19.227Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" }, +] + +[[package]] +name = "notebook" +version = "7.5.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, + { name = "jupyterlab" }, + { name = "jupyterlab-server" }, + { name = "notebook-shim" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/cb/cc7f4df5cee315dd126a47eb60890690a0438d5e0dd40c32d60ce16de377/notebook-7.5.3.tar.gz", hash = "sha256:393ceb269cf9fdb02a3be607a57d7bd5c2c14604f1818a17dbeb38e04f98cbfa", size = 14073140, upload-time = "2026-01-26T07:28:36.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/98/9286e7f35e5584ebb79f997f2fb0cb66745c86f6c5fccf15ba32aac5e908/notebook-7.5.3-py3-none-any.whl", hash = "sha256:c997bfa1a2a9eb58c9bbb7e77d50428befb1033dd6f02c482922e96851d67354", size = 14481744, upload-time = "2026-01-26T07:28:31.867Z" }, +] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-server" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/d2/92fa3243712b9a3e8bafaf60aac366da1cada3639ca767ff4b5b3654ec28/notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb", size = 13167, upload-time = "2024-02-14T23:35:18.353Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef", size = 13307, upload-time = "2024-02-14T23:35:16.286Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, +] + +[[package]] +name = "openai" +version = "2.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/6c/e4c964fcf1d527fdf4739e7cc940c60075a4114d50d03871d5d5b1e13a88/openai-2.16.0.tar.gz", hash = "sha256:42eaa22ca0d8ded4367a77374104d7a2feafee5bd60a107c3c11b5243a11cd12", size = 629649, upload-time = "2026-01-27T23:28:02.579Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/83/0315bf2cfd75a2ce8a7e54188e9456c60cec6c0cf66728ed07bd9859ff26/openai-2.16.0-py3-none-any.whl", hash = "sha256:5f46643a8f42899a84e80c38838135d7038e7718333ce61396994f887b09a59b", size = 1068612, upload-time = "2026-01-27T23:28:00.356Z" }, +] + +[[package]] +name = "ordered-set" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826, upload-time = "2022-01-26T14:38:56.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634, upload-time = "2022-01-26T14:38:48.677Z" }, +] + +[[package]] +name = "overrides" +version = "7.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/70/6f/3dd4940bbe001c06a65f88e36bad298bc7a0de5036115639926b0c5c0458/pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e", size = 8454, upload-time = "2024-01-18T20:08:13.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc", size = 8663, upload-time = "2024-01-18T20:08:11.28Z" }, +] + +[[package]] +name = "parso" +version = "0.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/de/53e0bcf53d13e005bd8c92e7855142494f41171b34c2536b86187474184d/parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a", size = 401205, upload-time = "2025-08-23T15:15:28.028Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887", size = 106668, upload-time = "2025-08-23T15:15:25.663Z" }, +] + +[[package]] +name = "peewee" +version = "3.19.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/b0/79462b42e89764998756e0557f2b58a15610a5b4512fbbcccae58fba7237/peewee-3.19.0.tar.gz", hash = "sha256:f88292a6f0d7b906cb26bca9c8599b8f4d8920ebd36124400d0cbaaaf915511f", size = 974035, upload-time = "2026-01-07T17:24:59.597Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/41/19c65578ef9a54b3083253c68a607f099642747168fe00f3a2bceb7c3a34/peewee-3.19.0-py3-none-any.whl", hash = "sha256:de220b94766e6008c466e00ce4ba5299b9a832117d9eb36d45d0062f3cfd7417", size = 411885, upload-time = "2026-01-07T17:24:58.33Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, +] + +[[package]] +name = "prometheus-client" +version = "0.24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/58/a794d23feb6b00fc0c72787d7e87d872a6730dd9ed7c7b3e954637d8f280/prometheus_client-0.24.1.tar.gz", hash = "sha256:7e0ced7fbbd40f7b84962d5d2ab6f17ef88a72504dcf7c0b40737b43b2a461f9", size = 85616, upload-time = "2026-01-14T15:26:26.965Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/c3/24a2f845e3917201628ecaba4f18bab4d18a337834c1df2a159ee9d22a42/prometheus_client-0.24.1-py3-none-any.whl", hash = "sha256:150db128af71a5c2482b36e588fc8a6b95e498750da4b17065947c16070f4055", size = 64057, upload-time = "2026-01-14T15:26:24.42Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "proto-plus" +version = "1.27.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/89/9cbe2f4bba860e149108b683bc2efec21f14d5f7ed6e25562ad86acbc373/proto_plus-1.27.0.tar.gz", hash = "sha256:873af56dd0d7e91836aee871e5799e1c6f1bda86ac9a983e0bb9f0c266a568c4", size = 56158, upload-time = "2025-12-16T13:46:25.729Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/24/3b7a0818484df9c28172857af32c2397b6d8fcd99d9468bd4684f98ebf0a/proto_plus-1.27.0-py3-none-any.whl", hash = "sha256:1baa7f81cf0f8acb8bc1f6d085008ba4171eaf669629d1b6d1673b21ed1c0a82", size = 50205, upload-time = "2025-12-16T13:46:24.76Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pyarrow" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/33/ffd9c3eb087fa41dd79c3cf20c4c0ae3cdb877c4f8e1107a446006344924/pyarrow-23.0.0.tar.gz", hash = "sha256:180e3150e7edfcd182d3d9afba72f7cf19839a497cc76555a8dce998a8f67615", size = 1167185, upload-time = "2026-01-18T16:19:42.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/c0/57fe251102ca834fee0ef69a84ad33cc0ff9d5dfc50f50b466846356ecd7/pyarrow-23.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5574d541923efcbfdf1294a2746ae3b8c2498a2dc6cd477882f6f4e7b1ac08d3", size = 34276762, upload-time = "2026-01-18T16:14:34.128Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4e/24130286548a5bc250cbed0b6bbf289a2775378a6e0e6f086ae8c68fc098/pyarrow-23.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:2ef0075c2488932e9d3c2eb3482f9459c4be629aa673b725d5e3cf18f777f8e4", size = 35821420, upload-time = "2026-01-18T16:14:40.699Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/a869e8529d487aa2e842d6c8865eb1e2c9ec33ce2786eb91104d2c3e3f10/pyarrow-23.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:65666fc269669af1ef1c14478c52222a2aa5c907f28b68fb50a203c777e4f60c", size = 44457412, upload-time = "2026-01-18T16:14:49.051Z" }, + { url = "https://files.pythonhosted.org/packages/36/81/1de4f0edfa9a483bbdf0082a05790bd6a20ed2169ea12a65039753be3a01/pyarrow-23.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:4d85cb6177198f3812db4788e394b757223f60d9a9f5ad6634b3e32be1525803", size = 47534285, upload-time = "2026-01-18T16:14:56.748Z" }, + { url = "https://files.pythonhosted.org/packages/f2/04/464a052d673b5ece074518f27377861662449f3c1fdb39ce740d646fd098/pyarrow-23.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1a9ff6fa4141c24a03a1a434c63c8fa97ce70f8f36bccabc18ebba905ddf0f17", size = 48157913, upload-time = "2026-01-18T16:15:05.114Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1b/32a4de9856ee6688c670ca2def588382e573cce45241a965af04c2f61687/pyarrow-23.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:84839d060a54ae734eb60a756aeacb62885244aaa282f3c968f5972ecc7b1ecc", size = 50582529, upload-time = "2026-01-18T16:15:12.846Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/d6581f03e9b9e44ea60b52d1750ee1a7678c484c06f939f45365a45f7eef/pyarrow-23.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a149a647dbfe928ce8830a713612aa0b16e22c64feac9d1761529778e4d4eaa5", size = 27542646, upload-time = "2026-01-18T16:15:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bd/c861d020831ee57609b73ea721a617985ece817684dc82415b0bc3e03ac3/pyarrow-23.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5961a9f646c232697c24f54d3419e69b4261ba8a8b66b0ac54a1851faffcbab8", size = 34189116, upload-time = "2026-01-18T16:15:28.054Z" }, + { url = "https://files.pythonhosted.org/packages/8c/23/7725ad6cdcbaf6346221391e7b3eecd113684c805b0a95f32014e6fa0736/pyarrow-23.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:632b3e7c3d232f41d64e1a4a043fb82d44f8a349f339a1188c6a0dd9d2d47d8a", size = 35803831, upload-time = "2026-01-18T16:15:33.798Z" }, + { url = "https://files.pythonhosted.org/packages/57/06/684a421543455cdc2944d6a0c2cc3425b028a4c6b90e34b35580c4899743/pyarrow-23.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:76242c846db1411f1d6c2cc3823be6b86b40567ee24493344f8226ba34a81333", size = 44436452, upload-time = "2026-01-18T16:15:41.598Z" }, + { url = "https://files.pythonhosted.org/packages/c6/6f/8f9eb40c2328d66e8b097777ddcf38494115ff9f1b5bc9754ba46991191e/pyarrow-23.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b73519f8b52ae28127000986bf228fda781e81d3095cd2d3ece76eb5cf760e1b", size = 47557396, upload-time = "2026-01-18T16:15:51.252Z" }, + { url = "https://files.pythonhosted.org/packages/10/6e/f08075f1472e5159553501fde2cc7bc6700944bdabe49a03f8a035ee6ccd/pyarrow-23.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:068701f6823449b1b6469120f399a1239766b117d211c5d2519d4ed5861f75de", size = 48147129, upload-time = "2026-01-18T16:16:00.299Z" }, + { url = "https://files.pythonhosted.org/packages/7d/82/d5a680cd507deed62d141cc7f07f7944a6766fc51019f7f118e4d8ad0fb8/pyarrow-23.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1801ba947015d10e23bca9dd6ef5d0e9064a81569a89b6e9a63b59224fd060df", size = 50596642, upload-time = "2026-01-18T16:16:08.502Z" }, + { url = "https://files.pythonhosted.org/packages/a9/26/4f29c61b3dce9fa7780303b86895ec6a0917c9af927101daaaf118fbe462/pyarrow-23.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:52265266201ec25b6839bf6bd4ea918ca6d50f31d13e1cf200b4261cd11dc25c", size = 27660628, upload-time = "2026-01-18T16:16:15.28Z" }, + { url = "https://files.pythonhosted.org/packages/66/34/564db447d083ec7ff93e0a883a597d2f214e552823bfc178a2d0b1f2c257/pyarrow-23.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:ad96a597547af7827342ffb3c503c8316e5043bb09b47a84885ce39394c96e00", size = 34184630, upload-time = "2026-01-18T16:16:22.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/3a/3999daebcb5e6119690c92a621c4d78eef2ffba7a0a1b56386d2875fcd77/pyarrow-23.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:b9edf990df77c2901e79608f08c13fbde60202334a4fcadb15c1f57bf7afee43", size = 35796820, upload-time = "2026-01-18T16:16:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/39195233056c6a8d0976d7d1ac1cd4fe21fb0ec534eca76bc23ef3f60e11/pyarrow-23.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:36d1b5bc6ddcaff0083ceec7e2561ed61a51f49cce8be079ee8ed406acb6fdef", size = 44438735, upload-time = "2026-01-18T16:16:38.79Z" }, + { url = "https://files.pythonhosted.org/packages/2c/41/6a7328ee493527e7afc0c88d105ecca69a3580e29f2faaeac29308369fd7/pyarrow-23.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4292b889cd224f403304ddda8b63a36e60f92911f89927ec8d98021845ea21be", size = 47557263, upload-time = "2026-01-18T16:16:46.248Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ee/34e95b21ee84db494eae60083ddb4383477b31fb1fd19fd866d794881696/pyarrow-23.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dfd9e133e60eaa847fd80530a1b89a052f09f695d0b9c34c235ea6b2e0924cf7", size = 48153529, upload-time = "2026-01-18T16:16:53.412Z" }, + { url = "https://files.pythonhosted.org/packages/52/88/8a8d83cea30f4563efa1b7bf51d241331ee5cd1b185a7e063f5634eca415/pyarrow-23.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832141cc09fac6aab1cd3719951d23301396968de87080c57c9a7634e0ecd068", size = 50598851, upload-time = "2026-01-18T16:17:01.133Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4c/2929c4be88723ba025e7b3453047dc67e491c9422965c141d24bab6b5962/pyarrow-23.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:7a7d067c9a88faca655c71bcc30ee2782038d59c802d57950826a07f60d83c4c", size = 27577747, upload-time = "2026-01-18T16:18:02.413Z" }, + { url = "https://files.pythonhosted.org/packages/64/52/564a61b0b82d72bd68ec3aef1adda1e3eba776f89134b9ebcb5af4b13cb6/pyarrow-23.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:ce9486e0535a843cf85d990e2ec5820a47918235183a5c7b8b97ed7e92c2d47d", size = 34446038, upload-time = "2026-01-18T16:17:07.861Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c9/232d4f9855fd1de0067c8a7808a363230d223c83aeee75e0fe6eab851ba9/pyarrow-23.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:075c29aeaa685fd1182992a9ed2499c66f084ee54eea47da3eb76e125e06064c", size = 35921142, upload-time = "2026-01-18T16:17:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/96/f2/60af606a3748367b906bb82d41f0032e059f075444445d47e32a7ff1df62/pyarrow-23.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:799965a5379589510d888be3094c2296efd186a17ca1cef5b77703d4d5121f53", size = 44490374, upload-time = "2026-01-18T16:17:23.93Z" }, + { url = "https://files.pythonhosted.org/packages/ff/2d/7731543050a678ea3a413955a2d5d80d2a642f270aa57a3cb7d5a86e3f46/pyarrow-23.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ef7cac8fe6fccd8b9e7617bfac785b0371a7fe26af59463074e4882747145d40", size = 47527896, upload-time = "2026-01-18T16:17:33.393Z" }, + { url = "https://files.pythonhosted.org/packages/5a/90/f3342553b7ac9879413aed46500f1637296f3c8222107523a43a1c08b42a/pyarrow-23.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15a414f710dc927132dd67c361f78c194447479555af57317066ee5116b90e9e", size = 48210401, upload-time = "2026-01-18T16:17:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/f3/da/9862ade205ecc46c172b6ce5038a74b5151c7401e36255f15975a45878b2/pyarrow-23.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e0d2e6915eca7d786be6a77bf227fbc06d825a75b5b5fe9bcbef121dec32685", size = 50579677, upload-time = "2026-01-18T16:17:50.241Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4c/f11f371f5d4740a5dafc2e11c76bcf42d03dfdb2d68696da97de420b6963/pyarrow-23.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4b317ea6e800b5704e5e5929acb6e2dc13e9276b708ea97a39eb8b345aa2658b", size = 27631889, upload-time = "2026-01-18T16:17:56.55Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/15aec78bcf43a0c004067bd33eb5352836a29a49db8581fc56f2b6ca88b7/pyarrow-23.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:20b187ed9550d233a872074159f765f52f9d92973191cd4b93f293a19efbe377", size = 34213265, upload-time = "2026-01-18T16:18:07.904Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/deb2c594bbba41c37c5d9aa82f510376998352aa69dfcb886cb4b18ad80f/pyarrow-23.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:18ec84e839b493c3886b9b5e06861962ab4adfaeb79b81c76afbd8d84c7d5fda", size = 35819211, upload-time = "2026-01-18T16:18:13.94Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/ee82af693cb7b5b2b74f6524cdfede0e6ace779d7720ebca24d68b57c36b/pyarrow-23.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e438dd3f33894e34fd02b26bd12a32d30d006f5852315f611aa4add6c7fab4bc", size = 44502313, upload-time = "2026-01-18T16:18:20.367Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/95c61ad82236495f3c31987e85135926ba3ec7f3819296b70a68d8066b49/pyarrow-23.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:a244279f240c81f135631be91146d7fa0e9e840e1dfed2aba8483eba25cd98e6", size = 47585886, upload-time = "2026-01-18T16:18:27.544Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6e/a72d901f305201802f016d015de1e05def7706fff68a1dedefef5dc7eff7/pyarrow-23.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c4692e83e42438dba512a570c6eaa42be2f8b6c0f492aea27dec54bdc495103a", size = 48207055, upload-time = "2026-01-18T16:18:35.425Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/5de029c537630ca18828db45c30e2a78da03675a70ac6c3528203c416fe3/pyarrow-23.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae7f30f898dfe44ea69654a35c93e8da4cef6606dc4c72394068fd95f8e9f54a", size = 50619812, upload-time = "2026-01-18T16:18:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/59/8d/2af846cd2412e67a087f5bda4a8e23dfd4ebd570f777db2e8686615dafc1/pyarrow-23.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:5b86bb649e4112fb0614294b7d0a175c7513738876b89655605ebb87c804f861", size = 28263851, upload-time = "2026-01-18T16:19:38.567Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7f/caab863e587041156f6786c52e64151b7386742c8c27140f637176e9230e/pyarrow-23.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:ebc017d765d71d80a3f8584ca0566b53e40464586585ac64176115baa0ada7d3", size = 34463240, upload-time = "2026-01-18T16:18:49.755Z" }, + { url = "https://files.pythonhosted.org/packages/c9/fa/3a5b8c86c958e83622b40865e11af0857c48ec763c11d472c87cd518283d/pyarrow-23.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:0800cc58a6d17d159df823f87ad66cefebf105b982493d4bad03ee7fab84b993", size = 35935712, upload-time = "2026-01-18T16:18:55.626Z" }, + { url = "https://files.pythonhosted.org/packages/c5/08/17a62078fc1a53decb34a9aa79cf9009efc74d63d2422e5ade9fed2f99e3/pyarrow-23.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3a7c68c722da9bb5b0f8c10e3eae71d9825a4b429b40b32709df5d1fa55beb3d", size = 44503523, upload-time = "2026-01-18T16:19:03.958Z" }, + { url = "https://files.pythonhosted.org/packages/cc/70/84d45c74341e798aae0323d33b7c39194e23b1abc439ceaf60a68a7a969a/pyarrow-23.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:bd5556c24622df90551063ea41f559b714aa63ca953db884cfb958559087a14e", size = 47542490, upload-time = "2026-01-18T16:19:11.208Z" }, + { url = "https://files.pythonhosted.org/packages/61/d9/d1274b0e6f19e235de17441e53224f4716574b2ca837022d55702f24d71d/pyarrow-23.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54810f6e6afc4ffee7c2e0051b61722fbea9a4961b46192dcfae8ea12fa09059", size = 48233605, upload-time = "2026-01-18T16:19:19.544Z" }, + { url = "https://files.pythonhosted.org/packages/39/07/e4e2d568cb57543d84482f61e510732820cddb0f47c4bb7df629abfed852/pyarrow-23.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:14de7d48052cf4b0ed174533eafa3cfe0711b8076ad70bede32cf59f744f0d7c", size = 50603979, upload-time = "2026-01-18T16:19:26.717Z" }, + { url = "https://files.pythonhosted.org/packages/72/9c/47693463894b610f8439b2e970b82ef81e9599c757bf2049365e40ff963c/pyarrow-23.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:427deac1f535830a744a4f04a6ac183a64fcac4341b3f618e693c41b7b98d2b0", size = 28338905, upload-time = "2026-01-18T16:19:32.93Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pymongo" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dnspython" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/65/9c/a4895c4b785fc9865a84a56e14b5bd21ca75aadc3dab79c14187cdca189b/pymongo-4.16.0.tar.gz", hash = "sha256:8ba8405065f6e258a6f872fe62d797a28f383a12178c7153c01ed04e845c600c", size = 2495323, upload-time = "2026-01-07T18:05:48.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/3a/907414a763c4270b581ad6d960d0c6221b74a70eda216a1fdd8fa82ba89f/pymongo-4.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6f2077ec24e2f1248f9cac7b9a2dfb894e50cc7939fcebfb1759f99304caabef", size = 862561, upload-time = "2026-01-07T18:04:00.628Z" }, + { url = "https://files.pythonhosted.org/packages/8c/58/787d8225dd65cb2383c447346ea5e200ecfde89962d531111521e3b53018/pymongo-4.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d4f7ba040f72a9f43a44059872af5a8c8c660aa5d7f90d5344f2ed1c3c02721", size = 862923, upload-time = "2026-01-07T18:04:02.213Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a7/cc2865aae32bc77ade7b35f957a58df52680d7f8506f93c6edbf458e5738/pymongo-4.16.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8a0f73af1ea56c422b2dcfc0437459148a799ef4231c6aee189d2d4c59d6728f", size = 1426779, upload-time = "2026-01-07T18:04:03.942Z" }, + { url = "https://files.pythonhosted.org/packages/81/25/3e96eb7998eec05382174da2fefc58d28613f46bbdf821045539d0ed60ab/pymongo-4.16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa30cd16ddd2f216d07ba01d9635c873e97ddb041c61cf0847254edc37d1c60e", size = 1454207, upload-time = "2026-01-07T18:04:05.387Z" }, + { url = "https://files.pythonhosted.org/packages/86/7b/8e817a7df8c5d565d39dd4ca417a5e0ef46cc5cc19aea9405f403fec6449/pymongo-4.16.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1d638b0b1b294d95d0fdc73688a3b61e05cc4188872818cd240d51460ccabcb5", size = 1511654, upload-time = "2026-01-07T18:04:08.458Z" }, + { url = "https://files.pythonhosted.org/packages/39/7a/50c4d075ccefcd281cdcfccc5494caa5665b096b85e65a5d6afabb80e09e/pymongo-4.16.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:21d02cc10a158daa20cb040985e280e7e439832fc6b7857bff3d53ef6914ad50", size = 1496794, upload-time = "2026-01-07T18:04:10.355Z" }, + { url = "https://files.pythonhosted.org/packages/0f/cd/ebdc1aaca5deeaf47310c369ef4083e8550e04e7bf7e3752cfb7d95fcdb8/pymongo-4.16.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fbb8d3552c2ad99d9e236003c0b5f96d5f05e29386ba7abae73949bfebc13dd", size = 1448371, upload-time = "2026-01-07T18:04:11.76Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c9/50fdd78c37f68ea49d590c027c96919fbccfd98f3a4cb39f84f79970bd37/pymongo-4.16.0-cp311-cp311-win32.whl", hash = "sha256:be1099a8295b1a722d03fb7b48be895d30f4301419a583dcf50e9045968a041c", size = 841024, upload-time = "2026-01-07T18:04:13.522Z" }, + { url = "https://files.pythonhosted.org/packages/4a/dd/a3aa1ade0cf9980744db703570afac70a62c85b432c391dea0577f6da7bb/pymongo-4.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:61567f712bda04c7545a037e3284b4367cad8d29b3dec84b4bf3b2147020a75b", size = 855838, upload-time = "2026-01-07T18:04:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/bf/10/9ad82593ccb895e8722e4884bad4c5ce5e8ff6683b740d7823a6c2bcfacf/pymongo-4.16.0-cp311-cp311-win_arm64.whl", hash = "sha256:c53338613043038005bf2e41a2fafa08d29cdbc0ce80891b5366c819456c1ae9", size = 845007, upload-time = "2026-01-07T18:04:17.099Z" }, + { url = "https://files.pythonhosted.org/packages/6a/03/6dd7c53cbde98de469a3e6fb893af896dca644c476beb0f0c6342bcc368b/pymongo-4.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bd4911c40a43a821dfd93038ac824b756b6e703e26e951718522d29f6eb166a8", size = 917619, upload-time = "2026-01-07T18:04:19.173Z" }, + { url = "https://files.pythonhosted.org/packages/73/e1/328915f2734ea1f355dc9b0e98505ff670f5fab8be5e951d6ed70971c6aa/pymongo-4.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25a6b03a68f9907ea6ec8bc7cf4c58a1b51a18e23394f962a6402f8e46d41211", size = 917364, upload-time = "2026-01-07T18:04:20.861Z" }, + { url = "https://files.pythonhosted.org/packages/41/fe/4769874dd9812a1bc2880a9785e61eba5340da966af888dd430392790ae0/pymongo-4.16.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:91ac0cb0fe2bf17616c2039dac88d7c9a5088f5cb5829b27c9d250e053664d31", size = 1686901, upload-time = "2026-01-07T18:04:22.219Z" }, + { url = "https://files.pythonhosted.org/packages/fa/8d/15707b9669fdc517bbc552ac60da7124dafe7ac1552819b51e97ed4038b4/pymongo-4.16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf0ec79e8ca7077f455d14d915d629385153b6a11abc0b93283ed73a8013e376", size = 1723034, upload-time = "2026-01-07T18:04:24.055Z" }, + { url = "https://files.pythonhosted.org/packages/5b/af/3d5d16ff11d447d40c1472da1b366a31c7380d7ea2922a449c7f7f495567/pymongo-4.16.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2d0082631a7510318befc2b4fdab140481eb4b9dd62d9245e042157085da2a70", size = 1797161, upload-time = "2026-01-07T18:04:25.964Z" }, + { url = "https://files.pythonhosted.org/packages/fb/04/725ab8664eeec73ec125b5a873448d80f5d8cf2750aaaf804cbc538a50a5/pymongo-4.16.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85dc2f3444c346ea019a371e321ac868a4fab513b7a55fe368f0cc78de8177cc", size = 1780938, upload-time = "2026-01-07T18:04:28.745Z" }, + { url = "https://files.pythonhosted.org/packages/22/50/dd7e9095e1ca35f93c3c844c92eb6eb0bc491caeb2c9bff3b32fe3c9b18f/pymongo-4.16.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dabbf3c14de75a20cc3c30bf0c6527157224a93dfb605838eabb1a2ee3be008d", size = 1714342, upload-time = "2026-01-07T18:04:30.331Z" }, + { url = "https://files.pythonhosted.org/packages/03/c9/542776987d5c31ae8e93e92680ea2b6e5a2295f398b25756234cabf38a39/pymongo-4.16.0-cp312-cp312-win32.whl", hash = "sha256:60307bb91e0ab44e560fe3a211087748b2b5f3e31f403baf41f5b7b0a70bd104", size = 887868, upload-time = "2026-01-07T18:04:32.124Z" }, + { url = "https://files.pythonhosted.org/packages/2e/d4/b4045a7ccc5680fb496d01edf749c7a9367cc8762fbdf7516cf807ef679b/pymongo-4.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:f513b2c6c0d5c491f478422f6b5b5c27ac1af06a54c93ef8631806f7231bd92e", size = 907554, upload-time = "2026-01-07T18:04:33.685Z" }, + { url = "https://files.pythonhosted.org/packages/60/4c/33f75713d50d5247f2258405142c0318ff32c6f8976171c4fcae87a9dbdf/pymongo-4.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:dfc320f08ea9a7ec5b2403dc4e8150636f0d6150f4b9792faaae539c88e7db3b", size = 892971, upload-time = "2026-01-07T18:04:35.594Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/148d8b5da8260f4679d6665196ae04ab14ffdf06f5fe670b0ab11942951f/pymongo-4.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d15f060bc6d0964a8bb70aba8f0cb6d11ae99715438f640cff11bbcf172eb0e8", size = 972009, upload-time = "2026-01-07T18:04:38.303Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/9f3a8daf583d0adaaa033a3e3e58194d2282737dc164014ff33c7a081103/pymongo-4.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a19ea46a0fe71248965305a020bc076a163311aefbaa1d83e47d06fa30ac747", size = 971784, upload-time = "2026-01-07T18:04:39.669Z" }, + { url = "https://files.pythonhosted.org/packages/ad/f2/b6c24361fcde24946198573c0176406bfd5f7b8538335f3d939487055322/pymongo-4.16.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:311d4549d6bf1f8c61d025965aebb5ba29d1481dc6471693ab91610aaffbc0eb", size = 1947174, upload-time = "2026-01-07T18:04:41.368Z" }, + { url = "https://files.pythonhosted.org/packages/47/1a/8634192f98cf740b3d174e1018dd0350018607d5bd8ac35a666dc49c732b/pymongo-4.16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46ffb728d92dd5b09fc034ed91acf5595657c7ca17d4cf3751322cd554153c17", size = 1991727, upload-time = "2026-01-07T18:04:42.965Z" }, + { url = "https://files.pythonhosted.org/packages/5a/2f/0c47ac84572b28e23028a23a3798a1f725e1c23b0cf1c1424678d16aff42/pymongo-4.16.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:acda193f440dd88c2023cb00aa8bd7b93a9df59978306d14d87a8b12fe426b05", size = 2082497, upload-time = "2026-01-07T18:04:44.652Z" }, + { url = "https://files.pythonhosted.org/packages/ba/57/9f46ef9c862b2f0cf5ce798f3541c201c574128d31ded407ba4b3918d7b6/pymongo-4.16.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5d9fdb386cf958e6ef6ff537d6149be7edb76c3268cd6833e6c36aa447e4443f", size = 2064947, upload-time = "2026-01-07T18:04:46.228Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/5421c0998f38e32288100a07f6cb2f5f9f352522157c901910cb2927e211/pymongo-4.16.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91899dd7fb9a8c50f09c3c1cf0cb73bfbe2737f511f641f19b9650deb61c00ca", size = 1980478, upload-time = "2026-01-07T18:04:48.017Z" }, + { url = "https://files.pythonhosted.org/packages/92/93/bfc448d025e12313a937d6e1e0101b50cc9751636b4b170e600fe3203063/pymongo-4.16.0-cp313-cp313-win32.whl", hash = "sha256:2cd60cd1e05de7f01927f8e25ca26b3ea2c09de8723241e5d3bcfdc70eaff76b", size = 934672, upload-time = "2026-01-07T18:04:49.538Z" }, + { url = "https://files.pythonhosted.org/packages/96/10/12710a5e01218d50c3dd165fd72c5ed2699285f77348a3b1a119a191d826/pymongo-4.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3ead8a0050c53eaa55935895d6919d393d0328ec24b2b9115bdbe881aa222673", size = 959237, upload-time = "2026-01-07T18:04:51.382Z" }, + { url = "https://files.pythonhosted.org/packages/0c/56/d288bcd1d05bc17ec69df1d0b1d67bc710c7c5dbef86033a5a4d2e2b08e6/pymongo-4.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:dbbc5b254c36c37d10abb50e899bc3939bbb7ab1e7c659614409af99bd3e7675", size = 940909, upload-time = "2026-01-07T18:04:52.904Z" }, + { url = "https://files.pythonhosted.org/packages/30/9e/4d343f8d0512002fce17915a89477b9f916bda1205729e042d8f23acf194/pymongo-4.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:8a254d49a9ffe9d7f888e3c677eed3729b14ce85abb08cd74732cead6ccc3c66", size = 1026634, upload-time = "2026-01-07T18:04:54.359Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e3/341f88c5535df40c0450fda915f582757bb7d988cdfc92990a5e27c4c324/pymongo-4.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a1bf44e13cf2d44d2ea2e928a8140d5d667304abe1a61c4d55b4906f389fbe64", size = 1026252, upload-time = "2026-01-07T18:04:56.642Z" }, + { url = "https://files.pythonhosted.org/packages/af/64/9471b22eb98f0a2ca0b8e09393de048502111b2b5b14ab1bd9e39708aab5/pymongo-4.16.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f1c5f1f818b669875d191323a48912d3fcd2e4906410e8297bb09ac50c4d5ccc", size = 2207399, upload-time = "2026-01-07T18:04:58.255Z" }, + { url = "https://files.pythonhosted.org/packages/87/ac/47c4d50b25a02f21764f140295a2efaa583ee7f17992a5e5fa542b3a690f/pymongo-4.16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77cfd37a43a53b02b7bd930457c7994c924ad8bbe8dff91817904bcbf291b371", size = 2260595, upload-time = "2026-01-07T18:04:59.788Z" }, + { url = "https://files.pythonhosted.org/packages/ee/1b/0ce1ce9dd036417646b2fe6f63b58127acff3cf96eeb630c34ec9cd675ff/pymongo-4.16.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:36ef2fee50eee669587d742fb456e349634b4fcf8926208766078b089054b24b", size = 2366958, upload-time = "2026-01-07T18:05:01.942Z" }, + { url = "https://files.pythonhosted.org/packages/3e/3c/a5a17c0d413aa9d6c17bc35c2b472e9e79cda8068ba8e93433b5f43028e9/pymongo-4.16.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55f8d5a6fe2fa0b823674db2293f92d74cd5f970bc0360f409a1fc21003862d3", size = 2346081, upload-time = "2026-01-07T18:05:03.576Z" }, + { url = "https://files.pythonhosted.org/packages/65/19/f815533d1a88fb8a3b6c6e895bb085ffdae68ccb1e6ed7102202a307f8e2/pymongo-4.16.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9caacac0dd105e2555521002e2d17afc08665187017b466b5753e84c016628e6", size = 2246053, upload-time = "2026-01-07T18:05:05.459Z" }, + { url = "https://files.pythonhosted.org/packages/c6/88/4be3ec78828dc64b212c123114bd6ae8db5b7676085a7b43cc75d0131bd2/pymongo-4.16.0-cp314-cp314-win32.whl", hash = "sha256:c789236366525c3ee3cd6e4e450a9ff629a7d1f4d88b8e18a0aea0615fd7ecf8", size = 989461, upload-time = "2026-01-07T18:05:07.018Z" }, + { url = "https://files.pythonhosted.org/packages/af/5a/ab8d5af76421b34db483c9c8ebc3a2199fb80ae63dc7e18f4cf1df46306a/pymongo-4.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b0714d7764efb29bf9d3c51c964aed7c4c7237b341f9346f15ceaf8321fdb35", size = 1017803, upload-time = "2026-01-07T18:05:08.499Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f4/98d68020728ac6423cf02d17cfd8226bf6cce5690b163d30d3f705e8297e/pymongo-4.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:12762e7cc0f8374a8cae3b9f9ed8dabb5d438c7b33329232dd9b7de783454033", size = 997184, upload-time = "2026-01-07T18:05:09.944Z" }, + { url = "https://files.pythonhosted.org/packages/50/00/dc3a271daf06401825b9c1f4f76f018182c7738281ea54b9762aea0560c1/pymongo-4.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1c01e8a7cd0ea66baf64a118005535ab5bf9f9eb63a1b50ac3935dccf9a54abe", size = 1083303, upload-time = "2026-01-07T18:05:11.702Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4b/b5375ee21d12eababe46215011ebc63801c0d2c5ffdf203849d0d79f9852/pymongo-4.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4c4872299ebe315a79f7f922051061634a64fda95b6b17677ba57ef00b2ba2a4", size = 1083233, upload-time = "2026-01-07T18:05:13.182Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e3/52efa3ca900622c7dcb56c5e70f15c906816d98905c22d2ee1f84d9a7b60/pymongo-4.16.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:78037d02389745e247fe5ab0bcad5d1ab30726eaac3ad79219c7d6bbb07eec53", size = 2527438, upload-time = "2026-01-07T18:05:14.981Z" }, + { url = "https://files.pythonhosted.org/packages/cb/96/43b1be151c734e7766c725444bcbfa1de6b60cc66bfb406203746839dd25/pymongo-4.16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c126fb72be2518395cc0465d4bae03125119136462e1945aea19840e45d89cfc", size = 2600399, upload-time = "2026-01-07T18:05:16.794Z" }, + { url = "https://files.pythonhosted.org/packages/e7/62/fa64a5045dfe3a1cd9217232c848256e7bc0136cffb7da4735c5e0d30e40/pymongo-4.16.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f3867dc225d9423c245a51eaac2cfcd53dde8e0a8d8090bb6aed6e31bd6c2d4f", size = 2720960, upload-time = "2026-01-07T18:05:18.498Z" }, + { url = "https://files.pythonhosted.org/packages/54/7b/01577eb97e605502821273a5bc16ce0fb0be5c978fe03acdbff471471202/pymongo-4.16.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f25001a955073b80510c0c3db0e043dbbc36904fd69e511c74e3d8640b8a5111", size = 2699344, upload-time = "2026-01-07T18:05:20.073Z" }, + { url = "https://files.pythonhosted.org/packages/55/68/6ef6372d516f703479c3b6cbbc45a5afd307173b1cbaccd724e23919bb1a/pymongo-4.16.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d9885aad05f82fd7ea0c9ca505d60939746b39263fa273d0125170da8f59098", size = 2577133, upload-time = "2026-01-07T18:05:22.052Z" }, + { url = "https://files.pythonhosted.org/packages/15/c7/b5337093bb01da852f945802328665f85f8109dbe91d81ea2afe5ff059b9/pymongo-4.16.0-cp314-cp314t-win32.whl", hash = "sha256:948152b30eddeae8355495f9943a3bf66b708295c0b9b6f467de1c620f215487", size = 1040560, upload-time = "2026-01-07T18:05:23.888Z" }, + { url = "https://files.pythonhosted.org/packages/96/8c/5b448cd1b103f3889d5713dda37304c81020ff88e38a826e8a75ddff4610/pymongo-4.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f6e42c1bc985d9beee884780ae6048790eb4cd565c46251932906bdb1630034a", size = 1075081, upload-time = "2026-01-07T18:05:26.874Z" }, + { url = "https://files.pythonhosted.org/packages/32/cd/ddc794cdc8500f6f28c119c624252fb6dfb19481c6d7ed150f13cf468a6d/pymongo-4.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6b2a20edb5452ac8daa395890eeb076c570790dfce6b7a44d788af74c2f8cf96", size = 1047725, upload-time = "2026-01-07T18:05:28.47Z" }, +] + +[[package]] +name = "pymysql" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/ae/1fe3fcd9f959efa0ebe200b8de88b5a5ce3e767e38c7ac32fb179f16a388/pymysql-1.1.2.tar.gz", hash = "sha256:4961d3e165614ae65014e361811a724e2044ad3ea3739de9903ae7c21f539f03", size = 48258, upload-time = "2025-08-24T12:55:55.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/4c/ad33b92b9864cbde84f259d5df035a6447f91891f5be77788e2a3892bce3/pymysql-1.1.2-py3-none-any.whl", hash = "sha256:e6b1d89711dd51f8f74b1631fe08f039e7d76cf67a42a323d3178f0f25762ed9", size = 45300, upload-time = "2025-08-24T12:55:53.394Z" }, +] + +[[package]] +name = "pyodbc" +version = "5.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/85/44b10070a769a56bd910009bb185c0c0a82daff8d567cd1a116d7d730c7d/pyodbc-5.3.0.tar.gz", hash = "sha256:2fe0e063d8fb66efd0ac6dc39236c4de1a45f17c33eaded0d553d21c199f4d05", size = 121770, upload-time = "2025-10-17T18:04:09.43Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/c7/534986d97a26cb8f40ef456dfcf00d8483161eade6d53fa45fcf2d5c2b87/pyodbc-5.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ebc3be93f61ea0553db88589e683ace12bf975baa954af4834ab89f5ee7bf8ae", size = 71958, upload-time = "2025-10-17T18:03:10.163Z" }, + { url = "https://files.pythonhosted.org/packages/69/3c/6fe3e9eae6db1c34d6616a452f9b954b0d5516c430f3dd959c9d8d725f2a/pyodbc-5.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9b987a25a384f31e373903005554230f5a6d59af78bce62954386736a902a4b3", size = 71843, upload-time = "2025-10-17T18:03:11.058Z" }, + { url = "https://files.pythonhosted.org/packages/44/0e/81a0315d0bf7e57be24338dbed616f806131ab706d87c70f363506dc13d5/pyodbc-5.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:676031723aac7dcbbd2813bddda0e8abf171b20ec218ab8dfb21d64a193430ea", size = 327191, upload-time = "2025-10-17T18:03:11.93Z" }, + { url = "https://files.pythonhosted.org/packages/43/ae/b95bb2068f911950322a97172c68675c85a3e87dc04a98448c339fcbef21/pyodbc-5.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5c30c5cd40b751f77bbc73edd32c4498630939bcd4e72ee7e6c9a4b982cc5ca", size = 332228, upload-time = "2025-10-17T18:03:13.096Z" }, + { url = "https://files.pythonhosted.org/packages/dc/21/2433625f7d5922ee9a34e3805805fa0f1355d01d55206c337bb23ec869bf/pyodbc-5.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2035c7dfb71677cd5be64d3a3eb0779560279f0a8dc6e33673499498caa88937", size = 1296469, upload-time = "2025-10-17T18:03:14.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/f4/c760caf7bb9b3ab988975d84bd3e7ebda739fe0075c82f476d04ee97324c/pyodbc-5.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5cbe4d753723c8a8f65020b7a259183ef5f14307587165ce37e8c7e251951852", size = 1353163, upload-time = "2025-10-17T18:03:16.272Z" }, + { url = "https://files.pythonhosted.org/packages/14/ad/f9ca1e9e44fd91058f6e35b233b1bb6213d590185bfcc2a2c4f1033266e7/pyodbc-5.3.0-cp311-cp311-win32.whl", hash = "sha256:d255f6b117d05cfc046a5201fdf39535264045352ea536c35777cf66d321fbb8", size = 62925, upload-time = "2025-10-17T18:03:17.649Z" }, + { url = "https://files.pythonhosted.org/packages/e6/cf/52b9b94efd8cfd11890ae04f31f50561710128d735e4e38a8fbb964cd2c2/pyodbc-5.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:f1ad0e93612a6201621853fc661209d82ff2a35892b7d590106fe8f97d9f1f2a", size = 69329, upload-time = "2025-10-17T18:03:18.474Z" }, + { url = "https://files.pythonhosted.org/packages/8b/6f/bf5433bb345007f93003fa062e045890afb42e4e9fc6bd66acc2c3bd12ca/pyodbc-5.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:0df7ff47fab91ea05548095b00e5eb87ed88ddf4648c58c67b4db95ea4913e23", size = 64447, upload-time = "2025-10-17T18:03:19.691Z" }, + { url = "https://files.pythonhosted.org/packages/f5/0c/7ecf8077f4b932a5d25896699ff5c394ffc2a880a9c2c284d6a3e6ea5949/pyodbc-5.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5ebf6b5d989395efe722b02b010cb9815698a4d681921bf5db1c0e1195ac1bde", size = 72994, upload-time = "2025-10-17T18:03:20.551Z" }, + { url = "https://files.pythonhosted.org/packages/03/78/9fbde156055d88c1ef3487534281a5b1479ee7a2f958a7e90714968749ac/pyodbc-5.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:197bb6ddafe356a916b8ee1b8752009057fce58e216e887e2174b24c7ab99269", size = 72535, upload-time = "2025-10-17T18:03:21.423Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f9/8c106dcd6946e95fee0da0f1ba58cd90eb872eebe8968996a2ea1f7ac3c1/pyodbc-5.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6ccb5315ec9e081f5cbd66f36acbc820ad172b8fa3736cf7f993cdf69bd8a96", size = 333565, upload-time = "2025-10-17T18:03:22.695Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/2c70f47a76a4fafa308d148f786aeb35a4d67a01d41002f1065b465d9994/pyodbc-5.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5dd3d5e469f89a3112cf8b0658c43108a4712fad65e576071e4dd44d2bd763c7", size = 340283, upload-time = "2025-10-17T18:03:23.691Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b2/0631d84731606bfe40d3b03a436b80cbd16b63b022c7b13444fb30761ca8/pyodbc-5.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b180bc5e49b74fd40a24ef5b0fe143d0c234ac1506febe810d7434bf47cb925b", size = 1302767, upload-time = "2025-10-17T18:03:25.311Z" }, + { url = "https://files.pythonhosted.org/packages/74/b9/707c5314cca9401081b3757301241c167a94ba91b4bd55c8fa591bf35a4a/pyodbc-5.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e3c39de3005fff3ae79246f952720d44affc6756b4b85398da4c5ea76bf8f506", size = 1361251, upload-time = "2025-10-17T18:03:26.538Z" }, + { url = "https://files.pythonhosted.org/packages/97/7c/893036c8b0c8d359082a56efdaa64358a38dda993124162c3faa35d1924d/pyodbc-5.3.0-cp312-cp312-win32.whl", hash = "sha256:d32c3259762bef440707098010035bbc83d1c73d81a434018ab8c688158bd3bb", size = 63413, upload-time = "2025-10-17T18:03:27.903Z" }, + { url = "https://files.pythonhosted.org/packages/c0/70/5e61b216cc13c7f833ef87f4cdeab253a7873f8709253f5076e9bb16c1b3/pyodbc-5.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:fe77eb9dcca5fc1300c9121f81040cc9011d28cff383e2c35416e9ec06d4bc95", size = 70133, upload-time = "2025-10-17T18:03:28.746Z" }, + { url = "https://files.pythonhosted.org/packages/aa/85/e7d0629c9714a85eb4f85d21602ce6d8a1ec0f313fde8017990cf913e3b4/pyodbc-5.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:afe7c4ac555a8d10a36234788fc6cfc22a86ce37fc5ba88a1f75b3e6696665dc", size = 64700, upload-time = "2025-10-17T18:03:29.638Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1d/9e74cbcc1d4878553eadfd59138364b38656369eb58f7e5b42fb344c0ce7/pyodbc-5.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7e9ab0b91de28a5ab838ac4db0253d7cc8ce2452efe4ad92ee6a57b922bf0c24", size = 72975, upload-time = "2025-10-17T18:03:30.466Z" }, + { url = "https://files.pythonhosted.org/packages/37/c7/27d83f91b3144d3e275b5b387f0564b161ddbc4ce1b72bb3b3653e7f4f7a/pyodbc-5.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6132554ffbd7910524d643f13ce17f4a72f3a6824b0adef4e9a7f66efac96350", size = 72541, upload-time = "2025-10-17T18:03:31.348Z" }, + { url = "https://files.pythonhosted.org/packages/1b/33/2bb24e7fc95e98a7b11ea5ad1f256412de35d2e9cc339be198258c1d9a76/pyodbc-5.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1629af4706e9228d79dabb4863c11cceb22a6dab90700db0ef449074f0150c0d", size = 343287, upload-time = "2025-10-17T18:03:32.287Z" }, + { url = "https://files.pythonhosted.org/packages/fa/24/88cde8b6dc07a93a92b6c15520a947db24f55db7bd8b09e85956642b7cf3/pyodbc-5.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ceaed87ba2ea848c11223f66f629ef121f6ebe621f605cde9cfdee4fd9f4b68", size = 350094, upload-time = "2025-10-17T18:03:33.336Z" }, + { url = "https://files.pythonhosted.org/packages/c2/99/53c08562bc171a618fa1699297164f8885e66cde38c3b30f454730d0c488/pyodbc-5.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3cc472c8ae2feea5b4512e23b56e2b093d64f7cbc4b970af51da488429ff7818", size = 1301029, upload-time = "2025-10-17T18:03:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/d8/10/68a0b5549876d4b53ba4c46eed2a7aca32d589624ed60beef5bd7382619e/pyodbc-5.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c79df54bbc25bce9f2d87094e7b39089c28428df5443d1902b0cc5f43fd2da6f", size = 1361420, upload-time = "2025-10-17T18:03:35.958Z" }, + { url = "https://files.pythonhosted.org/packages/41/0f/9dfe4987283ffcb981c49a002f0339d669215eb4a3fe4ee4e14537c52852/pyodbc-5.3.0-cp313-cp313-win32.whl", hash = "sha256:c2eb0b08e24fe5c40c7ebe9240c5d3bd2f18cd5617229acee4b0a0484dc226f2", size = 63399, upload-time = "2025-10-17T18:03:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/56/03/15dcefe549d3888b649652af7cca36eda97c12b6196d92937ca6d11306e9/pyodbc-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:01166162149adf2b8a6dc21a212718f205cabbbdff4047dc0c415af3fd85867e", size = 70133, upload-time = "2025-10-17T18:03:38.47Z" }, + { url = "https://files.pythonhosted.org/packages/c4/c1/c8b128ae59a14ecc8510e9b499208e342795aecc3af4c3874805c720b8db/pyodbc-5.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:363311bd40320b4a61454bebf7c38b243cd67c762ed0f8a5219de3ec90c96353", size = 64683, upload-time = "2025-10-17T18:03:39.68Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f2/c26d82a7ce1e90b8bbb8731d3d53de73814e2f6606b9db9d978303aa8d5f/pyodbc-5.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3f1bdb3ce6480a17afaaef4b5242b356d4997a872f39e96f015cabef00613797", size = 73513, upload-time = "2025-10-17T18:03:40.536Z" }, + { url = "https://files.pythonhosted.org/packages/82/d5/1ab1b7c4708cbd701990a8f7183c5bb5e0712d5e8479b919934e46dadab4/pyodbc-5.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7713c740a10f33df3cb08f49a023b7e1e25de0c7c99650876bbe717bc95ee780", size = 72631, upload-time = "2025-10-17T18:03:41.713Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/7e3831eeac2b09b31a77e6b3495491ce162035ff2903d7261b49d35aa3c2/pyodbc-5.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cf18797a12e70474e1b7f5027deeeccea816372497e3ff2d46b15bec2d18a0cc", size = 344580, upload-time = "2025-10-17T18:03:42.67Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a6/71d26d626a3c45951620b7ff356ec920e420f0e09b0a924123682aa5e4ab/pyodbc-5.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:08b2439500e212625471d32f8fde418075a5ddec556e095e5a4ba56d61df2dc6", size = 350224, upload-time = "2025-10-17T18:03:43.731Z" }, + { url = "https://files.pythonhosted.org/packages/93/14/f702c5e8c2d595776266934498505f11b7f1545baf21ffec1d32c258e9d3/pyodbc-5.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:729c535341bb09c476f219d6f7ab194bcb683c4a0a368010f1cb821a35136f05", size = 1301503, upload-time = "2025-10-17T18:03:45.013Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b2/ad92ebdd1b5c7fec36b065e586d1d34b57881e17ba5beec5c705f1031058/pyodbc-5.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c67e7f2ce649155ea89beb54d3b42d83770488f025cf3b6f39ca82e9c598a02e", size = 1361050, upload-time = "2025-10-17T18:03:46.298Z" }, + { url = "https://files.pythonhosted.org/packages/19/40/dc84e232da07056cb5aaaf5f759ba4c874bc12f37569f7f1670fc71e7ae1/pyodbc-5.3.0-cp314-cp314-win32.whl", hash = "sha256:a48d731432abaee5256ed6a19a3e1528b8881f9cb25cb9cf72d8318146ea991b", size = 65670, upload-time = "2025-10-17T18:03:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/b8/79/c48be07e8634f764662d7a279ac204f93d64172162dbf90f215e2398b0bd/pyodbc-5.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:58635a1cc859d5af3f878c85910e5d7228fe5c406d4571bffcdd281375a54b39", size = 72177, upload-time = "2025-10-17T18:03:57.296Z" }, + { url = "https://files.pythonhosted.org/packages/fc/79/e304574446b2263f428ce14df590ba52c2e0e0205e8d34b235b582b7d57e/pyodbc-5.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:754d052030d00c3ac38da09ceb9f3e240e8dd1c11da8906f482d5419c65b9ef5", size = 66668, upload-time = "2025-10-17T18:03:58.174Z" }, + { url = "https://files.pythonhosted.org/packages/43/17/f4eabf443b838a2728773554017d08eee3aca353102934a7e3ba96fb0e31/pyodbc-5.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f927b440c38ade1668f0da64047ffd20ec34e32d817f9a60d07553301324b364", size = 75780, upload-time = "2025-10-17T18:03:47.273Z" }, + { url = "https://files.pythonhosted.org/packages/59/ea/e79e168c3d38c27d59d5d96273fd9e3c3ba55937cc944c4e60618f51de90/pyodbc-5.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:25c4cfb2c08e77bc6e82f666d7acd52f0e52a0401b1876e60f03c73c3b8aedc0", size = 75503, upload-time = "2025-10-17T18:03:48.171Z" }, + { url = "https://files.pythonhosted.org/packages/90/81/d1d7c125ec4a20e83fdc28e119b8321192b2bd694f432cf63e1199b2b929/pyodbc-5.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc834567c2990584b9726cba365834d039380c9dbbcef3030ddeb00c6541b943", size = 398356, upload-time = "2025-10-17T18:03:49.131Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fc/f6be4b3cc3910f8c2aba37aa41671121fd6f37b402ae0fefe53a70ac7cd5/pyodbc-5.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8339d3094858893c1a68ee1af93efc4dff18b8b65de54d99104b99af6306320d", size = 397291, upload-time = "2025-10-17T18:03:50.18Z" }, + { url = "https://files.pythonhosted.org/packages/03/2e/0610b1ed05a5625528d52f6cece9610e84617d35f475c89c2a52f66d13f7/pyodbc-5.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74528fe148980d0c735c0ebb4a4dc74643ac4574337c43c1006ac4d09593f92d", size = 1353900, upload-time = "2025-10-17T18:03:51.339Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f1/43497e1d37f9f71b43b2b3172e7b1bdf50851e278390c3fb6b46a3630c53/pyodbc-5.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d89a7f2e24227150c13be8164774b7e1f9678321a4248f1356a465b9cc17d31e", size = 1406062, upload-time = "2025-10-17T18:03:52.546Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/88a1277c2f7d9ab1cec0a71e074ba24fd4a1710a43974682546da90a1343/pyodbc-5.3.0-cp314-cp314t-win32.whl", hash = "sha256:af4d8c9842fc4a6360c31c35508d6594d5a3b39922f61b282c2b4c9d9da99514", size = 70132, upload-time = "2025-10-17T18:03:53.715Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c7/ee98c62050de4aa8bafb6eb1e11b95e0b0c898bd5930137c6dc776e06a9b/pyodbc-5.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:bfeb3e34795d53b7d37e66dd54891d4f9c13a3889a8f5fe9640e56a82d770955", size = 79452, upload-time = "2025-10-17T18:03:54.664Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8f/d8889efd96bbe8e5d43ff9701f6b1565a8e09c3e1f58c388d550724f777b/pyodbc-5.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:13656184faa3f2d5c6f19b701b8f247342ed581484f58bf39af7315c054e69db", size = 70142, upload-time = "2025-10-17T18:03:55.551Z" }, +] + +[[package]] +name = "pyproject-hooks" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "python-json-logger" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "pywinpty" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/bb/a7cc2967c5c4eceb6cc49cfe39447d4bfc56e6c865e7c2249b6eb978935f/pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004", size = 30669, upload-time = "2025-10-03T21:16:29.205Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:327790d70e4c841ebd9d0f295a780177149aeb405bca44c7115a3de5c2054b23", size = 2050304, upload-time = "2025-10-03T21:19:29.466Z" }, + { url = "https://files.pythonhosted.org/packages/02/4e/1098484e042c9485f56f16eb2b69b43b874bd526044ee401512234cf9e04/pywinpty-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:99fdd9b455f0ad6419aba6731a7a0d2f88ced83c3c94a80ff9533d95fa8d8a9e", size = 2050391, upload-time = "2025-10-03T21:19:01.642Z" }, + { url = "https://files.pythonhosted.org/packages/fc/19/b757fe28008236a4a713e813283721b8a40aa60cd7d3f83549f2e25a3155/pywinpty-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:18f78b81e4cfee6aabe7ea8688441d30247b73e52cd9657138015c5f4ee13a51", size = 2050057, upload-time = "2025-10-03T21:19:26.732Z" }, + { url = "https://files.pythonhosted.org/packages/cb/44/cbae12ecf6f4fa4129c36871fd09c6bef4f98d5f625ecefb5e2449765508/pywinpty-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:663383ecfab7fc382cc97ea5c4f7f0bb32c2f889259855df6ea34e5df42d305b", size = 2049874, upload-time = "2025-10-03T21:18:53.923Z" }, + { url = "https://files.pythonhosted.org/packages/ca/15/f12c6055e2d7a617d4d5820e8ac4ceaff849da4cb124640ef5116a230771/pywinpty-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:28297cecc37bee9f24d8889e47231972d6e9e84f7b668909de54f36ca785029a", size = 2050386, upload-time = "2025-10-03T21:18:50.477Z" }, + { url = "https://files.pythonhosted.org/packages/de/24/c6907c5bb06043df98ad6a0a0ff5db2e0affcecbc3b15c42404393a3f72a/pywinpty-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:34b55ae9a1b671fe3eae071d86618110538e8eaad18fcb1531c0830b91a82767", size = 2049834, upload-time = "2025-10-03T21:19:25.688Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "pyzmq" +version = "27.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, + { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, + { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, + { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, + { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, + { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, + { url = "https://files.pythonhosted.org/packages/60/cb/84a13459c51da6cec1b7b1dc1a47e6db6da50b77ad7fd9c145842750a011/pyzmq-27.1.0-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:93ad4b0855a664229559e45c8d23797ceac03183c7b6f5b4428152a6b06684a5", size = 1122436, upload-time = "2025-09-08T23:08:20.801Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/94414759a69a26c3dd674570a81813c46a078767d931a6c70ad29fc585cb/pyzmq-27.1.0-cp313-cp313-android_24_x86_64.whl", hash = "sha256:fbb4f2400bfda24f12f009cba62ad5734148569ff4949b1b6ec3b519444342e6", size = 1156301, upload-time = "2025-09-08T23:08:22.47Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ad/15906493fd40c316377fd8a8f6b1f93104f97a752667763c9b9c1b71d42d/pyzmq-27.1.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:e343d067f7b151cfe4eb3bb796a7752c9d369eed007b91231e817071d2c2fec7", size = 1341197, upload-time = "2025-09-08T23:08:24.286Z" }, + { url = "https://files.pythonhosted.org/packages/14/1d/d343f3ce13db53a54cb8946594e567410b2125394dafcc0268d8dda027e0/pyzmq-27.1.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:08363b2011dec81c354d694bdecaef4770e0ae96b9afea70b3f47b973655cc05", size = 897275, upload-time = "2025-09-08T23:08:26.063Z" }, + { url = "https://files.pythonhosted.org/packages/69/2d/d83dd6d7ca929a2fc67d2c3005415cdf322af7751d773524809f9e585129/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d54530c8c8b5b8ddb3318f481297441af102517602b569146185fa10b63f4fa9", size = 660469, upload-time = "2025-09-08T23:08:27.623Z" }, + { url = "https://files.pythonhosted.org/packages/3e/cd/9822a7af117f4bc0f1952dbe9ef8358eb50a24928efd5edf54210b850259/pyzmq-27.1.0-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6f3afa12c392f0a44a2414056d730eebc33ec0926aae92b5ad5cf26ebb6cc128", size = 847961, upload-time = "2025-09-08T23:08:29.672Z" }, + { url = "https://files.pythonhosted.org/packages/9a/12/f003e824a19ed73be15542f172fd0ec4ad0b60cf37436652c93b9df7c585/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c65047adafe573ff023b3187bb93faa583151627bc9c51fc4fb2c561ed689d39", size = 1650282, upload-time = "2025-09-08T23:08:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4a/e82d788ed58e9a23995cee70dbc20c9aded3d13a92d30d57ec2291f1e8a3/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:90e6e9441c946a8b0a667356f7078d96411391a3b8f80980315455574177ec97", size = 2024468, upload-time = "2025-09-08T23:08:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/d9/94/2da0a60841f757481e402b34bf4c8bf57fa54a5466b965de791b1e6f747d/pyzmq-27.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:add071b2d25f84e8189aaf0882d39a285b42fa3853016ebab234a5e78c7a43db", size = 1885394, upload-time = "2025-09-08T23:08:35.51Z" }, + { url = "https://files.pythonhosted.org/packages/4f/6f/55c10e2e49ad52d080dc24e37adb215e5b0d64990b57598abc2e3f01725b/pyzmq-27.1.0-cp313-cp313t-win32.whl", hash = "sha256:7ccc0700cfdf7bd487bea8d850ec38f204478681ea02a582a8da8171b7f90a1c", size = 574964, upload-time = "2025-09-08T23:08:37.178Z" }, + { url = "https://files.pythonhosted.org/packages/87/4d/2534970ba63dd7c522d8ca80fb92777f362c0f321900667c615e2067cb29/pyzmq-27.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8085a9fba668216b9b4323be338ee5437a235fe275b9d1610e422ccc279733e2", size = 641029, upload-time = "2025-09-08T23:08:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fa/f8aea7a28b0641f31d40dea42d7ef003fded31e184ef47db696bc74cd610/pyzmq-27.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:6bb54ca21bcfe361e445256c15eedf083f153811c37be87e0514934d6913061e", size = 561541, upload-time = "2025-09-08T23:08:42.668Z" }, + { url = "https://files.pythonhosted.org/packages/87/45/19efbb3000956e82d0331bafca5d9ac19ea2857722fa2caacefb6042f39d/pyzmq-27.1.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ce980af330231615756acd5154f29813d553ea555485ae712c491cd483df6b7a", size = 1341197, upload-time = "2025-09-08T23:08:44.973Z" }, + { url = "https://files.pythonhosted.org/packages/48/43/d72ccdbf0d73d1343936296665826350cb1e825f92f2db9db3e61c2162a2/pyzmq-27.1.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1779be8c549e54a1c38f805e56d2a2e5c009d26de10921d7d51cfd1c8d4632ea", size = 897175, upload-time = "2025-09-08T23:08:46.601Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2e/a483f73a10b65a9ef0161e817321d39a770b2acf8bcf3004a28d90d14a94/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7200bb0f03345515df50d99d3db206a0a6bee1955fbb8c453c76f5bf0e08fb96", size = 660427, upload-time = "2025-09-08T23:08:48.187Z" }, + { url = "https://files.pythonhosted.org/packages/f5/d2/5f36552c2d3e5685abe60dfa56f91169f7a2d99bbaf67c5271022ab40863/pyzmq-27.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01c0e07d558b06a60773744ea6251f769cd79a41a97d11b8bf4ab8f034b0424d", size = 847929, upload-time = "2025-09-08T23:08:49.76Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2a/404b331f2b7bf3198e9945f75c4c521f0c6a3a23b51f7a4a401b94a13833/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:80d834abee71f65253c91540445d37c4c561e293ba6e741b992f20a105d69146", size = 1650193, upload-time = "2025-09-08T23:08:51.7Z" }, + { url = "https://files.pythonhosted.org/packages/1c/0b/f4107e33f62a5acf60e3ded67ed33d79b4ce18de432625ce2fc5093d6388/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:544b4e3b7198dde4a62b8ff6685e9802a9a1ebf47e77478a5eb88eca2a82f2fd", size = 2024388, upload-time = "2025-09-08T23:08:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/01/add31fe76512642fd6e40e3a3bd21f4b47e242c8ba33efb6809e37076d9b/pyzmq-27.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:cedc4c68178e59a4046f97eca31b148ddcf51e88677de1ef4e78cf06c5376c9a", size = 1885316, upload-time = "2025-09-08T23:08:55.702Z" }, + { url = "https://files.pythonhosted.org/packages/c4/59/a5f38970f9bf07cee96128de79590bb354917914a9be11272cfc7ff26af0/pyzmq-27.1.0-cp314-cp314t-win32.whl", hash = "sha256:1f0b2a577fd770aa6f053211a55d1c47901f4d537389a034c690291485e5fe92", size = 587472, upload-time = "2025-09-08T23:08:58.18Z" }, + { url = "https://files.pythonhosted.org/packages/70/d8/78b1bad170f93fcf5e3536e70e8fadac55030002275c9a29e8f5719185de/pyzmq-27.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:19c9468ae0437f8074af379e986c5d3d7d7bfe033506af442e8c879732bedbe0", size = 661401, upload-time = "2025-09-08T23:08:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/81/d6/4bfbb40c9a0b42fc53c7cf442f6385db70b40f74a783130c5d0a5aa62228/pyzmq-27.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dc5dbf68a7857b59473f7df42650c621d7e8923fb03fa74a526890f4d33cc4d7", size = 575170, upload-time = "2025-09-08T23:09:01.418Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, + { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "regex" +version = "2026.1.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/86/07d5056945f9ec4590b518171c4254a5925832eb727b56d3c38a7476f316/regex-2026.1.15.tar.gz", hash = "sha256:164759aa25575cbc0651bef59a0b18353e54300d79ace8084c818ad8ac72b7d5", size = 414811, upload-time = "2026-01-14T23:18:02.775Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/c9/0c80c96eab96948363d270143138d671d5731c3a692b417629bf3492a9d6/regex-2026.1.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ae6020fb311f68d753b7efa9d4b9a5d47a5d6466ea0d5e3b5a471a960ea6e4a", size = 488168, upload-time = "2026-01-14T23:14:16.129Z" }, + { url = "https://files.pythonhosted.org/packages/17/f0/271c92f5389a552494c429e5cc38d76d1322eb142fb5db3c8ccc47751468/regex-2026.1.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eddf73f41225942c1f994914742afa53dc0d01a6e20fe14b878a1b1edc74151f", size = 290636, upload-time = "2026-01-14T23:14:17.715Z" }, + { url = "https://files.pythonhosted.org/packages/a0/f9/5f1fd077d106ca5655a0f9ff8f25a1ab55b92128b5713a91ed7134ff688e/regex-2026.1.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e8cd52557603f5c66a548f69421310886b28b7066853089e1a71ee710e1cdc1", size = 288496, upload-time = "2026-01-14T23:14:19.326Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e1/8f43b03a4968c748858ec77f746c286d81f896c2e437ccf050ebc5d3128c/regex-2026.1.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5170907244b14303edc5978f522f16c974f32d3aa92109fabc2af52411c9433b", size = 793503, upload-time = "2026-01-14T23:14:20.922Z" }, + { url = "https://files.pythonhosted.org/packages/8d/4e/a39a5e8edc5377a46a7c875c2f9a626ed3338cb3bb06931be461c3e1a34a/regex-2026.1.15-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2748c1ec0663580b4510bd89941a31560b4b439a0b428b49472a3d9944d11cd8", size = 860535, upload-time = "2026-01-14T23:14:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/dc/1c/9dce667a32a9477f7a2869c1c767dc00727284a9fa3ff5c09a5c6c03575e/regex-2026.1.15-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2f2775843ca49360508d080eaa87f94fa248e2c946bbcd963bb3aae14f333413", size = 907225, upload-time = "2026-01-14T23:14:23.897Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3c/87ca0a02736d16b6262921425e84b48984e77d8e4e572c9072ce96e66c30/regex-2026.1.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9ea2604370efc9a174c1b5dcc81784fb040044232150f7f33756049edfc9026", size = 800526, upload-time = "2026-01-14T23:14:26.039Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/647d5715aeea7c87bdcbd2f578f47b415f55c24e361e639fe8c0cc88878f/regex-2026.1.15-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0dcd31594264029b57bf16f37fd7248a70b3b764ed9e0839a8f271b2d22c0785", size = 773446, upload-time = "2026-01-14T23:14:28.109Z" }, + { url = "https://files.pythonhosted.org/packages/af/89/bf22cac25cb4ba0fe6bff52ebedbb65b77a179052a9d6037136ae93f42f4/regex-2026.1.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c08c1f3e34338256732bd6938747daa3c0d5b251e04b6e43b5813e94d503076e", size = 783051, upload-time = "2026-01-14T23:14:29.929Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f4/6ed03e71dca6348a5188363a34f5e26ffd5db1404780288ff0d79513bce4/regex-2026.1.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e43a55f378df1e7a4fa3547c88d9a5a9b7113f653a66821bcea4718fe6c58763", size = 854485, upload-time = "2026-01-14T23:14:31.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/8e8560bd78caded8eb137e3e47612430a05b9a772caf60876435192d670a/regex-2026.1.15-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:f82110ab962a541737bd0ce87978d4c658f06e7591ba899192e2712a517badbb", size = 762195, upload-time = "2026-01-14T23:14:32.802Z" }, + { url = "https://files.pythonhosted.org/packages/38/6b/61fc710f9aa8dfcd764fe27d37edfaa023b1a23305a0d84fccd5adb346ea/regex-2026.1.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:27618391db7bdaf87ac6c92b31e8f0dfb83a9de0075855152b720140bda177a2", size = 845986, upload-time = "2026-01-14T23:14:34.898Z" }, + { url = "https://files.pythonhosted.org/packages/fd/2e/fbee4cb93f9d686901a7ca8d94285b80405e8c34fe4107f63ffcbfb56379/regex-2026.1.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bfb0d6be01fbae8d6655c8ca21b3b72458606c4aec9bbc932db758d47aba6db1", size = 788992, upload-time = "2026-01-14T23:14:37.116Z" }, + { url = "https://files.pythonhosted.org/packages/ed/14/3076348f3f586de64b1ab75a3fbabdaab7684af7f308ad43be7ef1849e55/regex-2026.1.15-cp311-cp311-win32.whl", hash = "sha256:b10e42a6de0e32559a92f2f8dc908478cc0fa02838d7dbe764c44dca3fa13569", size = 265893, upload-time = "2026-01-14T23:14:38.426Z" }, + { url = "https://files.pythonhosted.org/packages/0f/19/772cf8b5fc803f5c89ba85d8b1870a1ca580dc482aa030383a9289c82e44/regex-2026.1.15-cp311-cp311-win_amd64.whl", hash = "sha256:e9bf3f0bbdb56633c07d7116ae60a576f846efdd86a8848f8d62b749e1209ca7", size = 277840, upload-time = "2026-01-14T23:14:39.785Z" }, + { url = "https://files.pythonhosted.org/packages/78/84/d05f61142709474da3c0853222d91086d3e1372bcdab516c6fd8d80f3297/regex-2026.1.15-cp311-cp311-win_arm64.whl", hash = "sha256:41aef6f953283291c4e4e6850607bd71502be67779586a61472beacb315c97ec", size = 270374, upload-time = "2026-01-14T23:14:41.592Z" }, + { url = "https://files.pythonhosted.org/packages/92/81/10d8cf43c807d0326efe874c1b79f22bfb0fb226027b0b19ebc26d301408/regex-2026.1.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c8fcc5793dde01641a35905d6731ee1548f02b956815f8f1cab89e515a5bdf1", size = 489398, upload-time = "2026-01-14T23:14:43.741Z" }, + { url = "https://files.pythonhosted.org/packages/90/b0/7c2a74e74ef2a7c32de724658a69a862880e3e4155cba992ba04d1c70400/regex-2026.1.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bfd876041a956e6a90ad7cdb3f6a630c07d491280bfeed4544053cd434901681", size = 291339, upload-time = "2026-01-14T23:14:45.183Z" }, + { url = "https://files.pythonhosted.org/packages/19/4d/16d0773d0c818417f4cc20aa0da90064b966d22cd62a8c46765b5bd2d643/regex-2026.1.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9250d087bc92b7d4899ccd5539a1b2334e44eee85d848c4c1aef8e221d3f8c8f", size = 289003, upload-time = "2026-01-14T23:14:47.25Z" }, + { url = "https://files.pythonhosted.org/packages/c6/e4/1fc4599450c9f0863d9406e944592d968b8d6dfd0d552a7d569e43bceada/regex-2026.1.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8a154cf6537ebbc110e24dabe53095e714245c272da9c1be05734bdad4a61aa", size = 798656, upload-time = "2026-01-14T23:14:48.77Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e6/59650d73a73fa8a60b3a590545bfcf1172b4384a7df2e7fe7b9aab4e2da9/regex-2026.1.15-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8050ba2e3ea1d8731a549e83c18d2f0999fbc99a5f6bd06b4c91449f55291804", size = 864252, upload-time = "2026-01-14T23:14:50.528Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ab/1d0f4d50a1638849a97d731364c9a80fa304fec46325e48330c170ee8e80/regex-2026.1.15-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf065240704cb8951cc04972cf107063917022511273e0969bdb34fc173456c", size = 912268, upload-time = "2026-01-14T23:14:52.952Z" }, + { url = "https://files.pythonhosted.org/packages/dd/df/0d722c030c82faa1d331d1921ee268a4e8fb55ca8b9042c9341c352f17fa/regex-2026.1.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c32bef3e7aeee75746748643667668ef941d28b003bfc89994ecf09a10f7a1b5", size = 803589, upload-time = "2026-01-14T23:14:55.182Z" }, + { url = "https://files.pythonhosted.org/packages/66/23/33289beba7ccb8b805c6610a8913d0131f834928afc555b241caabd422a9/regex-2026.1.15-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d5eaa4a4c5b1906bd0d2508d68927f15b81821f85092e06f1a34a4254b0e1af3", size = 775700, upload-time = "2026-01-14T23:14:56.707Z" }, + { url = "https://files.pythonhosted.org/packages/e7/65/bf3a42fa6897a0d3afa81acb25c42f4b71c274f698ceabd75523259f6688/regex-2026.1.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:86c1077a3cc60d453d4084d5b9649065f3bf1184e22992bd322e1f081d3117fb", size = 787928, upload-time = "2026-01-14T23:14:58.312Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f5/13bf65864fc314f68cdd6d8ca94adcab064d4d39dbd0b10fef29a9da48fc/regex-2026.1.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:2b091aefc05c78d286657cd4db95f2e6313375ff65dcf085e42e4c04d9c8d410", size = 858607, upload-time = "2026-01-14T23:15:00.657Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/040e589834d7a439ee43fb0e1e902bc81bd58a5ba81acffe586bb3321d35/regex-2026.1.15-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:57e7d17f59f9ebfa9667e6e5a1c0127b96b87cb9cede8335482451ed00788ba4", size = 763729, upload-time = "2026-01-14T23:15:02.248Z" }, + { url = "https://files.pythonhosted.org/packages/9b/84/6921e8129687a427edf25a34a5594b588b6d88f491320b9de5b6339a4fcb/regex-2026.1.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c6c4dcdfff2c08509faa15d36ba7e5ef5fcfab25f1e8f85a0c8f45bc3a30725d", size = 850697, upload-time = "2026-01-14T23:15:03.878Z" }, + { url = "https://files.pythonhosted.org/packages/8a/87/3d06143d4b128f4229158f2de5de6c8f2485170c7221e61bf381313314b2/regex-2026.1.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf8ff04c642716a7f2048713ddc6278c5fd41faa3b9cab12607c7abecd012c22", size = 789849, upload-time = "2026-01-14T23:15:06.102Z" }, + { url = "https://files.pythonhosted.org/packages/77/69/c50a63842b6bd48850ebc7ab22d46e7a2a32d824ad6c605b218441814639/regex-2026.1.15-cp312-cp312-win32.whl", hash = "sha256:82345326b1d8d56afbe41d881fdf62f1926d7264b2fc1537f99ae5da9aad7913", size = 266279, upload-time = "2026-01-14T23:15:07.678Z" }, + { url = "https://files.pythonhosted.org/packages/f2/36/39d0b29d087e2b11fd8191e15e81cce1b635fcc845297c67f11d0d19274d/regex-2026.1.15-cp312-cp312-win_amd64.whl", hash = "sha256:4def140aa6156bc64ee9912383d4038f3fdd18fee03a6f222abd4de6357ce42a", size = 277166, upload-time = "2026-01-14T23:15:09.257Z" }, + { url = "https://files.pythonhosted.org/packages/28/32/5b8e476a12262748851fa8ab1b0be540360692325975b094e594dfebbb52/regex-2026.1.15-cp312-cp312-win_arm64.whl", hash = "sha256:c6c565d9a6e1a8d783c1948937ffc377dd5771e83bd56de8317c450a954d2056", size = 270415, upload-time = "2026-01-14T23:15:10.743Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2e/6870bb16e982669b674cce3ee9ff2d1d46ab80528ee6bcc20fb2292efb60/regex-2026.1.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e69d0deeb977ffe7ed3d2e4439360089f9c3f217ada608f0f88ebd67afb6385e", size = 489164, upload-time = "2026-01-14T23:15:13.962Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/9774542e203849b0286badf67199970a44ebdb0cc5fb739f06e47ada72f8/regex-2026.1.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3601ffb5375de85a16f407854d11cca8fe3f5febbe3ac78fb2866bb220c74d10", size = 291218, upload-time = "2026-01-14T23:15:15.647Z" }, + { url = "https://files.pythonhosted.org/packages/b2/87/b0cda79f22b8dee05f774922a214da109f9a4c0eca5da2c9d72d77ea062c/regex-2026.1.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4c5ef43b5c2d4114eb8ea424bb8c9cec01d5d17f242af88b2448f5ee81caadbc", size = 288895, upload-time = "2026-01-14T23:15:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/3b/6a/0041f0a2170d32be01ab981d6346c83a8934277d82c780d60b127331f264/regex-2026.1.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:968c14d4f03e10b2fd960f1d5168c1f0ac969381d3c1fcc973bc45fb06346599", size = 798680, upload-time = "2026-01-14T23:15:19.342Z" }, + { url = "https://files.pythonhosted.org/packages/58/de/30e1cfcdbe3e891324aa7568b7c968771f82190df5524fabc1138cb2d45a/regex-2026.1.15-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56a5595d0f892f214609c9f76b41b7428bed439d98dc961efafdd1354d42baae", size = 864210, upload-time = "2026-01-14T23:15:22.005Z" }, + { url = "https://files.pythonhosted.org/packages/64/44/4db2f5c5ca0ccd40ff052ae7b1e9731352fcdad946c2b812285a7505ca75/regex-2026.1.15-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf650f26087363434c4e560011f8e4e738f6f3e029b85d4904c50135b86cfa5", size = 912358, upload-time = "2026-01-14T23:15:24.569Z" }, + { url = "https://files.pythonhosted.org/packages/79/b6/e6a5665d43a7c42467138c8a2549be432bad22cbd206f5ec87162de74bd7/regex-2026.1.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18388a62989c72ac24de75f1449d0fb0b04dfccd0a1a7c1c43af5eb503d890f6", size = 803583, upload-time = "2026-01-14T23:15:26.526Z" }, + { url = "https://files.pythonhosted.org/packages/e7/53/7cd478222169d85d74d7437e74750005e993f52f335f7c04ff7adfda3310/regex-2026.1.15-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d220a2517f5893f55daac983bfa9fe998a7dbcaee4f5d27a88500f8b7873788", size = 775782, upload-time = "2026-01-14T23:15:29.352Z" }, + { url = "https://files.pythonhosted.org/packages/ca/b5/75f9a9ee4b03a7c009fe60500fe550b45df94f0955ca29af16333ef557c5/regex-2026.1.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9c08c2fbc6120e70abff5d7f28ffb4d969e14294fb2143b4b5c7d20e46d1714", size = 787978, upload-time = "2026-01-14T23:15:31.295Z" }, + { url = "https://files.pythonhosted.org/packages/72/b3/79821c826245bbe9ccbb54f6eadb7879c722fd3e0248c17bfc90bf54e123/regex-2026.1.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7ef7d5d4bd49ec7364315167a4134a015f61e8266c6d446fc116a9ac4456e10d", size = 858550, upload-time = "2026-01-14T23:15:33.558Z" }, + { url = "https://files.pythonhosted.org/packages/4a/85/2ab5f77a1c465745bfbfcb3ad63178a58337ae8d5274315e2cc623a822fa/regex-2026.1.15-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:6e42844ad64194fa08d5ccb75fe6a459b9b08e6d7296bd704460168d58a388f3", size = 763747, upload-time = "2026-01-14T23:15:35.206Z" }, + { url = "https://files.pythonhosted.org/packages/6d/84/c27df502d4bfe2873a3e3a7cf1bdb2b9cc10284d1a44797cf38bed790470/regex-2026.1.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cfecdaa4b19f9ca534746eb3b55a5195d5c95b88cac32a205e981ec0a22b7d31", size = 850615, upload-time = "2026-01-14T23:15:37.523Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b7/658a9782fb253680aa8ecb5ccbb51f69e088ed48142c46d9f0c99b46c575/regex-2026.1.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:08df9722d9b87834a3d701f3fca570b2be115654dbfd30179f30ab2f39d606d3", size = 789951, upload-time = "2026-01-14T23:15:39.582Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2a/5928af114441e059f15b2f63e188bd00c6529b3051c974ade7444b85fcda/regex-2026.1.15-cp313-cp313-win32.whl", hash = "sha256:d426616dae0967ca225ab12c22274eb816558f2f99ccb4a1d52ca92e8baf180f", size = 266275, upload-time = "2026-01-14T23:15:42.108Z" }, + { url = "https://files.pythonhosted.org/packages/4f/16/5bfbb89e435897bff28cf0352a992ca719d9e55ebf8b629203c96b6ce4f7/regex-2026.1.15-cp313-cp313-win_amd64.whl", hash = "sha256:febd38857b09867d3ed3f4f1af7d241c5c50362e25ef43034995b77a50df494e", size = 277145, upload-time = "2026-01-14T23:15:44.244Z" }, + { url = "https://files.pythonhosted.org/packages/56/c1/a09ff7392ef4233296e821aec5f78c51be5e91ffde0d163059e50fd75835/regex-2026.1.15-cp313-cp313-win_arm64.whl", hash = "sha256:8e32f7896f83774f91499d239e24cebfadbc07639c1494bb7213983842348337", size = 270411, upload-time = "2026-01-14T23:15:45.858Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/0cfd5a78e5c6db00e6782fdae70458f89850ce95baa5e8694ab91d89744f/regex-2026.1.15-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ec94c04149b6a7b8120f9f44565722c7ae31b7a6d2275569d2eefa76b83da3be", size = 492068, upload-time = "2026-01-14T23:15:47.616Z" }, + { url = "https://files.pythonhosted.org/packages/50/72/6c86acff16cb7c959c4355826bbf06aad670682d07c8f3998d9ef4fee7cd/regex-2026.1.15-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40c86d8046915bb9aeb15d3f3f15b6fd500b8ea4485b30e1bbc799dab3fe29f8", size = 292756, upload-time = "2026-01-14T23:15:49.307Z" }, + { url = "https://files.pythonhosted.org/packages/4e/58/df7fb69eadfe76526ddfce28abdc0af09ffe65f20c2c90932e89d705153f/regex-2026.1.15-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:726ea4e727aba21643205edad8f2187ec682d3305d790f73b7a51c7587b64bdd", size = 291114, upload-time = "2026-01-14T23:15:51.484Z" }, + { url = "https://files.pythonhosted.org/packages/ed/6c/a4011cd1cf96b90d2cdc7e156f91efbd26531e822a7fbb82a43c1016678e/regex-2026.1.15-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1cb740d044aff31898804e7bf1181cc72c03d11dfd19932b9911ffc19a79070a", size = 807524, upload-time = "2026-01-14T23:15:53.102Z" }, + { url = "https://files.pythonhosted.org/packages/1d/25/a53ffb73183f69c3e9f4355c4922b76d2840aee160af6af5fac229b6201d/regex-2026.1.15-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05d75a668e9ea16f832390d22131fe1e8acc8389a694c8febc3e340b0f810b93", size = 873455, upload-time = "2026-01-14T23:15:54.956Z" }, + { url = "https://files.pythonhosted.org/packages/66/0b/8b47fc2e8f97d9b4a851736f3890a5f786443aa8901061c55f24c955f45b/regex-2026.1.15-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d991483606f3dbec93287b9f35596f41aa2e92b7c2ebbb935b63f409e243c9af", size = 915007, upload-time = "2026-01-14T23:15:57.041Z" }, + { url = "https://files.pythonhosted.org/packages/c2/fa/97de0d681e6d26fabe71968dbee06dd52819e9a22fdce5dac7256c31ed84/regex-2026.1.15-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:194312a14819d3e44628a44ed6fea6898fdbecb0550089d84c403475138d0a09", size = 812794, upload-time = "2026-01-14T23:15:58.916Z" }, + { url = "https://files.pythonhosted.org/packages/22/38/e752f94e860d429654aa2b1c51880bff8dfe8f084268258adf9151cf1f53/regex-2026.1.15-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe2fda4110a3d0bc163c2e0664be44657431440722c5c5315c65155cab92f9e5", size = 781159, upload-time = "2026-01-14T23:16:00.817Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a7/d739ffaef33c378fc888302a018d7f81080393d96c476b058b8c64fd2b0d/regex-2026.1.15-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:124dc36c85d34ef2d9164da41a53c1c8c122cfb1f6e1ec377a1f27ee81deb794", size = 795558, upload-time = "2026-01-14T23:16:03.267Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c4/542876f9a0ac576100fc73e9c75b779f5c31e3527576cfc9cb3009dcc58a/regex-2026.1.15-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a1774cd1981cd212506a23a14dba7fdeaee259f5deba2df6229966d9911e767a", size = 868427, upload-time = "2026-01-14T23:16:05.646Z" }, + { url = "https://files.pythonhosted.org/packages/fc/0f/d5655bea5b22069e32ae85a947aa564912f23758e112cdb74212848a1a1b/regex-2026.1.15-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:b5f7d8d2867152cdb625e72a530d2ccb48a3d199159144cbdd63870882fb6f80", size = 769939, upload-time = "2026-01-14T23:16:07.542Z" }, + { url = "https://files.pythonhosted.org/packages/20/06/7e18a4fa9d326daeda46d471a44ef94201c46eaa26dbbb780b5d92cbfdda/regex-2026.1.15-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:492534a0ab925d1db998defc3c302dae3616a2fc3fe2e08db1472348f096ddf2", size = 854753, upload-time = "2026-01-14T23:16:10.395Z" }, + { url = "https://files.pythonhosted.org/packages/3b/67/dc8946ef3965e166f558ef3b47f492bc364e96a265eb4a2bb3ca765c8e46/regex-2026.1.15-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c661fc820cfb33e166bf2450d3dadbda47c8d8981898adb9b6fe24e5e582ba60", size = 799559, upload-time = "2026-01-14T23:16:12.347Z" }, + { url = "https://files.pythonhosted.org/packages/a5/61/1bba81ff6d50c86c65d9fd84ce9699dd106438ee4cdb105bf60374ee8412/regex-2026.1.15-cp313-cp313t-win32.whl", hash = "sha256:99ad739c3686085e614bf77a508e26954ff1b8f14da0e3765ff7abbf7799f952", size = 268879, upload-time = "2026-01-14T23:16:14.049Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5e/cef7d4c5fb0ea3ac5c775fd37db5747f7378b29526cc83f572198924ff47/regex-2026.1.15-cp313-cp313t-win_amd64.whl", hash = "sha256:32655d17905e7ff8ba5c764c43cb124e34a9245e45b83c22e81041e1071aee10", size = 280317, upload-time = "2026-01-14T23:16:15.718Z" }, + { url = "https://files.pythonhosted.org/packages/b4/52/4317f7a5988544e34ab57b4bde0f04944c4786128c933fb09825924d3e82/regex-2026.1.15-cp313-cp313t-win_arm64.whl", hash = "sha256:b2a13dd6a95e95a489ca242319d18fc02e07ceb28fa9ad146385194d95b3c829", size = 271551, upload-time = "2026-01-14T23:16:17.533Z" }, + { url = "https://files.pythonhosted.org/packages/52/0a/47fa888ec7cbbc7d62c5f2a6a888878e76169170ead271a35239edd8f0e8/regex-2026.1.15-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d920392a6b1f353f4aa54328c867fec3320fa50657e25f64abf17af054fc97ac", size = 489170, upload-time = "2026-01-14T23:16:19.835Z" }, + { url = "https://files.pythonhosted.org/packages/ac/c4/d000e9b7296c15737c9301708e9e7fbdea009f8e93541b6b43bdb8219646/regex-2026.1.15-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b5a28980a926fa810dbbed059547b02783952e2efd9c636412345232ddb87ff6", size = 291146, upload-time = "2026-01-14T23:16:21.541Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b6/921cc61982e538682bdf3bdf5b2c6ab6b34368da1f8e98a6c1ddc503c9cf/regex-2026.1.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:621f73a07595d83f28952d7bd1e91e9d1ed7625fb7af0064d3516674ec93a2a2", size = 288986, upload-time = "2026-01-14T23:16:23.381Z" }, + { url = "https://files.pythonhosted.org/packages/ca/33/eb7383dde0bbc93f4fb9d03453aab97e18ad4024ac7e26cef8d1f0a2cff0/regex-2026.1.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d7d92495f47567a9b1669c51fc8d6d809821849063d168121ef801bbc213846", size = 799098, upload-time = "2026-01-14T23:16:25.088Z" }, + { url = "https://files.pythonhosted.org/packages/27/56/b664dccae898fc8d8b4c23accd853f723bde0f026c747b6f6262b688029c/regex-2026.1.15-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8dd16fba2758db7a3780a051f245539c4451ca20910f5a5e6ea1c08d06d4a76b", size = 864980, upload-time = "2026-01-14T23:16:27.297Z" }, + { url = "https://files.pythonhosted.org/packages/16/40/0999e064a170eddd237bae9ccfcd8f28b3aa98a38bf727a086425542a4fc/regex-2026.1.15-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1e1808471fbe44c1a63e5f577a1d5f02fe5d66031dcbdf12f093ffc1305a858e", size = 911607, upload-time = "2026-01-14T23:16:29.235Z" }, + { url = "https://files.pythonhosted.org/packages/07/78/c77f644b68ab054e5a674fb4da40ff7bffb2c88df58afa82dbf86573092d/regex-2026.1.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0751a26ad39d4f2ade8fe16c59b2bf5cb19eb3d2cd543e709e583d559bd9efde", size = 803358, upload-time = "2026-01-14T23:16:31.369Z" }, + { url = "https://files.pythonhosted.org/packages/27/31/d4292ea8566eaa551fafc07797961c5963cf5235c797cc2ae19b85dfd04d/regex-2026.1.15-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0f0c7684c7f9ca241344ff95a1de964f257a5251968484270e91c25a755532c5", size = 775833, upload-time = "2026-01-14T23:16:33.141Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b2/cff3bf2fea4133aa6fb0d1e370b37544d18c8350a2fa118c7e11d1db0e14/regex-2026.1.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:74f45d170a21df41508cb67165456538425185baaf686281fa210d7e729abc34", size = 788045, upload-time = "2026-01-14T23:16:35.005Z" }, + { url = "https://files.pythonhosted.org/packages/8d/99/2cb9b69045372ec877b6f5124bda4eb4253bc58b8fe5848c973f752bc52c/regex-2026.1.15-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f1862739a1ffb50615c0fde6bae6569b5efbe08d98e59ce009f68a336f64da75", size = 859374, upload-time = "2026-01-14T23:16:36.919Z" }, + { url = "https://files.pythonhosted.org/packages/09/16/710b0a5abe8e077b1729a562d2f297224ad079f3a66dce46844c193416c8/regex-2026.1.15-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:453078802f1b9e2b7303fb79222c054cb18e76f7bdc220f7530fdc85d319f99e", size = 763940, upload-time = "2026-01-14T23:16:38.685Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/7585c8e744e40eb3d32f119191969b91de04c073fca98ec14299041f6e7e/regex-2026.1.15-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:a30a68e89e5a218b8b23a52292924c1f4b245cb0c68d1cce9aec9bbda6e2c160", size = 850112, upload-time = "2026-01-14T23:16:40.646Z" }, + { url = "https://files.pythonhosted.org/packages/af/d6/43e1dd85df86c49a347aa57c1f69d12c652c7b60e37ec162e3096194a278/regex-2026.1.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9479cae874c81bf610d72b85bb681a94c95722c127b55445285fb0e2c82db8e1", size = 789586, upload-time = "2026-01-14T23:16:42.799Z" }, + { url = "https://files.pythonhosted.org/packages/93/38/77142422f631e013f316aaae83234c629555729a9fbc952b8a63ac91462a/regex-2026.1.15-cp314-cp314-win32.whl", hash = "sha256:d639a750223132afbfb8f429c60d9d318aeba03281a5f1ab49f877456448dcf1", size = 271691, upload-time = "2026-01-14T23:16:44.671Z" }, + { url = "https://files.pythonhosted.org/packages/4a/a9/ab16b4649524ca9e05213c1cdbb7faa85cc2aa90a0230d2f796cbaf22736/regex-2026.1.15-cp314-cp314-win_amd64.whl", hash = "sha256:4161d87f85fa831e31469bfd82c186923070fc970b9de75339b68f0c75b51903", size = 280422, upload-time = "2026-01-14T23:16:46.607Z" }, + { url = "https://files.pythonhosted.org/packages/be/2a/20fd057bf3521cb4791f69f869635f73e0aaf2b9ad2d260f728144f9047c/regex-2026.1.15-cp314-cp314-win_arm64.whl", hash = "sha256:91c5036ebb62663a6b3999bdd2e559fd8456d17e2b485bf509784cd31a8b1705", size = 273467, upload-time = "2026-01-14T23:16:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/ad/77/0b1e81857060b92b9cad239104c46507dd481b3ff1fa79f8e7f865aae38a/regex-2026.1.15-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ee6854c9000a10938c79238de2379bea30c82e4925a371711af45387df35cab8", size = 492073, upload-time = "2026-01-14T23:16:51.154Z" }, + { url = "https://files.pythonhosted.org/packages/70/f3/f8302b0c208b22c1e4f423147e1913fd475ddd6230565b299925353de644/regex-2026.1.15-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2c2b80399a422348ce5de4fe40c418d6299a0fa2803dd61dc0b1a2f28e280fcf", size = 292757, upload-time = "2026-01-14T23:16:53.08Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f0/ef55de2460f3b4a6da9d9e7daacd0cb79d4ef75c64a2af316e68447f0df0/regex-2026.1.15-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:dca3582bca82596609959ac39e12b7dad98385b4fefccb1151b937383cec547d", size = 291122, upload-time = "2026-01-14T23:16:55.383Z" }, + { url = "https://files.pythonhosted.org/packages/cf/55/bb8ccbacabbc3a11d863ee62a9f18b160a83084ea95cdfc5d207bfc3dd75/regex-2026.1.15-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71d476caa6692eea743ae5ea23cde3260677f70122c4d258ca952e5c2d4e84", size = 807761, upload-time = "2026-01-14T23:16:57.251Z" }, + { url = "https://files.pythonhosted.org/packages/8f/84/f75d937f17f81e55679a0509e86176e29caa7298c38bd1db7ce9c0bf6075/regex-2026.1.15-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c243da3436354f4af6c3058a3f81a97d47ea52c9bd874b52fd30274853a1d5df", size = 873538, upload-time = "2026-01-14T23:16:59.349Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d9/0da86327df70349aa8d86390da91171bd3ca4f0e7c1d1d453a9c10344da3/regex-2026.1.15-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8355ad842a7c7e9e5e55653eade3b7d1885ba86f124dd8ab1f722f9be6627434", size = 915066, upload-time = "2026-01-14T23:17:01.607Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5e/f660fb23fc77baa2a61aa1f1fe3a4eea2bbb8a286ddec148030672e18834/regex-2026.1.15-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f192a831d9575271a22d804ff1a5355355723f94f31d9eef25f0d45a152fdc1a", size = 812938, upload-time = "2026-01-14T23:17:04.366Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/a47a29bfecebbbfd1e5cd3f26b28020a97e4820f1c5148e66e3b7d4b4992/regex-2026.1.15-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:166551807ec20d47ceaeec380081f843e88c8949780cd42c40f18d16168bed10", size = 781314, upload-time = "2026-01-14T23:17:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/65/ec/7ec2bbfd4c3f4e494a24dec4c6943a668e2030426b1b8b949a6462d2c17b/regex-2026.1.15-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:f9ca1cbdc0fbfe5e6e6f8221ef2309988db5bcede52443aeaee9a4ad555e0dac", size = 795652, upload-time = "2026-01-14T23:17:08.521Z" }, + { url = "https://files.pythonhosted.org/packages/46/79/a5d8651ae131fe27d7c521ad300aa7f1c7be1dbeee4d446498af5411b8a9/regex-2026.1.15-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b30bcbd1e1221783c721483953d9e4f3ab9c5d165aa709693d3f3946747b1aea", size = 868550, upload-time = "2026-01-14T23:17:10.573Z" }, + { url = "https://files.pythonhosted.org/packages/06/b7/25635d2809664b79f183070786a5552dd4e627e5aedb0065f4e3cf8ee37d/regex-2026.1.15-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:2a8d7b50c34578d0d3bf7ad58cde9652b7d683691876f83aedc002862a35dc5e", size = 769981, upload-time = "2026-01-14T23:17:12.871Z" }, + { url = "https://files.pythonhosted.org/packages/16/8b/fc3fcbb2393dcfa4a6c5ffad92dc498e842df4581ea9d14309fcd3c55fb9/regex-2026.1.15-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:9d787e3310c6a6425eb346be4ff2ccf6eece63017916fd77fe8328c57be83521", size = 854780, upload-time = "2026-01-14T23:17:14.837Z" }, + { url = "https://files.pythonhosted.org/packages/d0/38/dde117c76c624713c8a2842530be9c93ca8b606c0f6102d86e8cd1ce8bea/regex-2026.1.15-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:619843841e220adca114118533a574a9cd183ed8a28b85627d2844c500a2b0db", size = 799778, upload-time = "2026-01-14T23:17:17.369Z" }, + { url = "https://files.pythonhosted.org/packages/e3/0d/3a6cfa9ae99606afb612d8fb7a66b245a9d5ff0f29bb347c8a30b6ad561b/regex-2026.1.15-cp314-cp314t-win32.whl", hash = "sha256:e90b8db97f6f2c97eb045b51a6b2c5ed69cedd8392459e0642d4199b94fabd7e", size = 274667, upload-time = "2026-01-14T23:17:19.301Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b2/297293bb0742fd06b8d8e2572db41a855cdf1cae0bf009b1cb74fe07e196/regex-2026.1.15-cp314-cp314t-win_amd64.whl", hash = "sha256:5ef19071f4ac9f0834793af85bd04a920b4407715624e40cb7a0631a11137cdf", size = 284386, upload-time = "2026-01-14T23:17:21.231Z" }, + { url = "https://files.pythonhosted.org/packages/95/e4/a3b9480c78cf8ee86626cb06f8d931d74d775897d44201ccb813097ae697/regex-2026.1.15-cp314-cp314t-win_arm64.whl", hash = "sha256:ca89c5e596fc05b015f27561b3793dc2fa0917ea0d7507eebb448efd35274a70", size = 274837, upload-time = "2026-01-14T23:17:23.146Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "rfc3339-validator" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" }, +] + +[[package]] +name = "rfc3986-validator" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" }, +] + +[[package]] +name = "rfc3987-syntax" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lark" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, +] + +[[package]] +name = "scikit-learn" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "joblib" }, + { name = "numpy" }, + { name = "scipy" }, + { name = "threadpoolctl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/83/564e141eef908a5863a54da8ca342a137f45a0bfb71d1d79704c9894c9d1/scikit_learn-1.7.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7509693451651cd7361d30ce4e86a1347493554f172b1c72a39300fa2aea79e", size = 9331967, upload-time = "2025-09-09T08:20:32.421Z" }, + { url = "https://files.pythonhosted.org/packages/18/d6/ba863a4171ac9d7314c4d3fc251f015704a2caeee41ced89f321c049ed83/scikit_learn-1.7.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:0486c8f827c2e7b64837c731c8feff72c0bd2b998067a8a9cbc10643c31f0fe1", size = 8648645, upload-time = "2025-09-09T08:20:34.436Z" }, + { url = "https://files.pythonhosted.org/packages/ef/0e/97dbca66347b8cf0ea8b529e6bb9367e337ba2e8be0ef5c1a545232abfde/scikit_learn-1.7.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89877e19a80c7b11a2891a27c21c4894fb18e2c2e077815bcade10d34287b20d", size = 9715424, upload-time = "2025-09-09T08:20:36.776Z" }, + { url = "https://files.pythonhosted.org/packages/f7/32/1f3b22e3207e1d2c883a7e09abb956362e7d1bd2f14458c7de258a26ac15/scikit_learn-1.7.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8da8bf89d4d79aaec192d2bda62f9b56ae4e5b4ef93b6a56b5de4977e375c1f1", size = 9509234, upload-time = "2025-09-09T08:20:38.957Z" }, + { url = "https://files.pythonhosted.org/packages/9f/71/34ddbd21f1da67c7a768146968b4d0220ee6831e4bcbad3e03dd3eae88b6/scikit_learn-1.7.2-cp311-cp311-win_amd64.whl", hash = "sha256:9b7ed8d58725030568523e937c43e56bc01cadb478fc43c042a9aca1dacb3ba1", size = 8894244, upload-time = "2025-09-09T08:20:41.166Z" }, + { url = "https://files.pythonhosted.org/packages/a7/aa/3996e2196075689afb9fce0410ebdb4a09099d7964d061d7213700204409/scikit_learn-1.7.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8d91a97fa2b706943822398ab943cde71858a50245e31bc71dba62aab1d60a96", size = 9259818, upload-time = "2025-09-09T08:20:43.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/5d/779320063e88af9c4a7c2cf463ff11c21ac9c8bd730c4a294b0000b666c9/scikit_learn-1.7.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:acbc0f5fd2edd3432a22c69bed78e837c70cf896cd7993d71d51ba6708507476", size = 8636997, upload-time = "2025-09-09T08:20:45.468Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/0c577d9325b05594fdd33aa970bf53fb673f051a45496842caee13cfd7fe/scikit_learn-1.7.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e5bf3d930aee75a65478df91ac1225ff89cd28e9ac7bd1196853a9229b6adb0b", size = 9478381, upload-time = "2025-09-09T08:20:47.982Z" }, + { url = "https://files.pythonhosted.org/packages/82/70/8bf44b933837ba8494ca0fc9a9ab60f1c13b062ad0197f60a56e2fc4c43e/scikit_learn-1.7.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4d6e9deed1a47aca9fe2f267ab8e8fe82ee20b4526b2c0cd9e135cea10feb44", size = 9300296, upload-time = "2025-09-09T08:20:50.366Z" }, + { url = "https://files.pythonhosted.org/packages/c6/99/ed35197a158f1fdc2fe7c3680e9c70d0128f662e1fee4ed495f4b5e13db0/scikit_learn-1.7.2-cp312-cp312-win_amd64.whl", hash = "sha256:6088aa475f0785e01bcf8529f55280a3d7d298679f50c0bb70a2364a82d0b290", size = 8731256, upload-time = "2025-09-09T08:20:52.627Z" }, + { url = "https://files.pythonhosted.org/packages/ae/93/a3038cb0293037fd335f77f31fe053b89c72f17b1c8908c576c29d953e84/scikit_learn-1.7.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0b7dacaa05e5d76759fb071558a8b5130f4845166d88654a0f9bdf3eb57851b7", size = 9212382, upload-time = "2025-09-09T08:20:54.731Z" }, + { url = "https://files.pythonhosted.org/packages/40/dd/9a88879b0c1104259136146e4742026b52df8540c39fec21a6383f8292c7/scikit_learn-1.7.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:abebbd61ad9e1deed54cca45caea8ad5f79e1b93173dece40bb8e0c658dbe6fe", size = 8592042, upload-time = "2025-09-09T08:20:57.313Z" }, + { url = "https://files.pythonhosted.org/packages/46/af/c5e286471b7d10871b811b72ae794ac5fe2989c0a2df07f0ec723030f5f5/scikit_learn-1.7.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502c18e39849c0ea1a5d681af1dbcf15f6cce601aebb657aabbfe84133c1907f", size = 9434180, upload-time = "2025-09-09T08:20:59.671Z" }, + { url = "https://files.pythonhosted.org/packages/f1/fd/df59faa53312d585023b2da27e866524ffb8faf87a68516c23896c718320/scikit_learn-1.7.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a4c328a71785382fe3fe676a9ecf2c86189249beff90bf85e22bdb7efaf9ae0", size = 9283660, upload-time = "2025-09-09T08:21:01.71Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c7/03000262759d7b6f38c836ff9d512f438a70d8a8ddae68ee80de72dcfb63/scikit_learn-1.7.2-cp313-cp313-win_amd64.whl", hash = "sha256:63a9afd6f7b229aad94618c01c252ce9e6fa97918c5ca19c9a17a087d819440c", size = 8702057, upload-time = "2025-09-09T08:21:04.234Z" }, + { url = "https://files.pythonhosted.org/packages/55/87/ef5eb1f267084532c8e4aef98a28b6ffe7425acbfd64b5e2f2e066bc29b3/scikit_learn-1.7.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:9acb6c5e867447b4e1390930e3944a005e2cb115922e693c08a323421a6966e8", size = 9558731, upload-time = "2025-09-09T08:21:06.381Z" }, + { url = "https://files.pythonhosted.org/packages/93/f8/6c1e3fc14b10118068d7938878a9f3f4e6d7b74a8ddb1e5bed65159ccda8/scikit_learn-1.7.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:2a41e2a0ef45063e654152ec9d8bcfc39f7afce35b08902bfe290c2498a67a6a", size = 9038852, upload-time = "2025-09-09T08:21:08.628Z" }, + { url = "https://files.pythonhosted.org/packages/83/87/066cafc896ee540c34becf95d30375fe5cbe93c3b75a0ee9aa852cd60021/scikit_learn-1.7.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98335fb98509b73385b3ab2bd0639b1f610541d3988ee675c670371d6a87aa7c", size = 9527094, upload-time = "2025-09-09T08:21:11.486Z" }, + { url = "https://files.pythonhosted.org/packages/9c/2b/4903e1ccafa1f6453b1ab78413938c8800633988c838aa0be386cbb33072/scikit_learn-1.7.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:191e5550980d45449126e23ed1d5e9e24b2c68329ee1f691a3987476e115e09c", size = 9367436, upload-time = "2025-09-09T08:21:13.602Z" }, + { url = "https://files.pythonhosted.org/packages/b5/aa/8444be3cfb10451617ff9d177b3c190288f4563e6c50ff02728be67ad094/scikit_learn-1.7.2-cp313-cp313t-win_amd64.whl", hash = "sha256:57dc4deb1d3762c75d685507fbd0bc17160144b2f2ba4ccea5dc285ab0d0e973", size = 9275749, upload-time = "2025-09-09T08:21:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/dee5acf66837852e8e68df6d8d3a6cb22d3df997b733b032f513d95205b7/scikit_learn-1.7.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fa8f63940e29c82d1e67a45d5297bdebbcb585f5a5a50c4914cc2e852ab77f33", size = 9208906, upload-time = "2025-09-09T08:21:18.557Z" }, + { url = "https://files.pythonhosted.org/packages/3c/30/9029e54e17b87cb7d50d51a5926429c683d5b4c1732f0507a6c3bed9bf65/scikit_learn-1.7.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:f95dc55b7902b91331fa4e5845dd5bde0580c9cd9612b1b2791b7e80c3d32615", size = 8627836, upload-time = "2025-09-09T08:21:20.695Z" }, + { url = "https://files.pythonhosted.org/packages/60/18/4a52c635c71b536879f4b971c2cedf32c35ee78f48367885ed8025d1f7ee/scikit_learn-1.7.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9656e4a53e54578ad10a434dc1f993330568cfee176dff07112b8785fb413106", size = 9426236, upload-time = "2025-09-09T08:21:22.645Z" }, + { url = "https://files.pythonhosted.org/packages/99/7e/290362f6ab582128c53445458a5befd471ed1ea37953d5bcf80604619250/scikit_learn-1.7.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96dc05a854add0e50d3f47a1ef21a10a595016da5b007c7d9cd9d0bffd1fcc61", size = 9312593, upload-time = "2025-09-09T08:21:24.65Z" }, + { url = "https://files.pythonhosted.org/packages/8e/87/24f541b6d62b1794939ae6422f8023703bbf6900378b2b34e0b4384dfefd/scikit_learn-1.7.2-cp314-cp314-win_amd64.whl", hash = "sha256:bb24510ed3f9f61476181e4db51ce801e2ba37541def12dc9333b946fc7a9cf8", size = 8820007, upload-time = "2025-09-09T08:21:26.713Z" }, +] + +[[package]] +name = "scipy" +version = "1.15.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/37/6964b830433e654ec7485e45a00fc9a27cf868d622838f6b6d9c5ec0d532/scipy-1.15.3.tar.gz", hash = "sha256:eae3cf522bc7df64b42cad3925c876e1b0b6c35c1337c93e12c0f366f55b0eaf", size = 59419214, upload-time = "2025-05-08T16:13:05.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/ab/5cc9f80f28f6a7dff646c5756e559823614a42b1939d86dd0ed550470210/scipy-1.15.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:993439ce220d25e3696d1b23b233dd010169b62f6456488567e830654ee37a6b", size = 38714255, upload-time = "2025-05-08T16:05:14.596Z" }, + { url = "https://files.pythonhosted.org/packages/4a/4a/66ba30abe5ad1a3ad15bfb0b59d22174012e8056ff448cb1644deccbfed2/scipy-1.15.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:34716e281f181a02341ddeaad584205bd2fd3c242063bd3423d61ac259ca7eba", size = 30111035, upload-time = "2025-05-08T16:05:20.152Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/a7e5b95afd80d24313307f03624acc65801846fa75599034f8ceb9e2cbf6/scipy-1.15.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3b0334816afb8b91dab859281b1b9786934392aa3d527cd847e41bb6f45bee65", size = 22384499, upload-time = "2025-05-08T16:05:24.494Z" }, + { url = "https://files.pythonhosted.org/packages/17/99/f3aaddccf3588bb4aea70ba35328c204cadd89517a1612ecfda5b2dd9d7a/scipy-1.15.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:6db907c7368e3092e24919b5e31c76998b0ce1684d51a90943cb0ed1b4ffd6c1", size = 25152602, upload-time = "2025-05-08T16:05:29.313Z" }, + { url = "https://files.pythonhosted.org/packages/56/c5/1032cdb565f146109212153339f9cb8b993701e9fe56b1c97699eee12586/scipy-1.15.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:721d6b4ef5dc82ca8968c25b111e307083d7ca9091bc38163fb89243e85e3889", size = 35503415, upload-time = "2025-05-08T16:05:34.699Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/89f19c8c05505d0601ed5650156e50eb881ae3918786c8fd7262b4ee66d3/scipy-1.15.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39cb9c62e471b1bb3750066ecc3a3f3052b37751c7c3dfd0fd7e48900ed52982", size = 37652622, upload-time = "2025-05-08T16:05:40.762Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/be59513aa9695519b18e1851bb9e487de66f2d31f835201f1b42f5d4d475/scipy-1.15.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:795c46999bae845966368a3c013e0e00947932d68e235702b5c3f6ea799aa8c9", size = 37244796, upload-time = "2025-05-08T16:05:48.119Z" }, + { url = "https://files.pythonhosted.org/packages/10/c0/4f5f3eeccc235632aab79b27a74a9130c6c35df358129f7ac8b29f562ac7/scipy-1.15.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18aaacb735ab38b38db42cb01f6b92a2d0d4b6aabefeb07f02849e47f8fb3594", size = 40047684, upload-time = "2025-05-08T16:05:54.22Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a7/0ddaf514ce8a8714f6ed243a2b391b41dbb65251affe21ee3077ec45ea9a/scipy-1.15.3-cp311-cp311-win_amd64.whl", hash = "sha256:ae48a786a28412d744c62fd7816a4118ef97e5be0bee968ce8f0a2fba7acf3bb", size = 41246504, upload-time = "2025-05-08T16:06:00.437Z" }, + { url = "https://files.pythonhosted.org/packages/37/4b/683aa044c4162e10ed7a7ea30527f2cbd92e6999c10a8ed8edb253836e9c/scipy-1.15.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac6310fdbfb7aa6612408bd2f07295bcbd3fda00d2d702178434751fe48e019", size = 38766735, upload-time = "2025-05-08T16:06:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7e/f30be3d03de07f25dc0ec926d1681fed5c732d759ac8f51079708c79e680/scipy-1.15.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:185cd3d6d05ca4b44a8f1595af87f9c372bb6acf9c808e99aa3e9aa03bd98cf6", size = 30173284, upload-time = "2025-05-08T16:06:11.686Z" }, + { url = "https://files.pythonhosted.org/packages/07/9c/0ddb0d0abdabe0d181c1793db51f02cd59e4901da6f9f7848e1f96759f0d/scipy-1.15.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:05dc6abcd105e1a29f95eada46d4a3f251743cfd7d3ae8ddb4088047f24ea477", size = 22446958, upload-time = "2025-05-08T16:06:15.97Z" }, + { url = "https://files.pythonhosted.org/packages/af/43/0bce905a965f36c58ff80d8bea33f1f9351b05fad4beaad4eae34699b7a1/scipy-1.15.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:06efcba926324df1696931a57a176c80848ccd67ce6ad020c810736bfd58eb1c", size = 25242454, upload-time = "2025-05-08T16:06:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/56/30/a6f08f84ee5b7b28b4c597aca4cbe545535c39fe911845a96414700b64ba/scipy-1.15.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05045d8b9bfd807ee1b9f38761993297b10b245f012b11b13b91ba8945f7e45", size = 35210199, upload-time = "2025-05-08T16:06:26.159Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1f/03f52c282437a168ee2c7c14a1a0d0781a9a4a8962d84ac05c06b4c5b555/scipy-1.15.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:271e3713e645149ea5ea3e97b57fdab61ce61333f97cfae392c28ba786f9bb49", size = 37309455, upload-time = "2025-05-08T16:06:32.778Z" }, + { url = "https://files.pythonhosted.org/packages/89/b1/fbb53137f42c4bf630b1ffdfc2151a62d1d1b903b249f030d2b1c0280af8/scipy-1.15.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cfd56fc1a8e53f6e89ba3a7a7251f7396412d655bca2aa5611c8ec9a6784a1e", size = 36885140, upload-time = "2025-05-08T16:06:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/2e/2e/025e39e339f5090df1ff266d021892694dbb7e63568edcfe43f892fa381d/scipy-1.15.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ff17c0bb1cb32952c09217d8d1eed9b53d1463e5f1dd6052c7857f83127d539", size = 39710549, upload-time = "2025-05-08T16:06:45.729Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/3bf6ea8ab7f1503dca3a10df2e4b9c3f6b3316df07f6c0ded94b281c7101/scipy-1.15.3-cp312-cp312-win_amd64.whl", hash = "sha256:52092bc0472cfd17df49ff17e70624345efece4e1a12b23783a1ac59a1b728ed", size = 40966184, upload-time = "2025-05-08T16:06:52.623Z" }, + { url = "https://files.pythonhosted.org/packages/73/18/ec27848c9baae6e0d6573eda6e01a602e5649ee72c27c3a8aad673ebecfd/scipy-1.15.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c620736bcc334782e24d173c0fdbb7590a0a436d2fdf39310a8902505008759", size = 38728256, upload-time = "2025-05-08T16:06:58.696Z" }, + { url = "https://files.pythonhosted.org/packages/74/cd/1aef2184948728b4b6e21267d53b3339762c285a46a274ebb7863c9e4742/scipy-1.15.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:7e11270a000969409d37ed399585ee530b9ef6aa99d50c019de4cb01e8e54e62", size = 30109540, upload-time = "2025-05-08T16:07:04.209Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d8/59e452c0a255ec352bd0a833537a3bc1bfb679944c4938ab375b0a6b3a3e/scipy-1.15.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8c9ed3ba2c8a2ce098163a9bdb26f891746d02136995df25227a20e71c396ebb", size = 22383115, upload-time = "2025-05-08T16:07:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/08/f5/456f56bbbfccf696263b47095291040655e3cbaf05d063bdc7c7517f32ac/scipy-1.15.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0bdd905264c0c9cfa74a4772cdb2070171790381a5c4d312c973382fc6eaf730", size = 25163884, upload-time = "2025-05-08T16:07:14.091Z" }, + { url = "https://files.pythonhosted.org/packages/a2/66/a9618b6a435a0f0c0b8a6d0a2efb32d4ec5a85f023c2b79d39512040355b/scipy-1.15.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79167bba085c31f38603e11a267d862957cbb3ce018d8b38f79ac043bc92d825", size = 35174018, upload-time = "2025-05-08T16:07:19.427Z" }, + { url = "https://files.pythonhosted.org/packages/b5/09/c5b6734a50ad4882432b6bb7c02baf757f5b2f256041da5df242e2d7e6b6/scipy-1.15.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9deabd6d547aee2c9a81dee6cc96c6d7e9a9b1953f74850c179f91fdc729cb7", size = 37269716, upload-time = "2025-05-08T16:07:25.712Z" }, + { url = "https://files.pythonhosted.org/packages/77/0a/eac00ff741f23bcabd352731ed9b8995a0a60ef57f5fd788d611d43d69a1/scipy-1.15.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dde4fc32993071ac0c7dd2d82569e544f0bdaff66269cb475e0f369adad13f11", size = 36872342, upload-time = "2025-05-08T16:07:31.468Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/4379be86dd74b6ad81551689107360d9a3e18f24d20767a2d5b9253a3f0a/scipy-1.15.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f77f853d584e72e874d87357ad70f44b437331507d1c311457bed8ed2b956126", size = 39670869, upload-time = "2025-05-08T16:07:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/87/2e/892ad2862ba54f084ffe8cc4a22667eaf9c2bcec6d2bff1d15713c6c0703/scipy-1.15.3-cp313-cp313-win_amd64.whl", hash = "sha256:b90ab29d0c37ec9bf55424c064312930ca5f4bde15ee8619ee44e69319aab163", size = 40988851, upload-time = "2025-05-08T16:08:33.671Z" }, + { url = "https://files.pythonhosted.org/packages/1b/e9/7a879c137f7e55b30d75d90ce3eb468197646bc7b443ac036ae3fe109055/scipy-1.15.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3ac07623267feb3ae308487c260ac684b32ea35fd81e12845039952f558047b8", size = 38863011, upload-time = "2025-05-08T16:07:44.039Z" }, + { url = "https://files.pythonhosted.org/packages/51/d1/226a806bbd69f62ce5ef5f3ffadc35286e9fbc802f606a07eb83bf2359de/scipy-1.15.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6487aa99c2a3d509a5227d9a5e889ff05830a06b2ce08ec30df6d79db5fcd5c5", size = 30266407, upload-time = "2025-05-08T16:07:49.891Z" }, + { url = "https://files.pythonhosted.org/packages/e5/9b/f32d1d6093ab9eeabbd839b0f7619c62e46cc4b7b6dbf05b6e615bbd4400/scipy-1.15.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:50f9e62461c95d933d5c5ef4a1f2ebf9a2b4e83b0db374cb3f1de104d935922e", size = 22540030, upload-time = "2025-05-08T16:07:54.121Z" }, + { url = "https://files.pythonhosted.org/packages/e7/29/c278f699b095c1a884f29fda126340fcc201461ee8bfea5c8bdb1c7c958b/scipy-1.15.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:14ed70039d182f411ffc74789a16df3835e05dc469b898233a245cdfd7f162cb", size = 25218709, upload-time = "2025-05-08T16:07:58.506Z" }, + { url = "https://files.pythonhosted.org/packages/24/18/9e5374b617aba742a990581373cd6b68a2945d65cc588482749ef2e64467/scipy-1.15.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a769105537aa07a69468a0eefcd121be52006db61cdd8cac8a0e68980bbb723", size = 34809045, upload-time = "2025-05-08T16:08:03.929Z" }, + { url = "https://files.pythonhosted.org/packages/e1/fe/9c4361e7ba2927074360856db6135ef4904d505e9b3afbbcb073c4008328/scipy-1.15.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db984639887e3dffb3928d118145ffe40eff2fa40cb241a306ec57c219ebbbb", size = 36703062, upload-time = "2025-05-08T16:08:09.558Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8e/038ccfe29d272b30086b25a4960f757f97122cb2ec42e62b460d02fe98e9/scipy-1.15.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:40e54d5c7e7ebf1aa596c374c49fa3135f04648a0caabcb66c52884b943f02b4", size = 36393132, upload-time = "2025-05-08T16:08:15.34Z" }, + { url = "https://files.pythonhosted.org/packages/10/7e/5c12285452970be5bdbe8352c619250b97ebf7917d7a9a9e96b8a8140f17/scipy-1.15.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5e721fed53187e71d0ccf382b6bf977644c533e506c4d33c3fb24de89f5c3ed5", size = 38979503, upload-time = "2025-05-08T16:08:21.513Z" }, + { url = "https://files.pythonhosted.org/packages/81/06/0a5e5349474e1cbc5757975b21bd4fad0e72ebf138c5592f191646154e06/scipy-1.15.3-cp313-cp313t-win_amd64.whl", hash = "sha256:76ad1fb5f8752eabf0fa02e4cc0336b4e8f021e2d5f061ed37d6d264db35e3ca", size = 40308097, upload-time = "2025-05-08T16:08:27.627Z" }, +] + +[[package]] +name = "send2trash" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/f0/184b4b5f8d00f2a92cf96eec8967a3d550b52cf94362dad1100df9e48d57/send2trash-2.1.0.tar.gz", hash = "sha256:1c72b39f09457db3c05ce1d19158c2cbef4c32b8bedd02c155e49282b7ea7459", size = 17255, upload-time = "2026-01-14T06:27:36.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl", hash = "sha256:0da2f112e6d6bb22de6aa6daa7e144831a4febf2a87261451c4ad849fe9a873c", size = 17610, upload-time = "2026-01-14T06:27:35.218Z" }, +] + +[[package]] +name = "setuptools" +version = "80.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "terminado" +version = "0.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess", marker = "os_name != 'nt'" }, + { name = "pywinpty", marker = "os_name == 'nt'" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" }, +] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/4d/08c89e34946fce2aec4fbb45c9016efd5f4d7f24af8e5d93296e935631d8/threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e", size = 21274, upload-time = "2025-03-13T13:49:23.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb", size = 18638, upload-time = "2025-03-13T13:49:21.846Z" }, +] + +[[package]] +name = "tiktoken" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/46/21ea696b21f1d6d1efec8639c204bdf20fde8bafb351e1355c72c5d7de52/tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb", size = 1051565, upload-time = "2025-10-06T20:21:44.566Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d9/35c5d2d9e22bb2a5f74ba48266fb56c63d76ae6f66e02feb628671c0283e/tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa", size = 995284, upload-time = "2025-10-06T20:21:45.622Z" }, + { url = "https://files.pythonhosted.org/packages/01/84/961106c37b8e49b9fdcf33fe007bb3a8fdcc380c528b20cc7fbba80578b8/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc", size = 1129201, upload-time = "2025-10-06T20:21:47.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d0/3d9275198e067f8b65076a68894bb52fd253875f3644f0a321a720277b8a/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded", size = 1152444, upload-time = "2025-10-06T20:21:48.139Z" }, + { url = "https://files.pythonhosted.org/packages/78/db/a58e09687c1698a7c592e1038e01c206569b86a0377828d51635561f8ebf/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd", size = 1195080, upload-time = "2025-10-06T20:21:49.246Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/a9e4d2bf91d515c0f74afc526fd773a812232dd6cda33ebea7f531202325/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967", size = 1255240, upload-time = "2025-10-06T20:21:50.274Z" }, + { url = "https://files.pythonhosted.org/packages/9d/15/963819345f1b1fb0809070a79e9dd96938d4ca41297367d471733e79c76c/tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def", size = 879422, upload-time = "2025-10-06T20:21:51.734Z" }, + { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728, upload-time = "2025-10-06T20:21:52.756Z" }, + { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049, upload-time = "2025-10-06T20:21:53.782Z" }, + { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008, upload-time = "2025-10-06T20:21:54.832Z" }, + { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665, upload-time = "2025-10-06T20:21:56.129Z" }, + { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230, upload-time = "2025-10-06T20:21:57.546Z" }, + { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688, upload-time = "2025-10-06T20:21:58.619Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694, upload-time = "2025-10-06T20:21:59.876Z" }, + { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802, upload-time = "2025-10-06T20:22:00.96Z" }, + { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995, upload-time = "2025-10-06T20:22:02.788Z" }, + { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948, upload-time = "2025-10-06T20:22:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986, upload-time = "2025-10-06T20:22:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222, upload-time = "2025-10-06T20:22:06.265Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097, upload-time = "2025-10-06T20:22:07.403Z" }, + { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117, upload-time = "2025-10-06T20:22:08.418Z" }, + { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309, upload-time = "2025-10-06T20:22:10.939Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712, upload-time = "2025-10-06T20:22:12.115Z" }, + { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725, upload-time = "2025-10-06T20:22:13.541Z" }, + { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875, upload-time = "2025-10-06T20:22:14.559Z" }, + { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451, upload-time = "2025-10-06T20:22:15.545Z" }, + { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794, upload-time = "2025-10-06T20:22:16.624Z" }, + { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777, upload-time = "2025-10-06T20:22:18.036Z" }, + { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188, upload-time = "2025-10-06T20:22:19.563Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978, upload-time = "2025-10-06T20:22:20.702Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271, upload-time = "2025-10-06T20:22:22.06Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216, upload-time = "2025-10-06T20:22:23.085Z" }, + { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860, upload-time = "2025-10-06T20:22:24.602Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567, upload-time = "2025-10-06T20:22:25.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067, upload-time = "2025-10-06T20:22:26.753Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473, upload-time = "2025-10-06T20:22:27.775Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855, upload-time = "2025-10-06T20:22:28.799Z" }, + { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022, upload-time = "2025-10-06T20:22:29.981Z" }, + { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736, upload-time = "2025-10-06T20:22:30.996Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" }, + { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, +] + +[[package]] +name = "tinycss2" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7a/fd/7a5ee21fd08ff70d3d33a5781c255cbe779659bd03278feb98b19ee550f4/tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7", size = 87085, upload-time = "2024-10-24T14:58:29.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/34/ebdc18bae6aa14fbee1a08b63c015c72b64868ff7dae68808ab500c492e2/tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289", size = 26610, upload-time = "2024-10-24T14:58:28.029Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.22.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, + { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, + { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, + { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, + { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, + { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, + { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, + { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, + { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, + { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, + { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, + { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, + { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, + { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, + { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, + { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, + { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/89/4b0001b2dab8df0a5ee2787dcbe771de75ded01f18f1f8d53dedeea2882b/tqdm-4.67.2.tar.gz", hash = "sha256:649aac53964b2cb8dec76a14b405a4c0d13612cb8933aae547dd144eacc99653", size = 169514, upload-time = "2026-01-30T23:12:06.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/e2/31eac96de2915cf20ccaed0225035db149dfb9165a9ed28d4b252ef3f7f7/tqdm-4.67.2-py3-none-any.whl", hash = "sha256:9a12abcbbff58b6036b2167d9d3853042b9d436fe7330f06ae047867f2f8e0a7", size = 78354, upload-time = "2026-01-30T23:12:04.368Z" }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621, upload-time = "2024-04-19T11:11:49.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, +] + +[[package]] +name = "typer-slim" +version = "0.21.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/17/d4/064570dec6358aa9049d4708e4a10407d74c99258f8b2136bb8702303f1a/typer_slim-0.21.1.tar.gz", hash = "sha256:73495dd08c2d0940d611c5a8c04e91c2a0a98600cbd4ee19192255a233b6dbfd", size = 110478, upload-time = "2026-01-06T11:21:11.176Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/0a/4aca634faf693e33004796b6cee0ae2e1dba375a800c16ab8d3eff4bb800/typer_slim-0.21.1-py3-none-any.whl", hash = "sha256:6e6c31047f171ac93cc5a973c9e617dbc5ab2bddc4d0a3135dc161b4e2020e0d", size = 47444, upload-time = "2026-01-06T11:21:12.441Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + +[[package]] +name = "uri-template" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "vega-datasets" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pandas" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/a0/ce608d9a5b82fce2ebaa2311136b1e1d1dc2807f501bbdfa56bd174fff76/vega_datasets-0.9.0.tar.gz", hash = "sha256:9dbe9834208e8ec32ab44970df315de9102861e4cda13d8e143aab7a80d93fc0", size = 215013, upload-time = "2020-11-26T13:56:59.421Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/9f/ca52771fe972e0dcc5167fedb609940e01516066938ff2ee28b273ae4f29/vega_datasets-0.9.0-py3-none-any.whl", hash = "sha256:3d7c63917be6ca9b154b565f4779a31fedce57b01b5b9d99d8a34a7608062a1d", size = 210822, upload-time = "2020-11-26T13:56:57.776Z" }, +] + +[[package]] +name = "vl-convert-python" +version = "1.9.0.post1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/93/89/36722344d1758ec2106f4e8eca980f173cfe8f8d0358c1b77cc5d2e035a4/vl_convert_python-1.9.0.post1.tar.gz", hash = "sha256:a5b06b3128037519001166f5341ec7831e19fbd7f3a5f78f73d557ac2d5859ef", size = 4663469, upload-time = "2026-01-21T00:09:55.61Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/59/e5862245972ff467d38b0eb5ad28154685e23ecabb47e14f2b6962da7b56/vl_convert_python-1.9.0.post1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:43e9515f65bbcd317d1ef328787fd7bf0344c2fde9292eb7a0e64d5d3d29fccb", size = 30512930, upload-time = "2026-01-21T00:09:43.198Z" }, + { url = "https://files.pythonhosted.org/packages/62/e6/e7d0b538c2f0daaf120901dc113bd5d5d1fa51a9532fa5ffd90234e8c69e/vl_convert_python-1.9.0.post1-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b0e7a3245f32addec7e7abeb1badf72b1513ed71ba1dba7aca853901217b3f4e", size = 29738742, upload-time = "2026-01-21T00:09:46.016Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e2/5645a1bc174c53ff8cd305ed76a4a76ba36e155302db20b42b7e78daeef8/vl_convert_python-1.9.0.post1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6ecfe4b7e2ea9e8c30fd6d6eaea3ef85475be1ad249407d9796dce4ecdb5b32", size = 33366278, upload-time = "2026-01-21T00:09:48.42Z" }, + { url = "https://files.pythonhosted.org/packages/a0/18/88e02899b72fa8273ffb32bde12b0e5776ee0fd9fb29559a49c48ec4c5fa/vl_convert_python-1.9.0.post1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3c1558fa0055e88c465bd3d71760cde9fa2c94a95f776a0ef9178252fd820b1f", size = 33520215, upload-time = "2026-01-21T00:09:50.992Z" }, + { url = "https://files.pythonhosted.org/packages/2f/db/6e8616587035bf0745d0f10b1791c7e945180ac5d6b28677d2f2b3ca693c/vl_convert_python-1.9.0.post1-cp37-abi3-win_amd64.whl", hash = "sha256:7e263269ac0d304640ca842b44dfe430ed863accd9edecff42e279bfc48ce940", size = 32051516, upload-time = "2026-01-21T00:09:53.47Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/3e/3d456efe55d2d5e7938b5f9abd68333dd8dceb14e829f51f9a8deed2217e/wcwidth-0.5.2.tar.gz", hash = "sha256:c022c39a02a0134d1e10810da36d1f984c79648181efcc70a389f4569695f5ae", size = 152817, upload-time = "2026-01-29T19:32:52.22Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/72/da5a6f511a8267f962a08637464a70409736ac72f9f75b069e0e96d69b64/wcwidth-0.5.2-py3-none-any.whl", hash = "sha256:46912178a64217749bf3426b21e36e849fbc46e05c949407b3e364d9f7ffcadf", size = 90088, upload-time = "2026-01-29T19:32:50.592Z" }, +] + +[[package]] +name = "webcolors" +version = "25.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" }, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/02/ae6ceac1baeda530866a85075641cec12989bd8d31af6d5ab4a3e8c92f47/webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923", size = 9721, upload-time = "2017-04-05T20:21:34.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" }, +] + +[[package]] +name = "websocket-client" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, +] + +[[package]] +name = "websockets" +version = "16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/24/4b2031d72e840ce4c1ccb255f693b15c334757fc50023e4db9537080b8c4/websockets-16.0.tar.gz", hash = "sha256:5f6261a5e56e8d5c42a4497b364ea24d94d9563e8fbd44e78ac40879c60179b5", size = 179346, upload-time = "2026-01-10T09:23:47.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/db/de907251b4ff46ae804ad0409809504153b3f30984daf82a1d84a9875830/websockets-16.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:31a52addea25187bde0797a97d6fc3d2f92b6f72a9370792d65a6e84615ac8a8", size = 177340, upload-time = "2026-01-10T09:22:34.539Z" }, + { url = "https://files.pythonhosted.org/packages/f3/fa/abe89019d8d8815c8781e90d697dec52523fb8ebe308bf11664e8de1877e/websockets-16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:417b28978cdccab24f46400586d128366313e8a96312e4b9362a4af504f3bbad", size = 175022, upload-time = "2026-01-10T09:22:36.332Z" }, + { url = "https://files.pythonhosted.org/packages/58/5d/88ea17ed1ded2079358b40d31d48abe90a73c9e5819dbcde1606e991e2ad/websockets-16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:af80d74d4edfa3cb9ed973a0a5ba2b2a549371f8a741e0800cb07becdd20f23d", size = 175319, upload-time = "2026-01-10T09:22:37.602Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ae/0ee92b33087a33632f37a635e11e1d99d429d3d323329675a6022312aac2/websockets-16.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:08d7af67b64d29823fed316505a89b86705f2b7981c07848fb5e3ea3020c1abe", size = 184631, upload-time = "2026-01-10T09:22:38.789Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c5/27178df583b6c5b31b29f526ba2da5e2f864ecc79c99dae630a85d68c304/websockets-16.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7be95cfb0a4dae143eaed2bcba8ac23f4892d8971311f1b06f3c6b78952ee70b", size = 185870, upload-time = "2026-01-10T09:22:39.893Z" }, + { url = "https://files.pythonhosted.org/packages/87/05/536652aa84ddc1c018dbb7e2c4cbcd0db884580bf8e95aece7593fde526f/websockets-16.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d6297ce39ce5c2e6feb13c1a996a2ded3b6832155fcfc920265c76f24c7cceb5", size = 185361, upload-time = "2026-01-10T09:22:41.016Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e2/d5332c90da12b1e01f06fb1b85c50cfc489783076547415bf9f0a659ec19/websockets-16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1c1b30e4f497b0b354057f3467f56244c603a79c0d1dafce1d16c283c25f6e64", size = 184615, upload-time = "2026-01-10T09:22:42.442Z" }, + { url = "https://files.pythonhosted.org/packages/77/fb/d3f9576691cae9253b51555f841bc6600bf0a983a461c79500ace5a5b364/websockets-16.0-cp311-cp311-win32.whl", hash = "sha256:5f451484aeb5cafee1ccf789b1b66f535409d038c56966d6101740c1614b86c6", size = 178246, upload-time = "2026-01-10T09:22:43.654Z" }, + { url = "https://files.pythonhosted.org/packages/54/67/eaff76b3dbaf18dcddabc3b8c1dba50b483761cccff67793897945b37408/websockets-16.0-cp311-cp311-win_amd64.whl", hash = "sha256:8d7f0659570eefb578dacde98e24fb60af35350193e4f56e11190787bee77dac", size = 178684, upload-time = "2026-01-10T09:22:44.941Z" }, + { url = "https://files.pythonhosted.org/packages/84/7b/bac442e6b96c9d25092695578dda82403c77936104b5682307bd4deb1ad4/websockets-16.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:71c989cbf3254fbd5e84d3bff31e4da39c43f884e64f2551d14bb3c186230f00", size = 177365, upload-time = "2026-01-10T09:22:46.787Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/136ccece61bd690d9c1f715baaeefd953bb2360134de73519d5df19d29ca/websockets-16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b6e209ffee39ff1b6d0fa7bfef6de950c60dfb91b8fcead17da4ee539121a79", size = 175038, upload-time = "2026-01-10T09:22:47.999Z" }, + { url = "https://files.pythonhosted.org/packages/40/1e/9771421ac2286eaab95b8575b0cb701ae3663abf8b5e1f64f1fd90d0a673/websockets-16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86890e837d61574c92a97496d590968b23c2ef0aeb8a9bc9421d174cd378ae39", size = 175328, upload-time = "2026-01-10T09:22:49.809Z" }, + { url = "https://files.pythonhosted.org/packages/18/29/71729b4671f21e1eaa5d6573031ab810ad2936c8175f03f97f3ff164c802/websockets-16.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9b5aca38b67492ef518a8ab76851862488a478602229112c4b0d58d63a7a4d5c", size = 184915, upload-time = "2026-01-10T09:22:51.071Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/21c36b7dbbafc85d2d480cd65df02a1dc93bf76d97147605a8e27ff9409d/websockets-16.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e0334872c0a37b606418ac52f6ab9cfd17317ac26365f7f65e203e2d0d0d359f", size = 186152, upload-time = "2026-01-10T09:22:52.224Z" }, + { url = "https://files.pythonhosted.org/packages/4a/34/9bf8df0c0cf88fa7bfe36678dc7b02970c9a7d5e065a3099292db87b1be2/websockets-16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0b31e0b424cc6b5a04b8838bbaec1688834b2383256688cf47eb97412531da1", size = 185583, upload-time = "2026-01-10T09:22:53.443Z" }, + { url = "https://files.pythonhosted.org/packages/47/88/4dd516068e1a3d6ab3c7c183288404cd424a9a02d585efbac226cb61ff2d/websockets-16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:485c49116d0af10ac698623c513c1cc01c9446c058a4e61e3bf6c19dff7335a2", size = 184880, upload-time = "2026-01-10T09:22:55.033Z" }, + { url = "https://files.pythonhosted.org/packages/91/d6/7d4553ad4bf1c0421e1ebd4b18de5d9098383b5caa1d937b63df8d04b565/websockets-16.0-cp312-cp312-win32.whl", hash = "sha256:eaded469f5e5b7294e2bdca0ab06becb6756ea86894a47806456089298813c89", size = 178261, upload-time = "2026-01-10T09:22:56.251Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f0/f3a17365441ed1c27f850a80b2bc680a0fa9505d733fe152fdf5e98c1c0b/websockets-16.0-cp312-cp312-win_amd64.whl", hash = "sha256:5569417dc80977fc8c2d43a86f78e0a5a22fee17565d78621b6bb264a115d4ea", size = 178693, upload-time = "2026-01-10T09:22:57.478Z" }, + { url = "https://files.pythonhosted.org/packages/cc/9c/baa8456050d1c1b08dd0ec7346026668cbc6f145ab4e314d707bb845bf0d/websockets-16.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:878b336ac47938b474c8f982ac2f7266a540adc3fa4ad74ae96fea9823a02cc9", size = 177364, upload-time = "2026-01-10T09:22:59.333Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/8811fc53e9bcff68fe7de2bcbe75116a8d959ac699a3200f4847a8925210/websockets-16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:52a0fec0e6c8d9a784c2c78276a48a2bdf099e4ccc2a4cad53b27718dbfd0230", size = 175039, upload-time = "2026-01-10T09:23:01.171Z" }, + { url = "https://files.pythonhosted.org/packages/aa/82/39a5f910cb99ec0b59e482971238c845af9220d3ab9fa76dd9162cda9d62/websockets-16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e6578ed5b6981005df1860a56e3617f14a6c307e6a71b4fff8c48fdc50f3ed2c", size = 175323, upload-time = "2026-01-10T09:23:02.341Z" }, + { url = "https://files.pythonhosted.org/packages/bd/28/0a25ee5342eb5d5f297d992a77e56892ecb65e7854c7898fb7d35e9b33bd/websockets-16.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:95724e638f0f9c350bb1c2b0a7ad0e83d9cc0c9259f3ea94e40d7b02a2179ae5", size = 184975, upload-time = "2026-01-10T09:23:03.756Z" }, + { url = "https://files.pythonhosted.org/packages/f9/66/27ea52741752f5107c2e41fda05e8395a682a1e11c4e592a809a90c6a506/websockets-16.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0204dc62a89dc9d50d682412c10b3542d748260d743500a85c13cd1ee4bde82", size = 186203, upload-time = "2026-01-10T09:23:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/37/e5/8e32857371406a757816a2b471939d51c463509be73fa538216ea52b792a/websockets-16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:52ac480f44d32970d66763115edea932f1c5b1312de36df06d6b219f6741eed8", size = 185653, upload-time = "2026-01-10T09:23:06.301Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/f926bac29882894669368dc73f4da900fcdf47955d0a0185d60103df5737/websockets-16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6e5a82b677f8f6f59e8dfc34ec06ca6b5b48bc4fcda346acd093694cc2c24d8f", size = 184920, upload-time = "2026-01-10T09:23:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a1/3d6ccdcd125b0a42a311bcd15a7f705d688f73b2a22d8cf1c0875d35d34a/websockets-16.0-cp313-cp313-win32.whl", hash = "sha256:abf050a199613f64c886ea10f38b47770a65154dc37181bfaff70c160f45315a", size = 178255, upload-time = "2026-01-10T09:23:09.245Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ae/90366304d7c2ce80f9b826096a9e9048b4bb760e44d3b873bb272cba696b/websockets-16.0-cp313-cp313-win_amd64.whl", hash = "sha256:3425ac5cf448801335d6fdc7ae1eb22072055417a96cc6b31b3861f455fbc156", size = 178689, upload-time = "2026-01-10T09:23:10.483Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1d/e88022630271f5bd349ed82417136281931e558d628dd52c4d8621b4a0b2/websockets-16.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8cc451a50f2aee53042ac52d2d053d08bf89bcb31ae799cb4487587661c038a0", size = 177406, upload-time = "2026-01-10T09:23:12.178Z" }, + { url = "https://files.pythonhosted.org/packages/f2/78/e63be1bf0724eeb4616efb1ae1c9044f7c3953b7957799abb5915bffd38e/websockets-16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:daa3b6ff70a9241cf6c7fc9e949d41232d9d7d26fd3522b1ad2b4d62487e9904", size = 175085, upload-time = "2026-01-10T09:23:13.511Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f4/d3c9220d818ee955ae390cf319a7c7a467beceb24f05ee7aaaa2414345ba/websockets-16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fd3cb4adb94a2a6e2b7c0d8d05cb94e6f1c81a0cf9dc2694fb65c7e8d94c42e4", size = 175328, upload-time = "2026-01-10T09:23:14.727Z" }, + { url = "https://files.pythonhosted.org/packages/63/bc/d3e208028de777087e6fb2b122051a6ff7bbcca0d6df9d9c2bf1dd869ae9/websockets-16.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:781caf5e8eee67f663126490c2f96f40906594cb86b408a703630f95550a8c3e", size = 185044, upload-time = "2026-01-10T09:23:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/ad/6e/9a0927ac24bd33a0a9af834d89e0abc7cfd8e13bed17a86407a66773cc0e/websockets-16.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:caab51a72c51973ca21fa8a18bd8165e1a0183f1ac7066a182ff27107b71e1a4", size = 186279, upload-time = "2026-01-10T09:23:17.148Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ca/bf1c68440d7a868180e11be653c85959502efd3a709323230314fda6e0b3/websockets-16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:19c4dc84098e523fd63711e563077d39e90ec6702aff4b5d9e344a60cb3c0cb1", size = 185711, upload-time = "2026-01-10T09:23:18.372Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f8/fdc34643a989561f217bb477cbc47a3a07212cbda91c0e4389c43c296ebf/websockets-16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a5e18a238a2b2249c9a9235466b90e96ae4795672598a58772dd806edc7ac6d3", size = 184982, upload-time = "2026-01-10T09:23:19.652Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d1/574fa27e233764dbac9c52730d63fcf2823b16f0856b3329fc6268d6ae4f/websockets-16.0-cp314-cp314-win32.whl", hash = "sha256:a069d734c4a043182729edd3e9f247c3b2a4035415a9172fd0f1b71658a320a8", size = 177915, upload-time = "2026-01-10T09:23:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f1/ae6b937bf3126b5134ce1f482365fde31a357c784ac51852978768b5eff4/websockets-16.0-cp314-cp314-win_amd64.whl", hash = "sha256:c0ee0e63f23914732c6d7e0cce24915c48f3f1512ec1d079ed01fc629dab269d", size = 178381, upload-time = "2026-01-10T09:23:22.715Z" }, + { url = "https://files.pythonhosted.org/packages/06/9b/f791d1db48403e1f0a27577a6beb37afae94254a8c6f08be4a23e4930bc0/websockets-16.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a35539cacc3febb22b8f4d4a99cc79b104226a756aa7400adc722e83b0d03244", size = 177737, upload-time = "2026-01-10T09:23:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/bd/40/53ad02341fa33b3ce489023f635367a4ac98b73570102ad2cdd770dacc9a/websockets-16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b784ca5de850f4ce93ec85d3269d24d4c82f22b7212023c974c401d4980ebc5e", size = 175268, upload-time = "2026-01-10T09:23:25.781Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/6158d4e459b984f949dcbbb0c5d270154c7618e11c01029b9bbd1bb4c4f9/websockets-16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:569d01a4e7fba956c5ae4fc988f0d4e187900f5497ce46339c996dbf24f17641", size = 175486, upload-time = "2026-01-10T09:23:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/2d/7583b30208b639c8090206f95073646c2c9ffd66f44df967981a64f849ad/websockets-16.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:50f23cdd8343b984957e4077839841146f67a3d31ab0d00e6b824e74c5b2f6e8", size = 185331, upload-time = "2026-01-10T09:23:28.259Z" }, + { url = "https://files.pythonhosted.org/packages/45/b0/cce3784eb519b7b5ad680d14b9673a31ab8dcb7aad8b64d81709d2430aa8/websockets-16.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:152284a83a00c59b759697b7f9e9cddf4e3c7861dd0d964b472b70f78f89e80e", size = 186501, upload-time = "2026-01-10T09:23:29.449Z" }, + { url = "https://files.pythonhosted.org/packages/19/60/b8ebe4c7e89fb5f6cdf080623c9d92789a53636950f7abacfc33fe2b3135/websockets-16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc59589ab64b0022385f429b94697348a6a234e8ce22544e3681b2e9331b5944", size = 186062, upload-time = "2026-01-10T09:23:31.368Z" }, + { url = "https://files.pythonhosted.org/packages/88/a8/a080593f89b0138b6cba1b28f8df5673b5506f72879322288b031337c0b8/websockets-16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32da954ffa2814258030e5a57bc73a3635463238e797c7375dc8091327434206", size = 185356, upload-time = "2026-01-10T09:23:32.627Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b6/b9afed2afadddaf5ebb2afa801abf4b0868f42f8539bfe4b071b5266c9fe/websockets-16.0-cp314-cp314t-win32.whl", hash = "sha256:5a4b4cc550cb665dd8a47f868c8d04c8230f857363ad3c9caf7a0c3bf8c61ca6", size = 178085, upload-time = "2026-01-10T09:23:33.816Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3e/28135a24e384493fa804216b79a6a6759a38cc4ff59118787b9fb693df93/websockets-16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b14dc141ed6d2dde437cddb216004bcac6a1df0935d79656387bd41632ba0bbd", size = 178531, upload-time = "2026-01-10T09:23:35.016Z" }, + { url = "https://files.pythonhosted.org/packages/72/07/c98a68571dcf256e74f1f816b8cc5eae6eb2d3d5cfa44d37f801619d9166/websockets-16.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:349f83cd6c9a415428ee1005cadb5c2c56f4389bc06a9af16103c3bc3dcc8b7d", size = 174947, upload-time = "2026-01-10T09:23:36.166Z" }, + { url = "https://files.pythonhosted.org/packages/7e/52/93e166a81e0305b33fe416338be92ae863563fe7bce446b0f687b9df5aea/websockets-16.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:4a1aba3340a8dca8db6eb5a7986157f52eb9e436b74813764241981ca4888f03", size = 175260, upload-time = "2026-01-10T09:23:37.409Z" }, + { url = "https://files.pythonhosted.org/packages/56/0c/2dbf513bafd24889d33de2ff0368190a0e69f37bcfa19009ef819fe4d507/websockets-16.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f4a32d1bd841d4bcbffdcb3d2ce50c09c3909fbead375ab28d0181af89fd04da", size = 176071, upload-time = "2026-01-10T09:23:39.158Z" }, + { url = "https://files.pythonhosted.org/packages/a5/8f/aea9c71cc92bf9b6cc0f7f70df8f0b420636b6c96ef4feee1e16f80f75dd/websockets-16.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0298d07ee155e2e9fda5be8a9042200dd2e3bb0b8a38482156576f863a9d457c", size = 176968, upload-time = "2026-01-10T09:23:41.031Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3f/f70e03f40ffc9a30d817eef7da1be72ee4956ba8d7255c399a01b135902a/websockets-16.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a653aea902e0324b52f1613332ddf50b00c06fdaf7e92624fbf8c77c78fa5767", size = 178735, upload-time = "2026-01-10T09:23:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/6f/28/258ebab549c2bf3e64d2b0217b973467394a9cea8c42f70418ca2c5d0d2e/websockets-16.0-py3-none-any.whl", hash = "sha256:1637db62fad1dc833276dded54215f2c7fa46912301a24bd94d45d46a011ceec", size = 171598, upload-time = "2026-01-10T09:23:45.395Z" }, +] + +[[package]] +name = "werkzeug" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/70/1469ef1d3542ae7c2c7b72bd5e3a4e6ee69d7978fa8a3af05a38eca5becf/werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67", size = 864754, upload-time = "2026-01-08T17:49:23.247Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/e4/8d97cca767bcc1be76d16fb76951608305561c6e056811587f36cb1316a8/werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc", size = 225025, upload-time = "2026-01-08T17:49:21.859Z" }, +] + +[[package]] +name = "widgetsnbextension" +version = "4.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402, upload-time = "2025-11-01T21:15:55.178Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503, upload-time = "2025-11-01T21:15:53.565Z" }, +] + +[[package]] +name = "wrapt" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2a/6de8a50cb435b7f42c46126cf1a54b2aab81784e74c8595c8e025e8f36d3/wrapt-2.0.1.tar.gz", hash = "sha256:9c9c635e78497cacb81e84f8b11b23e0aacac7a136e73b8e5b2109a1d9fc468f", size = 82040, upload-time = "2025-11-07T00:45:33.312Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/60/553997acf3939079dab022e37b67b1904b5b0cc235503226898ba573b10c/wrapt-2.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e17283f533a0d24d6e5429a7d11f250a58d28b4ae5186f8f47853e3e70d2590", size = 77480, upload-time = "2025-11-07T00:43:30.573Z" }, + { url = "https://files.pythonhosted.org/packages/2d/50/e5b3d30895d77c52105c6d5cbf94d5b38e2a3dd4a53d22d246670da98f7c/wrapt-2.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85df8d92158cb8f3965aecc27cf821461bb5f40b450b03facc5d9f0d4d6ddec6", size = 60690, upload-time = "2025-11-07T00:43:31.594Z" }, + { url = "https://files.pythonhosted.org/packages/f0/40/660b2898703e5cbbb43db10cdefcc294274458c3ca4c68637c2b99371507/wrapt-2.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1be685ac7700c966b8610ccc63c3187a72e33cab53526a27b2a285a662cd4f7", size = 61578, upload-time = "2025-11-07T00:43:32.918Z" }, + { url = "https://files.pythonhosted.org/packages/5b/36/825b44c8a10556957bc0c1d84c7b29a40e05fcf1873b6c40aa9dbe0bd972/wrapt-2.0.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:df0b6d3b95932809c5b3fecc18fda0f1e07452d05e2662a0b35548985f256e28", size = 114115, upload-time = "2025-11-07T00:43:35.605Z" }, + { url = "https://files.pythonhosted.org/packages/83/73/0a5d14bb1599677304d3c613a55457d34c344e9b60eda8a737c2ead7619e/wrapt-2.0.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da7384b0e5d4cae05c97cd6f94faaf78cc8b0f791fc63af43436d98c4ab37bb", size = 116157, upload-time = "2025-11-07T00:43:37.058Z" }, + { url = "https://files.pythonhosted.org/packages/01/22/1c158fe763dbf0a119f985d945711d288994fe5514c0646ebe0eb18b016d/wrapt-2.0.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ec65a78fbd9d6f083a15d7613b2800d5663dbb6bb96003899c834beaa68b242c", size = 112535, upload-time = "2025-11-07T00:43:34.138Z" }, + { url = "https://files.pythonhosted.org/packages/5c/28/4f16861af67d6de4eae9927799b559c20ebdd4fe432e89ea7fe6fcd9d709/wrapt-2.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7de3cc939be0e1174969f943f3b44e0d79b6f9a82198133a5b7fc6cc92882f16", size = 115404, upload-time = "2025-11-07T00:43:39.214Z" }, + { url = "https://files.pythonhosted.org/packages/a0/8b/7960122e625fad908f189b59c4aae2d50916eb4098b0fb2819c5a177414f/wrapt-2.0.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fb1a5b72cbd751813adc02ef01ada0b0d05d3dcbc32976ce189a1279d80ad4a2", size = 111802, upload-time = "2025-11-07T00:43:40.476Z" }, + { url = "https://files.pythonhosted.org/packages/3e/73/7881eee5ac31132a713ab19a22c9e5f1f7365c8b1df50abba5d45b781312/wrapt-2.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3fa272ca34332581e00bf7773e993d4f632594eb2d1b0b162a9038df0fd971dd", size = 113837, upload-time = "2025-11-07T00:43:42.921Z" }, + { url = "https://files.pythonhosted.org/packages/45/00/9499a3d14e636d1f7089339f96c4409bbc7544d0889f12264efa25502ae8/wrapt-2.0.1-cp311-cp311-win32.whl", hash = "sha256:fc007fdf480c77301ab1afdbb6ab22a5deee8885f3b1ed7afcb7e5e84a0e27be", size = 58028, upload-time = "2025-11-07T00:43:47.369Z" }, + { url = "https://files.pythonhosted.org/packages/70/5d/8f3d7eea52f22638748f74b102e38fdf88cb57d08ddeb7827c476a20b01b/wrapt-2.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:47434236c396d04875180171ee1f3815ca1eada05e24a1ee99546320d54d1d1b", size = 60385, upload-time = "2025-11-07T00:43:44.34Z" }, + { url = "https://files.pythonhosted.org/packages/14/e2/32195e57a8209003587bbbad44d5922f13e0ced2a493bb46ca882c5b123d/wrapt-2.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:837e31620e06b16030b1d126ed78e9383815cbac914693f54926d816d35d8edf", size = 58893, upload-time = "2025-11-07T00:43:46.161Z" }, + { url = "https://files.pythonhosted.org/packages/cb/73/8cb252858dc8254baa0ce58ce382858e3a1cf616acebc497cb13374c95c6/wrapt-2.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1fdbb34da15450f2b1d735a0e969c24bdb8d8924892380126e2a293d9902078c", size = 78129, upload-time = "2025-11-07T00:43:48.852Z" }, + { url = "https://files.pythonhosted.org/packages/19/42/44a0db2108526ee6e17a5ab72478061158f34b08b793df251d9fbb9a7eb4/wrapt-2.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3d32794fe940b7000f0519904e247f902f0149edbe6316c710a8562fb6738841", size = 61205, upload-time = "2025-11-07T00:43:50.402Z" }, + { url = "https://files.pythonhosted.org/packages/4d/8a/5b4b1e44b791c22046e90d9b175f9a7581a8cc7a0debbb930f81e6ae8e25/wrapt-2.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:386fb54d9cd903ee0012c09291336469eb7b244f7183d40dc3e86a16a4bace62", size = 61692, upload-time = "2025-11-07T00:43:51.678Z" }, + { url = "https://files.pythonhosted.org/packages/11/53/3e794346c39f462bcf1f58ac0487ff9bdad02f9b6d5ee2dc84c72e0243b2/wrapt-2.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7b219cb2182f230676308cdcacd428fa837987b89e4b7c5c9025088b8a6c9faf", size = 121492, upload-time = "2025-11-07T00:43:55.017Z" }, + { url = "https://files.pythonhosted.org/packages/c6/7e/10b7b0e8841e684c8ca76b462a9091c45d62e8f2de9c4b1390b690eadf16/wrapt-2.0.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:641e94e789b5f6b4822bb8d8ebbdfc10f4e4eae7756d648b717d980f657a9eb9", size = 123064, upload-time = "2025-11-07T00:43:56.323Z" }, + { url = "https://files.pythonhosted.org/packages/0e/d1/3c1e4321fc2f5ee7fd866b2d822aa89b84495f28676fd976c47327c5b6aa/wrapt-2.0.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe21b118b9f58859b5ebaa4b130dee18669df4bd111daad082b7beb8799ad16b", size = 117403, upload-time = "2025-11-07T00:43:53.258Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b0/d2f0a413cf201c8c2466de08414a15420a25aa83f53e647b7255cc2fab5d/wrapt-2.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:17fb85fa4abc26a5184d93b3efd2dcc14deb4b09edcdb3535a536ad34f0b4dba", size = 121500, upload-time = "2025-11-07T00:43:57.468Z" }, + { url = "https://files.pythonhosted.org/packages/bd/45/bddb11d28ca39970a41ed48a26d210505120f925918592283369219f83cc/wrapt-2.0.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:b89ef9223d665ab255ae42cc282d27d69704d94be0deffc8b9d919179a609684", size = 116299, upload-time = "2025-11-07T00:43:58.877Z" }, + { url = "https://files.pythonhosted.org/packages/81/af/34ba6dd570ef7a534e7eec0c25e2615c355602c52aba59413411c025a0cb/wrapt-2.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a453257f19c31b31ba593c30d997d6e5be39e3b5ad9148c2af5a7314061c63eb", size = 120622, upload-time = "2025-11-07T00:43:59.962Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3e/693a13b4146646fb03254636f8bafd20c621955d27d65b15de07ab886187/wrapt-2.0.1-cp312-cp312-win32.whl", hash = "sha256:3e271346f01e9c8b1130a6a3b0e11908049fe5be2d365a5f402778049147e7e9", size = 58246, upload-time = "2025-11-07T00:44:03.169Z" }, + { url = "https://files.pythonhosted.org/packages/a7/36/715ec5076f925a6be95f37917b66ebbeaa1372d1862c2ccd7a751574b068/wrapt-2.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:2da620b31a90cdefa9cd0c2b661882329e2e19d1d7b9b920189956b76c564d75", size = 60492, upload-time = "2025-11-07T00:44:01.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3e/62451cd7d80f65cc125f2b426b25fbb6c514bf6f7011a0c3904fc8c8df90/wrapt-2.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:aea9c7224c302bc8bfc892b908537f56c430802560e827b75ecbde81b604598b", size = 58987, upload-time = "2025-11-07T00:44:02.095Z" }, + { url = "https://files.pythonhosted.org/packages/ad/fe/41af4c46b5e498c90fc87981ab2972fbd9f0bccda597adb99d3d3441b94b/wrapt-2.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:47b0f8bafe90f7736151f61482c583c86b0693d80f075a58701dd1549b0010a9", size = 78132, upload-time = "2025-11-07T00:44:04.628Z" }, + { url = "https://files.pythonhosted.org/packages/1c/92/d68895a984a5ebbbfb175512b0c0aad872354a4a2484fbd5552e9f275316/wrapt-2.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cbeb0971e13b4bd81d34169ed57a6dda017328d1a22b62fda45e1d21dd06148f", size = 61211, upload-time = "2025-11-07T00:44:05.626Z" }, + { url = "https://files.pythonhosted.org/packages/e8/26/ba83dc5ae7cf5aa2b02364a3d9cf74374b86169906a1f3ade9a2d03cf21c/wrapt-2.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb7cffe572ad0a141a7886a1d2efa5bef0bf7fe021deeea76b3ab334d2c38218", size = 61689, upload-time = "2025-11-07T00:44:06.719Z" }, + { url = "https://files.pythonhosted.org/packages/cf/67/d7a7c276d874e5d26738c22444d466a3a64ed541f6ef35f740dbd865bab4/wrapt-2.0.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c8d60527d1ecfc131426b10d93ab5d53e08a09c5fa0175f6b21b3252080c70a9", size = 121502, upload-time = "2025-11-07T00:44:09.557Z" }, + { url = "https://files.pythonhosted.org/packages/0f/6b/806dbf6dd9579556aab22fc92908a876636e250f063f71548a8660382184/wrapt-2.0.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c654eafb01afac55246053d67a4b9a984a3567c3808bb7df2f8de1c1caba2e1c", size = 123110, upload-time = "2025-11-07T00:44:10.64Z" }, + { url = "https://files.pythonhosted.org/packages/e5/08/cdbb965fbe4c02c5233d185d070cabed2ecc1f1e47662854f95d77613f57/wrapt-2.0.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:98d873ed6c8b4ee2418f7afce666751854d6d03e3c0ec2a399bb039cd2ae89db", size = 117434, upload-time = "2025-11-07T00:44:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d1/6aae2ce39db4cb5216302fa2e9577ad74424dfbe315bd6669725569e048c/wrapt-2.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c9e850f5b7fc67af856ff054c71690d54fa940c3ef74209ad9f935b4f66a0233", size = 121533, upload-time = "2025-11-07T00:44:12.142Z" }, + { url = "https://files.pythonhosted.org/packages/79/35/565abf57559fbe0a9155c29879ff43ce8bd28d2ca61033a3a3dd67b70794/wrapt-2.0.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e505629359cb5f751e16e30cf3f91a1d3ddb4552480c205947da415d597f7ac2", size = 116324, upload-time = "2025-11-07T00:44:13.28Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e0/53ff5e76587822ee33e560ad55876d858e384158272cd9947abdd4ad42ca/wrapt-2.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2879af909312d0baf35f08edeea918ee3af7ab57c37fe47cb6a373c9f2749c7b", size = 120627, upload-time = "2025-11-07T00:44:14.431Z" }, + { url = "https://files.pythonhosted.org/packages/7c/7b/38df30fd629fbd7612c407643c63e80e1c60bcc982e30ceeae163a9800e7/wrapt-2.0.1-cp313-cp313-win32.whl", hash = "sha256:d67956c676be5a24102c7407a71f4126d30de2a569a1c7871c9f3cabc94225d7", size = 58252, upload-time = "2025-11-07T00:44:17.814Z" }, + { url = "https://files.pythonhosted.org/packages/85/64/d3954e836ea67c4d3ad5285e5c8fd9d362fd0a189a2db622df457b0f4f6a/wrapt-2.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:9ca66b38dd642bf90c59b6738af8070747b610115a39af2498535f62b5cdc1c3", size = 60500, upload-time = "2025-11-07T00:44:15.561Z" }, + { url = "https://files.pythonhosted.org/packages/89/4e/3c8b99ac93527cfab7f116089db120fef16aac96e5f6cdb724ddf286086d/wrapt-2.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:5a4939eae35db6b6cec8e7aa0e833dcca0acad8231672c26c2a9ab7a0f8ac9c8", size = 58993, upload-time = "2025-11-07T00:44:16.65Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f4/eff2b7d711cae20d220780b9300faa05558660afb93f2ff5db61fe725b9a/wrapt-2.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a52f93d95c8d38fed0669da2ebdb0b0376e895d84596a976c15a9eb45e3eccb3", size = 82028, upload-time = "2025-11-07T00:44:18.944Z" }, + { url = "https://files.pythonhosted.org/packages/0c/67/cb945563f66fd0f61a999339460d950f4735c69f18f0a87ca586319b1778/wrapt-2.0.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e54bbf554ee29fcceee24fa41c4d091398b911da6e7f5d7bffda963c9aed2e1", size = 62949, upload-time = "2025-11-07T00:44:20.074Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ca/f63e177f0bbe1e5cf5e8d9b74a286537cd709724384ff20860f8f6065904/wrapt-2.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:908f8c6c71557f4deaa280f55d0728c3bca0960e8c3dd5ceeeafb3c19942719d", size = 63681, upload-time = "2025-11-07T00:44:21.345Z" }, + { url = "https://files.pythonhosted.org/packages/39/a1/1b88fcd21fd835dca48b556daef750952e917a2794fa20c025489e2e1f0f/wrapt-2.0.1-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e2f84e9af2060e3904a32cea9bb6db23ce3f91cfd90c6b426757cf7cc01c45c7", size = 152696, upload-time = "2025-11-07T00:44:24.318Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/d9185500c1960d9f5f77b9c0b890b7fc62282b53af7ad1b6bd779157f714/wrapt-2.0.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3612dc06b436968dfb9142c62e5dfa9eb5924f91120b3c8ff501ad878f90eb3", size = 158859, upload-time = "2025-11-07T00:44:25.494Z" }, + { url = "https://files.pythonhosted.org/packages/91/60/5d796ed0f481ec003220c7878a1d6894652efe089853a208ea0838c13086/wrapt-2.0.1-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d2d947d266d99a1477cd005b23cbd09465276e302515e122df56bb9511aca1b", size = 146068, upload-time = "2025-11-07T00:44:22.81Z" }, + { url = "https://files.pythonhosted.org/packages/04/f8/75282dd72f102ddbfba137e1e15ecba47b40acff32c08ae97edbf53f469e/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7d539241e87b650cbc4c3ac9f32c8d1ac8a54e510f6dca3f6ab60dcfd48c9b10", size = 155724, upload-time = "2025-11-07T00:44:26.634Z" }, + { url = "https://files.pythonhosted.org/packages/5a/27/fe39c51d1b344caebb4a6a9372157bdb8d25b194b3561b52c8ffc40ac7d1/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:4811e15d88ee62dbf5c77f2c3ff3932b1e3ac92323ba3912f51fc4016ce81ecf", size = 144413, upload-time = "2025-11-07T00:44:27.939Z" }, + { url = "https://files.pythonhosted.org/packages/83/2b/9f6b643fe39d4505c7bf926d7c2595b7cb4b607c8c6b500e56c6b36ac238/wrapt-2.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c1c91405fcf1d501fa5d55df21e58ea49e6b879ae829f1039faaf7e5e509b41e", size = 150325, upload-time = "2025-11-07T00:44:29.29Z" }, + { url = "https://files.pythonhosted.org/packages/bb/b6/20ffcf2558596a7f58a2e69c89597128781f0b88e124bf5a4cadc05b8139/wrapt-2.0.1-cp313-cp313t-win32.whl", hash = "sha256:e76e3f91f864e89db8b8d2a8311d57df93f01ad6bb1e9b9976d1f2e83e18315c", size = 59943, upload-time = "2025-11-07T00:44:33.211Z" }, + { url = "https://files.pythonhosted.org/packages/87/6a/0e56111cbb3320151eed5d3821ee1373be13e05b376ea0870711f18810c3/wrapt-2.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:83ce30937f0ba0d28818807b303a412440c4b63e39d3d8fc036a94764b728c92", size = 63240, upload-time = "2025-11-07T00:44:30.935Z" }, + { url = "https://files.pythonhosted.org/packages/1d/54/5ab4c53ea1f7f7e5c3e7c1095db92932cc32fd62359d285486d00c2884c3/wrapt-2.0.1-cp313-cp313t-win_arm64.whl", hash = "sha256:4b55cacc57e1dc2d0991dbe74c6419ffd415fb66474a02335cb10efd1aa3f84f", size = 60416, upload-time = "2025-11-07T00:44:32.002Z" }, + { url = "https://files.pythonhosted.org/packages/73/81/d08d83c102709258e7730d3cd25befd114c60e43ef3891d7e6877971c514/wrapt-2.0.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5e53b428f65ece6d9dad23cb87e64506392b720a0b45076c05354d27a13351a1", size = 78290, upload-time = "2025-11-07T00:44:34.691Z" }, + { url = "https://files.pythonhosted.org/packages/f6/14/393afba2abb65677f313aa680ff0981e829626fed39b6a7e3ec807487790/wrapt-2.0.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ad3ee9d0f254851c71780966eb417ef8e72117155cff04821ab9b60549694a55", size = 61255, upload-time = "2025-11-07T00:44:35.762Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/a4a1f2fba205a9462e36e708ba37e5ac95f4987a0f1f8fd23f0bf1fc3b0f/wrapt-2.0.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7b822c61ed04ee6ad64bc90d13368ad6eb094db54883b5dde2182f67a7f22c0", size = 61797, upload-time = "2025-11-07T00:44:37.22Z" }, + { url = "https://files.pythonhosted.org/packages/12/db/99ba5c37cf1c4fad35349174f1e38bd8d992340afc1ff27f526729b98986/wrapt-2.0.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7164a55f5e83a9a0b031d3ffab4d4e36bbec42e7025db560f225489fa929e509", size = 120470, upload-time = "2025-11-07T00:44:39.425Z" }, + { url = "https://files.pythonhosted.org/packages/30/3f/a1c8d2411eb826d695fc3395a431757331582907a0ec59afce8fe8712473/wrapt-2.0.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e60690ba71a57424c8d9ff28f8d006b7ad7772c22a4af432188572cd7fa004a1", size = 122851, upload-time = "2025-11-07T00:44:40.582Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8d/72c74a63f201768d6a04a8845c7976f86be6f5ff4d74996c272cefc8dafc/wrapt-2.0.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3cd1a4bd9a7a619922a8557e1318232e7269b5fb69d4ba97b04d20450a6bf970", size = 117433, upload-time = "2025-11-07T00:44:38.313Z" }, + { url = "https://files.pythonhosted.org/packages/c7/5a/df37cf4042cb13b08256f8e27023e2f9b3d471d553376616591bb99bcb31/wrapt-2.0.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b4c2e3d777e38e913b8ce3a6257af72fb608f86a1df471cb1d4339755d0a807c", size = 121280, upload-time = "2025-11-07T00:44:41.69Z" }, + { url = "https://files.pythonhosted.org/packages/54/34/40d6bc89349f9931e1186ceb3e5fbd61d307fef814f09fbbac98ada6a0c8/wrapt-2.0.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3d366aa598d69416b5afedf1faa539fac40c1d80a42f6b236c88c73a3c8f2d41", size = 116343, upload-time = "2025-11-07T00:44:43.013Z" }, + { url = "https://files.pythonhosted.org/packages/70/66/81c3461adece09d20781dee17c2366fdf0cb8754738b521d221ca056d596/wrapt-2.0.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c235095d6d090aa903f1db61f892fffb779c1eaeb2a50e566b52001f7a0f66ed", size = 119650, upload-time = "2025-11-07T00:44:44.523Z" }, + { url = "https://files.pythonhosted.org/packages/46/3a/d0146db8be8761a9e388cc9cc1c312b36d583950ec91696f19bbbb44af5a/wrapt-2.0.1-cp314-cp314-win32.whl", hash = "sha256:bfb5539005259f8127ea9c885bdc231978c06b7a980e63a8a61c8c4c979719d0", size = 58701, upload-time = "2025-11-07T00:44:48.277Z" }, + { url = "https://files.pythonhosted.org/packages/1a/38/5359da9af7d64554be63e9046164bd4d8ff289a2dd365677d25ba3342c08/wrapt-2.0.1-cp314-cp314-win_amd64.whl", hash = "sha256:4ae879acc449caa9ed43fc36ba08392b9412ee67941748d31d94e3cedb36628c", size = 60947, upload-time = "2025-11-07T00:44:46.086Z" }, + { url = "https://files.pythonhosted.org/packages/aa/3f/96db0619276a833842bf36343685fa04f987dd6e3037f314531a1e00492b/wrapt-2.0.1-cp314-cp314-win_arm64.whl", hash = "sha256:8639b843c9efd84675f1e100ed9e99538ebea7297b62c4b45a7042edb84db03e", size = 59359, upload-time = "2025-11-07T00:44:47.164Z" }, + { url = "https://files.pythonhosted.org/packages/71/49/5f5d1e867bf2064bf3933bc6cf36ade23505f3902390e175e392173d36a2/wrapt-2.0.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:9219a1d946a9b32bb23ccae66bdb61e35c62773ce7ca6509ceea70f344656b7b", size = 82031, upload-time = "2025-11-07T00:44:49.4Z" }, + { url = "https://files.pythonhosted.org/packages/2b/89/0009a218d88db66ceb83921e5685e820e2c61b59bbbb1324ba65342668bc/wrapt-2.0.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fa4184e74197af3adad3c889a1af95b53bb0466bced92ea99a0c014e48323eec", size = 62952, upload-time = "2025-11-07T00:44:50.74Z" }, + { url = "https://files.pythonhosted.org/packages/ae/18/9b968e920dd05d6e44bcc918a046d02afea0fb31b2f1c80ee4020f377cbe/wrapt-2.0.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c5ef2f2b8a53b7caee2f797ef166a390fef73979b15778a4a153e4b5fedce8fa", size = 63688, upload-time = "2025-11-07T00:44:52.248Z" }, + { url = "https://files.pythonhosted.org/packages/a6/7d/78bdcb75826725885d9ea26c49a03071b10c4c92da93edda612910f150e4/wrapt-2.0.1-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e042d653a4745be832d5aa190ff80ee4f02c34b21f4b785745eceacd0907b815", size = 152706, upload-time = "2025-11-07T00:44:54.613Z" }, + { url = "https://files.pythonhosted.org/packages/dd/77/cac1d46f47d32084a703df0d2d29d47e7eb2a7d19fa5cbca0e529ef57659/wrapt-2.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2afa23318136709c4b23d87d543b425c399887b4057936cd20386d5b1422b6fa", size = 158866, upload-time = "2025-11-07T00:44:55.79Z" }, + { url = "https://files.pythonhosted.org/packages/8a/11/b521406daa2421508903bf8d5e8b929216ec2af04839db31c0a2c525eee0/wrapt-2.0.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6c72328f668cf4c503ffcf9434c2b71fdd624345ced7941bc6693e61bbe36bef", size = 146148, upload-time = "2025-11-07T00:44:53.388Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c0/340b272bed297baa7c9ce0c98ef7017d9c035a17a6a71dce3184b8382da2/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3793ac154afb0e5b45d1233cb94d354ef7a983708cc3bb12563853b1d8d53747", size = 155737, upload-time = "2025-11-07T00:44:56.971Z" }, + { url = "https://files.pythonhosted.org/packages/f3/93/bfcb1fb2bdf186e9c2883a4d1ab45ab099c79cbf8f4e70ea453811fa3ea7/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:fec0d993ecba3991645b4857837277469c8cc4c554a7e24d064d1ca291cfb81f", size = 144451, upload-time = "2025-11-07T00:44:58.515Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6b/dca504fb18d971139d232652656180e3bd57120e1193d9a5899c3c0b7cdd/wrapt-2.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:949520bccc1fa227274da7d03bf238be15389cd94e32e4297b92337df9b7a349", size = 150353, upload-time = "2025-11-07T00:44:59.753Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f6/a1de4bd3653afdf91d250ca5c721ee51195df2b61a4603d4b373aa804d1d/wrapt-2.0.1-cp314-cp314t-win32.whl", hash = "sha256:be9e84e91d6497ba62594158d3d31ec0486c60055c49179edc51ee43d095f79c", size = 60609, upload-time = "2025-11-07T00:45:03.315Z" }, + { url = "https://files.pythonhosted.org/packages/01/3a/07cd60a9d26fe73efead61c7830af975dfdba8537632d410462672e4432b/wrapt-2.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:61c4956171c7434634401db448371277d07032a81cc21c599c22953374781395", size = 64038, upload-time = "2025-11-07T00:45:00.948Z" }, + { url = "https://files.pythonhosted.org/packages/41/99/8a06b8e17dddbf321325ae4eb12465804120f699cd1b8a355718300c62da/wrapt-2.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:35cdbd478607036fee40273be8ed54a451f5f23121bd9d4be515158f9498f7ad", size = 60634, upload-time = "2025-11-07T00:45:02.087Z" }, + { url = "https://files.pythonhosted.org/packages/15/d1/b51471c11592ff9c012bd3e2f7334a6ff2f42a7aed2caffcf0bdddc9cb89/wrapt-2.0.1-py3-none-any.whl", hash = "sha256:4d2ce1bf1a48c5277d7969259232b57645aae5686dba1eaeade39442277afbca", size = 44046, upload-time = "2025-11-07T00:45:32.116Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, + { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, + { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] + +[[package]] +name = "yfinance" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "curl-cffi" }, + { name = "frozendict" }, + { name = "multitasking" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "peewee" }, + { name = "platformdirs" }, + { name = "protobuf" }, + { name = "pytz" }, + { name = "requests" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/04/f76bc0cb6767a3074698c8d416c7f2137e6d1ccc4f393ed82421393111b7/yfinance-1.1.0.tar.gz", hash = "sha256:1e852ce10a5d6679200efa2b09ed3f3b01dbe84505e2c1c139be7d2b3a597b10", size = 140171, upload-time = "2026-01-24T16:07:34.394Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/13/63c35603f07f549b0d044b6001956aeb941850e0ef75ef7c8eb8b0e410ab/yfinance-1.1.0-py2.py3-none-any.whl", hash = "sha256:e610fec1d2b052e3b8f2cf44bdcff014bcf15458a9b072d5a3e02507e20d69d2", size = 129928, upload-time = "2026-01-24T16:07:32.983Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, +] From a65798d886142588a92c0f266f2a78ee7ccb8dc8 Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Sat, 31 Jan 2026 13:43:57 -0800 Subject: [PATCH 02/50] make import more clear --- README.md | 34 ++++- py-src/data_formulator/__init__.py | 2 +- py-src/data_formulator/app.py | 139 ++++++------------ .../data_formulator/data_loader/__init__.py | 13 +- 4 files changed, 85 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 5b4f9e84..db7ff79a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

@@ -32,6 +32,9 @@ https://github.com/user-attachments/assets/8ca57b68-4d7a-42cb-bcce-43f8b1681ce2 ## News 🔥🔥🔥 +[01-31-2025] **uv support** — Faster installation with uv +- 🚀 **Install with uv**: Data Formulator now supports installation via [uv](https://docs.astral.sh/uv/), the ultra-fast Python package manager. Get started in seconds with `uvx data_formulator` or `uv pip install data_formulator`. + [01-25-2025] **Data Formulator 0.6** — Real-time insights from live data - ⚡ **Connect to live data**: Connect to URLs and databases with automatic refresh intervals. Visualizations update automatically as your data changes to provide you live insights. [Demo: track international space station position speed live](https://github.com/microsoft/data-formulator/releases/tag/0.6) - 🎨 **UI Updates**: Unified UI for data loading; direct drag-and-drop fields from the data table to update visualization designs. @@ -127,9 +130,30 @@ Data Formulator enables analysts to iteratively explore and visualize data. Star Play with Data Formulator with one of the following options: -- **Option 1: Install via Python PIP** +- **Option 1: Install via uv (recommended)** + + [uv](https://docs.astral.sh/uv/) is an extremely fast Python package manager. If you have uv installed, you can run Data Formulator directly without any setup: + + ```bash + # Run data formulator directly (no install needed) + uvx data_formulator + ``` + + Or install it in a project/virtual environment: + + ```bash + # Install data_formulator + uv pip install data_formulator + + # Run data formulator + python -m data_formulator + ``` + + Data Formulator will be automatically opened in the browser at [http://localhost:5000](http://localhost:5000). + +- **Option 2: Install via pip** - Use Python PIP for an easy setup experience, running locally (recommend: install it in a virtual environment). + Use pip for installation (recommend: install it in a virtual environment). ```bash # install data_formulator @@ -143,13 +167,13 @@ Play with Data Formulator with one of the following options: *you can specify the port number (e.g., 8080) by `python -m data_formulator --port 8080` if the default port is occupied.* -- **Option 2: Codespaces (5 minutes)** +- **Option 3: Codespaces (5 minutes)** You can also run Data Formulator in Codespaces; we have everything pre-configured. For more details, see [CODESPACES.md](CODESPACES.md). [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/microsoft/data-formulator?quickstart=1) -- **Option 3: Working in the developer mode** +- **Option 4: Working in the developer mode** You can build Data Formulator locally if you prefer full control over your development environment and develop your own version on top. For detailed instructions, refer to [DEVELOPMENT.md](DEVELOPMENT.md). diff --git a/py-src/data_formulator/__init__.py b/py-src/data_formulator/__init__.py index 2f2fd61f..ee0d133d 100644 --- a/py-src/data_formulator/__init__.py +++ b/py-src/data_formulator/__init__.py @@ -3,7 +3,7 @@ def run_app(): """Launch the Data Formulator Flask application.""" - # Import app only when actually running to avoid side effects + # Import app only when actually running to avoid heavy imports at package load from data_formulator.app import run_app as _run_app return _run_app() diff --git a/py-src/data_formulator/app.py b/py-src/data_formulator/app.py index 634f0870..187f09e0 100644 --- a/py-src/data_formulator/app.py +++ b/py-src/data_formulator/app.py @@ -2,11 +2,9 @@ # Licensed under the MIT License. import argparse -import random import sys import os import mimetypes -from functools import lru_cache mimetypes.add_type('application/javascript', '.js') mimetypes.add_type('application/javascript', '.mjs') @@ -17,7 +15,6 @@ import webbrowser import threading import numpy as np -import datetime import time import logging @@ -28,28 +25,14 @@ from dotenv import load_dotenv import secrets import base64 -APP_ROOT = Path(Path(__file__).parent).absolute() - -import os -# blueprints -from data_formulator.tables_routes import tables_bp -from data_formulator.agent_routes import agent_bp -from data_formulator.demo_stream_routes import demo_stream_bp, limiter as demo_stream_limiter -from data_formulator.db_manager import db_manager -from data_formulator.example_datasets_config import EXAMPLE_DATASETS - -import queue -from typing import Any +APP_ROOT = Path(Path(__file__).parent).absolute() +# Create Flask app (lightweight, no heavy imports yet) app = Flask(__name__, static_url_path='', static_folder=os.path.join(APP_ROOT, "dist")) -app.secret_key = secrets.token_hex(16) # Generate a random secret key for sessions +app.secret_key = secrets.token_hex(16) app.json.sort_keys = False -# Initialize rate limiter for demo stream routes that call external APIs -# The limiter is defined in demo_stream_routes.py to avoid circular imports -demo_stream_limiter.init_app(app) - class CustomJSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, np.int64): @@ -65,7 +48,7 @@ def default(self, obj): load_dotenv(os.path.join(APP_ROOT, 'api-keys.env')) load_dotenv(os.path.join(APP_ROOT, '.env')) -# Add this line to store args at app level +# Default config from env (can be overridden by CLI args) app.config['CLI_ARGS'] = { 'exec_python_in_subprocess': os.environ.get('EXEC_PYTHON_IN_SUBPROCESS', 'false').lower() == 'true', 'disable_display_keys': os.environ.get('DISABLE_DISPLAY_KEYS', 'false').lower() == 'true', @@ -74,19 +57,11 @@ def default(self, obj): 'project_front_page': os.environ.get('PROJECT_FRONT_PAGE', 'false').lower() == 'true' } -# register blueprints -# Only register tables blueprint if database is not disabled -if not app.config['CLI_ARGS']['disable_database']: - app.register_blueprint(tables_bp) -app.register_blueprint(agent_bp) -app.register_blueprint(demo_stream_bp) - # Get logger for this module (logging config moved to run_app function) logger = logging.getLogger(__name__) def configure_logging(): """Configure logging for the Flask application.""" - # Configure root logger for general application logging logging.basicConfig( level=logging.ERROR, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', @@ -98,14 +73,38 @@ def configure_logging(): logging.getLogger('litellm').setLevel(logging.WARNING) logging.getLogger('openai').setLevel(logging.WARNING) - # Configure Flask app logger to use the same settings app.logger.handlers = [] for handler in logging.getLogger().handlers: app.logger.addHandler(handler) +def _register_blueprints(disable_database: bool): + """ + Import and register blueprints. This is where heavy imports happen. + Called from run_app() with progress feedback. + """ + # Import tables routes (imports database connectors) + print(" Loading database connectors...", flush=True) + from data_formulator.tables_routes import tables_bp + + # Import agent routes (imports AI/ML libraries: litellm, sklearn, etc.) + print(" Loading AI agents...", flush=True) + from data_formulator.agent_routes import agent_bp + + # Import demo stream routes + from data_formulator.demo_stream_routes import demo_stream_bp, limiter as demo_stream_limiter + demo_stream_limiter.init_app(app) + + # Register blueprints + if not disable_database: + app.register_blueprint(tables_bp) + app.register_blueprint(agent_bp) + app.register_blueprint(demo_stream_bp) + + @app.route('/api/example-datasets') def get_sample_datasets(): + from data_formulator.example_datasets_config import EXAMPLE_DATASETS return flask.jsonify(EXAMPLE_DATASETS) @@ -116,72 +115,21 @@ def index_alt(path): @app.errorhandler(404) def page_not_found(e): - # your processing here logger.info(app.static_folder) - return send_from_directory(app.static_folder, "index.html") #'Hello 404!' #send_from_directory(app.static_folder, "index.html") - -###### test functions ###### - -@app.route('/api/hello') -def hello(): - values = [ - {"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43}, - {"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53}, - {"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52} - ] - spec = { - "$schema": "https://vega.github.io/schema/vega-lite/v5.json", - "description": "A simple bar chart with embedded data.", - "data": { "values": values }, - "mark": "bar", - "encoding": { - "x": {"field": "a", "type": "nominal", "axis": {"labelAngle": 0}}, - "y": {"field": "b", "type": "quantitative"} - } - } - return json.dumps(spec) - -@app.route('/api/hello-stream') -def streamed_response(): - def generate(): - values = [ - {"a": "A", "b": 28}, {"a": "B", "b": 55}, {"a": "C", "b": 43}, - {"a": "D", "b": 91}, {"a": "E", "b": 81}, {"a": "F", "b": 53}, - {"a": "G", "b": 19}, {"a": "H", "b": 87}, {"a": "I", "b": 52} - ] - spec = { - "$schema": "https://vega.github.io/schema/vega-lite/v5.json", - "description": "A simple bar chart with embedded data.", - "data": { "values": [] }, - "mark": "bar", - "encoding": { - "x": {"field": "a", "type": "nominal", "axis": {"labelAngle": 0}}, - "y": {"field": "b", "type": "quantitative"} - } - } - for i in range(3): - time.sleep(3) - spec["data"]["values"] = values[i:] - yield json.dumps(spec) - return Response(stream_with_context(generate())) + return send_from_directory(app.static_folder, "index.html") + @app.route('/api/get-session-id', methods=['GET', 'POST']) def get_session_id(): """Endpoint to get or confirm a session ID from the client""" - # if it is a POST request, we expect a session_id in the body - # if it is a GET request, we do not expect a session_id in the query params - current_session_id = None if request.is_json: content = request.get_json() current_session_id = content.get("session_id", None) - # Check if database is disabled database_disabled = app.config['CLI_ARGS']['disable_database'] if database_disabled: - # When database is disabled, don't use Flask sessions (cookies) - # Just return the provided session_id or generate a new one if current_session_id is None: current_session_id = secrets.token_hex(16) logger.info(f"Generated session ID for disabled database: {current_session_id}") @@ -193,14 +141,12 @@ def get_session_id(): "session_id": current_session_id }) else: - # When database is enabled, use Flask sessions (cookies) as before if current_session_id is None: if 'session_id' not in session: session['session_id'] = secrets.token_hex(16) session.permanent = True logger.info(f"Created new session: {session['session_id']}") else: - # override the session_id session['session_id'] = current_session_id session.permanent = True @@ -214,7 +160,6 @@ def get_app_config(): """Provide frontend configuration settings from CLI arguments""" args = app.config['CLI_ARGS'] - # When database is disabled, don't try to access session session_id = None if not args['disable_database']: session_id = session.get('session_id', None) @@ -238,7 +183,6 @@ def database_disabled_fallback(path): "message": "Database functionality is disabled. Use --disable-database=false to enable table operations." }), 503 else: - # If database is not disabled but we're hitting this route, it means the tables blueprint wasn't registered return flask.jsonify({ "status": "error", "message": "Table routes are not available" @@ -264,12 +208,12 @@ def parse_args() -> argparse.Namespace: def run_app(): - # Configure logging only when actually running the app - configure_logging() + print("Starting Data Formulator...", flush=True) + configure_logging() args = parse_args() - # Add this line to make args available to routes - # override the args from the env file + + # Override config from CLI args app.config['CLI_ARGS'] = { 'exec_python_in_subprocess': args.exec_python_in_subprocess, 'disable_display_keys': args.disable_display_keys, @@ -278,18 +222,21 @@ def run_app(): 'project_front_page': args.project_front_page } + # Register blueprints (this is where heavy imports happen) + _register_blueprints(args.disable_database) + # Update database manager state + from data_formulator.db_manager import db_manager db_manager._disabled = args.disable_database + url = "http://localhost:{0}".format(args.port) + print(f"Ready! Open {url} in your browser.", flush=True) + if not args.dev: - url = "http://localhost:{0}".format(args.port) - threading.Timer(2, lambda: webbrowser.open(url, new=2)).start() + threading.Timer(1.5, lambda: webbrowser.open(url, new=2)).start() - # Enable debug mode and auto-reload in development mode debug_mode = args.dev app.run(host='0.0.0.0', port=args.port, debug=debug_mode, use_reloader=debug_mode) if __name__ == '__main__': - #app.run(debug=True, host='127.0.0.1', port=5000) - #use 0.0.0.0 for public run_app() diff --git a/py-src/data_formulator/data_loader/__init__.py b/py-src/data_formulator/data_loader/__init__.py index f61a6851..898c50f5 100644 --- a/py-src/data_formulator/data_loader/__init__.py +++ b/py-src/data_formulator/data_loader/__init__.py @@ -21,4 +21,15 @@ "athena": AthenaDataLoader } -__all__ = ["ExternalDataLoader", "MySQLDataLoader", "MSSQLDataLoader", "KustoDataLoader", "S3DataLoader", "AzureBlobDataLoader", "PostgreSQLDataLoader", "MongoDBDataLoader", "BigQueryDataLoader", "AthenaDataLoader", "DATA_LOADERS"] \ No newline at end of file +__all__ = [ + "ExternalDataLoader", + "MySQLDataLoader", + "MSSQLDataLoader", + "KustoDataLoader", + "S3DataLoader", + "AzureBlobDataLoader", + "PostgreSQLDataLoader", + "MongoDBDataLoader", + "BigQueryDataLoader", + "AthenaDataLoader", + "DATA_LOADERS"] \ No newline at end of file From fe60de01805fa2592fcbe395550a93b52be6df57 Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Sun, 1 Feb 2026 10:14:16 -0800 Subject: [PATCH 03/50] clean up data loader to prep for new design --- .../data_loader/athena_data_loader.py | 47 ----- .../data_loader/azure_blob_data_loader.py | 20 +-- .../data_loader/bigquery_data_loader.py | 36 ---- .../data_loader/external_data_loader.py | 9 - .../data_loader/kusto_data_loader.py | 12 +- .../data_loader/mongodb_data_loader.py | 48 ----- .../data_loader/mssql_data_loader.py | 44 ----- .../data_loader/mysql_data_loader.py | 24 --- .../data_loader/postgresql_data_loader.py | 20 --- .../data_loader/s3_data_loader.py | 20 +-- py-src/data_formulator/security/__init__.py | 6 - .../security/query_validator.py | 166 ------------------ 12 files changed, 3 insertions(+), 449 deletions(-) delete mode 100644 py-src/data_formulator/security/__init__.py delete mode 100644 py-src/data_formulator/security/query_validator.py diff --git a/py-src/data_formulator/data_loader/athena_data_loader.py b/py-src/data_formulator/data_loader/athena_data_loader.py index bb50ad26..1198315e 100644 --- a/py-src/data_formulator/data_loader/athena_data_loader.py +++ b/py-src/data_formulator/data_loader/athena_data_loader.py @@ -6,7 +6,6 @@ from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name from typing import Any -from data_formulator.security import validate_sql_query try: import boto3 @@ -511,49 +510,3 @@ def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1 """) log.info(f"Successfully ingested data into table '{name_as}'") - - def view_query_sample(self, query: str) -> list[dict[str, Any]]: - """Execute query and return sample results.""" - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - # Add LIMIT if not present to avoid large result sets - query_upper = query.upper() - if "LIMIT" not in query_upper: - query = f"{query.rstrip().rstrip(';')} LIMIT 10" - - # Execute query on Athena - result_location = self._execute_query(query) - - # Validate the result location is a proper S3 URL - _validate_s3_url(result_location) - - # Load results from S3 - df = self.duck_db_conn.execute(f"SELECT * FROM read_csv_auto('{_escape_sql_string(result_location)}')").df() - - return json.loads(df.head(10).to_json(orient="records")) - - def ingest_data_from_query(self, query: str, name_as: str): - """Execute Athena query and ingest results into DuckDB.""" - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - name_as = sanitize_table_name(name_as) - - # Execute query on Athena - log.info(f"Executing Athena query for table '{name_as}'") - result_location = self._execute_query(query) - - # Validate the result location is a proper S3 URL - _validate_s3_url(result_location) - - # Load results from S3 into DuckDB - log.info(f"Loading query results from {result_location}") - self.duck_db_conn.execute(f""" - CREATE OR REPLACE TABLE main.{name_as} AS - SELECT * FROM read_csv_auto('{_escape_sql_string(result_location)}') - """) - - log.info(f"Successfully ingested data into table '{name_as}'") diff --git a/py-src/data_formulator/data_loader/azure_blob_data_loader.py b/py-src/data_formulator/data_loader/azure_blob_data_loader.py index 663c8b95..83b53b4a 100644 --- a/py-src/data_formulator/data_loader/azure_blob_data_loader.py +++ b/py-src/data_formulator/data_loader/azure_blob_data_loader.py @@ -5,7 +5,6 @@ from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name from typing import Any -from data_formulator.security import validate_sql_query try: from azure.storage.blob import BlobServiceClient, ContainerClient @@ -384,21 +383,4 @@ def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1 LIMIT {size} """) else: - raise ValueError(f"Unsupported file type: {table_name}") - - def view_query_sample(self, query: str) -> list[dict[str, Any]]: - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - return json.loads(self.duck_db_conn.execute(query).df().head(10).to_json(orient="records")) - - def ingest_data_from_query(self, query: str, name_as: str): - # Execute the query and get results as a DataFrame - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - df = self.duck_db_conn.execute(query).df() - # Use the base class's method to ingest the DataFrame - self.ingest_df_to_duckdb(df, sanitize_table_name(name_as)) \ No newline at end of file + raise ValueError(f"Unsupported file type: {table_name}") \ No newline at end of file diff --git a/py-src/data_formulator/data_loader/bigquery_data_loader.py b/py-src/data_formulator/data_loader/bigquery_data_loader.py index 25819cb5..a61d0f4b 100644 --- a/py-src/data_formulator/data_loader/bigquery_data_loader.py +++ b/py-src/data_formulator/data_loader/bigquery_data_loader.py @@ -6,7 +6,6 @@ import duckdb from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name -from data_formulator.security import validate_sql_query try: from google.cloud import bigquery @@ -295,38 +294,3 @@ def process_field(field, parent_path: str = ""): df = self._convert_bigquery_dtypes(df) self.ingest_df_to_duckdb(df, name_as) - - def view_query_sample(self, query: str) -> list[dict[str, Any]]: - """Execute query and return sample results as a list of dictionaries""" - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - # Add LIMIT if not present - if "LIMIT" not in query.upper(): - query += " LIMIT 10" - - df = self.client.query(query).to_dataframe() - return json.loads(df.to_json(orient="records")) - - def ingest_data_from_query(self, query: str, name_as: str) -> pd.DataFrame: - """Execute custom query and ingest results into DuckDB""" - name_as = sanitize_table_name(name_as) - - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - # Execute query and get DataFrame - df = self.client.query(query).to_dataframe() - - # Drop duplicate columns - df = df.loc[:, ~df.columns.duplicated()] - - # Convert BigQuery-specific dtypes - df = self._convert_bigquery_dtypes(df) - - # Use base class method to ingest DataFrame - self.ingest_df_to_duckdb(df, name_as) - - return df diff --git a/py-src/data_formulator/data_loader/external_data_loader.py b/py-src/data_formulator/data_loader/external_data_loader.py index 552e5d41..0e50c0b0 100644 --- a/py-src/data_formulator/data_loader/external_data_loader.py +++ b/py-src/data_formulator/data_loader/external_data_loader.py @@ -104,12 +104,3 @@ def ingest_data(self, table_name: str, name_as: str = None, size: int = 1000000, sort_order: Sort direction, 'asc' for ascending or 'desc' for descending """ pass - - @abstractmethod - def view_query_sample(self, query: str) -> list[dict[str, Any]]: - pass - - @abstractmethod - def ingest_data_from_query(self, query: str, name_as: str): - pass - diff --git a/py-src/data_formulator/data_loader/kusto_data_loader.py b/py-src/data_formulator/data_loader/kusto_data_loader.py index 97788247..a5044005 100644 --- a/py-src/data_formulator/data_loader/kusto_data_loader.py +++ b/py-src/data_formulator/data_loader/kusto_data_loader.py @@ -252,14 +252,4 @@ def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 5 self.duck_db_conn.execute(f"INSERT INTO {name_as} SELECT * FROM df_temp_{random_suffix}") self.duck_db_conn.execute(f"DROP VIEW df_temp_{random_suffix}") - total_rows_ingested += len(chunk_df) - - def view_query_sample(self, query: str) -> list[dict[str, Any]]: - df = self.query(query).head(10) - return json.loads(df.to_json(orient="records", date_format='iso')) - - def ingest_data_from_query(self, query: str, name_as: str) -> pd.DataFrame: - # Sanitize the table name for SQL compatibility - name_as = sanitize_table_name(name_as) - df = self.query(query) - self.ingest_df_to_duckdb(df, name_as) \ No newline at end of file + total_rows_ingested += len(chunk_df) \ No newline at end of file diff --git a/py-src/data_formulator/data_loader/mongodb_data_loader.py b/py-src/data_formulator/data_loader/mongodb_data_loader.py index e8434834..55087867 100644 --- a/py-src/data_formulator/data_loader/mongodb_data_loader.py +++ b/py-src/data_formulator/data_loader/mongodb_data_loader.py @@ -9,8 +9,6 @@ from datetime import datetime from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name - -from data_formulator.security import validate_sql_query from typing import Any @@ -275,52 +273,6 @@ def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1 self._load_dataframe_to_duckdb(df, name_as, size) return - - - def view_query_sample(self, query: str) -> list[dict[str, Any]]: - - self._existed_collections_in_duckdb() - self._difference_collections() - self._preload_all_collections(self.collection.name if self.collection else "") - - result, error_message = validate_sql_query(query) - if not result: - print(error_message) - raise ValueError(error_message) - - result_query = json.loads(self.duck_db_conn.execute(query).df().head(10).to_json(orient="records")) - - self._drop_all_loaded_tables() - - for collection_name, df in self.existed_collections.items(): - self._load_dataframe_to_duckdb(df, collection_name) - - return result_query - - def ingest_data_from_query(self, query: str, name_as: str) -> pd.DataFrame: - """ - Create a new table from query results - """ - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - name_as = sanitize_table_name(name_as) - - self._existed_collections_in_duckdb() - self._difference_collections() - self._preload_all_collections(self.collection.name if self.collection else "") - - query_result_df = self.duck_db_conn.execute(query).df() - - self._drop_all_loaded_tables() - - for collection_name, existing_df in self.existed_collections.items(): - self._load_dataframe_to_duckdb(existing_df, collection_name) - - self._load_dataframe_to_duckdb(query_result_df, name_as) - - return query_result_df @staticmethod def _quote_identifier(name: str) -> str: diff --git a/py-src/data_formulator/data_loader/mssql_data_loader.py b/py-src/data_formulator/data_loader/mssql_data_loader.py index 875358b2..44a8fdb5 100644 --- a/py-src/data_formulator/data_loader/mssql_data_loader.py +++ b/py-src/data_formulator/data_loader/mssql_data_loader.py @@ -12,7 +12,6 @@ PYODBC_AVAILABLE = False from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name -from data_formulator.security import validate_sql_query log = logging.getLogger(__name__) @@ -420,46 +419,3 @@ def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1 except Exception as e: log.error(f"Failed to ingest data from {table_name}: {e}") raise - - def view_query_sample(self, query: str) -> list[dict[str, Any]]: - """Execute a custom query and return sample results""" - try: - # Add TOP 10 if not already present for SELECT queries - modified_query = query.strip() - if ( - modified_query.upper().startswith("SELECT") - and not modified_query.upper().startswith("SELECT TOP") - and "TOP " not in modified_query.upper()[:50] - ): # Check first 50 chars - modified_query = modified_query.replace("SELECT", "SELECT TOP 10", 1) - - result, error_message = validate_sql_query(modified_query) - if not result: - raise ValueError(error_message) - - df = self._execute_query(modified_query) - - # Handle NaN values for JSON serialization - df_clean = df.fillna(value=None) - return json.loads( - df_clean.head(10).to_json(orient="records", date_format="iso", default_handler=str) - ) - except Exception as e: - log.error(f"Failed to execute query sample: {e}") - raise - - def ingest_data_from_query(self, query: str, name_as: str) -> pd.DataFrame: - """Execute a custom query and ingest results into DuckDB""" - try: - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - df = self._execute_query(query) - # Use the base class's method to ingest the DataFrame - self.ingest_df_to_duckdb(df, sanitize_table_name(name_as)) - log.info(f"Successfully ingested {len(df)} rows from custom query to {name_as}") - return df - except Exception as e: - log.error(f"Failed to execute and ingest custom query: {e}") - raise diff --git a/py-src/data_formulator/data_loader/mysql_data_loader.py b/py-src/data_formulator/data_loader/mysql_data_loader.py index f12a192c..76ee3c15 100644 --- a/py-src/data_formulator/data_loader/mysql_data_loader.py +++ b/py-src/data_formulator/data_loader/mysql_data_loader.py @@ -5,8 +5,6 @@ import duckdb from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name - -from data_formulator.security import validate_sql_query from typing import Any try: @@ -264,28 +262,6 @@ def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1 self.ingest_df_to_duckdb(df, name_as) logger.info(f"Successfully ingested {len(df)} rows from {table_name} into DuckDB table {name_as}") - def view_query_sample(self, query: str) -> list[dict[str, Any]]: - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - # Execute query via native MySQL connection - df = self._execute_query(query) - return json.loads(df.head(10).to_json(orient="records", date_format='iso')) - - def ingest_data_from_query(self, query: str, name_as: str) -> pd.DataFrame: - """Execute custom query and ingest results into DuckDB.""" - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - # Execute query via native MySQL connection - df = self._execute_query(query) - - # Ingest into DuckDB using the base class method - self.ingest_df_to_duckdb(df, sanitize_table_name(name_as)) - return df - def close(self): """Explicitly close the MySQL connection.""" if hasattr(self, 'mysql_conn') and self.mysql_conn: diff --git a/py-src/data_formulator/data_loader/postgresql_data_loader.py b/py-src/data_formulator/data_loader/postgresql_data_loader.py index 47ce41e3..21766770 100644 --- a/py-src/data_formulator/data_loader/postgresql_data_loader.py +++ b/py-src/data_formulator/data_loader/postgresql_data_loader.py @@ -4,9 +4,7 @@ import duckdb from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name - from typing import Any -from data_formulator.security import validate_sql_query class PostgreSQLDataLoader(ExternalDataLoader): @@ -151,21 +149,3 @@ def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1 {order_by_clause} LIMIT {size} """) - - def view_query_sample(self, query: str) -> list[dict[str, Any]]: - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - return json.loads(self.duck_db_conn.execute(query).df().head(10).to_json(orient="records")) - - def ingest_data_from_query(self, query: str, name_as: str) -> pd.DataFrame: - # Execute the query and get results as a DataFrame - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - df = self.duck_db_conn.execute(query).df() - # Use the base class's method to ingest the DataFrame - self.ingest_df_to_duckdb(df, sanitize_table_name(name_as)) - return df diff --git a/py-src/data_formulator/data_loader/s3_data_loader.py b/py-src/data_formulator/data_loader/s3_data_loader.py index c318da2d..3c194d23 100644 --- a/py-src/data_formulator/data_loader/s3_data_loader.py +++ b/py-src/data_formulator/data_loader/s3_data_loader.py @@ -5,7 +5,6 @@ from data_formulator.data_loader.external_data_loader import ExternalDataLoader, sanitize_table_name from typing import Any -from data_formulator.security import validate_sql_query try: import boto3 @@ -217,21 +216,4 @@ def ingest_data(self, table_name: str, name_as: str | None = None, size: int = 1 LIMIT {size} """) else: - raise ValueError(f"Unsupported file type: {table_name}") - - def view_query_sample(self, query: str) -> list[dict[str, Any]]: - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - return json.loads(self.duck_db_conn.execute(query).df().head(10).to_json(orient="records")) - - def ingest_data_from_query(self, query: str, name_as: str): - # Execute the query and get results as a DataFrame - result, error_message = validate_sql_query(query) - if not result: - raise ValueError(error_message) - - df = self.duck_db_conn.execute(query).df() - # Use the base class's method to ingest the DataFrame - self.ingest_df_to_duckdb(df, sanitize_table_name(name_as)) \ No newline at end of file + raise ValueError(f"Unsupported file type: {table_name}") \ No newline at end of file diff --git a/py-src/data_formulator/security/__init__.py b/py-src/data_formulator/security/__init__.py deleted file mode 100644 index a536b8d0..00000000 --- a/py-src/data_formulator/security/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -from .query_validator import validate_sql_query - -__all__ = [ 'validate_sql_query'] \ No newline at end of file diff --git a/py-src/data_formulator/security/query_validator.py b/py-src/data_formulator/security/query_validator.py deleted file mode 100644 index 7e0e5e0a..00000000 --- a/py-src/data_formulator/security/query_validator.py +++ /dev/null @@ -1,166 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - -import re -import logging -from typing import Any - -logger = logging.getLogger(__name__) - - -class QueryValidationError(Exception): - """Custom exception for query validation failures""" - pass - - -def normalize_query(query: str) -> str: - """ - Normalize query for case-insensitive matching - """ - query_normalized = re.sub(r'--.*$', '', query, flags=re.MULTILINE) # Single line comments - query_normalized = re.sub(r'/\*.*?\*/', '', query_normalized, flags=re.DOTALL) # Multi-line comments - return query_normalized.strip().lower() - -def validate_sql_query(query: str) -> tuple[bool, str]: - """ - Simple regex-based SQL query validation for dangerous operations. - - Args: - query: SQL query string to validate - - Returns: - Tuple of (is_valid, error_message) - """ - try: - # Normalize query for case-insensitive matching - query_normalized = normalize_query(query) - - # Remove SQL comments - - - # Define dangerous patterns as regex patterns - dangerous_patterns = { - # File read operations - 'file_read_operations': [ - r'\bread_csv_auto\b', r'\bread_csv\b', r'\bread_json\b', r'\bread_parquet\b', - r'\bread_ndjson\b', r'\bread_delim\b', r'\bread_fwf\b', r'\bread_excel\b', - r'\bread_sql\b', r'\bread_table\b', r'\bread_html\b', r'\bread_xml\b', - r'\bread_feather\b', r'\bread_hdf\b', r'\bread_stata\b', r'\bread_sas\b', - r'\bread_spss\b', r'\bread_rdata\b', r'\bread_rds\b' - ], - - # File write operations - 'file_write_operations': [ - r'\bwrite_csv\b', r'\bwrite_json\b', r'\bwrite_parquet\b', r'\bwrite_excel\b', - r'\bwrite_sql\b', r'\bwrite_table\b', r'\bwrite_html\b', r'\bwrite_xml\b', - r'\bwrite_feather\b', r'\bwrite_hdf\b', r'\bwrite_stata\b', r'\bwrite_sas\b', - r'\bwrite_spss\b', r'\bwrite_rdata\b', r'\bwrite_rds\b' - ], - - # File system operations - 'file_system_operations': [ - r'\bglob\b', r'\bcopy\b', r'\bmove\b', r'\brename\b', r'\bdelete\b', - r'\bremove\b', r'\bunlink\b', r'\bmkdir\b', r'\bmakedirs\b', r'\brmdir\b', - r'\bremovedirs\b', r'\bchmod\b', r'\bchown\b', r'\bsymlink\b', r'\blink\b', - r'\btouch\b', r'\btruncate\b', r'\bwrite\b', r'\bappend\b' - ], - - # System operations - 'system_operations': [ - r'\bsystem\b', r'\bexec\b', r'\beval\b', r'\bcompile\b', r'\bexecfile\b', - r'\binput\b', r'\bos\.system\b', r'\bos\.popen\b', r'\bos\.spawn\b', - r'\bos\.fork\b', r'\bos\.kill\b', r'\bsubprocess\b', r'\bsubprocess\.call\b', - r'\bsubprocess\.run\b', r'\bsubprocess\.popen\b', r'\bsubprocess\.check_call\b', - r'\bsubprocess\.check_output\b' - ], - - # Network operations - 'network_operations': [ - r'\burllib\b', r'\brequests\b', r'\bhttp://\b', r'\bhttps://\b', r'\bftp://\b', - r'\bsmtp\b', r'\bpop3\b', r'\bsocket\b', r'\btelnet\b', r'\bssh\b', r'\bscp\b', - r'\bwget\b', r'\bcurl\b' - ], - - # Shell operations - 'shell_operations': [ - r'\bshell\b', r'\bcmd\b', r'\bbash\b', r'\bsh\b', r'\bpowershell\b', - r'\bcmd\.exe\b', r'\bcommand\b', r'\bexecute\b', r'\brun\b', r'\bcall\b', - r'\binvoke\b' - ], - - # DuckDB dangerous operations - 'duckdb_dangerous_operations': [ - r'\binstall\b', r'\bload\b', r'\bunload\b', r'\bexport\b', r'\bimport\b', - r'\bcopy_to\b' - ], - - # SQL injection patterns - 'sql_injection_patterns': [ - r';\s*--', # Comment after semicolon - r';\s*/\*', # Block comment after semicolon - r'\bunion\s+all\s+select\b', # UNION ALL SELECT - r'\bunion\s+select\b', # UNION SELECT - r'\bxp_cmdshell\b', # SQL Server command shell - r'\bsp_executesql\b', # SQL Server dynamic SQL - ], - - # Dangerous SQL keywords - 'dangerous_sql_keywords': [ - r'\binsert\b', r'\bupdate\b', r'\bdelete\b', r'\bdrop\b', r'\bcreate\b', - r'\balter\b', r'\btruncate\b', r'\bgrant\b', r'\brevoke\b', r'\bexecute\b', - r'\bexec\b', r'\bcall\b', r'\bbegin\b', r'\bcommit\b', r'\brollback\b' - ], - - # File path patterns - 'file_path_patterns': [ - r'file://', r'file:///', r'c:\\', r'd:\\', r'e:\\', - r'/etc/', r'/var/', r'/tmp/', r'/home/', r'/root/', - r'/usr/', r'/bin/', r'/sbin/', r'http://', r'https://', - r'ftp://', r'sftp://', r'ssh://' - ] - } - - # Check each category of dangerous patterns - for category, patterns in dangerous_patterns.items(): - for pattern in patterns: - if re.search(pattern, query_normalized, re.IGNORECASE): - return False, f"Dangerous {category.replace('_', ' ')} detected: {pattern}" - - # Check for file paths in string literals - string_literals = re.findall(r"'([^']*)'", query_normalized) + re.findall(r'"([^"]*)"', query_normalized) - for literal in string_literals: - for pattern in dangerous_patterns['file_path_patterns']: - if re.search(pattern, literal, re.IGNORECASE): - return False, f"Dangerous file path detected in string literal: {literal}" - - return True, "Query validation passed" - - except Exception as e: - logger.error(f"Error during query validation: {e}") - return False, f"Query validation error: {str(e)}" - - -def validate_sql_query_strict(query: str) -> tuple[bool, str]: - """ - Strict validation that only allows SELECT queries and basic operations. - - Args: - query: SQL query string to validate - - Returns: - Tuple of (is_valid, error_message) - """ - try: - # Normalize query - query_normalized = normalize_query(query) - - # Check if it's a SELECT query - if not query_normalized.startswith('select'): - return False, "Only SELECT queries are allowed" - - # Perform regular validation - return validate_sql_query(query) - - except Exception as e: - return False, f"Strict validation error: {str(e)}" - From fd5a2332bc7318173e7d6514ad088b5a22dbd861 Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Sun, 1 Feb 2026 11:59:25 -0800 Subject: [PATCH 04/50] udpate auth workflow to prep for new workspace manage --- DEVELOPMENT.md | 92 +++++++++++++- py-src/data_formulator/agent_routes.py | 26 ++-- py-src/data_formulator/app.py | 43 +------ py-src/data_formulator/auth.py | 103 ++++++++++++++++ py-src/data_formulator/tables_routes.py | 82 +++++-------- src/app/App.tsx | 49 +++++--- src/app/dfSlice.tsx | 45 +++---- src/app/identity.ts | 108 +++++++++++++++++ src/app/useDataRefresh.tsx | 18 +-- src/app/utils.tsx | 152 +++++++++--------------- src/views/ChartRecBox.tsx | 6 +- src/views/DBTableManager.tsx | 55 ++++----- src/views/DataFormulator.tsx | 4 +- src/views/DataLoadingThread.tsx | 4 +- src/views/DataThread.tsx | 4 +- src/views/EncodingBox.tsx | 4 +- src/views/EncodingShelfCard.tsx | 4 +- src/views/ModelSelectionDialog.tsx | 6 +- src/views/ReportView.tsx | 4 +- src/views/SelectableDataGrid.tsx | 4 +- src/views/UnifiedDataUploadDialog.tsx | 6 +- src/views/VisualizationView.tsx | 6 +- 22 files changed, 513 insertions(+), 312 deletions(-) create mode 100644 py-src/data_formulator/auth.py create mode 100644 src/app/identity.ts diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 4b2afe3c..2a16d785 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -145,9 +145,10 @@ When deploying Data Formulator to production, please be aware of the following s 1. **Local DuckDB Files**: When database functionality is enabled (default), Data Formulator stores DuckDB database files locally on the server. These files contain user data and are stored in the system's temporary directory or a configured `LOCAL_DB_DIR`. -2. **Session Management**: - - When database is **enabled**: Session IDs are stored in Flask sessions (cookies) and linked to local DuckDB files - - When database is **disabled**: No persistent storage is used, and no cookies are set. Session IDs are generated per request for API consistency +2. **Identity Management**: + - Each user's data is isolated by a namespaced identity key (e.g., `user:alice@example.com` or `browser:550e8400-...`) + - Anonymous users get a browser-based UUID stored in localStorage + - Authenticated users get their verified user ID from the auth provider 3. **Data Persistence**: User data processed through Data Formulator may be temporarily stored in these local DuckDB files, which could be a security risk in multi-tenant environments. @@ -171,5 +172,90 @@ For production deployment, consider: python -m data_formulator.app --disable-database ``` +## Authentication Architecture + +Data Formulator supports a **hybrid identity system** that supports both anonymous and authenticated users. + +### Identity Flow Overview + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ Frontend Request │ +├─────────────────────────────────────────────────────────────────────┤ +│ Headers: │ +│ X-Identity-Id: "browser:550e8400-..." (namespace sent by client) │ +│ Authorization: Bearer (if custom auth implemented) │ +│ (Azure also adds X-MS-CLIENT-PRINCIPAL-ID automatically) │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ Backend Identity Resolution │ +│ (auth.py: get_identity_id) │ +├─────────────────────────────────────────────────────────────────────┤ +│ Priority 1: Azure X-MS-CLIENT-PRINCIPAL-ID → "user:" │ +│ Priority 2: JWT Bearer token (if implemented) → "user:" │ +│ Priority 3: X-Identity-Id header → ALWAYS "browser:" │ +│ (client-provided namespace is IGNORED for security) │ +└─────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ Storage Isolation │ +├─────────────────────────────────────────────────────────────────────┤ +│ "user:alice@example.com" → alice's DuckDB file (ONLY via auth) │ +│ "browser:550e8400-..." → anonymous user's DuckDB file │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +### Security Model + +**Critical Security Rule:** The backend NEVER trusts the namespace prefix from the client-provided `X-Identity-Id` header. Even if a client sends `X-Identity-Id: "user:alice@..."`, the backend strips the prefix and forces `browser:alice@...`. Only verified authentication (Azure headers or JWT) can result in a `user:` prefixed identity. + +The key security principle is **namespaced isolation with forced prefixing**: + +| Scenario | X-Identity-Id Sent | Backend Resolution | Storage Key | +|----------|-------------------|-------------------|-------------| +| Anonymous user | `browser:550e8400-...` | Strips prefix, forces `browser:` | `browser:550e8400-...` | +| Azure logged-in user | `browser:550e8400-...` | Uses Azure header (priority 1) | `user:alice@...` | +| Attacker spoofing | `user:alice@...` (forged) | No valid auth, strips & forces `browser:` | `browser:alice@...` | + +**Why this is secure:** An attacker sending `X-Identity-Id: user:alice@...` gets `browser:alice@...` as their storage key, which is completely separate from the real `user:alice@...` that only authenticated Alice can access. + +### Implementing Custom Authentication + +To add JWT-based authentication: + +1. **Backend** (`tables_routes.py`): Uncomment and configure the JWT verification code in `get_identity_id()` +2. **Frontend** (`utils.tsx`): Implement `getAuthToken()` to retrieve the JWT from your auth context +3. **Add JWT secret** to Flask config: `current_app.config['JWT_SECRET']` + +### Azure App Service Authentication + +When deployed to Azure with EasyAuth enabled: +- Azure automatically adds `X-MS-CLIENT-PRINCIPAL-ID` header to authenticated requests +- The backend reads this header first (highest priority) +- No frontend changes needed - Azure handles the auth flow + +### Frontend Identity Management + +The frontend (`src/app/identity.ts`) manages identity as follows: + +```typescript +// Identity is always initialized with browser ID +identity: { type: 'browser', id: getBrowserId() } + +// If user logs in (e.g., via Azure), it's updated to: +identity: { type: 'user', id: userInfo.userId } + +// All API requests send namespaced identity: +// X-Identity-Id: "browser:550e8400-..." or "user:alice@..." +``` + +This ensures: +1. **Anonymous users**: Work immediately with localStorage-based browser ID +2. **Logged-in users**: Get their verified user ID from the auth provider +3. **Cross-tab consistency**: Browser ID is shared via localStorage across all tabs + ## Usage See the [Usage section on the README.md page](README.md#usage). diff --git a/py-src/data_formulator/agent_routes.py b/py-src/data_formulator/agent_routes.py index 3de374bb..8103bcdd 100644 --- a/py-src/data_formulator/agent_routes.py +++ b/py-src/data_formulator/agent_routes.py @@ -12,7 +12,7 @@ mimetypes.add_type('application/javascript', '.mjs') import flask -from flask import request, session, jsonify, Blueprint, current_app, Response, stream_with_context +from flask import request, jsonify, Blueprint, current_app, Response, stream_with_context import logging import json @@ -28,6 +28,7 @@ from data_formulator.agents.agent_sql_data_rec import SQLDataRecAgent from data_formulator.agents.agent_sort_data import SortDataAgent +from data_formulator.auth import get_identity_id from data_formulator.agents.agent_data_load import DataLoadAgent from data_formulator.agents.agent_data_clean import DataCleanAgent from data_formulator.agents.agent_data_clean_stream import DataCleanAgentStream @@ -180,10 +181,8 @@ def process_data_on_load_request(): logger.info(f" model: {content['model']}") - try: - conn = db_manager.get_connection(session['session_id']) - except Exception as e: - conn = None + identity_id = get_identity_id() + conn = db_manager.get_connection(identity_id) agent = DataLoadAgent(client=client, conn=conn) @@ -395,7 +394,8 @@ def derive_data(): if chart_encodings == {}: mode = "recommendation" - conn = db_manager.get_connection(session['session_id']) if language == "sql" else None + identity_id = get_identity_id() + conn = db_manager.get_connection(identity_id) if language == "sql" else None if mode == "recommendation": # now it's in recommendation mode @@ -465,7 +465,8 @@ def generate(): "api_version": content['model'].get('api_version', '') } - session_id = session.get('session_id') if language == "sql" else None + # Get identity for SQL mode database connections + identity_id = get_identity_id() if language == "sql" else None exec_python_in_subprocess = current_app.config['CLI_ARGS']['exec_python_in_subprocess'] try: @@ -474,7 +475,7 @@ def generate(): input_tables=input_tables, initial_plan=initial_plan, language=language, - session_id=session_id, + session_id=identity_id, exec_python_in_subprocess=exec_python_in_subprocess, max_iterations=max_iterations, max_repair_attempts=max_repair_attempts, @@ -563,7 +564,8 @@ def refine_data(): logger.info(chart_encodings) logger.info(new_instruction) - conn = db_manager.get_connection(session['session_id']) if language == "sql" else None + identity_id = get_identity_id() + conn = db_manager.get_connection(identity_id) if language == "sql" else None # always resort to the data transform agent agent = SQLDataTransformationAgent(client=client, conn=conn, agent_coding_rules=agent_coding_rules) if language == "sql" else PythonDataTransformationAgent(client=client, exec_python_in_subprocess=current_app.config['CLI_ARGS']['exec_python_in_subprocess'], agent_coding_rules=agent_coding_rules) @@ -626,7 +628,8 @@ def generate(): language = content.get("language", "python") if language == "sql": - db_conn = db_manager.get_connection(session['session_id']) + identity_id = get_identity_id() + db_conn = db_manager.get_connection(identity_id) else: db_conn = None @@ -677,7 +680,8 @@ def generate(): language = content.get("language", "python") if language == "sql": - db_conn = db_manager.get_connection(session['session_id']) + identity_id = get_identity_id() + db_conn = db_manager.get_connection(identity_id) else: db_conn = None diff --git a/py-src/data_formulator/app.py b/py-src/data_formulator/app.py index 187f09e0..b5f0bbed 100644 --- a/py-src/data_formulator/app.py +++ b/py-src/data_formulator/app.py @@ -9,7 +9,7 @@ mimetypes.add_type('application/javascript', '.mjs') import flask -from flask import Flask, request, send_from_directory, session +from flask import Flask, request, send_from_directory from flask import stream_with_context, Response import webbrowser @@ -119,58 +119,17 @@ def page_not_found(e): return send_from_directory(app.static_folder, "index.html") -@app.route('/api/get-session-id', methods=['GET', 'POST']) -def get_session_id(): - """Endpoint to get or confirm a session ID from the client""" - current_session_id = None - if request.is_json: - content = request.get_json() - current_session_id = content.get("session_id", None) - - database_disabled = app.config['CLI_ARGS']['disable_database'] - - if database_disabled: - if current_session_id is None: - current_session_id = secrets.token_hex(16) - logger.info(f"Generated session ID for disabled database: {current_session_id}") - else: - logger.info(f"Using provided session ID for disabled database: {current_session_id}") - - return flask.jsonify({ - "status": "ok", - "session_id": current_session_id - }) - else: - if current_session_id is None: - if 'session_id' not in session: - session['session_id'] = secrets.token_hex(16) - session.permanent = True - logger.info(f"Created new session: {session['session_id']}") - else: - session['session_id'] = current_session_id - session.permanent = True - - return flask.jsonify({ - "status": "ok", - "session_id": session['session_id'] - }) - @app.route('/api/app-config', methods=['GET']) def get_app_config(): """Provide frontend configuration settings from CLI arguments""" args = app.config['CLI_ARGS'] - session_id = None - if not args['disable_database']: - session_id = session.get('session_id', None) - config = { "EXEC_PYTHON_IN_SUBPROCESS": args['exec_python_in_subprocess'], "DISABLE_DISPLAY_KEYS": args['disable_display_keys'], "DISABLE_DATABASE": args['disable_database'], "DISABLE_FILE_UPLOAD": args['disable_file_upload'], "PROJECT_FRONT_PAGE": args['project_front_page'], - "SESSION_ID": session_id } return flask.jsonify(config) diff --git a/py-src/data_formulator/auth.py b/py-src/data_formulator/auth.py new file mode 100644 index 00000000..ba54b7fb --- /dev/null +++ b/py-src/data_formulator/auth.py @@ -0,0 +1,103 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +""" +Authentication and identity management for Data Formulator. + +This module provides a hybrid identity system that supports both anonymous +browser-based users and authenticated users (via Azure App Service or JWT). + +Security Model: +- Anonymous users: Browser UUID from X-Identity-Id header (prefixed with "browser:") +- Authenticated users: Verified identity from Azure headers or JWT (prefixed with "user:") +- Namespacing ensures authenticated user data cannot be accessed by spoofing headers +""" + +import logging +from flask import request, current_app + +logger = logging.getLogger(__name__) + + +def get_identity_id() -> str: + """ + Get identity ID with proper security priority: + + 1. Verified user from Azure App Service auth headers (trusted, set by Azure) + 2. Verified user from JWT bearer token (trusted, cryptographically verified) + 3. Browser ID from X-Identity-Id header (untrusted, for anonymous users only) + + The key insight: for anonymous users, we trust X-Identity-Id because there's + no security risk (who cares if someone "steals" a random UUID?). For authenticated + users, we MUST extract identity from verified sources, not client-provided headers. + + Identity is namespaced as "user:" or "browser:" to ensure authenticated + user data is never accessible via anonymous browser identity spoofing. + + Returns: + str: The namespaced identity ID string (e.g., "user:alice@..." or "browser:550e8400-...") + + Raises: + ValueError: If no identity could be determined + """ + + # Priority 1: Azure App Service Authentication (EasyAuth) + # When deployed to Azure with authentication enabled, Azure injects these headers. + # These are SET BY AZURE (not the client) after verifying the user's identity. + azure_principal_id = request.headers.get('X-MS-CLIENT-PRINCIPAL-ID') + if azure_principal_id: + logger.debug(f"Using Azure principal ID: {azure_principal_id[:8]}...") + return f"user:{azure_principal_id}" + + # Priority 2: JWT Bearer Token (for custom auth implementations) + # If you implement your own auth, verify the JWT here and extract user ID. + # Example (uncomment and configure when implementing JWT auth): + # + # auth_header = request.headers.get('Authorization', '') + # if auth_header.startswith('Bearer '): + # token = auth_header[7:] + # try: + # import jwt + # payload = jwt.decode(token, current_app.config['JWT_SECRET'], algorithms=['HS256']) + # user_id = payload.get('sub') or payload.get('user_id') + # if user_id: + # logger.debug(f"Using JWT user ID: {user_id[:8]}...") + # return f"user:{user_id}" + # except Exception as e: + # logger.warning(f"Invalid JWT token: {e}") + # # Fall through to browser identity + + # Priority 3: Anonymous browser identity (UNTRUSTED - from client header) + # SECURITY: We NEVER trust the namespace prefix from X-Identity-Id header. + # Even if client sends "user:alice@...", we force "browser:" prefix. + # Only verified auth (Azure headers, JWT) can result in "user:" prefix. + client_identity = request.headers.get('X-Identity-Id') + if client_identity: + # Extract the ID part, ignoring any client-provided prefix + # e.g., "browser:550e8400-..." → "550e8400-..." + # e.g., "user:alice@..." → "alice@..." (but forced to browser: namespace) + if ':' in client_identity: + # Strip the prefix - we don't trust client-provided namespaces + identity_value = client_identity.split(':', 1)[1] + else: + identity_value = client_identity + + # Always use browser: prefix for client-provided identities + return f"browser:{identity_value}" + + raise ValueError("X-Identity-Id header is required. Please refresh the page.") + + +def get_identity_id_optional() -> str | None: + """ + Get identity ID if available, returning None instead of raising an error. + + Useful for endpoints where identity is optional (e.g., when language != "sql"). + + Returns: + str | None: The namespaced identity ID string, or None if not available + """ + try: + return get_identity_id() + except ValueError: + return None diff --git a/py-src/data_formulator/tables_routes.py b/py-src/data_formulator/tables_routes.py index 01da3aff..d5496d50 100644 --- a/py-src/data_formulator/tables_routes.py +++ b/py-src/data_formulator/tables_routes.py @@ -9,7 +9,7 @@ mimetypes.add_type('application/javascript', '.mjs') import json import traceback -from flask import request, send_from_directory, session, jsonify, Blueprint +from flask import request, send_from_directory, jsonify, Blueprint import pandas as pd import random import string @@ -18,6 +18,7 @@ from data_formulator.db_manager import db_manager from data_formulator.data_loader import DATA_LOADERS +from data_formulator.auth import get_identity_id import re @@ -34,7 +35,7 @@ def list_tables(): """List all tables in the current session""" try: result = [] - with db_manager.connection(session['session_id']) as db: + with db_manager.connection(get_identity_id()) as db: table_metadata_list = db.execute(""" SELECT database_name, schema_name, table_name, schema_name==current_schema() as is_current_schema, 'table' as object_type FROM duckdb_tables() @@ -174,7 +175,7 @@ def sample_table(): total_row_count = 0 # Validate field names against table columns to prevent SQL injection - with db_manager.connection(session['session_id']) as db: + with db_manager.connection(get_identity_id()) as db: # Get valid column names columns = [col[0] for col in db.execute(f"DESCRIBE {table_id}").fetchall()] @@ -235,7 +236,7 @@ def sample_table(): def get_table_data(): """Get data from a specific table""" try: - with db_manager.connection(session['session_id']) as db: + with db_manager.connection(get_identity_id()) as db: table_name = request.args.get('table_name') # Get pagination parameters @@ -316,7 +317,7 @@ def create_table(): sanitized_table_name = sanitize_table_name(table_name) - with db_manager.connection(session['session_id']) as db: + with db_manager.connection(get_identity_id()) as db: # Check if table exists and generate unique name if needed base_name = sanitized_table_name counter = 1 @@ -363,7 +364,7 @@ def drop_table(): if not table_name: return jsonify({"status": "error", "message": "No table name provided"}), 400 - with db_manager.connection(session['session_id']) as db: + with db_manager.connection(get_identity_id()) as db: # First check if it exists as a view view_exists = db.execute(f"SELECT view_name FROM duckdb_views() WHERE view_name = '{table_name}'").fetchone() is not None if view_exists: @@ -405,18 +406,14 @@ def upload_db_file(): if not file.filename.endswith('.db'): return jsonify({"status": "error", "message": "Invalid file format. Only .db files are supported"}), 400 - # Get the session ID - if 'session_id' not in session: - return jsonify({"status": "error", "message": "No session ID found"}), 400 - - session_id = session['session_id'] + identity_id = get_identity_id() # Create temp directory if it doesn't exist temp_dir = os.path.join(tempfile.gettempdir()) os.makedirs(temp_dir, exist_ok=True) # Save the file temporarily to verify it - temp_db_path = os.path.join(temp_dir, f"temp_{session_id}.db") + temp_db_path = os.path.join(temp_dir, f"temp_{identity_id}.db") file.save(temp_db_path) # Verify if it's a valid DuckDB file @@ -429,11 +426,11 @@ def upload_db_file(): conn.close() # If we get here, the file is valid - move it to final location - db_file_path = os.path.join(temp_dir, f"df_{session_id}.db") + db_file_path = os.path.join(temp_dir, f"df_{identity_id}.db") os.replace(temp_db_path, db_file_path) # Update the db_manager's file mapping - db_manager._db_files[session_id] = db_file_path + db_manager._db_files[identity_id] = db_file_path except Exception as db_error: # Clean up temp file @@ -448,7 +445,7 @@ def upload_db_file(): return jsonify({ "status": "success", "message": "Database file uploaded successfully", - "session_id": session_id + "identity_id": identity_id }) except Exception as e: @@ -464,23 +461,16 @@ def upload_db_file(): def download_db_file(): """Download the db file for a session""" try: - # Check if session exists - if 'session_id' not in session: - return jsonify({ - "status": "error", - "message": "No session ID found" - }), 400 - - session_id = session['session_id'] + identity_id = get_identity_id() # Get the database file path from db_manager - if session_id not in db_manager._db_files: + if identity_id not in db_manager._db_files: return jsonify({ "status": "error", - "message": "No database file found for this session" + "message": "No database file found for this identity" }), 404 - db_file_path = db_manager._db_files[session_id] + db_file_path = db_manager._db_files[identity_id] # Check if file exists if not os.path.exists(db_file_path): @@ -490,7 +480,7 @@ def download_db_file(): }), 404 # Generate a filename for download - download_name = f"data_formulator_{session_id}.db" + download_name = f"data_formulator_{identity_id}.db" # Return the file as an attachment return send_from_directory( @@ -514,34 +504,28 @@ def download_db_file(): def reset_db_file(): """Reset the db file for a session""" try: - if 'session_id' not in session: - return jsonify({ - "status": "error", - "message": "No session ID found" - }), 400 - - session_id = session['session_id'] + identity_id = get_identity_id() - logger.info(f"session_id: {session_id}") + logger.info(f"identity_id: {identity_id}") # First check if there's a reference in db_manager - if session_id in db_manager._db_files: - db_file_path = db_manager._db_files[session_id] + if identity_id in db_manager._db_files: + db_file_path = db_manager._db_files[identity_id] # Remove the file if it exists if db_file_path and os.path.exists(db_file_path): os.remove(db_file_path) # Clear the reference - db_manager._db_files[session_id] = None + db_manager._db_files[identity_id] = None # Also check for any temporary files - temp_db_path = os.path.join(tempfile.gettempdir(), f"temp_{session_id}.db") + temp_db_path = os.path.join(tempfile.gettempdir(), f"temp_{identity_id}.db") if os.path.exists(temp_db_path): os.remove(temp_db_path) # Check for the main db file - main_db_path = os.path.join(tempfile.gettempdir(), f"df_{session_id}.db") + main_db_path = os.path.join(tempfile.gettempdir(), f"df_{identity_id}.db") if os.path.exists(main_db_path): os.remove(main_db_path) @@ -569,7 +553,7 @@ def analyze_table(): if not table_name: return jsonify({"status": "error", "message": "No table name provided"}), 400 - with db_manager.connection(session['session_id']) as db: + with db_manager.connection(get_identity_id()) as db: # Get column information columns = db.execute(f"DESCRIBE {table_name}").fetchall() @@ -686,7 +670,7 @@ def sanitize_db_error_message(error: Exception) -> tuple[str, int]: # Data loader errors r"Entity ID": (error_msg, 500), - r"session_id": ("session_id not found, please refresh the page", 500), + r"identity": ("Identity not found, please refresh the page", 500), } # Check if error matches any safe pattern @@ -737,7 +721,7 @@ def data_loader_list_tables(): if data_loader_type not in DATA_LOADERS: return jsonify({"status": "error", "message": f"Invalid data loader type. Must be one of: {', '.join(DATA_LOADERS.keys())}"}), 400 - with db_manager.connection(session['session_id']) as duck_db_conn: + with db_manager.connection(get_identity_id()) as duck_db_conn: data_loader = DATA_LOADERS[data_loader_type](data_loader_params, duck_db_conn) # Pass table_filter to list_tables if the data loader supports it @@ -931,7 +915,7 @@ def data_loader_ingest_data(): if data_loader_type not in DATA_LOADERS: return jsonify({"status": "error", "message": f"Invalid data loader type. Must be one of: {', '.join(DATA_LOADERS.keys())}"}), 400 - with db_manager.connection(session['session_id']) as duck_db_conn: + with db_manager.connection(get_identity_id()) as duck_db_conn: data_loader = DATA_LOADERS[data_loader_type](data_loader_params, duck_db_conn) data_loader.ingest_data(table_name, size=row_limit, sort_columns=sort_columns, sort_order=sort_order) @@ -975,7 +959,7 @@ def data_loader_view_query_sample(): if data_loader_type not in DATA_LOADERS: return jsonify({"status": "error", "message": f"Invalid data loader type. Must be one of: {', '.join(DATA_LOADERS.keys())}"}), 400 - with db_manager.connection(session['session_id']) as duck_db_conn: + with db_manager.connection(get_identity_id()) as duck_db_conn: data_loader = DATA_LOADERS[data_loader_type](data_loader_params, duck_db_conn) sample = data_loader.view_query_sample(query) @@ -1008,7 +992,7 @@ def data_loader_ingest_data_from_query(): if data_loader_type not in DATA_LOADERS: return jsonify({"status": "error", "message": f"Invalid data loader type. Must be one of: {', '.join(DATA_LOADERS.keys())}"}), 400 - with db_manager.connection(session['session_id']) as duck_db_conn: + with db_manager.connection(get_identity_id()) as duck_db_conn: data_loader = DATA_LOADERS[data_loader_type](data_loader_params, duck_db_conn) data_loader.ingest_data_from_query(query, name_as) @@ -1052,7 +1036,7 @@ def data_loader_refresh_table(): if not table_name: return jsonify({"status": "error", "message": "table_name is required"}), 400 - with db_manager.connection(session['session_id']) as duck_db_conn: + with db_manager.connection(get_identity_id()) as duck_db_conn: # Get stored metadata metadata = get_table_metadata(duck_db_conn, table_name) @@ -1131,7 +1115,7 @@ def data_loader_get_table_metadata(): if not table_name: return jsonify({"status": "error", "message": "table_name is required"}), 400 - with db_manager.connection(session['session_id']) as duck_db_conn: + with db_manager.connection(get_identity_id()) as duck_db_conn: metadata = get_table_metadata(duck_db_conn, table_name) if metadata: @@ -1159,7 +1143,7 @@ def data_loader_get_table_metadata(): def data_loader_list_table_metadata(): """Get source metadata for all tables""" try: - with db_manager.connection(session['session_id']) as duck_db_conn: + with db_manager.connection(get_identity_id()) as duck_db_conn: metadata_list = get_all_table_metadata(duck_db_conn) return jsonify({ diff --git a/src/app/App.tsx b/src/app/App.tsx index 209f3ac5..e4d09a4c 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -10,8 +10,8 @@ import { dfActions, dfSelectors, fetchAvailableModels, - getSessionId, } from './dfSlice' +import { getBrowserId } from './identity'; import { red, purple, blue, brown, yellow, orange, } from '@mui/material/colors'; @@ -68,7 +68,7 @@ import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import UploadFileIcon from '@mui/icons-material/UploadFile'; import DownloadIcon from '@mui/icons-material/Download'; import { handleDBDownload } from '../views/DBTableManager'; -import { getUrls } from './utils'; +import { getUrls, fetchWithIdentity } from './utils'; import { UnifiedDataUploadDialog } from '../views/UnifiedDataUploadDialog'; import ChatIcon from '@mui/icons-material/Chat'; import { AgentRulesDialog } from '../views/AgentRulesDialog'; @@ -159,7 +159,7 @@ export const ImportStateButton: React.FC<{}> = ({ }) => { } export const ExportStateButton: React.FC<{}> = ({ }) => { - const sessionId = useSelector((state: DataFormulatorState) => state.sessionId); + const identity = useSelector((state: DataFormulatorState) => state.identity); const tables = useSelector((state: DataFormulatorState) => state.tables); const fullStateJson = useSelector((state: DataFormulatorState) => { // Fields to exclude from serialization @@ -168,7 +168,7 @@ export const ExportStateButton: React.FC<{}> = ({ }) => { 'selectedModelId', 'testedModels', 'dataLoaderConnectParams', - 'sessionId', + 'identity', 'agentRules', 'serverConfig', ]); @@ -197,7 +197,7 @@ export const ExportStateButton: React.FC<{}> = ({ }) => { a.click(); } let firstTableName = tables.length > 0 ? tables[0].id: ''; - download(fullStateJson, `df_state_${firstTableName}_${sessionId?.slice(0, 4)}.json`, 'text/plain'); + download(fullStateJson, `df_state_${firstTableName}_${identity.id.slice(0, 4)}.json`, 'text/plain'); }} startIcon={} > @@ -241,7 +241,7 @@ const TableMenu: React.FC = () => { const SessionMenu: React.FC = () => { const [anchorEl, setAnchorEl] = useState(null); const open = Boolean(anchorEl); - const sessionId = useSelector((state: DataFormulatorState) => state.sessionId); + const identity = useSelector((state: DataFormulatorState) => state.identity); const tables = useSelector((state: DataFormulatorState) => state.tables); const theme = useTheme(); @@ -274,12 +274,12 @@ const SessionMenu: React.FC = () => { database file - {sessionId && tables.some(t => t.virtual) && + {tables.some(t => t.virtual) && This session contains data stored in the database, export and reload the database to resume the session later. } - t.virtual)} onClick={() => { - handleDBDownload(sessionId ?? ''); + t.virtual)} onClick={() => { + handleDBDownload(identity.id); }}> {}}> - - - ) : ( + let actionButtons = ( + + + + + + ); +}); + const EditableTableName: FC<{ initialValue: string, tableId: string, @@ -634,55 +699,6 @@ const EditableTableName: FC<{ ); }; -// Compact view for thread0 - displays table cards with charts in a simple grid -// Reuses SingleThreadGroupView with compact mode -let CompactThread0View: FC<{ - scrollRef: any, - leafTables: DictTable[]; - chartElements: { tableId: string, chartId: string, element: any }[]; - sx?: SxProps -}> = function ({ - scrollRef, - leafTables, - chartElements, - sx -}) { - const theme = useTheme(); - - return ( - - - - - workspace - - - - - - - - ); -} - let SingleThreadGroupView: FC<{ scrollRef: any, threadIdx: number, @@ -739,6 +755,29 @@ let SingleThreadGroupView: FC<{ })); } + // Rename popup state + const [renamePopupOpen, setRenamePopupOpen] = useState(false); + const [selectedTableForRename, setSelectedTableForRename] = useState(null); + const [renameAnchorEl, setRenameAnchorEl] = useState(null); + + const handleOpenRenamePopup = (table: DictTable, anchorEl: HTMLElement) => { + setSelectedTableForRename(table); + setRenameAnchorEl(anchorEl); + setRenamePopupOpen(true); + }; + + const handleCloseRenamePopup = () => { + setRenamePopupOpen(false); + setSelectedTableForRename(null); + setRenameAnchorEl(null); + }; + + const handleSaveRename = (newName: string) => { + if (selectedTableForRename) { + handleUpdateTableDisplayId(selectedTableForRename.id, newName); + } + }; + const handleOpenMetadataPopup = (table: DictTable, anchorEl: HTMLElement) => { setSelectedTableForMetadata(table); setMetadataAnchorEl(anchorEl); @@ -917,20 +956,25 @@ let SingleThreadGroupView: FC<{ ; return {triggerCard} - - - + ; } @@ -1037,26 +1081,8 @@ let SingleThreadGroupView: FC<{ }}> - {/* For non-derived tables: icon opens menu; for derived tables: icon toggles anchored */} - {table?.derive == undefined ? ( - - { - event.stopPropagation(); - handleOpenTableMenu(table!, event.currentTarget); - }}> - {tableCardIcon} - - - ) : ( + {/* For derived tables: icon toggles anchored */} + {/* {table?.derive != undefined && ( {tableCardIcon} - )} + )} */} + {tableCardIcon} {/* Only show streaming icon when actively watching for updates */} {(table?.source?.type === 'stream' || table?.source?.type === 'database') && table?.source?.autoRefresh ? ( @@ -1114,21 +1141,32 @@ let SingleThreadGroupView: FC<{ ) : ""} - {focusedTableId == tableId ? : {table?.displayId || tableId}} + }}>{table?.displayId || tableId} + {table?.derive == undefined && ( + + { + event.stopPropagation(); + handleOpenTableMenu(table!, event.currentTarget); + }} + > + + + + )} +

e.stopPropagation()} > + { + e.stopPropagation(); + if (selectedTableForMenu) { + handleOpenRenamePopup(selectedTableForMenu, tableMenuAnchorEl!); + } + handleCloseTableMenu(); + }} + sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }} + > + + Rename + { e.stopPropagation(); @@ -1431,6 +1490,14 @@ let SingleThreadGroupView: FC<{ initialValue={selectedTableForMetadata?.attachedMetadata || ''} tableName={selectedTableForMetadata?.displayId || selectedTableForMetadata?.id || ''} /> + {/* Table actions menu for non-derived, non-virtual tables */} e.stopPropagation()} > + { + e.stopPropagation(); + if (selectedTableForMenu) { + handleOpenRenamePopup(selectedTableForMenu, tableMenuAnchorEl!); + } + handleCloseTableMenu(); + }} + sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }} + > + + Rename + { e.stopPropagation(); @@ -1723,6 +1803,194 @@ const MemoizedChartObject = memo<{ ); }); +// Thread dimension info for height estimation +interface ThreadDimension { + tableCount: number; // number of table cards + triggerCount: number; // number of trigger/instruction cards + chartCount: number; // number of chart cards + messageCount: number; // number of visible agent status messages + isCompact: boolean; // thread0 compact mode +} + +// Height estimation constants (px) – derived from actual rendered sizes: +// table card ≈ 36px, trigger card ≈ 90px (multi-line text + arrow), +// chart card ≈ 140px (canvas maxHeight:100 + card chrome), +// agent message ≈ 60px, thread header/separator ≈ 28px, thread padding ≈ 24px +const LAYOUT_TABLE_CARD_HEIGHT = 36; +const LAYOUT_TRIGGER_CARD_HEIGHT = 65; +const LAYOUT_CHART_HEIGHT = 110; +const LAYOUT_MESSAGE_HEIGHT = 60; +const LAYOUT_THREAD_HEADER_HEIGHT = 28; +const LAYOUT_THREAD_PADDING = 24; +const LAYOUT_COMPACT_TABLE_HEIGHT = 32; +const LAYOUT_THREAD_GAP = 8; // my: 0.5 = 4px top + 4px bottom between threads + +function estimateThreadHeight(dim: ThreadDimension): number { + if (dim.isCompact) { + return LAYOUT_THREAD_PADDING + dim.tableCount * LAYOUT_COMPACT_TABLE_HEIGHT; + } + return LAYOUT_THREAD_HEADER_HEIGHT + LAYOUT_THREAD_PADDING + + dim.tableCount * LAYOUT_TABLE_CARD_HEIGHT + + dim.triggerCount * LAYOUT_TRIGGER_CARD_HEIGHT + + dim.chartCount * LAYOUT_CHART_HEIGHT + + dim.messageCount * LAYOUT_MESSAGE_HEIGHT; +} + +/** + * Compute a balanced column layout for threads. + * + * @param heights – Estimated pixel height for each thread (in display order). + * @param numColumns – Maximum number of columns to distribute into. + * @param flexOrder – When true, threads may be reordered across columns for + * better balance (LPT heuristic). When false, the original + * order is preserved (optimal contiguous partitioning via + * binary-search on the maximum column height). + * @returns An array of columns, where each column is an array of original + * thread indices. Empty columns are omitted. + */ +function computeThreadColumnLayout( + heights: number[], + numColumns: number, + flexOrder: boolean = false, +): number[][] { + if (heights.length === 0) return []; + if (heights.length === 1) return [[0]]; + + const cols = Math.min(numColumns, heights.length); + if (cols <= 1) return [heights.map((_, i) => i)]; + + return flexOrder + ? layoutFlexOrder(heights, cols) + : layoutPreserveOrder(heights, cols); +} + +/** + * Balanced layout *with* reordering (LPT – Longest Processing Time first). + * Assigns the tallest unplaced thread to whichever column is currently shortest. + */ +function layoutFlexOrder(heights: number[], numColumns: number): number[][] { + const indexed = heights.map((h, i) => ({ idx: i, h })); + indexed.sort((a, b) => b.h - a.h); // tallest first + + const columns: number[][] = Array.from({ length: numColumns }, () => []); + const colH: number[] = new Array(numColumns).fill(0); + + for (const item of indexed) { + let minCol = 0; + for (let c = 1; c < numColumns; c++) { + if (colH[c] < colH[minCol]) minCol = c; + } + columns[minCol].push(item.idx); + colH[minCol] += item.h; + } + + return columns.filter(c => c.length > 0); +} + +/** + * Balanced layout *preserving* thread order. + * + * Uses binary-search on the maximum column height to find the tightest + * contiguous partitioning of threads into ≤ numColumns groups. + */ +function layoutPreserveOrder(heights: number[], numColumns: number): number[][] { + const maxH = Math.max(...heights); + const totalH = heights.reduce((s, h) => s + h, 0); + + // Can we fit all threads into `numColumns` columns with no column > target? + const canPartition = (target: number): boolean => { + let cols = 1, cur = 0; + for (const h of heights) { + if (cur + h > target && cur > 0) { + cols++; + cur = h; + if (cols > numColumns) return false; + } else { + cur += h; + } + } + return true; + }; + + // Binary-search for the minimum feasible max-column height + let lo = maxH, hi = totalH; + while (lo < hi) { + const mid = Math.floor((lo + hi) / 2); + if (canPartition(mid)) hi = mid; else lo = mid + 1; + } + + // Build the actual partition with the optimal target + const target = lo; + const columns: number[][] = [[]]; + let cur = 0; + for (let i = 0; i < heights.length; i++) { + if (cur + heights[i] > target && columns[columns.length - 1].length > 0) { + columns.push([]); + cur = 0; + } + columns[columns.length - 1].push(i); + cur += heights[i]; + } + + return columns; +} + +/** + * Choose the best column layout that balances scroll burden vs whitespace. + * + * 1. If a single column fits within SCROLL_TOLERANCE × viewportHeight, + * use one column — the small scroll is preferable to the whitespace + * of an extra column (e.g. one long thread + one tiny thread). + * 2. Otherwise, evaluate layouts for 1 … maxColumns and pick the smallest + * column count whose tallest column fits within viewportHeight. + * 3. If nothing eliminates scrolling, pick the layout that minimises the + * tallest column (least scrolling). + */ +const SCROLL_TOLERANCE = 1.5; // allow up to 50% overflow before adding columns + +function chooseBestColumnLayout( + heights: number[], + maxColumns: number, + viewportHeight: number, + flexOrder: boolean = false, +): number[][] { + if (heights.length === 0) return []; + + const cap = Math.min(maxColumns, heights.length); + const tolerantHeight = viewportHeight * SCROLL_TOLERANCE; + + // Compute effective column height including gaps between threads + const columnEffectiveHeight = (col: number[]) => { + const contentH = col.reduce((sum, idx) => sum + heights[idx], 0); + const gapH = Math.max(0, col.length - 1) * LAYOUT_THREAD_GAP; + return contentH + gapH; + }; + + // Evaluate every candidate column count (1 … cap). + // Pick the smallest n whose tallest column fits within tolerance. + // If none fits, pick the one with the shortest tallest column. + let bestLayout: number[][] = []; + let bestMaxH = Infinity; + + for (let n = 1; n <= cap; n++) { + const layout = computeThreadColumnLayout(heights, n, flexOrder); + const maxH = Math.max(...layout.map(columnEffectiveHeight)); + + // Smallest n that fits within tolerance → least whitespace + if (maxH <= tolerantHeight) { + return layout; + } + + // Otherwise track the layout with the shortest tallest column + if (maxH < bestMaxH) { + bestMaxH = maxH; + bestLayout = layout; + } + } + + return bestLayout; +} + export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { let tables = useSelector((state: DataFormulatorState) => state.tables); @@ -1732,10 +2000,10 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { let chartSynthesisInProgress = useSelector((state: DataFormulatorState) => state.chartSynthesisInProgress); const conceptShelfItems = useSelector((state: DataFormulatorState) => state.conceptShelfItems); - - let [threadDrawerOpen, setThreadDrawerOpen] = useState(false); + const agentActions = useSelector((state: DataFormulatorState) => state.agentActions); const scrollRef = useRef(null) + const containerRef = useRef(null) const executeScroll = (smooth: boolean = true) => { if (scrollRef.current != null) { @@ -1745,16 +2013,9 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { }) } } - // run this function from an event handler or an effect to execute scroll - const dispatch = useDispatch(); - useEffect(() => { - // make it smooth when drawer from open -> close, otherwise just jump - executeScroll(!threadDrawerOpen); - }, [threadDrawerOpen]) - useEffect(() => { // load the example datasets if (focusedTableId) { @@ -1886,146 +2147,172 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { thread0Group['thread0'] = hangingTables; } - let drawerOpen = threadDrawerOpen && (Object.keys(filteredLeafTableGroups).length > 0 || hangingTables.length > 0); - let allGroupsForWidth = { ...filteredLeafTableGroups, ...thread0Group }; - let collaposedViewWidth = Math.max(...Object.values(allGroupsForWidth).map(x => x.length)) > 1 ? 248 : 232 + let hasContent = Object.keys(filteredLeafTableGroups).length > 0 || hangingTables.length > 0; - let view = - {/* Render thread0 (hanging tables) first if it exists - using compact view */} - {Object.entries(thread0Group).map(([groupId, leafTables], i) => { - return (); + + // thread0 first — claim all its table IDs + Object.entries(thread0Group).forEach(([groupId, lts]) => { + lts.forEach(lt => { + claimedTableIds.add(lt.id); + getTriggers(lt, tables).forEach(t => claimedTableIds.add(t.tableId)); + }); + + allThreadEntries.push({ + key: `thread0-${groupId}`, + groupId, + leafTables: lts, + isCompact: true, + threadIdx: -1, + }); + allThreadHeights.push(estimateThreadHeight({ + tableCount: lts.length, + triggerCount: 0, + chartCount: 0, + messageCount: 0, + isCompact: true, + })); + }); + + // then regular threads — only count NEW (unclaimed) tables in each thread + Object.entries(filteredLeafTableGroups).forEach(([groupId, lts], i) => { + // Collect all table IDs in this thread's chains + let threadTableIds = new Set(); + lts.forEach(lt => { + const triggers = getTriggers(lt, tables); + triggers.forEach(t => threadTableIds.add(t.tableId)); + threadTableIds.add(lt.id); + }); + + // Only new (unclaimed) tables contribute to this thread's height + let newTableIds = [...threadTableIds].filter(id => !claimedTableIds.has(id)); + + // Triggers are only for new intermediate tables (not the leaf) + let newTriggerCount = lts.reduce((max, lt) => { + const triggers = getTriggers(lt, tables); + const newTriggers = triggers.filter(t => newTableIds.includes(t.resultTableId)); + return Math.max(max, newTriggers.length); + }, 0); + + // Charts only on new tables + let chartCount = newTableIds.reduce((sum, tid) => { + return sum + chartElements.filter(ce => ce.tableId === tid).length; + }, 0); + + // Agent messages only on new tables + let messageCount = newTableIds.reduce((sum, tid) => { + return sum + agentActions.filter(a => a.tableId === tid && !a.hidden).length; + }, 0); + + // Claim this thread's tables for subsequent threads + threadTableIds.forEach(id => claimedTableIds.add(id)); + + allThreadEntries.push({ + key: `thread-${groupId}-${i}`, + groupId, + leafTables: lts, + isCompact: false, + threadIdx: i, + }); + allThreadHeights.push(estimateThreadHeight({ + tableCount: newTableIds.length, + triggerCount: newTriggerCount, + chartCount, + messageCount, + isCompact: false, + })); + }); + + // Pick the best column layout: balances scroll burden vs whitespace. + // Measure actual panel height from the DOM (accounts for browser zoom, panel resizing, etc.) + const availableHeight = containerRef.current?.clientHeight ?? 600; + const MAX_COLUMNS = 3; + const columnLayout: number[][] = chooseBestColumnLayout( + allThreadHeights, MAX_COLUMNS, availableHeight, /* flexOrder */ false + ); + const actualColumns = columnLayout.length || 1; + + let renderThreadEntry = (entry: ThreadEntry) => { + if (entry.isCompact) { + return 1 ? '216px' : '200px', + width: entry.leafTables.length > 1 ? '216px' : '200px', transition: 'all 0.3s ease', - }} /> - })} - {/* Render regular threads (length > 1) */} - {Object.entries(filteredLeafTableGroups).map(([groupId, leafTables], i) => { - // Calculate used tables from thread0 and previous threads + }} />; + } else { + // Calculate used tables from thread0 and previous regular threads let usedIntermediateTableIds = Object.values(thread0Group).flat() - .map(x => [ ...getTriggers(x, tables).map(y => y.tableId) || []]).flat(); + .map(x => [...getTriggers(x, tables).map(y => y.tableId) || []]).flat(); let usedLeafTableIds = Object.values(thread0Group).flat().map(x => x.id); - - // Add tables from previous regular threads - const previousThreadGroups = Object.values(filteredLeafTableGroups).slice(0, i); + + const previousThreadGroups = Object.values(filteredLeafTableGroups).slice(0, entry.threadIdx); usedIntermediateTableIds = [...usedIntermediateTableIds, ...previousThreadGroups.flat() - .map(x => [ ...getTriggers(x, tables).map(y => y.tableId) || []]).flat()]; + .map(x => [...getTriggers(x, tables).map(y => y.tableId) || []]).flat()]; usedLeafTableIds = [...usedLeafTableIds, ...previousThreadGroups.flat().map(x => x.id)]; - + return 1 ? '216px' : '200px', + width: entry.leafTables.length > 1 ? '216px' : '200px', transition: 'all 0.3s ease', - }} /> - })} - + }} />; + } + }; - // Calculate total thread count (thread0 + regular threads) - let totalThreadCount = Object.keys(filteredLeafTableGroups).length + (Object.keys(thread0Group).length > 0 ? 1 : 0); + // Thread navigation buttons let threadIndices: number[] = []; if (Object.keys(thread0Group).length > 0) { threadIndices.push(-1); // thread0 } threadIndices.push(...Array.from({length: Object.keys(filteredLeafTableGroups).length}, (_, i) => i)); - let jumpButtonsDrawerOpen = - {_.chunk(threadIndices, 3).map((group, groupIdx) => { - const getLabel = (idx: number) => idx === -1 ? '0' : String(idx + 1); - const startNum = getLabel(group[0]); - const endNum = getLabel(group[group.length - 1]); - const label = startNum === endNum ? startNum : `${startNum}-${endNum}`; - - return ( - - { - setTimeout(() => { - // Get currently most visible thread index - const viewportCenter = window.innerWidth / 2; - const currentIndex = Array.from(document.querySelectorAll('[data-thread-index]')).reduce((closest, element) => { - const rect = element.getBoundingClientRect(); - const distance = Math.abs(rect.left + rect.width/2 - viewportCenter); - const idx = parseInt(element.getAttribute('data-thread-index') || '0'); - if (!closest || distance < closest.distance) { - return { index: idx, distance }; - } - return closest; - }, null as { index: number, distance: number } | null)?.index || 0; - - // If moving from larger to smaller numbers (scrolling left), target first element - // If moving from smaller to larger numbers (scrolling right), target last element - const targetIndex = currentIndex > group[0] ? group[0] : group[group.length - 1]; - - const targetElement = document.querySelector(`[data-thread-index="${targetIndex}"]`); - if (targetElement) { - targetElement.scrollIntoView({ - behavior: 'smooth', - block: 'nearest', // Don't change vertical scroll - inline: currentIndex > group[group.length - 1] ? 'start' : 'end' - }); - } - }, 100); - }} - > - {label} - - - ); - })} - - - let jumpButtonDrawerClosed = + let jumpButtons = {threadIndices.map((threadIdx) => { const label = threadIdx === -1 ? '0' : String(threadIdx + 1); return ( - + { const threadElement = document.querySelector(`[data-thread-index="${threadIdx}"]`); - threadElement?.scrollIntoView({ behavior: 'smooth' }); + threadElement?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }} > {label} @@ -2035,9 +2322,41 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { })} - let jumpButtons = drawerOpen ? jumpButtonsDrawerOpen : jumpButtonDrawerClosed; + // Column-based panel width: each column ≈ 224px + gaps + padding + const COLUMN_WIDTH = 224; + const panelWidth = actualColumns * COLUMN_WIDTH + (actualColumns - 1) * 8 + 16; + + let view = hasContent ? ( + + {columnLayout.map((columnIndices: number[], colIdx: number) => ( + + {columnIndices.map((idx: number) => { + const entry = allThreadEntries[idx]; + return entry ? renderThreadEntry(entry) : null; + })} + + ))} + + ) : null; - let carousel = ( + return ( = function ({ sx }) { Data Threads - {jumpButtons} - - - - - - { setThreadDrawerOpen(false); }}> - - - - - - - { - setThreadDrawerOpen(true); - }}> - - - - + {threadIndices.length > 0 && jumpButtons} - {view} ); - - return carousel; } diff --git a/src/views/VisualizationView.tsx b/src/views/VisualizationView.tsx index 4ce00876..b0fada0d 100644 --- a/src/views/VisualizationView.tsx +++ b/src/views/VisualizationView.tsx @@ -649,11 +649,11 @@ export const ChartEditorFC: FC<{}> = function ChartEditorFC({}) { key="chat-dialog-btn" onClick={() => { setChatDialogOpen(!chatDialogOpen) }} sx={{ - backgroundColor: conceptExplanationsOpen ? 'rgba(25, 118, 210, 0.2)' : 'transparent', - color: conceptExplanationsOpen ? 'primary.main' : 'text.secondary', - fontWeight: conceptExplanationsOpen ? 600 : 500, + backgroundColor: chatDialogOpen ? 'rgba(25, 118, 210, 0.2)' : 'transparent', + color: chatDialogOpen ? 'primary.main' : 'text.secondary', + fontWeight: chatDialogOpen ? 600 : 500, '&:hover': { - backgroundColor: conceptExplanationsOpen ? 'rgba(25, 118, 210, 0.25)' : 'rgba(25, 118, 210, 0.08)', + backgroundColor: chatDialogOpen ? 'rgba(25, 118, 210, 0.25)' : 'rgba(25, 118, 210, 0.08)', }, }} > From b14e2429f613896f46c205ab4f70c8611fb83a5d Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Sat, 7 Feb 2026 14:41:28 -0800 Subject: [PATCH 13/50] useless? ui design --- .../workflows/create_vl_plots.py | 9 + src/app/App.tsx | 14 +- src/app/utils.tsx | 9 +- src/components/semanticTypes.ts | 4 +- src/views/DataFormulator.tsx | 39 +- src/views/DataThread.tsx | 1487 +++++++---------- src/views/DataThreadCards.tsx | 385 +++++ src/views/EncodingShelfCard.tsx | 42 +- 8 files changed, 1066 insertions(+), 923 deletions(-) create mode 100644 src/views/DataThreadCards.tsx diff --git a/py-src/data_formulator/workflows/create_vl_plots.py b/py-src/data_formulator/workflows/create_vl_plots.py index 74b38006..902ef643 100644 --- a/py-src/data_formulator/workflows/create_vl_plots.py +++ b/py-src/data_formulator/workflows/create_vl_plots.py @@ -452,6 +452,15 @@ def assemble_vegailte_chart( encoding_obj["field"] = field_name encoding_obj["type"] = field_type + # For ordinal fields on positional channels, use quantitative if the + # underlying data is numeric. Ordinal on a numeric axis makes Vega-Lite + # treat values as discrete categories, which breaks grouped/stacked bar + # charts and continuous axis layouts. Ordinal is only meaningful for + # aesthetic/facet channels (color, size, column, row). + if field_type == 'ordinal' and channel not in ['color', 'size', 'column', 'row']: + if pd.api.types.is_numeric_dtype(df[field_name]): + encoding_obj["type"] = "quantitative" + # Special handling for year/date fields if pd.api.types.is_datetime64_any_dtype(df[field_name]): if channel in ['color', 'size', 'column', 'row']: diff --git a/src/app/App.tsx b/src/app/App.tsx index 5bc8d6ea..f5e8ef65 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -100,6 +100,12 @@ const AppBar = styled(MuiAppBar)(({ theme }) => ({ })); declare module '@mui/material/styles' { + interface PaletteColor { + bgcolor?: string; + } + interface SimplePaletteColorOptions { + bgcolor?: string; + } interface Palette { derived: Palette['primary']; custom: Palette['primary']; @@ -587,16 +593,20 @@ export const AppFC: FC = function AppFC(appProps) { // Microsoft Fluent UI palette (alternative option) palette: { primary: { - main: '#0078d4' // Fluent UI themePrimary (Microsoft Blue) + main: '#0078d4', // Fluent UI themePrimary (Microsoft Blue) + bgcolor: '#f2f8fd', // solid equiv of 5% #0078d4 on white }, secondary: { - main: '#8764b8' // Fluent UI purple + main: '#8764b8', // Fluent UI purple + bgcolor: '#f7f5fb', // solid equiv of 5% #8764b8 on white }, derived: { main: '#ffb900', // Fluent UI yellow/gold + bgcolor: '#fffbf2', // solid equiv of 5% #ffb900 on white }, custom: { main: '#d83b01', // Fluent UI orange (Office orange) + bgcolor: '#fdf5f2', // solid equiv of 5% #d83b01 on white }, warning: { main: '#a4262c', // Fluent UI red (accessible) diff --git a/src/app/utils.tsx b/src/app/utils.tsx index 3c4ed36e..1df2724d 100644 --- a/src/app/utils.tsx +++ b/src/app/utils.tsx @@ -487,11 +487,16 @@ export const assembleVegaChart = ( break; case 'ordinal': // For ordinal types (Year, Month, Rank, Score, etc.) - // Use ordinal for x/y axes, nominal for color/facet + // Use nominal for color/facet channels if (['color', 'size', 'column', 'row'].includes(channel)) { encodingObj["type"] = "nominal"; } else { - encodingObj["type"] = "ordinal"; + // For positional channels (x, y, etc.), check underlying data type: + // If the data is numeric, use quantitative so grouped bar charts etc. work correctly. + // Ordinal on a numeric axis makes Vega-Lite treat values as discrete categories, + // which breaks grouped/stacked bar charts and continuous axis layouts. + const underlyingType = getDType(fieldMetadata.type, fieldValues); + encodingObj["type"] = underlyingType === 'quantitative' ? "quantitative" : "ordinal"; } break; case 'quantitative': diff --git a/src/components/semanticTypes.ts b/src/components/semanticTypes.ts index fffcdb96..521255cf 100644 --- a/src/components/semanticTypes.ts +++ b/src/components/semanticTypes.ts @@ -365,8 +365,8 @@ const VIZ_CATEGORY_MAP: Record = { Distance: 'quantitative', Area: 'quantitative', Volume: 'quantitative', Weight: 'quantitative', Temperature: 'quantitative', Speed: 'quantitative', - // Discrete numerics → ordinal (except ID) - Rank: 'ordinal', Index: 'ordinal', Score: 'ordinal', Rating: 'ordinal', Level: 'ordinal', + // Discrete numerics → ordinal (except ID, Score, Rating which are quantitative) + Rank: 'ordinal', Index: 'ordinal', Score: 'quantitative', Rating: 'quantitative', Level: 'ordinal', ID: 'nominal', // Geographic coordinates → geographic diff --git a/src/views/DataFormulator.tsx b/src/views/DataFormulator.tsx index eac00410..a8d08722 100644 --- a/src/views/DataFormulator.tsx +++ b/src/views/DataFormulator.tsx @@ -35,6 +35,7 @@ import { DndProvider } from 'react-dnd' import { HTML5Backend } from 'react-dnd-html5-backend' import { toolName } from '../app/App'; import { DataThread } from './DataThread'; +import { ChartRecBox } from './ChartRecBox'; import dfLogo from '../assets/df-logo.png'; import exampleImageTable from "../assets/example-image-table.png"; @@ -48,6 +49,7 @@ import { useDataRefresh, useDerivedTableRefresh } from '../app/useDataRefresh'; export const DataFormulatorFC = ({ }) => { const tables = useSelector((state: DataFormulatorState) => state.tables); + const focusedTableId = useSelector((state: DataFormulatorState) => state.focusedTableId); const models = useSelector((state: DataFormulatorState) => state.models); const selectedModelId = useSelector((state: DataFormulatorState) => state.selectedModelId); const viewMode = useSelector((state: DataFormulatorState) => state.viewMode); @@ -209,7 +211,8 @@ export const DataFormulatorFC = ({ }) => { + display: 'flex', height: '100%', width: 'fit-content', flexDirection: 'column', + position: 'relative'}}> {tables.length > 0 ? { overflow: 'hidden', alignContent: 'flex-start', height: '100%', - }}/> : ""} + }}/> : ""} + {/* Floating chat chip for exploration — overlays bottom of DataThread + {tables.length > 0 && ( + + + + )} */} ( (({ tableId, relevantAgentActions, dispatch }) => { - - let theme = useTheme(); - - let agentStatus = undefined; - - let getAgentStatusColor = (status: string) => { - switch (status) { - case 'running': - return `${theme.palette.text.secondary} !important`; - case 'completed': - return `${theme.palette.success.main} !important`; - case 'failed': - return `${theme.palette.error.main} !important`; - case 'warning': - return `${theme.palette.warning.main} !important`; - default: - return `${theme.palette.text.secondary} !important`; - } - } - - let currentActions = relevantAgentActions; - - if (currentActions.some(a => a.status == 'running')) { - agentStatus = 'running'; - } else if (currentActions.every(a => a.status == 'completed')) { - agentStatus = 'completed'; - } else if (currentActions.every(a => a.status == 'failed')) { - agentStatus = 'failed'; - } else { - agentStatus = 'warning'; - } - - if (currentActions.length === 0) { - return null; - } - - return ( - - {( - - {agentStatus === 'running' && ThinkingBanner('thinking...', { py: 0.5 })} - {agentStatus === 'completed' && } - {agentStatus === 'failed' && } - {agentStatus === 'warning' && } - - {agentStatus === 'warning' && 'hmm...'} - {agentStatus === 'failed' && 'oops...'} - {agentStatus === 'completed' && 'completed'} - {agentStatus === 'running' && ''} - - - { - event.stopPropagation(); - dispatch(dfActions.deleteAgentWorkInProgress(relevantAgentActions[0].actionId)); - }} - > - - - - - )} - {currentActions.map((a, index, array) => { - let descriptions = String(a.description).split('\n'); - return ( - - - {descriptions.map((line: string, lineIndex: number) => ( - - - {line} - - {lineIndex < descriptions.length - 1 && } - - ))} - - {index < array.length - 1 && array.length > 1 && ( - - )} - - ) - })} - - ); -}); -let buildChartCard = ( - chartElement: { tableId: string, chartId: string, element: any }, - focusedChartId?: string, - unread?: boolean -) => { - let selectedClassName = focusedChartId == chartElement.chartId ? 'selected-card' : ''; - return - {chartElement.element} - -} // Rename table popup - opens as a small popper with a text field const RenameTablePopup = memo<{ @@ -580,139 +432,28 @@ const RenameTablePopup = memo<{ ); }); -const EditableTableName: FC<{ - initialValue: string, - tableId: string, - handleUpdateTableDisplayId: (tableId: string, displayId: string) => void, - nonEditingSx?: SxProps -}> = ({ initialValue, tableId, handleUpdateTableDisplayId, nonEditingSx }) => { - const [isEditing, setIsEditing] = useState(false); - const [inputValue, setInputValue] = useState(initialValue); - - const handleSubmit = (e?: React.MouseEvent | React.KeyboardEvent) => { - if (e) { - e.preventDefault(); - e.stopPropagation(); - } - - if (inputValue.trim() !== '') { // Only update if input is not empty - handleUpdateTableDisplayId(tableId, inputValue); - setIsEditing(false); - } - }; - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - handleSubmit(e); - } else if (e.key === 'Escape') { - setInputValue(initialValue); - setIsEditing(false); - } - }; - - if (!isEditing) { - return ( - - { - event.stopPropagation(); - setIsEditing(true); - }} - sx={{ - ...nonEditingSx, - fontSize: 'inherit', - minWidth: '60px', - maxWidth: '90px', - wordWrap: 'break-word', - whiteSpace: 'normal', - ml: 0.25, - padding: '2px', - '&:hover': { - backgroundColor: 'rgba(0,0,0,0.04)', - borderRadius: '2px', - cursor: 'pointer' - } - }} - > - {initialValue} - - - ); - } - - return ( - event.stopPropagation()} - sx={{ - display: 'flex', - alignItems: 'center', - position: 'relative', - ml: 0.25, - }} - > - setInputValue(e.target.value)} - onKeyDown={handleKeyDown} - autoFocus - variant="filled" - size="small" - onBlur={(e) => { - // Only reset if click is not on the submit button - if (!e.currentTarget.contains(e.relatedTarget as Node)) { - setInputValue(initialValue); - setIsEditing(false); - } - }} - sx={{ - '& .MuiFilledInput-root': { - fontSize: 'inherit', - padding: 0, - '& input': { - padding: '2px 24px 2px 8px', - width: '64px', - } - } - }} - /> - { - e.preventDefault(); // Prevent blur from firing before click - }} - onClick={(e) => handleSubmit(e)} - sx={{ - position: 'absolute', - right: 2, - padding: '2px', - minWidth: 'unset', - zIndex: 1, - '& .MuiSvgIcon-root': { - fontSize: '0.8rem' - } - }} - > - - - - ); -}; - let SingleThreadGroupView: FC<{ scrollRef: any, threadIdx: number, + threadLabel?: string, // Custom label like "thread 1.1" for split sub-threads + isSplitThread?: boolean, // When true, truncate used tables to immediate parent + "..." leafTables: DictTable[]; chartElements: { tableId: string, chartId: string, element: any }[]; usedIntermediateTableIds: string[], + globalHighlightedTableIds: string[], + focusedThreadLeafId?: string, // The leaf table ID of the thread containing the focused table compact?: boolean, // When true, only show table cards in a simple column (for thread0) sx?: SxProps }> = function ({ scrollRef, threadIdx, + threadLabel, + isSplitThread = false, leafTables, chartElements, usedIntermediateTableIds, // tables that have been used + globalHighlightedTableIds, + focusedThreadLeafId, compact = false, sx }) { @@ -721,6 +462,20 @@ let SingleThreadGroupView: FC<{ const { manualRefresh } = useDataRefresh(); let leafTableIds = leafTables.map(lt => lt.id); + // Thread is highlighted only if this thread's leaf tables include the focused thread's leaf + const threadHighlighted = focusedThreadLeafId + ? leafTableIds.includes(focusedThreadLeafId) + : false; + // Ancestor thread: not the focused thread, but *owns* some highlighted tables + // (tables that only appear as used/shared references don't count) + const isAncestorThread = !threadHighlighted && globalHighlightedTableIds.length > 0 + && leafTables.some(lt => { + const trigs = getTriggers(lt, tables); + const chainIds = [...trigs.map(t => t.tableId), lt.id]; + const ownedIds = chainIds.filter(id => !usedIntermediateTableIds.includes(id)); + return ownedIds.some(id => globalHighlightedTableIds.includes(id)); + }); + const shouldHighlightThread = threadHighlighted || isAncestorThread; let parentTableId = leafTables[0].derive?.trigger.tableId || undefined; let parentTable = tables.find(t => t.id == parentTableId) as DictTable; @@ -949,297 +704,6 @@ let SingleThreadGroupView: FC<{ } }; - let buildTriggerCard = (trigger: Trigger) => { - let selectedClassName = trigger.chart?.id == focusedChartId ? 'selected-card' : ''; - - let triggerCard =
- - - -
; - - return - {triggerCard} - - ; - } - - let buildTableCard = (tableId: string, compact = false) => { - - if (parentTable && tableId == parentTable.id && parentTable.anchored && tableIdList.length > 1) { - let table = tables.find(t => t.id == tableId); - return - { - event.stopPropagation(); - dispatch(dfActions.setFocusedTable(tableId)); - - // Find and set the first chart associated with this table - let firstRelatedChart = charts.find((c: Chart) => c.tableRef == tableId && c.source != "trigger"); - - if (firstRelatedChart) { - dispatch(dfActions.setFocusedChart(firstRelatedChart.id)); - } - }} - > - - - - {table?.displayId || tableId} - - - - - } - - // filter charts relavent to this - let relevantCharts = chartElements.filter(ce => ce.tableId == tableId && !usedIntermediateTableIds.includes(tableId)); - - let table = tables.find(t => t.id == tableId); - - let selectedClassName = tableId == focusedTableId ? 'selected-card' : ''; - - let collapsedProps = collapsed ? { width: '50%', "& canvas": { width: 60, maxHeight: 50 } } : { width: '100%' } - - let releventChartElements = relevantCharts.map((ce, j) => - - {buildChartCard(ce, focusedChartId, charts.find(c => c.id == ce.chartId)?.unread)} - ) - - // only charts without dependency can be deleted - let tableDeleteEnabled = !tables.some(t => t.derive?.trigger.tableId == tableId); - - const iconColor = tableId === focusedTableId ? theme.palette.primary.main : 'rgba(0,0,0,0.6)'; - const iconOpacity = table?.anchored ? 1 : 0.5; - - let tableCardIcon = table?.virtual ? ( - - ) : ( - - ) - - let regularTableBox = c.chartId == focusedChartId) ? scrollRef : null} - sx={{ padding: '0px' }}> - { - dispatch(dfActions.setFocusedTable(tableId)); - if (focusedChart?.tableRef != tableId) { - let firstRelatedChart = charts.find((c: Chart) => c.tableRef == tableId && c.source != 'trigger'); - if (firstRelatedChart) { - dispatch(dfActions.setFocusedChart(firstRelatedChart.id)); - } else { - //dispatch(dfActions.createNewChart({ tableId: tableId, chartType: '?' })); - } - } - }}> - - - {/* For derived tables: icon toggles anchored */} - {/* {table?.derive != undefined && ( - t.derive?.trigger.tableId == tableId)} - onClick={(event) => { - event.stopPropagation(); - dispatch(dfActions.updateTableAnchored({tableId: tableId, anchored: !table?.anchored})); - }}> - {tableCardIcon} - - )} */} - {tableCardIcon} - - {/* Only show streaming icon when actively watching for updates */} - {(table?.source?.type === 'stream' || table?.source?.type === 'database') && table?.source?.autoRefresh ? ( - - { - event.stopPropagation(); - handleOpenStreamingSettingsPopup(table!, event.currentTarget); - }} - sx={{ - padding: 0.25, - '&:hover': { - transform: 'scale(1.2)', - transition: 'all 0.1s linear' - } - }} - > - - - - ) : ""} - {table?.displayId || tableId} - - - - {table?.derive == undefined && ( - - { - event.stopPropagation(); - handleOpenTableMenu(table!, event.currentTarget); - }} - > - - - - )} - - { - event.stopPropagation(); - dispatch(dfActions.setFocusedTable(tableId)); - dispatch(dfActions.setFocusedChart(undefined)); - }} - > - - - - - {/* Delete button - shown for all deletable tables */} - {tableDeleteEnabled && ( - - { - event.stopPropagation(); - dispatch(dfActions.deleteTable(tableId)); - }} - > - - - - )} - - - - - - let chartElementProps = collapsed ? { display: 'flex', flexWrap: 'wrap' } : {} - - let relevantAgentActions = agentActions.filter(a => a.tableId == tableId).filter(a => a.hidden == false); - - let agentActionBox = ( - - ) - - return [ - regularTableBox, - - {!leafTableIds.includes(tableId) && - - } - - {releventChartElements} - {agentActionBox} - - - ] - } - const theme = useTheme(); let focusedChart = useSelector((state: DataFormulatorState) => charts.find(c => c.id == focusedChartId)); @@ -1257,65 +721,278 @@ let SingleThreadGroupView: FC<{ let newTableIds = tableIdList.filter(id => !usedTableIdsInThread.includes(id)); let newTriggers = triggers.filter(tg => newTableIds.includes(tg.resultTableId)); - let highlightedTableIds: string[] = []; - if (focusedTableId && leafTableIds.includes(focusedTableId)) { - highlightedTableIds = [...tableIdList, focusedTableId]; - } else if (focusedTableId && newTableIds.includes(focusedTableId)) { - highlightedTableIds = tableIdList.slice(0, tableIdList.indexOf(focusedTableId) + 1); + // Use the global highlighted table IDs (computed at DataThread level from the focused table's full ancestor chain) + let highlightedTableIds = globalHighlightedTableIds; + + let _buildTriggerCard = (trigger: Trigger) => { + return buildTriggerCard(trigger, focusedChartId); } - let tableElementList = newTableIds.map((tableId, i) => buildTableCard(tableId)); - let triggerCards = newTriggers.map((trigger) => buildTriggerCard(trigger)); + // Shared props for buildTableCard calls + let tableCardProps: Omit = { + tables, charts, chartElements, usedIntermediateTableIds, + highlightedTableIds, agentActions, focusedTableId, focusedChartId, focusedChart, + parentTable, tableIdList, collapsed, scrollRef, dispatch, + handleOpenTableMenu, primaryBgColor: theme.palette.primary.bgcolor, + }; + + let _buildTableCard = (tableId: string) => { + return buildTableCard({ tableId, ...tableCardProps }); + } - let leafTableComp = leafTables.length > 1 ? leafTables.map((lt, i) => { + let tableElementList = newTableIds.map((tableId, i) => _buildTableCard(tableId)); + let triggerCards = newTriggers.map((trigger) => _buildTriggerCard(trigger)); + + // Build a flat sequence of timeline items: [trigger, table, charts, trigger, table, charts, ...] + let timelineItems: { key: string; element: React.ReactNode; type: 'used-table' | 'trigger' | 'table' | 'chart' | 'leaf-trigger' | 'leaf-table'; highlighted: boolean; tableId?: string }[] = []; + + // Add used (shared) tables at the top + // Only show the immediate parent + "..." for further ancestors + let displayedUsedTableIds = usedTableIdsInThread; + if (usedTableIdsInThread.length > 1) { + // Keep only the last (immediate parent), prepend "..." placeholder + displayedUsedTableIds = usedTableIdsInThread.slice(-1); + timelineItems.push({ + key: 'used-table-ellipsis', + type: 'used-table', + highlighted: false, + element: ( + + … + + ), + }); + } + displayedUsedTableIds.forEach((tableId, i) => { + let table = tables.find(t => t.id === tableId) as DictTable; + timelineItems.push({ + key: `used-table-${tableId}-${i}`, + type: 'used-table', + tableId: tableId, + highlighted: highlightedTableIds.includes(tableId), + element: ( + { dispatch(dfActions.setFocusedTable(tableId)) }}> + {table.displayId || tableId} + + ), + }); + }); + // Interleave triggers and tables for the main thread body + newTableIds.forEach((tableId, i) => { + const trigger = newTriggers.find(t => t.resultTableId === tableId); + const isHighlighted = highlightedTableIds.includes(tableId); + + // Add trigger card if exists + if (trigger) { + const triggerCard = triggerCards[newTriggers.indexOf(trigger)]; + if (triggerCard) { + timelineItems.push({ + key: triggerCard?.key || `woven-trigger-${tableId}`, + type: 'trigger', + highlighted: isHighlighted, + element: triggerCard, + }); + } + } + + // Add table card and its charts + const tableCard = tableElementList[i]; + if (Array.isArray(tableCard)) { + tableCard.forEach((subItem: any, j: number) => { + if (!subItem) return; + const subKey = subItem?.key || `woven-${tableId}-${j}`; + const isChart = subKey.includes('chart') || subKey.includes('agent'); + timelineItems.push({ + key: subKey, + type: isChart ? 'chart' : 'table', + tableId: isChart ? undefined : tableId, + highlighted: isHighlighted, + element: subItem, + }); + }); + } + }); + + // Add leaf table components + leafTables.forEach((lt, i) => { let leafTrigger = lt.derive?.trigger; + if (leafTrigger) { + timelineItems.push({ + key: `leaf-trigger-${lt.id}`, + type: 'leaf-trigger', + highlighted: highlightedTableIds.includes(lt.id), + element: _buildTriggerCard(leafTrigger), + }); + } + let leafCards = _buildTableCard(lt.id); + if (Array.isArray(leafCards)) { + leafCards.forEach((subItem: any, j: number) => { + if (!subItem) return; + const subKey = subItem?.key || `leaf-card-${lt.id}-${j}`; + const isChart = subKey.includes('chart') || subKey.includes('agent'); + timelineItems.push({ + key: subKey, + type: isChart ? 'chart' : 'leaf-table', + tableId: isChart ? undefined : lt.id, + highlighted: highlightedTableIds.includes(lt.id), + element: subItem, + }); + }); + } + }); - let leftBorder = i == leafTables.length - 1 ? `none` : `1px dashed rgba(0, 0, 0, 0.3)`; - let stackML = '8px'; - let spaceBox = + // Timeline rendering helper + const TIMELINE_WIDTH = 16; + const DOT_SIZE = 6; + const CARD_PY = '4px'; // vertical padding for each timeline row + + const getTimelineDot = (item: typeof timelineItems[0]) => { + const isTable = item.type === 'table' || item.type === 'leaf-table' || item.type === 'used-table'; + const color = item.highlighted + ? theme.palette.primary.main + : 'rgba(0,0,0,0.25)'; + + // For table items, show a type-specific icon instead of a dot + if (isTable && item.tableId) { + const tableForDot = tables.find(t => t.id === item.tableId); + const iconSx = { fontSize: 14, color }; + const isStreaming = tableForDot && (tableForDot.source?.type === 'stream' || tableForDot.source?.type === 'database') && tableForDot.source?.autoRefresh; + + if (isStreaming) { + return ; + } + if (tableForDot?.virtual) { + return ; + } + return ; + } - if (focusedTableId && leafTableIds.indexOf(focusedTableId) > i) { - leftBorder = `3px solid ${theme.palette.primary.light}`; - stackML = '7px'; + return ; + }; + + const renderTimelineItem = (item: typeof timelineItems[0], index: number, isLast: boolean) => { + const isTrigger = item.type === 'trigger' || item.type === 'leaf-trigger'; + const isTable = item.type === 'table' || item.type === 'leaf-table' || item.type === 'used-table'; + const isChart = item.type === 'chart'; + const dashedColor = item.highlighted ? theme.palette.primary.main : 'rgba(0,0,0,0.1)'; + const dashedWidth = item.highlighted ? '1.5px' : '1px'; + const dashedStyle = item.highlighted ? 'solid' : 'dashed'; + const triggerColor = item.highlighted + ? alpha(theme.palette.custom.main, 0.5) + : 'rgba(0,0,0,0.15)'; + // In ancestor threads, non-highlighted items get white bg to dim them out + const rowHighlightSx = (isAncestorThread && !item.highlighted && item.type !== 'used-table') + ? { backgroundColor: 'rgba(255,255,255,0.75)', borderRadius: 0, mx: -1, px: 1 } + : {}; + + // Triggers: solid colored line on the left (no dash), indicating an action area + if (isTrigger) { + return ( + + + {/* Dashed connector from previous element */} + + {/* Solid bar alongside trigger content */} + + {/* Dashed connector to next element */} + + + + {item.element} + + + ); } - if (focusedTableId && lt.id == focusedTableId) { - spaceBox = + // Charts/agents: dot on the timeline with a horizontal tick line to the chart + if (isChart) { + return ( + + + + + {getTimelineDot(item)} + + + {!isLast && } + {isLast && } + + + {item.element} + + + ); } - return - {spaceBox} - - {leafTrigger && buildTriggerCard(leafTrigger)} - {buildTableCard(lt.id)} - - ; - }) : leafTables.map((lt, i) => { - return - - {lt.derive?.trigger && buildTriggerCard(lt.derive.trigger)} - {buildTableCard(lt.id)} + // Tables (primary nodes): settings icon on the timeline, more vertical spacing + const tableForItem = item.tableId ? tables.find(t => t.id === item.tableId) : undefined; + return ( + + + {index > 0 && ( + + )} + {index === 0 && } + + {getTimelineDot(item)} + + {!isLast && ( + + )} + {isLast && } + + + {item.element} + - ; - }); + ); + }; // Compact mode: just show leaf table cards in a simple column if (compact) { - // For compact mode, ensure highlightedTableIds includes focused table if it's a leaf - if (focusedTableId && leafTableIds.includes(focusedTableId)) { - highlightedTableIds = [focusedTableId]; - } - return ( {leafTables.map((table) => { - const tableCardResult = buildTableCard(table.id, compact); + const tableCardResult = _buildTableCard(table.id); // buildTableCard returns an array [regularTableBox, chartBox] // In compact mode, we want to show them stacked return ( @@ -1359,24 +1036,44 @@ let SingleThreadGroupView: FC<{ Rename
- { - e.stopPropagation(); - if (selectedTableForMenu) { - handleOpenMetadataPopup(selectedTableForMenu, tableMenuAnchorEl!); - } - handleCloseTableMenu(); - }} - sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }} - > - - {selectedTableForMenu?.attachedMetadata ? "Edit metadata" : "Attach metadata"} - + {/* Pin option - only for derived tables */} + {selectedTableForMenu?.derive != undefined && ( + { + e.stopPropagation(); + if (selectedTableForMenu) { + dispatch(dfActions.updateTableAnchored({tableId: selectedTableForMenu.id, anchored: !selectedTableForMenu.anchored})); + } + handleCloseTableMenu(); + }} + sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }} + > + + {selectedTableForMenu?.anchored ? "Unpin table" : "Pin table"} + + )} + {/* Non-derived table options */} + {selectedTableForMenu?.derive == undefined && ( + { + e.stopPropagation(); + if (selectedTableForMenu) { + handleOpenMetadataPopup(selectedTableForMenu, tableMenuAnchorEl!); + } + handleCloseTableMenu(); + }} + sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }} + > + + {selectedTableForMenu?.attachedMetadata ? "Edit metadata" : "Attach metadata"} + + )} {/* Watch for updates option - only shown when table has stream/database source but not actively watching */} {selectedTableForMenu && + selectedTableForMenu.derive == undefined && (selectedTableForMenu.source?.type === 'stream' || selectedTableForMenu.source?.type === 'database') && ( )} - {/* Refresh data - hidden for database tables */} - {selectedTableForMenu?.source?.type !== 'database' && ( + {/* Refresh data - hidden for database tables and derived tables */} + {selectedTableForMenu?.derive == undefined && selectedTableForMenu?.source?.type !== 'database' && ( { e.stopPropagation(); @@ -1408,6 +1105,22 @@ let SingleThreadGroupView: FC<{ Refresh data )} + {/* Delete table */} + {selectedTableForMenu && !tables.some(t => t.derive?.trigger.tableId === selectedTableForMenu.id) && ( + { + e.stopPropagation(); + if (selectedTableForMenu) { + dispatch(dfActions.deleteTable(selectedTableForMenu.id)); + } + handleCloseTableMenu(); + }} + sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1, color: 'warning.main' }} + > + + Delete table + + )}
{selectedTableForRefresh && ( + > - {threadIdx === -1 ? 'thread0' : `thread - ${threadIdx + 1}`} + {threadLabel || (threadIdx === -1 ? 'thread0' : `thread - ${threadIdx + 1}`)}
- {usedTableIdsInThread.map((tableId, i) => { - let table = tables.find(t => t.id === tableId) as DictTable; - return [ - { dispatch(dfActions.setFocusedTable(tableId)) }}> - {table.displayId || tableId} - , - - - ] - })} - - {tableElementList.length > triggerCards.length ? - w(tableElementList, triggerCards, "") : w(triggerCards, tableElementList, "")} - - {leafTableComp} + {timelineItems.map((item, index) => renderTimelineItem(item, index, index === timelineItems.length - 1))}
- {/* Table actions menu for non-derived, non-virtual tables */} + {/* Table actions menu */} Rename - { - e.stopPropagation(); - if (selectedTableForMenu) { - handleOpenMetadataPopup(selectedTableForMenu, tableMenuAnchorEl!); - } - handleCloseTableMenu(); - }} - sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }} - > - - {selectedTableForMenu?.attachedMetadata ? "Edit metadata" : "Attach metadata"} - + {/* Pin option - only for derived tables */} + {selectedTableForMenu?.derive != undefined && ( + { + e.stopPropagation(); + if (selectedTableForMenu) { + dispatch(dfActions.updateTableAnchored({tableId: selectedTableForMenu.id, anchored: !selectedTableForMenu.anchored})); + } + handleCloseTableMenu(); + }} + sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }} + > + + {selectedTableForMenu?.anchored ? "Unpin table" : "Pin table"} + + )} + {/* Non-derived table options */} + {selectedTableForMenu?.derive == undefined && ( + { + e.stopPropagation(); + if (selectedTableForMenu) { + handleOpenMetadataPopup(selectedTableForMenu, tableMenuAnchorEl!); + } + handleCloseTableMenu(); + }} + sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }} + > + + {selectedTableForMenu?.attachedMetadata ? "Edit metadata" : "Attach metadata"} + + )} {/* Watch for updates option - only shown when table has stream/database source but not actively watching */} {selectedTableForMenu && + selectedTableForMenu.derive == undefined && (selectedTableForMenu.source?.type === 'stream' || selectedTableForMenu.source?.type === 'database') && !selectedTableForMenu.source?.autoRefresh && ( )} - {/* Refresh data - hidden for database tables */} - {selectedTableForMenu?.source?.type !== 'database' && ( + {/* Refresh data - hidden for database tables and derived tables */} + {selectedTableForMenu?.derive == undefined && selectedTableForMenu?.source?.type !== 'database' && ( { e.stopPropagation(); @@ -1803,37 +1510,22 @@ const MemoizedChartObject = memo<{ ); }); -// Thread dimension info for height estimation -interface ThreadDimension { - tableCount: number; // number of table cards - triggerCount: number; // number of trigger/instruction cards - chartCount: number; // number of chart cards - messageCount: number; // number of visible agent status messages - isCompact: boolean; // thread0 compact mode -} - -// Height estimation constants (px) – derived from actual rendered sizes: -// table card ≈ 36px, trigger card ≈ 90px (multi-line text + arrow), -// chart card ≈ 140px (canvas maxHeight:100 + card chrome), -// agent message ≈ 60px, thread header/separator ≈ 28px, thread padding ≈ 24px -const LAYOUT_TABLE_CARD_HEIGHT = 36; -const LAYOUT_TRIGGER_CARD_HEIGHT = 65; -const LAYOUT_CHART_HEIGHT = 110; -const LAYOUT_MESSAGE_HEIGHT = 60; -const LAYOUT_THREAD_HEADER_HEIGHT = 28; -const LAYOUT_THREAD_PADDING = 24; -const LAYOUT_COMPACT_TABLE_HEIGHT = 32; -const LAYOUT_THREAD_GAP = 8; // my: 0.5 = 4px top + 4px bottom between threads - -function estimateThreadHeight(dim: ThreadDimension): number { - if (dim.isCompact) { - return LAYOUT_THREAD_PADDING + dim.tableCount * LAYOUT_COMPACT_TABLE_HEIGHT; - } - return LAYOUT_THREAD_HEADER_HEIGHT + LAYOUT_THREAD_PADDING - + dim.tableCount * LAYOUT_TABLE_CARD_HEIGHT - + dim.triggerCount * LAYOUT_TRIGGER_CARD_HEIGHT - + dim.chartCount * LAYOUT_CHART_HEIGHT - + dim.messageCount * LAYOUT_MESSAGE_HEIGHT; +// Height estimation constants (px) – per-type heights + py:4px (8px) gap per row +const LAYOUT_TABLE_HEIGHT = 28 + 8; // table card + row padding +const LAYOUT_TRIGGER_HEIGHT = 43 + 8; // trigger card (2 lines) + row padding +const LAYOUT_CHART_HEIGHT = 90 + 8; // chart card (~70-110) + row padding +const LAYOUT_MESSAGE_HEIGHT = 80 + 8; // agent message (~60-120) + row padding +const LAYOUT_THREAD_OVERHEAD = 52; // header divider + thread padding +const LAYOUT_THREAD_GAP = 8; // my: 0.5 = 4px top + 4px bottom between threads + +function estimateThreadHeight( + tableCount: number, triggerCount: number, chartCount: number, messageCount: number +): number { + return LAYOUT_THREAD_OVERHEAD + + tableCount * LAYOUT_TABLE_HEIGHT + + triggerCount * LAYOUT_TRIGGER_HEIGHT + + chartCount * LAYOUT_CHART_HEIGHT + + messageCount * LAYOUT_MESSAGE_HEIGHT; } /** @@ -1953,10 +1645,12 @@ function chooseBestColumnLayout( maxColumns: number, viewportHeight: number, flexOrder: boolean = false, + minColumns: number = 1, ): number[][] { if (heights.length === 0) return []; const cap = Math.min(maxColumns, heights.length); + const start = Math.min(Math.max(minColumns, 1), cap); const tolerantHeight = viewportHeight * SCROLL_TOLERANCE; // Compute effective column height including gaps between threads @@ -1966,13 +1660,13 @@ function chooseBestColumnLayout( return contentH + gapH; }; - // Evaluate every candidate column count (1 … cap). + // Evaluate every candidate column count (start … cap). // Pick the smallest n whose tallest column fits within tolerance. // If none fits, pick the one with the shortest tallest column. let bestLayout: number[][] = []; let bestMaxH = Infinity; - for (let n = 1; n <= cap; n++) { + for (let n = start; n <= cap; n++) { const layout = computeThreadColumnLayout(heights, n, flexOrder); const maxH = Math.max(...layout.map(columnEffectiveHeight)); @@ -2054,7 +1748,31 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { return false; } let leafTables = [ ...tables.filter(t => isLeafTable(t)) ]; - + + // Split long derivation chains by promoting intermediate tables as additional "leaves". + // If a chain has more than MAX_CHAIN_TABLES tables, we add a split point every + // MAX_CHAIN_TABLES steps. The sort (shorter chains first) ensures the intermediate + // leaf is processed before the real leaf, so its tables get claimed — making the + // real leaf's thread show only the remaining (new) tables. + const MAX_CHAIN_TABLES = 5; + const extraLeaves: DictTable[] = []; + for (const lt of leafTables) { + const triggers = getTriggers(lt, tables); + const chainLen = triggers.length + 1; // total tables in this derivation chain + if (chainLen > MAX_CHAIN_TABLES) { + for (let pos = MAX_CHAIN_TABLES - 1; pos < triggers.length; pos += MAX_CHAIN_TABLES) { + const midId = triggers[pos].tableId; + const midTable = tables.find(t => t.id === midId); + if (midTable && !leafTables.includes(midTable) && !extraLeaves.includes(midTable)) { + extraLeaves.push(midTable); + } + } + } + } + if (extraLeaves.length > 0) { + leafTables.push(...extraLeaves); + } + // we want to sort the leaf tables by the order of their ancestors // for example if ancestor of list a is [0, 3] and the ancestor of list b is [0, 2] then b should come before a // when tables are anchored, we want to give them a higher order (so that they are displayed after their peers) @@ -2079,175 +1797,212 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { return aOrders.length - bOrders.length; }); - // Identify hanging tables (tables with no descendants or parents) - let isHangingTable = (table: DictTable) => { - // A table is hanging if: - // 1. It has no derive.source (no parent) - // 2. No other table derives from it (no descendants) - const hasNoParent = table.derive == undefined; - const hasNoDescendants = !tables.some(t => t.derive?.trigger.tableId == table.id); - return hasNoParent && hasNoDescendants; - }; - - // Separate hanging tables from regular leaf tables - let hangingTables = leafTables.filter(t => isHangingTable(t)); - let regularLeafTables = leafTables.filter(t => !isHangingTable(t)); - - // Build groups for regular leaf tables (excluding hanging tables) - let leafTableGroups = regularLeafTables.reduce((groups: { [groupId: string]: DictTable[] }, leafTable) => { - // Get the immediate parent table ID (first trigger in the chain) - const triggers = getTriggers(leafTable, tables); - const immediateParentTableId = triggers.length > 0 ? triggers[triggers.length - 1].tableId : 'root'; - - let groupId = immediateParentTableId + (leafTable.anchored ? ('-' + leafTable.id) : ''); - - let subgroupIdCount = 0; - while (groups[groupId] && groups[groupId].length >= 4) { - groupId = groupId + '-' + subgroupIdCount; - subgroupIdCount++; - } - - // Initialize group if it doesn't exist - if (!groups[groupId]) { - groups[groupId] = []; + // Compute global highlighted table IDs from the focused table's full ancestor chain + let globalHighlightedTableIds: string[] = useMemo(() => { + if (!focusedTableId) return []; + let focusedTable = tables.find(t => t.id === focusedTableId); + if (!focusedTable) return []; + // Walk up the trigger chain from the focused table to collect all ancestor IDs + let ids: string[] = [focusedTableId]; + let current = focusedTable; + while (current.derive && !current.anchored) { + let parentId = current.derive.trigger.tableId; + ids.unshift(parentId); + let parent = tables.find(t => t.id === parentId); + if (!parent) break; + current = parent; } - - // Add leaf table to its group - groups[groupId].push(leafTable); - - return groups; - }, {}); - - // Filter threads to only include those with length > 1 - let filteredLeafTableGroups: { [groupId: string]: DictTable[] } = {}; - Object.entries(leafTableGroups).forEach(([groupId, groupTables]) => { - // Calculate thread length: count all tables in the thread chain - const threadLength = groupTables.reduce((maxLength, leafTable) => { - const triggers = getTriggers(leafTable, tables); - // Thread length = number of triggers + 1 (the leaf table itself) - return Math.max(maxLength, triggers.length + 1); - }, 0); - - // Only include threads with length > 1 - if (threadLength > 1) { - filteredLeafTableGroups[groupId] = groupTables; - } else { - // Add single-table threads to hanging tables (they go to thread0) - groupTables.forEach(table => { - if (!hangingTables.includes(table)) { - hangingTables.push(table); - } - }); + return ids; + }, [focusedTableId, tables]); + + // Determine which leaf table's thread the focused table belongs to + let focusedThreadLeafId: string | undefined = useMemo(() => { + if (!focusedTableId) return undefined; + // Check if focused table IS a leaf table + let directLeaf = leafTables.find(lt => lt.id === focusedTableId); + if (directLeaf) return directLeaf.id; + // Otherwise, find the leaf table whose ancestor chain includes the focused table + for (const lt of leafTables) { + const triggers = getTriggers(lt, tables); + const chainIds = [...triggers.map(t => t.tableId), lt.id]; + if (chainIds.includes(focusedTableId)) { + return lt.id; + } } - }); + return undefined; + }, [focusedTableId, leafTables, tables]); - // Create thread0 group for hanging tables - let thread0Group: { [groupId: string]: DictTable[] } = {}; - if (hangingTables.length > 0) { - thread0Group['thread0'] = hangingTables; - } + let hasContent = leafTables.length > 0; - let hasContent = Object.keys(filteredLeafTableGroups).length > 0 || hangingTables.length > 0; + // Separate standalone tables (no derivation chain) from threaded tables + let standaloneTables = leafTables.filter(lt => { + const triggers = getTriggers(lt, tables); + return triggers.length + 1 <= 1; + }); + let threadedTables = leafTables.filter(lt => { + const triggers = getTriggers(lt, tables); + return triggers.length + 1 > 1; + }); // Build thread entries and their estimated heights for layout - type ThreadEntry = { key: string; groupId: string; leafTables: DictTable[]; isCompact: boolean; threadIdx: number }; + type ThreadEntry = { key: string; groupId: string; leafTables: DictTable[]; isCompact: boolean; threadIdx: number; threadLabel?: string; isSplitThread?: boolean }; let allThreadEntries: ThreadEntry[] = []; let allThreadHeights: number[] = []; + // Track which leaf tables are promoted (split) vs real leaves + const extraLeafIds = new Set(extraLeaves.map(t => t.id)); + // Track which table IDs have been claimed by earlier threads - // (mirrors usedIntermediateTableIds logic in rendering) let claimedTableIds = new Set(); - // thread0 first — claim all its table IDs - Object.entries(thread0Group).forEach(([groupId, lts]) => { - lts.forEach(lt => { - claimedTableIds.add(lt.id); - getTriggers(lt, tables).forEach(t => claimedTableIds.add(t.tableId)); - }); - + // thread0: group all standalone tables into one compact thread + if (standaloneTables.length > 0) { + standaloneTables.forEach(lt => claimedTableIds.add(lt.id)); allThreadEntries.push({ - key: `thread0-${groupId}`, - groupId, - leafTables: lts, + key: 'thread0', + groupId: 'thread0', + leafTables: standaloneTables, isCompact: true, threadIdx: -1, }); - allThreadHeights.push(estimateThreadHeight({ - tableCount: lts.length, - triggerCount: 0, - chartCount: 0, - messageCount: 0, - isCompact: true, - })); - }); + let standaloneChartCount = standaloneTables.reduce((sum, lt) => { + return sum + chartElements.filter(ce => ce.tableId === lt.id).length; + }, 0); + allThreadHeights.push(estimateThreadHeight(standaloneTables.length, 0, standaloneChartCount, 0)); + } - // then regular threads — only count NEW (unclaimed) tables in each thread - Object.entries(filteredLeafTableGroups).forEach(([groupId, lts], i) => { - // Collect all table IDs in this thread's chains - let threadTableIds = new Set(); - lts.forEach(lt => { + // Regular threads: one per threaded leaf table + // Assign sub-thread numbering: split (promoted) threads get the main index (1, 2, ...), + // real leaf tables whose chain was split get a sub-index (1.1, 1.2, ...) + let realThreadIdx = 0; // counter for main threads + // Pre-scan: find which real leaf each extra leaf belongs to + let extraLeafToRealLeaf = new Map(); + // Also build reverse: real leaf -> list of extra leaves in its chain + let realLeafToExtraLeaves = new Map(); + for (const lt of threadedTables) { + if (!extraLeafIds.has(lt.id)) { + // This is a real leaf — find all extra leaves that are ancestors of it const triggers = getTriggers(lt, tables); - triggers.forEach(t => threadTableIds.add(t.tableId)); - threadTableIds.add(lt.id); - }); + const chainIds = triggers.map(t => t.tableId); + const myExtras: string[] = []; + for (const extraId of extraLeafIds) { + if (chainIds.includes(extraId)) { + if (!extraLeafToRealLeaf.has(extraId)) { + extraLeafToRealLeaf.set(extraId, lt.id); + } + myExtras.push(extraId); + } + } + if (myExtras.length > 0) { + realLeafToExtraLeaves.set(lt.id, myExtras); + } + } + } + // Map from extra leaf id -> its assigned main thread index + let extraLeafToThreadIdx = new Map(); + // Track sub-index counters per chain (keyed by first extra leaf's thread idx) + let subThreadCounters = new Map(); + + threadedTables.forEach((lt, i) => { + const triggers = getTriggers(lt, tables); + + // Collect all table IDs in this thread's chain + let threadTableIds = new Set(); + triggers.forEach(t => threadTableIds.add(t.tableId)); + threadTableIds.add(lt.id); // Only new (unclaimed) tables contribute to this thread's height let newTableIds = [...threadTableIds].filter(id => !claimedTableIds.has(id)); - // Triggers are only for new intermediate tables (not the leaf) - let newTriggerCount = lts.reduce((max, lt) => { - const triggers = getTriggers(lt, tables); - const newTriggers = triggers.filter(t => newTableIds.includes(t.resultTableId)); - return Math.max(max, newTriggers.length); - }, 0); + let newTriggerCount = triggers.filter(t => newTableIds.includes(t.resultTableId)).length; + let chartCount = newTableIds.reduce((sum, tid) => sum + chartElements.filter(ce => ce.tableId === tid).length, 0); + let messageCount = newTableIds.reduce((sum, tid) => sum + agentActions.filter(a => a.tableId === tid && !a.hidden).length, 0); - // Charts only on new tables - let chartCount = newTableIds.reduce((sum, tid) => { - return sum + chartElements.filter(ce => ce.tableId === tid).length; - }, 0); - - // Agent messages only on new tables - let messageCount = newTableIds.reduce((sum, tid) => { - return sum + agentActions.filter(a => a.tableId === tid && !a.hidden).length; - }, 0); + // +1 table and +1 trigger for the leaf table itself + let totalTables = newTableIds.length + 1; + let totalTriggers = newTriggerCount + 1; // Claim this thread's tables for subsequent threads threadTableIds.forEach(id => claimedTableIds.add(id)); + // Determine thread label and whether this is a split sub-thread + const isSplit = extraLeafIds.has(lt.id); + // A real leaf is a "continuation" if it has extra leaves in its chain + const isContinuation = !isSplit && realLeafToExtraLeaves.has(lt.id); + let threadLabel: string; + let threadIdxForEntry: number; + + if (isSplit) { + // Promoted intermediate — gets a main thread index + realThreadIdx++; + extraLeafToThreadIdx.set(lt.id, realThreadIdx); + threadLabel = `thread - ${realThreadIdx}`; + threadIdxForEntry = realThreadIdx - 1; + } else if (isContinuation) { + // Real leaf whose chain was split — gets sub-index under the last extra leaf's index + const myExtras = realLeafToExtraLeaves.get(lt.id) || []; + // Use the last extra leaf's thread index (the one closest to this leaf in the chain) + const lastExtra = myExtras[myExtras.length - 1]; + const parentIdx = extraLeafToThreadIdx.get(lastExtra) ?? realThreadIdx; + const subIdx = (subThreadCounters.get(parentIdx) || 0) + 1; + subThreadCounters.set(parentIdx, subIdx); + threadLabel = `thread - ${parentIdx}.${subIdx}`; + threadIdxForEntry = i; + } else { + // Normal thread (no splitting involved) + realThreadIdx++; + threadLabel = `thread - ${realThreadIdx}`; + threadIdxForEntry = realThreadIdx - 1; + } + allThreadEntries.push({ - key: `thread-${groupId}-${i}`, - groupId, - leafTables: lts, + key: `thread-${lt.id}-${i}`, + groupId: lt.id, + leafTables: [lt], isCompact: false, - threadIdx: i, + threadIdx: threadIdxForEntry, + threadLabel, + isSplitThread: isContinuation, }); - allThreadHeights.push(estimateThreadHeight({ - tableCount: newTableIds.length, - triggerCount: newTriggerCount, - chartCount, - messageCount, - isCompact: false, - })); + allThreadHeights.push(estimateThreadHeight(totalTables, totalTriggers, chartCount, messageCount)); }); // Pick the best column layout: balances scroll burden vs whitespace. // Measure actual panel height from the DOM (accounts for browser zoom, panel resizing, etc.) const availableHeight = containerRef.current?.clientHeight ?? 600; const MAX_COLUMNS = 3; + const hasMultipleThreads = allThreadEntries.length > 1; const columnLayout: number[][] = chooseBestColumnLayout( - allThreadHeights, MAX_COLUMNS, availableHeight, /* flexOrder */ false + allThreadHeights, MAX_COLUMNS, availableHeight, /* flexOrder */ false, + /* minColumns */ hasMultipleThreads ? 2 : 1 ); const actualColumns = columnLayout.length || 1; + const CARD_WIDTH = hasMultipleThreads ? 220 : 240; + const CARD_GAP = 12; // padding + spacing between cards in a column + let renderThreadEntry = (entry: ThreadEntry) => { + // Calculate used tables from all previous threads + const previousEntries = allThreadEntries.slice(0, allThreadEntries.indexOf(entry)); + let usedTableIds = previousEntries.flatMap(e => { + return e.leafTables.flatMap(lt => { + const triggers = getTriggers(lt, tables); + return [...triggers.map(t => t.tableId), lt.id]; + }); + }); + if (entry.isCompact) { return = function ({ sx }) { display: 'flex', flexDirection: 'column', height: 'fit-content', - width: entry.leafTables.length > 1 ? '216px' : '200px', + width: CARD_WIDTH, transition: 'all 0.3s ease', }} />; } else { - // Calculate used tables from thread0 and previous regular threads - let usedIntermediateTableIds = Object.values(thread0Group).flat() - .map(x => [...getTriggers(x, tables).map(y => y.tableId) || []]).flat(); - let usedLeafTableIds = Object.values(thread0Group).flat().map(x => x.id); - - const previousThreadGroups = Object.values(filteredLeafTableGroups).slice(0, entry.threadIdx); - usedIntermediateTableIds = [...usedIntermediateTableIds, ...previousThreadGroups.flat() - .map(x => [...getTriggers(x, tables).map(y => y.tableId) || []]).flat()]; - usedLeafTableIds = [...usedLeafTableIds, ...previousThreadGroups.flat().map(x => x.id)]; - return = function ({ sx }) { display: 'flex', flexDirection: 'column', height: 'fit-content', - width: entry.leafTables.length > 1 ? '216px' : '200px', + width: CARD_WIDTH, transition: 'all 0.3s ease', }} />; } }; - // Thread navigation buttons - let threadIndices: number[] = []; - if (Object.keys(thread0Group).length > 0) { - threadIndices.push(-1); // thread0 - } - threadIndices.push(...Array.from({length: Object.keys(filteredLeafTableGroups).length}, (_, i) => i)); - - let jumpButtons = - {threadIndices.map((threadIdx) => { - const label = threadIdx === -1 ? '0' : String(threadIdx + 1); - return ( - - { - const threadElement = document.querySelector(`[data-thread-index="${threadIdx}"]`); - threadElement?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - }} - > - {label} - - - ); - })} - - - // Column-based panel width: each column ≈ 224px + gaps + padding - const COLUMN_WIDTH = 224; - const panelWidth = actualColumns * COLUMN_WIDTH + (actualColumns - 1) * 8 + 16; + // Column-based panel width: each column = CARD_WIDTH + CARD_GAP + const COLUMN_WIDTH = CARD_WIDTH + CARD_GAP; + const MIN_PANEL_WIDTH = 0; // ensure enough room for floating chat chip + const panelWidth = Math.max(actualColumns * COLUMN_WIDTH + 16, MIN_PANEL_WIDTH); let view = hasContent ? ( = function ({ sx }) { flexDirection: 'row', direction: 'ltr', height: 'calc(100% - 16px)', - gap: 1, + gap: 0.25, p: 1, width: panelWidth, }}> @@ -2343,6 +2065,7 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { = function ({ sx }) { return ( - - - - Data Threads - - {threadIndices.length > 0 && jumpButtons} - - - (({ tableId, relevantAgentActions, dispatch }) => { + + let theme = useTheme(); + + let agentStatus = undefined; + + let getAgentStatusColor = (status: string) => { + switch (status) { + case 'running': + return `${theme.palette.text.secondary} !important`; + case 'completed': + return `${theme.palette.success.main} !important`; + case 'failed': + return `${theme.palette.error.main} !important`; + case 'warning': + return `${theme.palette.warning.main} !important`; + default: + return `${theme.palette.text.secondary} !important`; + } + } + + let currentActions = relevantAgentActions; + + if (currentActions.some(a => a.status == 'running')) { + agentStatus = 'running'; + } else if (currentActions.every(a => a.status == 'completed')) { + agentStatus = 'completed'; + } else if (currentActions.every(a => a.status == 'failed')) { + agentStatus = 'failed'; + } else { + agentStatus = 'warning'; + } + + if (currentActions.length === 0) { + return null; + } + + return ( + + {( + + {agentStatus === 'running' && ThinkingBanner('thinking...', { py: 0.5 })} + {agentStatus === 'completed' && } + {agentStatus === 'failed' && } + {agentStatus === 'warning' && } + + {agentStatus === 'warning' && 'hmm...'} + {agentStatus === 'failed' && 'oops...'} + {agentStatus === 'completed' && 'completed'} + {agentStatus === 'running' && ''} + + + { + event.stopPropagation(); + dispatch(dfActions.deleteAgentWorkInProgress(relevantAgentActions[0].actionId)); + }} + > + + + + + )} + {currentActions.map((a, index, array) => { + let descriptions = String(a.description).split('\n'); + return ( + + + {descriptions.map((line: string, lineIndex: number) => ( + + + {line} + + {lineIndex < descriptions.length - 1 && } + + ))} + + {index < array.length - 1 && array.length > 1 && ( + + )} + + ) + })} + + ); +}); + +// ─── Chart Card ────────────────────────────────────────────────────────────── + +export let buildChartCard = ( + chartElement: { tableId: string, chartId: string, element: any }, + focusedChartId?: string, + unread?: boolean +) => { + let selectedClassName = focusedChartId == chartElement.chartId ? 'selected-card' : ''; + return + {chartElement.element} + +} + +// ─── Trigger Card Wrapper ──────────────────────────────────────────────────── + +export let buildTriggerCard = ( + trigger: Trigger, + focusedChartId: string | undefined, +) => { + let selectedClassName = trigger.chart?.id == focusedChartId ? 'selected-card' : ''; + + let triggerCard =
+ + + +
; + + return + {triggerCard} + ; +} + +// ─── Table Card ────────────────────────────────────────────────────────────── + +export interface BuildTableCardProps { + tableId: string; + tables: DictTable[]; + charts: Chart[]; + chartElements: { tableId: string, chartId: string, element: any }[]; + usedIntermediateTableIds: string[]; + highlightedTableIds: string[]; + agentActions: any[]; + focusedTableId: string | undefined; + focusedChartId: string | undefined; + focusedChart: Chart | undefined; + parentTable: DictTable | undefined; + tableIdList: string[]; + collapsed: boolean; + scrollRef: any; + dispatch: any; + handleOpenTableMenu: (table: DictTable, anchorEl: HTMLElement) => void; + primaryBgColor: string | undefined; +} + +export let buildTableCard = (props: BuildTableCardProps) => { + const { + tableId, tables, charts, chartElements, usedIntermediateTableIds, + highlightedTableIds, agentActions, focusedTableId, focusedChartId, focusedChart, + parentTable, tableIdList, collapsed, scrollRef, dispatch, + handleOpenTableMenu, primaryBgColor, + } = props; + + if (parentTable && tableId == parentTable.id && parentTable.anchored && tableIdList.length > 1) { + let table = tables.find(t => t.id == tableId); + return + { + event.stopPropagation(); + dispatch(dfActions.setFocusedTable(tableId)); + + // Find and set the first chart associated with this table + let firstRelatedChart = charts.find((c: Chart) => c.tableRef == tableId && c.source != "trigger"); + + if (firstRelatedChart) { + dispatch(dfActions.setFocusedChart(firstRelatedChart.id)); + } + }} + > + + + + {table?.displayId || tableId} + + + + + } + + // filter charts relevant to this + let relevantCharts = chartElements.filter(ce => ce.tableId == tableId && !usedIntermediateTableIds.includes(tableId)); + + let table = tables.find(t => t.id == tableId); + + let selectedClassName = tableId == focusedTableId ? 'selected-card' : ''; + + let collapsedProps = collapsed ? { width: '50%', "& canvas": { width: 60, maxHeight: 50 } } : { width: '100%' } + + let releventChartElements = relevantCharts.map((ce, j) => + + {buildChartCard(ce, focusedChartId, charts.find(c => c.id == ce.chartId)?.unread)} + ) + + const isHighlighted = highlightedTableIds.includes(tableId); + + let regularTableBox = c.chartId == focusedChartId) ? scrollRef : null} + sx={{ padding: '0px' }}> + { + dispatch(dfActions.setFocusedTable(tableId)); + if (focusedChart?.tableRef != tableId) { + let firstRelatedChart = charts.find((c: Chart) => c.tableRef == tableId && c.source != 'trigger'); + if (firstRelatedChart) { + dispatch(dfActions.setFocusedChart(firstRelatedChart.id)); + } + } + }}> + + + + {table?.displayId || tableId} + + + + + { + event.stopPropagation(); + handleOpenTableMenu(table!, event.currentTarget); + }} + > + + + + + { + event.stopPropagation(); + dispatch(dfActions.setFocusedTable(tableId)); + dispatch(dfActions.setFocusedChart(undefined)); + }} + > + + + + + + + + + let relevantAgentActions = agentActions.filter(a => a.tableId == tableId).filter(a => a.hidden == false); + + let agentActionBox = ( + + ) + + return [ + regularTableBox, + ...releventChartElements, + ...(relevantAgentActions.length > 0 ? [ + + {agentActionBox} + + ] : []) + ] +} diff --git a/src/views/EncodingShelfCard.tsx b/src/views/EncodingShelfCard.tsx index e60b1f74..9bc7aba1 100644 --- a/src/views/EncodingShelfCard.tsx +++ b/src/views/EncodingShelfCard.tsx @@ -191,7 +191,7 @@ export const TriggerCard: FC<{ // Process the prompt to highlight content in ** ** const processedPrompt = renderTextWithEmphasis(prompt, { - fontSize: mini ? 10 : 12, padding: '1px 4px', + fontSize: mini ? 10 : 11, padding: '1px 4px', borderRadius: '4px', background: alpha(theme.palette.custom.main, 0.08), }); @@ -216,37 +216,25 @@ export const TriggerCard: FC<{ } - return - - {hideFields ? "" : {encodingComp}} - - {prompt.length > 0 && } - {processedPrompt} - - - + + {processedPrompt} + {hideFields ? "" : <>{" "}{encodingComp}} + } From 08a58402c408b9ff58c0524bf25c4c1231bcb8fd Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Sat, 7 Feb 2026 16:36:41 -0800 Subject: [PATCH 14/50] refactor design into design tokens --- src/app/tokens.ts | 95 +++++++++++ src/views/About.tsx | 5 +- src/views/AgentRulesDialog.tsx | 5 +- src/views/ChartRecBox.tsx | 5 +- src/views/ChatDialog.tsx | 3 +- src/views/DBTableManager.tsx | 5 +- src/views/DataFormulator.tsx | 7 +- src/views/DataLoadingChat.tsx | 14 +- src/views/DataLoadingThread.tsx | 30 ++-- src/views/DataThread.tsx | 163 ++++++++++++------- src/views/DataThreadCards.tsx | 14 +- src/views/EncodingShelfCard.tsx | 32 ++-- src/views/EncodingShelfThread.tsx | 223 +++++++++++++------------- src/views/ExplComponents.tsx | 26 ++- src/views/MessageSnackbar.tsx | 5 +- src/views/RefreshDataDialog.tsx | 11 +- src/views/ReportView.tsx | 28 ++-- src/views/SelectableDataGrid.tsx | 3 +- src/views/TableSelectionView.tsx | 4 +- src/views/UnifiedDataUploadDialog.tsx | 14 +- src/views/VisualizationView.tsx | 6 +- 21 files changed, 424 insertions(+), 274 deletions(-) create mode 100644 src/app/tokens.ts diff --git a/src/app/tokens.ts b/src/app/tokens.ts new file mode 100644 index 00000000..265655e5 --- /dev/null +++ b/src/app/tokens.ts @@ -0,0 +1,95 @@ +// ════════════════════════════════════════════════════════════════════════ +// Design tokens — single source of truth for visual constants. +// +// Usage: +// import { borderColor, shadow, transition, radius, ComponentBorderStyle } from '../app/tokens'; +// sx={{ ...ComponentBorderStyle, borderRadius: radius.md, transition: transition.fast }} +// sx={{ borderBottom: `1px solid ${borderColor.divider}`, boxShadow: shadow.sm }} +// ════════════════════════════════════════════════════════════════════════ + +import type { SxProps } from '@mui/material'; + +// ── Border colors ────────────────────────────────────────────────────── + +export const borderColor = { + /** 0.12 — section dividers, table borders, tab underlines, sidebar edges + * DataLoadingChat, ExplComponents, RefreshDataDialog, ReportView tables, + * TableSelectionView, DataLoadingThread, DBTableManager */ + divider: 'rgba(0, 0, 0, 0.12)', + + /** 0.15 — inner components: cards, chips, inputs, thumbnails + * DataThreadCards table card, EncodingShelfCard tab divider */ + component: 'rgba(0, 0, 0, 0.15)', + + /** 0.20 — outer containers: panels, dialogs, popovers, drop zones + * DataThread popups, UnifiedDataUploadDialog, AgentRulesDialog */ + view: 'rgba(0, 0, 0, 0.2)', +} as const; + +// ── Composite border styles (spread into sx) ─────────────────────────── + +/** Section divider border — tabs, sidebars, table wrappers */ +export const DividerBorderStyle: SxProps = { border: `1px solid ${borderColor.divider}` }; + +/** Inner component border — cards, chips, input fields */ +export const ComponentBorderStyle: SxProps = { border: `1px solid ${borderColor.component}` }; + +/** Outer container border — panels, dialogs, popovers */ +export const ViewBorderStyle: SxProps = { border: `1px solid ${borderColor.view}` }; + +// ── Box shadows ──────────────────────────────────────────────────────── + +export const shadow = { + /** Resting cards, chips + * ExplComponents, DataLoadingThread, DataThreadCards */ + sm: '0 1px 2px rgba(0,0,0,0.05)', + + /** Hovered cards, table headers + * DataThreadCards, SelectableDataGrid, DataLoadingThread hover */ + md: '0 2px 4px rgba(0,0,0,0.08)', + + /** Expanded items, hovered panels + * ExplComponents hover, ReportView compose toolbar */ + lg: '0 2px 8px rgba(0,0,0,0.12)', + + /** Floating overlays, dialogs, snackbars + * ReportView hover, DataFormulator overlay, MessageSnackbar */ + xl: '0 4px 12px rgba(0,0,0,0.10)', +} as const; + +// ── Transitions ──────────────────────────────────────────────────────── + +export const transition = { + /** Hover highlights, tab toggles, icon reactions + * DataThread, EncodingShelfCard, DataThreadCards, ChartRecBox */ + fast: 'all 0.1s linear', + + /** Panel animations, hover effects, expand/collapse + * ReportView, RefreshDataDialog, UnifiedDataUploadDialog, SelectableDataGrid */ + normal: 'all 0.2s ease', + + /** Drawer slides, focus rings, snackbar entrances + * MessageSnackbar, DataLoadingChat, AgentRulesDialog */ + slow: 'all 0.3s ease', +} as const; + +// ── Border radius ────────────────────────────────────────────────────── +// Values are MUI spacing units (1 unit = 4px via theme.spacing) + +export const radius = { + /** Cards, chips, inputs, code blocks + * UnifiedDataUploadDialog, AgentRulesDialog, ChatDialog, ExplComponents */ + sm: 1, + + /** Floating panels, dialogs, chat cards, table containers + * DataThread popups, ChatDialog, About, DataLoadingChat, TableSelectionView */ + md: 2, + + /** Status indicators, model icons + * ModelSelectionDialog status badges */ + lg: 3, + + /** Fully rounded pill shape — FABs, floating overlays + * DataFormulator chart overlay */ + pill: '16px', +} as const; diff --git a/src/views/About.tsx b/src/views/About.tsx index cfb5f9c6..bd328b10 100644 --- a/src/views/About.tsx +++ b/src/views/About.tsx @@ -2,6 +2,7 @@ // Licensed under the MIT License. import { Box, Typography, Button, useTheme, alpha, Divider } from "@mui/material"; +import { borderColor, radius } from '../app/tokens'; import React, { FC } from "react"; import GridViewIcon from '@mui/icons-material/GridView'; import GitHubIcon from '@mui/icons-material/GitHub'; @@ -167,9 +168,9 @@ export const About: FC<{}> = function About({ }) { {/* Media Content */} {feature.mediaType === 'video' ? ( { { = function ChatDialog({code, dialog backgroundColor: isUser ? alpha(theme.palette.primary.main, 0.05) : alpha(theme.palette.custom.main, 0.05), border: isUser ? "1px solid" : "1px solid", borderColor: isUser ? alpha(theme.palette.primary.main, 0.2) : alpha(theme.palette.custom.main, 0.2), - borderRadius: 2, + borderRadius: radius.md, }}> {/* Available Tables Section - always visible */} @@ -1182,7 +1183,7 @@ export const DataLoaderForm: React.FC<{ } let tableMetadataBox = [ - + diff --git a/src/views/DataFormulator.tsx b/src/views/DataFormulator.tsx index a8d08722..714077ef 100644 --- a/src/views/DataFormulator.tsx +++ b/src/views/DataFormulator.tsx @@ -26,6 +26,7 @@ import { useTheme, alpha, } from '@mui/material'; +import { borderColor, shadow, radius } from '../app/tokens'; import { FreeDataViewFC } from './DataView'; import { VisualizationViewFC } from './VisualizationView'; @@ -201,8 +202,8 @@ export const DataFormulatorFC = ({ }) => { ); let borderBoxStyle = { - border: '1px solid rgba(0,0,0,0.1)', - borderRadius: '16px', + border: `1px solid ${borderColor.view}`, + borderRadius: radius.pill, //boxShadow: '0 0 5px rgba(0,0,0,0.1)', } @@ -243,7 +244,7 @@ export const DataFormulatorFC = ({ }) => { backgroundColor: 'rgba(255, 255, 255, 0.95)', backdropFilter: 'blur(8px)', borderRadius: '14px', - boxShadow: '0 2px 12px rgba(0,0,0,0.08)', + boxShadow: shadow.xl, px: 1.5, py: 0.25, '& .MuiCard-root': { border: 'none', diff --git a/src/views/DataLoadingChat.tsx b/src/views/DataLoadingChat.tsx index 66021496..527e6493 100644 --- a/src/views/DataLoadingChat.tsx +++ b/src/views/DataLoadingChat.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import { useEffect, useRef, useState } from 'react'; import { Box, Button, Divider, IconButton, Typography, Tooltip, CircularProgress, alpha, useTheme } from '@mui/material'; +import { borderColor, transition, radius } from '../app/tokens'; import { useDispatch, useSelector } from 'react-redux'; @@ -50,13 +51,13 @@ export const DataLoadingChat: React.FC = () => { let threadsComponent = dataCleanBlocksThread.map((thread, i) => { return }) @@ -189,8 +190,7 @@ export const DataLoadingChat: React.FC = () => { maxWidth: 240, overflow: 'hidden', height: '100%', - borderRight: '1px solid', - borderColor: 'divider' + borderRight: `1px solid ${borderColor.view}` }}> { )} { flexDirection: 'row', alignItems: 'center', gap: 1, - borderTop: '1px solid', - borderColor: 'divider', + borderTop: `1px solid ${borderColor.divider}`, '& .MuiButton-root': { textTransform: 'none' } }}> - - } + + + ); + }; + + let buildTimelineTriggerRow = (trigger: Trigger) => { + const triggerColor = alpha(theme.palette.custom.main, 0.4); + return ( + + + + + + + + + + + ); + }; + + let buildTimelineEllipsisRow = () => ( + + + + + + + + + ); let tableList = activeTableThread.map((tableId) => { let table = tables.find(t => t.id == tableId) as DictTable; if (!table) { return null; } - return buildTableCard(tableId); - }).filter(x => x !== null); + return tableId; + }).filter(x => x !== null) as string[]; let leafTable = tables.find(t => t.id == activeTableThread[activeTableThread.length - 1]) as DictTable; - let triggers = getTriggers(leafTable, tables) + let triggers = getTriggers(leafTable, tables) - let instructionCards = triggers.map((trigger, i) => { - let extractActiveFields = (t: Trigger) => { - let encodingMap = allCharts.find(c => c.id == t.chart?.id)?.encodingMap; - if (!encodingMap) { - return []; - } - return Array.from(Object.values(encodingMap)).map((enc: EncodingItem) => enc.fieldID).filter(x => x != undefined) - }; - let previousActiveFields = new Set(i == 0 ? [] : extractActiveFields(triggers[i - 1])) - let currentActiveFields = new Set(extractActiveFields(trigger)) - let fieldsIdentical = _.isEqual(previousActiveFields, currentActiveFields) - return - - - - + // Simplified timeline: source table → (…) → last trigger → current table + let timelineRows: React.ReactNode[] = []; + if (tableList.length > 0) { + // Source table + timelineRows.push(buildTimelineTableRow(tableList[0], true, false)); + // Ellipsis if intermediate steps were skipped + if (tableList.length > 2) { + timelineRows.push(buildTimelineEllipsisRow()); + } + // Most recent trigger (if any) + if (triggers.length > 0) { + timelineRows.push(buildTimelineTriggerRow(triggers[triggers.length - 1])); + } + // Current table (if different from source) + if (tableList.length > 1) { + timelineRows.push(buildTimelineTableRow(tableList[tableList.length - 1], false, true)); + } + } + + previousInstructions = ( + + {timelineRows} - }) - - let spaceElement = "" //; - - let truncated = tableList.length > 3; - - previousInstructions = truncated ? - - {tableList[0]} - - - ... - - - {tableList[tableList.length - 3]} - {instructionCards[instructionCards.length - 2]} - {tableList[tableList.length - 2]} - {instructionCards[instructionCards.length - 1]} - {tableList[tableList.length - 1]} - - : - - {interleaveArrays(tableList, instructionCards, spaceElement)} - ; + ); let postInstruction : any = ""; if (chartTrigger) { @@ -283,43 +284,39 @@ export const EncodingShelfThread: FC = function ({ cha }) postInstruction = - - - - {buildTableCard(resultTable.id)} - - - - - {endChartCards} - - + {buildTimelineTableRow(resultTable.id, false, endChartCards.length === 0)} + {endChartCards.length > 0 && ( + + + + + + {endChartCards} + + + )} + } + // Connector between previousInstructions and EncodingShelfCard + const timelineConnector = ( + + + + + + ); + const encodingShelf = ( - {[ - - {previousInstructions} - , - ]} - - - + {previousInstructions} + {timelineConnector} {postInstruction} diff --git a/src/views/ExplComponents.tsx b/src/views/ExplComponents.tsx index 22cb5784..e6a91b55 100644 --- a/src/views/ExplComponents.tsx +++ b/src/views/ExplComponents.tsx @@ -13,6 +13,7 @@ import { } from '@mui/material'; import { styled } from '@mui/material/styles'; import { alpha } from '@mui/material/styles'; +import { borderColor, shadow, transition, radius } from '../app/tokens'; import 'katex/dist/katex.min.css'; import { InlineMath, BlockMath } from 'react-katex'; @@ -129,12 +130,12 @@ const ConceptExplanationCard = styled(Card, { margin: '4px', borderRadius: '6px', - border: `1px solid ${alpha(theme.palette.divider, 0.2)}`, - boxShadow: '0 1px 2px rgba(0,0,0,0.05)', - transition: 'all 0.2s ease-in-out', + border: `1px solid ${borderColor.divider}`, + boxShadow: shadow.sm, + transition: transition.normal, backgroundColor: alpha(theme.palette.background.paper, 0.9), '&:hover': { - boxShadow: '0 2px 6px rgba(0,0,0,0.1)', + boxShadow: shadow.lg, borderColor: !secondary ? theme.palette.primary.light : theme.palette.secondary.light, transform: 'translateY(-1px)', }, @@ -224,8 +225,7 @@ export const ConceptExplCards: FC = ({ justifyContent: 'center', marginTop: 1, paddingTop: 1, - borderTop: '1px solid', - borderColor: 'divider', + borderTop: `1px solid ${borderColor.divider}`, }}> diff --git a/src/views/MessageSnackbar.tsx b/src/views/MessageSnackbar.tsx index ee17fb23..fe993aab 100644 --- a/src/views/MessageSnackbar.tsx +++ b/src/views/MessageSnackbar.tsx @@ -9,6 +9,7 @@ import CloseIcon from '@mui/icons-material/Close'; import { DataFormulatorState, dfActions } from '../app/dfSlice'; import { useDispatch, useSelector } from 'react-redux'; import { Alert, alpha, Box, Chip, Collapse, Divider, Paper, Tooltip, Typography } from '@mui/material'; +import { shadow, transition } from '../app/tokens'; import InfoIcon from '@mui/icons-material/Info'; import AssignmentIcon from '@mui/icons-material/Assignment'; import DeleteIcon from '@mui/icons-material/Delete'; @@ -136,8 +137,8 @@ export function MessageSnackbar() { }, border: '1px solid', - boxShadow: '0 0 10px rgba(0,0,0,0.1)', - transition: 'all 0.3s ease' + boxShadow: shadow.xl, + transition: transition.slow }}}> = ({ {isLoading && } - + @@ -429,7 +430,7 @@ export const RefreshDataDialog: React.FC = ({ p: 1, backgroundColor: alpha(theme.palette.text.secondary, 0.04), borderRadius: 1, - border: `1px solid ${alpha(theme.palette.divider, 0.5)}` + border: `1px solid ${borderColor.divider}` }}> Large content ({Math.round(pasteContent.length / 1000)}KB) • {showFullContent ? 'Full view' : 'Preview'} @@ -505,12 +506,12 @@ export const RefreshDataDialog: React.FC = ({ { backgroundColor: 'rgba(255, 255, 255, 0.9)', backdropFilter: 'blur(12px)', border: '1px solid', - borderColor: 'rgba(0, 0, 0, 0.08)', - boxShadow: '0 2px 8px rgba(0, 0, 0, 0.06)', + borderColor: borderColor.view, + boxShadow: shadow.lg, '&:hover': { backgroundColor: 'rgba(255, 255, 255, 0.95)', - borderColor: 'rgba(0, 0, 0, 0.12)', - boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)', - transition: 'all 0.2s ease-in-out' + borderColor: borderColor.view, + boxShadow: shadow.xl, + transition: transition.normal }, '.MuiTypography-root': { fontSize: '1rem', @@ -1000,10 +1001,10 @@ export const ReportView: FC = () => { cursor: 'pointer', position: 'relative', overflow: 'hidden', backgroundColor: selectedChartIds.has(chart.id) ? alpha(theme.palette.primary.main, 0.08) : 'background.paper', border: selectedChartIds.has(chart.id) ? '2px solid' : '1px solid', - borderColor: selectedChartIds.has(chart.id) ? 'primary.main' : 'divider', + borderColor: selectedChartIds.has(chart.id) ? 'primary.main' : borderColor.divider, '&:hover': { backgroundColor: 'action.hover', boxShadow: 3, - transform: 'translateY(-2px)', transition: 'all 0.2s ease-in-out' + transform: 'translateY(-2px)', transition: transition.normal }, }} onClick={() => toggleChartSelection(chart.id)} @@ -1079,8 +1080,7 @@ export const ReportView: FC = () => { display: 'flex', overflowY: 'auto', flexDirection: 'column', - borderRight: 1, - borderColor: 'divider', + borderRight: `1px solid ${borderColor.view}`, height: 'fit-content', background: alpha(theme.palette.background.paper, 0.9), }}> diff --git a/src/views/SelectableDataGrid.tsx b/src/views/SelectableDataGrid.tsx index 6afa6270..a344dbbd 100644 --- a/src/views/SelectableDataGrid.tsx +++ b/src/views/SelectableDataGrid.tsx @@ -2,6 +2,7 @@ // Licensed under the MIT License. import * as React from 'react'; +import { shadow, transition } from '../app/tokens'; import { TableVirtuoso } from 'react-virtuoso'; import Table from '@mui/material/Table'; import TableBody from '@mui/material/TableBody'; @@ -178,7 +179,7 @@ const DraggableHeader: React.FC = ({ ...(field && { '&:hover': { backgroundColor: hoverBackgroundColor, - boxShadow: '0 2px 4px rgba(0,0,0,0.08)', + boxShadow: shadow.md, }, }), }} diff --git a/src/views/TableSelectionView.tsx b/src/views/TableSelectionView.tsx index a7ceab50..64b6a240 100644 --- a/src/views/TableSelectionView.tsx +++ b/src/views/TableSelectionView.tsx @@ -7,6 +7,7 @@ import { useEffect, useState, useMemo } from 'react'; import Typography from '@mui/material/Typography'; import Box from '@mui/material/Box'; import { Button, Chip } from '@mui/material'; +import { borderColor } from '../app/tokens'; import StreamIcon from '@mui/icons-material/Stream'; import { createTableFromFromObjectArray } from '../data/utils'; import { MultiTablePreview } from './MultiTablePreview'; @@ -89,8 +90,7 @@ export const DatasetSelectionView: React.FC = functio width: 180, display: 'flex', flexDirection: 'column', - borderRight: 1, - borderColor: 'divider', + borderRight: `1px solid ${borderColor.view}`, overflow: 'hidden', height: '100%' }}> diff --git a/src/views/UnifiedDataUploadDialog.tsx b/src/views/UnifiedDataUploadDialog.tsx index 48901340..a9888a98 100644 --- a/src/views/UnifiedDataUploadDialog.tsx +++ b/src/views/UnifiedDataUploadDialog.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; import { useState, useCallback, useEffect, useRef } from 'react'; +import { borderColor, transition, radius } from '../app/tokens'; import { Box, Button, @@ -99,11 +100,10 @@ const DataSourceCard: React.FC = ({ sx={{ p: 1.5, cursor: disabled ? 'not-allowed' : 'pointer', - border: '1px solid', - borderColor: 'divider', - borderRadius: 1, + border: `1px solid ${borderColor.divider}`, + borderRadius: radius.sm, opacity: disabled ? 0.5 : 1, - transition: 'all 0.15s ease', + transition: transition.fast, display: 'flex', alignItems: 'center', gap: 1.5, @@ -1044,12 +1044,12 @@ export const UnifiedDataUploadDialog: React.FC = ( = function ChartEditorFC({}) { backgroundColor: 'rgba(0, 0, 0, 0.02)', borderRadius: 1, padding: '2px', - border: '1px solid rgba(0, 0, 0, 0.06)' + border: `1px solid ${borderColor.component}` }}> = function ChartEditorFC({}) { - + { setCodeViewOpen(false); From 10526c0fb2db28616bf8811b47b7b67c3ad4285b Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Sat, 7 Feb 2026 23:26:18 -0800 Subject: [PATCH 15/50] optimize data loader --- py-src/data_formulator/tables_routes.py | 54 ++ src/app/App.tsx | 47 +- src/app/dfSlice.tsx | 4 +- src/app/tableThunks.ts | 364 +++++++ src/app/utils.tsx | 10 +- src/components/componentType.tsx | 4 +- src/scss/VisualizationView.scss | 2 +- src/views/DBTableManager.tsx | 1187 +++++------------------ src/views/DataLoadingChat.tsx | 7 +- src/views/DataThread.tsx | 192 +++- src/views/DataThreadCards.tsx | 7 +- src/views/EncodingShelfCard.tsx | 5 +- src/views/UnifiedDataUploadDialog.tsx | 58 +- 13 files changed, 894 insertions(+), 1047 deletions(-) create mode 100644 src/app/tableThunks.ts diff --git a/py-src/data_formulator/tables_routes.py b/py-src/data_formulator/tables_routes.py index 3d3e8db8..cd5f07e3 100644 --- a/py-src/data_formulator/tables_routes.py +++ b/py-src/data_formulator/tables_routes.py @@ -697,6 +697,60 @@ def data_loader_view_query_sample(): return jsonify({"status": "error", "sample": [], "message": safe_msg}), status_code +@tables_bp.route('/data-loader/fetch-data', methods=['POST']) +def data_loader_fetch_data(): + """Fetch data from an external data loader and return as JSON rows WITHOUT saving to workspace. + + This is used when storeOnServer=false (local-only / incognito mode). + The data is returned directly to the frontend without being persisted as parquet. + """ + try: + data = request.get_json() + data_loader_type = data.get('data_loader_type') + data_loader_params = data.get('data_loader_params') + table_name = data.get('table_name') + row_limit = data.get('row_limit', 10000) + sort_columns = data.get('sort_columns') + sort_order = data.get('sort_order', 'asc') + + if not data_loader_type or not table_name: + return jsonify({"status": "error", "message": "data_loader_type and table_name are required"}), 400 + + if data_loader_type not in DATA_LOADERS: + return jsonify({"status": "error", "message": f"Invalid data loader type. Must be one of: {', '.join(DATA_LOADERS.keys())}"}), 400 + + data_loader = DATA_LOADERS[data_loader_type](data_loader_params) + + # Fetch data as DataFrame (not Arrow, since we need JSON output not parquet) + df = data_loader.fetch_data_as_dataframe( + source_table=table_name, + size=row_limit, + sort_columns=sort_columns, + sort_order=sort_order, + ) + + total_row_count = len(df) + # Apply row limit + if len(df) > row_limit: + df = df.head(row_limit) + + rows = json.loads(df.to_json(orient='records', date_format='iso')) + columns = [{"name": col, "type": str(df[col].dtype)} for col in df.columns] + + return jsonify({ + "status": "success", + "rows": rows, + "columns": columns, + "total_row_count": total_row_count, + "row_limit_applied": row_limit, + }) + except Exception as e: + logger.error(f"Error fetching data from data loader: {str(e)}") + logger.error(traceback.format_exc()) + safe_msg, status_code = sanitize_db_error_message(e) + return jsonify({"status": "error", "message": safe_msg}), status_code + + @tables_bp.route('/data-loader/ingest-data-from-query', methods=['POST']) def data_loader_ingest_data_from_query(): """Ingest data from a query into the workspace as parquet.""" diff --git a/src/app/App.tsx b/src/app/App.tsx index f5e8ef65..e7eecb77 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -346,7 +346,13 @@ const ResetDialog: React.FC = () => { - - - ); - } - let tableSelectionPanel = - {/* Recent Data Loaders */} - - - External Data Loaders - - ( + - ); - })} - - {/* Derived Views Section */} - {dbTables.filter(t => t.view_source !== null).length > 0 && ( - - - - - t.view_source !== null && t.view_source !== undefined && !tables.some(t2 => t2.id === t.name)).length === 0} - sx={{ - padding: 0.5, - mr: 0.5, - '&:hover': { - backgroundColor: alpha(theme.palette.primary.main, 0.08), - } - }} - > - - - - - - - {dbTables.filter(t => t.view_source !== null).map((t, i) => { - return ( - - ); - })} - - - - )} + {dataLoaderType} + + ))} - let dataConnectorView = + let dataConnectorView = - - {/* File upload */} - {selectedDataLoader === 'file upload' && ( - - {uploadFileButton({isUploading ? 'uploading...' : 'upload a csv/tsv file to the local database'})} + {/* Empty state when no loader selected */} + {selectedDataLoader === '' && ( + + + Select a data loader from the left panel + )} {/* Data loader forms */} {dataLoaderMetadata && Object.entries(dataLoaderMetadata).map(([dataLoaderType, metadata]) => ( selectedDataLoader === dataLoaderType && ( - + { setIsUploading(false); - fetchTables().then(() => { - // Switch back to tables view after import - setSelectedDataLoader(""); - // Navigate to the first imported table after tables are fetched - if (status === "success" && importedTables && importedTables.length > 0) { - setSelectedTabKey(importedTables[0]); - } - }); - if (status === "error") { + if (status === "success") { + onClose?.(); + } else { setSystemMessage(message, "error"); } }} @@ -888,172 +339,11 @@ export const DBManagerPane: React.FC<{ ))} ; - let tableView = - {/* Empty state */} - {selectedTabKey === '' && ( - - The database is empty, refresh the table list or import some data to get started. - - )} - - {/* Table content */} - {dbTables.map((t, i) => { - if (selectedTabKey !== t.name) return null; - - const currentTable = t; - - return ( - - - - {currentTable.view_source ? : } - - {currentTable.name} - - - {currentTable.source_metadata && `imported from ${currentTable.source_metadata.data_loader_type}.${currentTable.source_metadata.source_table_name}`} - - - - handleDropTable(currentTable.name)} - title="Drop Table" - > - - - - - - - { - return Object.fromEntries( - currentTable.columns.map((col) => [col.name, String(row[col.name] ?? '')]) - ); - })} - columnDefs={currentTable.columns.map((col) => ({ - id: col.name, - label: col.name, - minWidth: 80 - }))} - rowsPerPageNum={-1} - compact={false} - maxCellWidth={80} - isIncompleteTable={currentTable.row_count > 10} - maxHeight={340} - /> - - {currentTable.row_count > 10 && ( - - - Showing first 9 rows of {currentTable.row_count} total rows - - - )} - - {tables.some(t => t.id === currentTable.name) ? ( - - - - Loaded - - - ) : ( - - {/* Watch settings - only show for tables that can be refreshed */} - {currentTable.source_metadata && ( - - - setWatchEnabled(e.target.checked)} - size="small" - /> - } - label={ - - Watch Mode - - } - /> - {watchEnabled ? ( - - - check for updates every - - {[ - { seconds: 10, label: '10s' }, - { seconds: 30, label: '30s' }, - { seconds: 60, label: '1m' }, - { seconds: 300, label: '5m' }, - { seconds: 600, label: '10m' }, - { seconds: 1800, label: '30m' }, - { seconds: 3600, label: '1h' }, - { seconds: 86400, label: '24h' }, - ].map((opt) => ( - setWatchInterval(opt.seconds)} - sx={{ - cursor: 'pointer', - fontSize: '0.7rem', - height: 24, - }} - /> - ))} - - ) : - automatically check and refresh data from the database at regular intervals - } - - - )} - - - )} - - ); - })} - ; - let mainContent = {/* Button navigation - similar to TableSelectionView */} - + - {/* Reset Confirmation Popover */} - setResetAnchorEl(null)} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', - }} - transformOrigin={{ - vertical: 'top', - horizontal: 'left', - }} - > - - - Reset backend database and delete all tables? This cannot be undone. - - - - - - - - {/* Content area - show connector view if a connector is selected, otherwise show table view */} + {/* Content area - show selected data loader form */} - {selectedDataLoader !== "" ? dataConnectorView : tableView} + {dataConnectorView} @@ -1151,9 +400,10 @@ export const DataLoaderForm: React.FC<{ onFinish: (status: "success" | "error", message: string, importedTables?: string[]) => void }> = ({dataLoaderType, paramDefs, authInstructions, onImport, onFinish}) => { - const dispatch = useDispatch(); + const dispatch = useDispatch(); const theme = useTheme(); const params = useSelector((state: DataFormulatorState) => state.dataLoaderConnectParams[dataLoaderType] ?? {}); + const frontendRowLimit = useSelector((state: DataFormulatorState) => state.config?.frontendRowLimit ?? 10000); const [tableMetadata, setTableMetadata] = useState>({}); let [displaySamples, setDisplaySamples] = useState>({}); @@ -1161,6 +411,9 @@ export const DataLoaderForm: React.FC<{ const [tableImportConfigs, setTableImportConfigs] = useState>({}); const [subsetConfigAnchor, setSubsetConfigAnchor] = useState<{element: HTMLElement, tableName: string} | null>(null); + // Store on server toggle for data loader imports + const [importStoreOnServer, setImportStoreOnServer] = useState(true); + // Helper to get import config for a table (defaults to 'none') const getTableConfig = (tableName: string): TableImportConfig => { return tableImportConfigs[tableName] ?? { mode: 'none' }; @@ -1490,69 +743,95 @@ export const DataLoaderForm: React.FC<{ ); })()} , - Object.keys(tableMetadata).length > 0 && + Object.keys(tableMetadata).length > 0 && + + setImportStoreOnServer(e.target.checked)} + size="small" + /> + } + label={ + + {importStoreOnServer ? 'Store on server' : `Local only (≤${frontendRowLimit.toLocaleString()} rows)`} + + } + /> + ] @@ -1560,7 +839,7 @@ export const DataLoaderForm: React.FC<{ const isConnected = Object.keys(tableMetadata).length > 0; return ( - + Import tables from {dataLoaderType} @@ -1573,111 +852,94 @@ export const DataLoaderForm: React.FC<{ } {isConnected ? ( // Connected state: show connection parameters and disconnect button - - - - - {paramDefs.filter((paramDef) => params[paramDef.name]).map((paramDef, index) => ( - - - {paramDef.name}: - - - {params[paramDef.name] || '(empty)'} - - {index < paramDefs.filter((paramDef) => params[paramDef.name]).length - 1 && ( - - • - - )} - - ))} - - - - - - - - table filter + + + + {paramDefs.filter((paramDef) => params[paramDef.name]).map((paramDef, index) => ( + + + {paramDef.name}: - + + {params[paramDef.name] || '(empty)'} + + {index < paramDefs.filter((p) => params[p.name]).length - 1 && ( + · + )} + + ))} + + + + + table filter + setTableFilter(event.target.value)} /> - - - - + + @@ -1686,33 +948,40 @@ export const DataLoaderForm: React.FC<{ ) : ( // Not connected: show connection forms <> - + {paramDefs.map((paramDef) => ( - + {paramDef.name} - {paramDef.required && *} + {paramDef.required && *} { dispatch(dfActions.updateDataLoaderConnectParam({ dataLoaderType, paramName: paramDef.name, @@ -1721,35 +990,26 @@ export const DataLoaderForm: React.FC<{ /> ))} - - - - - table filter - - + + + + + table filter + setTableFilter(event.target.value)} /> @@ -1758,7 +1018,8 @@ export const DataLoaderForm: React.FC<{ } - - - - - {authInstructions.trim()} - - + {authInstructions.trim() && ( + + {authInstructions.trim()} + + )} )} ); diff --git a/src/views/DataLoadingChat.tsx b/src/views/DataLoadingChat.tsx index 527e6493..6fec0c4b 100644 --- a/src/views/DataLoadingChat.tsx +++ b/src/views/DataLoadingChat.tsx @@ -12,6 +12,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { AppDispatch } from '../app/store'; import { DataFormulatorState, dfActions, fetchFieldSemanticType } from '../app/dfSlice'; import { createTableFromText } from '../data/utils'; +import { loadTable } from '../app/tableThunks'; import { createOrderedThreadBlocks, DataLoadingInputBox, DataPreviewBox, SingleDataCleanThreadView } from './DataLoadingThread'; @@ -30,7 +31,7 @@ const getUniqueTableName = (baseName: string, existingNames: Set): strin return uniqueName; }; -export const DataLoadingChat: React.FC = () => { +export const DataLoadingChat: React.FC<{storeOnServer?: boolean}> = ({storeOnServer = true}) => { const theme = useTheme(); const dispatch = useDispatch(); const inputBoxRef = useRef<(() => void) | null>(null); @@ -83,8 +84,8 @@ export const DataLoadingChat: React.FC = () => { const unique = getUniqueTableName(base, existingNames); const table = createTableFromText(unique, selectedTable.content.value, selectedTable.context); if (table) { - dispatch(dfActions.loadTable(table)); - dispatch(fetchFieldSemanticType(table)); + const tableWithSource = { ...table, source: { type: 'extract' as const } }; + dispatch(loadTable({ table: tableWithSource, storeOnServer })); } }; diff --git a/src/views/DataThread.tsx b/src/views/DataThread.tsx index 732f7b80..9f1988e7 100644 --- a/src/views/DataThread.tsx +++ b/src/views/DataThread.tsx @@ -59,6 +59,7 @@ import EditIcon from '@mui/icons-material/Edit'; import RefreshIcon from '@mui/icons-material/Refresh'; import StreamIcon from '@mui/icons-material/Stream'; import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline'; +import AddIcon from '@mui/icons-material/Add'; import { alpha } from '@mui/material/styles'; @@ -69,6 +70,7 @@ import { AppDispatch } from '../app/store'; import StopIcon from '@mui/icons-material/Stop'; import { useDataRefresh } from '../app/useDataRefresh'; import { AgentStatusBox, buildChartCard, buildTriggerCard, buildTableCard, BuildTableCardProps } from './DataThreadCards'; +import { UnifiedDataUploadDialog } from './UnifiedDataUploadDialog'; import { ViewBorderStyle, transition, radius } from '../app/tokens'; @@ -491,6 +493,9 @@ let SingleThreadGroupView: FC<{ const [selectedTableForMetadata, setSelectedTableForMetadata] = useState(null); const [metadataAnchorEl, setMetadataAnchorEl] = useState(null); + // Upload dialog state (workspace "add data" button) + const [uploadDialogOpen, setUploadDialogOpen] = useState(false); + // Table menu state const [tableMenuAnchorEl, setTableMenuAnchorEl] = useState(null); const [selectedTableForMenu, setSelectedTableForMenu] = useState(null); @@ -726,8 +731,8 @@ let SingleThreadGroupView: FC<{ // Use the global highlighted table IDs (computed at DataThread level from the focused table's full ancestor chain) let highlightedTableIds = globalHighlightedTableIds; - let _buildTriggerCard = (trigger: Trigger) => { - return buildTriggerCard(trigger, focusedChartId); + let _buildTriggerCard = (trigger: Trigger, highlighted: boolean = false) => { + return buildTriggerCard(trigger, focusedChartId, highlighted); } // Shared props for buildTableCard calls @@ -743,7 +748,11 @@ let SingleThreadGroupView: FC<{ } let tableElementList = newTableIds.map((tableId, i) => _buildTableCard(tableId)); - let triggerCards = newTriggers.map((trigger) => _buildTriggerCard(trigger)); + let triggerCards = newTriggers.map((trigger) => { + const triggerTableId = trigger.resultTableId; + const isHL = triggerTableId ? highlightedTableIds.includes(triggerTableId) : false; + return _buildTriggerCard(trigger, isHL); + }); // Build a flat sequence of timeline items: [trigger, table, charts, trigger, table, charts, ...] let timelineItems: { key: string; element: React.ReactNode; type: 'used-table' | 'trigger' | 'table' | 'chart' | 'leaf-trigger' | 'leaf-table'; highlighted: boolean; tableId?: string; isRunning?: boolean }[] = []; @@ -836,7 +845,7 @@ let SingleThreadGroupView: FC<{ key: `leaf-trigger-${lt.id}`, type: 'leaf-trigger', highlighted: highlightedTableIds.includes(lt.id), - element: _buildTriggerCard(leafTrigger), + element: _buildTriggerCard(leafTrigger, highlightedTableIds.includes(lt.id)), }); } let leafCards = _buildTableCard(lt.id); @@ -868,7 +877,7 @@ let SingleThreadGroupView: FC<{ const isTable = item.type === 'table' || item.type === 'leaf-table' || item.type === 'used-table'; const color = item.highlighted ? theme.palette.primary.main - : 'rgba(0,0,0,0.25)'; + : 'rgba(0,0,0,0.4)'; // For running agent items, show a spinner instead of a dot if (item.isRunning) { @@ -884,7 +893,7 @@ let SingleThreadGroupView: FC<{ if (isStreaming) { return { + const tableCards = _buildTableCard(table.id); + if (Array.isArray(tableCards)) { + const group: typeof timelineItems = []; + tableCards.forEach((subItem: any, j: number) => { + if (!subItem) return; + const subKey = subItem?.key || `compact-${table.id}-${j}`; + const isChart = subKey.includes('chart') || subKey.includes('agent'); + const isAgent = subKey.includes('agent'); + const isAgentRunning = isAgent && runningAgentTableIds.has(table.id); + group.push({ + key: subKey, + type: isChart ? 'chart' : 'table', + tableId: isChart ? undefined : table.id, + highlighted: highlightedTableIds.includes(table.id), + element: subItem, + ...(isAgentRunning ? { isRunning: true } : {}), + }); + }); + if (group.length > 0) { + compactTimelineGroups.push(group); + } + } + }); + return ( - - {leafTables.map((table) => { - const tableCardResult = _buildTableCard(table.id); - // buildTableCard returns an array [regularTableBox, chartBox] - // In compact mode, we want to show them stacked - return ( - - {tableCardResult} - - ); - })} + + + + + workspace + + + +
+ {/* Vertical backbone line through timeline column */} + + {compactTimelineGroups.map((group, groupIndex) => ( +
+ {group.map((item, index) => renderTimelineItem(item, index, index === group.length - 1))} +
+ ))} + {/* Add data button — styled like a table card row */} + + + + + setUploadDialogOpen(true)} + sx={{ + flex: 1, minWidth: 0, pl: 0.5, + display: 'flex', alignItems: 'center', + py: '4px', + }} + > + + + add data + + + + +
+ setUploadDialogOpen(false)} + initialTab="menu" + /> - + - + {threadLabel || (threadIdx === -1 ? 'thread0' : `thread - ${threadIdx + 1}`)} @@ -1882,13 +1985,12 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { return undefined; }, [focusedTableId, leafTables, tables]); - let hasContent = leafTables.length > 0; + let hasContent = leafTables.length > 0 || tables.some(t => !t.derive); - // Separate standalone tables (no derivation chain) from threaded tables - let standaloneTables = leafTables.filter(lt => { - const triggers = getTriggers(lt, tables); - return triggers.length + 1 <= 1; - }); + // Collect all base (non-derived) tables for thread 0 — they serve as source tables + // and will automatically appear as "used table" references in derived threads. + let baseTables = tables.filter(t => !t.derive); + // Threaded tables: leaf tables that have a derivation chain let threadedTables = leafTables.filter(lt => { const triggers = getTriggers(lt, tables); return triggers.length + 1 > 1; @@ -1905,20 +2007,20 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { // Track which table IDs have been claimed by earlier threads let claimedTableIds = new Set(); - // thread0: group all standalone tables into one compact thread - if (standaloneTables.length > 0) { - standaloneTables.forEach(lt => claimedTableIds.add(lt.id)); + // thread0: group all base (non-derived) tables into one compact thread + if (baseTables.length > 0) { + baseTables.forEach(lt => claimedTableIds.add(lt.id)); allThreadEntries.push({ key: 'thread0', groupId: 'thread0', - leafTables: standaloneTables, + leafTables: baseTables, isCompact: true, threadIdx: -1, }); - let standaloneChartCount = standaloneTables.reduce((sum, lt) => { + let baseChartCount = baseTables.reduce((sum, lt) => { return sum + chartElements.filter(ce => ce.tableId === lt.id).length; }, 0); - allThreadHeights.push(estimateThreadHeight(standaloneTables.length, 0, standaloneChartCount, 0)); + allThreadHeights.push(estimateThreadHeight(baseTables.length, 0, baseChartCount, 0)); } // Regular threads: one per threaded leaf table diff --git a/src/views/DataThreadCards.tsx b/src/views/DataThreadCards.tsx index 72a54a31..78c5e00a 100644 --- a/src/views/DataThreadCards.tsx +++ b/src/views/DataThreadCards.tsx @@ -173,11 +173,9 @@ export let buildChartCard = ( display: 'flex', position: 'relative', border: 'none', + borderRadius: '6px', backgroundColor: 'white', px: 1, - ...(unread && { - boxShadow: '0 0 6px rgba(255, 152, 0, 0.15), 0 0 12px rgba(255, 152, 0, 0.15)', - }) }}> {chartElement.element} @@ -188,6 +186,7 @@ export let buildChartCard = ( export let buildTriggerCard = ( trigger: Trigger, focusedChartId: string | undefined, + highlighted: boolean = false, ) => { let selectedClassName = trigger.chart?.id == focusedChartId ? 'selected-card' : ''; @@ -195,6 +194,7 @@ export let buildTriggerCard = ( { sx={{ width: '100%', backgroundColor: primaryBgColor, ...ComponentBorderStyle, + ...(isHighlighted ? { borderColor: 'primary.main' } : {}), borderRadius: '6px', }} onClick={() => { diff --git a/src/views/EncodingShelfCard.tsx b/src/views/EncodingShelfCard.tsx index b9991db0..46c798ee 100644 --- a/src/views/EncodingShelfCard.tsx +++ b/src/views/EncodingShelfCard.tsx @@ -135,7 +135,8 @@ export const TriggerCard: FC<{ trigger: Trigger, hideFields?: boolean, mini?: boolean, - sx?: SxProps}> = function ({ className, trigger, hideFields, mini = false, sx }) { + highlighted?: boolean, + sx?: SxProps}> = function ({ className, trigger, hideFields, mini = false, highlighted = false, sx }) { let theme = useTheme(); @@ -225,7 +226,7 @@ export const TriggerCard: FC<{ px: 1, borderRadius: radius.sm, backgroundColor: theme.palette.custom.bgcolor, - border: `1px solid ${borderColor.component}`, + border: `1px solid ${highlighted ? theme.palette.custom.main : borderColor.component}`, '& .MuiChip-label': { px: 0.5, fontSize: "10px"}, ...sx, }} diff --git a/src/views/UnifiedDataUploadDialog.tsx b/src/views/UnifiedDataUploadDialog.tsx index a9888a98..a3928c8d 100644 --- a/src/views/UnifiedDataUploadDialog.tsx +++ b/src/views/UnifiedDataUploadDialog.tsx @@ -36,6 +36,7 @@ import StreamIcon from '@mui/icons-material/Stream'; import { useDispatch, useSelector } from 'react-redux'; import { DataFormulatorState, dfActions, fetchFieldSemanticType } from '../app/dfSlice'; import { AppDispatch } from '../app/store'; +import { loadTable } from '../app/tableThunks'; import { DataSourceConfig, DictTable } from '../components/ComponentType'; import { createTableFromFromObjectArray, createTableFromText, loadTextDataWrapper, loadBinaryDataWrapper } from '../data/utils'; import { DataLoadingChat } from './DataLoadingChat'; @@ -465,12 +466,16 @@ export const UnifiedDataUploadDialog: React.FC = ( const existingTables = useSelector((state: DataFormulatorState) => state.tables); const serverConfig = useSelector((state: DataFormulatorState) => state.serverConfig); const dataCleanBlocks = useSelector((state: DataFormulatorState) => state.dataCleanBlocks); + const frontendRowLimit = useSelector((state: DataFormulatorState) => state.config?.frontendRowLimit ?? 10000); const existingNames = new Set(existingTables.map(t => t.id)); const [activeTab, setActiveTab] = useState(initialTab === 'menu' ? 'menu' : initialTab); const fileInputRef = useRef(null); const urlInputRef = useRef(null); + // Store on server toggle (default: true, like normal browsing mode) + const [storeOnServer, setStoreOnServer] = useState(true); + // Paste tab state const [pasteContent, setPasteContent] = useState(""); const [isLargeContent, setIsLargeContent] = useState(false); @@ -711,8 +716,11 @@ export const UnifiedDataUploadDialog: React.FC = ( if (table) { const sourceConfig: DataSourceConfig = { type: 'file', fileName: filePreviewFiles[0]?.name }; const tableWithSource = { ...table, source: sourceConfig }; - dispatch(dfActions.loadTable(tableWithSource)); - dispatch(fetchFieldSemanticType(tableWithSource)); + dispatch(loadTable({ + table: tableWithSource, + storeOnServer, + file: storeOnServer ? filePreviewFiles[filePreviewActiveIndex] || filePreviewFiles[0] : undefined, + })); handleClose(); } }; @@ -725,8 +733,11 @@ export const UnifiedDataUploadDialog: React.FC = ( const table = filePreviewTables[i]; const sourceConfig: DataSourceConfig = { type: 'file', fileName: filePreviewFiles[i]?.name || filePreviewFiles[0]?.name }; const tableWithSource = { ...table, source: sourceConfig }; - dispatch(dfActions.loadTable(tableWithSource)); - dispatch(fetchFieldSemanticType(tableWithSource)); + dispatch(loadTable({ + table: tableWithSource, + storeOnServer, + file: storeOnServer ? filePreviewFiles[i] || filePreviewFiles[0] : undefined, + })); } handleClose(); }; @@ -784,8 +795,7 @@ export const UnifiedDataUploadDialog: React.FC = ( if (table) { // Add source info for paste data const tableWithSource = { ...table, source: { type: 'paste' as const } }; - dispatch(dfActions.loadTable(tableWithSource)); - dispatch(fetchFieldSemanticType(tableWithSource)); + dispatch(loadTable({ table: tableWithSource, storeOnServer })); handleClose(); } }; @@ -883,8 +893,7 @@ export const UnifiedDataUploadDialog: React.FC = ( sourceConfig = { type: 'url', url: tableURL }; } const tableWithSource = { ...table, source: sourceConfig }; - dispatch(dfActions.loadTable(tableWithSource)); - dispatch(fetchFieldSemanticType(tableWithSource)); + dispatch(loadTable({ table: tableWithSource, storeOnServer })); handleClose(); } }; @@ -908,8 +917,7 @@ export const UnifiedDataUploadDialog: React.FC = ( sourceConfig = { type: 'url', url: tableURL }; } const tableWithSource = { ...table, source: sourceConfig }; - dispatch(dfActions.loadTable(tableWithSource)); - dispatch(fetchFieldSemanticType(tableWithSource)); + dispatch(loadTable({ table: tableWithSource, storeOnServer })); } handleClose(); }; @@ -993,8 +1001,29 @@ export const UnifiedDataUploadDialog: React.FC = (
)} + {activeTab !== 'menu' && activeTab !== 'database' && ( + + setStoreOnServer(e.target.checked)} + size="small" + /> + } + label={ + + {storeOnServer ? 'Store on server' : `Local only (≤${frontendRowLimit.toLocaleString()} rows)`} + + } + /> + + )} = (
) : ( - + )} {/* Extract Data Tab */} - + {/* Explore Sample Datasets Tab */} @@ -1509,8 +1538,7 @@ export const UnifiedDataUploadDialog: React.FC = ( // Regular example data dictTable.source = { type: 'example', url: table.url }; } - dispatch(dfActions.loadTable(dictTable)); - dispatch(fetchFieldSemanticType(dictTable)); + dispatch(loadTable({ table: dictTable, storeOnServer })); } }); } From 463d541046ffe4de512019d3e621bee95cc12536 Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Sun, 8 Feb 2026 01:08:56 -0800 Subject: [PATCH 16/50] fix --- src/app/App.tsx | 2 - src/views/AgentRulesDialog.tsx | 16 +- src/views/DataThread.tsx | 533 ++++++++++++--------------------- 3 files changed, 206 insertions(+), 345 deletions(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index e7eecb77..9a4f4427 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -71,7 +71,6 @@ import { handleDBDownload } from '../views/DBTableManager'; import { getUrls, fetchWithIdentity } from './utils'; import { UnifiedDataUploadDialog } from '../views/UnifiedDataUploadDialog'; import ChatIcon from '@mui/icons-material/Chat'; -import { AgentRulesDialog } from '../views/AgentRulesDialog'; import ArticleIcon from '@mui/icons-material/Article'; import EditIcon from '@mui/icons-material/Edit'; import ArrowBackIcon from '@mui/icons-material/ArrowBack'; @@ -767,7 +766,6 @@ export const AppFC: FC = function AppFC(appProps) { - } diff --git a/src/views/AgentRulesDialog.tsx b/src/views/AgentRulesDialog.tsx index 9b581466..93a76496 100644 --- a/src/views/AgentRulesDialog.tsx +++ b/src/views/AgentRulesDialog.tsx @@ -23,9 +23,13 @@ import { useDispatch, useSelector } from 'react-redux'; import { DataFormulatorState, dfActions } from '../app/dfSlice'; import Editor from 'react-simple-code-editor'; -export const AgentRulesDialog: React.FC = () => { +export const AgentRulesDialog: React.FC<{ + externalOpen?: boolean; + onExternalClose?: () => void; +}> = ({ externalOpen, onExternalClose }) => { const theme = useTheme(); - const [open, setOpen] = useState(false); + const [internalOpen, setInternalOpen] = useState(false); + const open = externalOpen !== undefined ? externalOpen : internalOpen; const dispatch = useDispatch(); const agentRules = useSelector((state: DataFormulatorState) => state.agentRules); @@ -84,7 +88,11 @@ export const AgentRulesDialog: React.FC = () => { // Reset to original values setCodingRules(agentRules.coding); setExplorationRules(agentRules.exploration); - setOpen(false); + if (onExternalClose) { + onExternalClose(); + } else { + setInternalOpen(false); + } }; // Check if there are changes for each tab @@ -118,7 +126,7 @@ export const AgentRulesDialog: React.FC = () => { - + + + )} , sx?: SxProps, -}> = function ({ tables, chartElements, sx }) { +}> = function ({ tables, chartElements, suppressScrollRef, sx }) { const theme = useTheme(); const dispatch = useDispatch(); const focusedTableId = useSelector((state: DataFormulatorState) => state.focusedTableId); @@ -432,6 +441,7 @@ const WorkspacePanel: FC<{ const charts = useSelector(dfSelectors.getAllCharts); const conceptShelfItems = useSelector((state: DataFormulatorState) => state.conceptShelfItems); const [uploadDialogOpen, setUploadDialogOpen] = useState(false); + const [agentRulesOpen, setAgentRulesOpen] = useState(false); const [workspaceExpanded, setWorkspaceExpanded] = useState(false); const fileItemSx = (isActive: boolean, isNested: boolean = false) => ({ @@ -439,7 +449,7 @@ const WorkspacePanel: FC<{ alignItems: 'center', gap: 0.5, px: 0.75, - py: '3px', + py: '1px', borderRadius: '3px', cursor: 'pointer', fontSize: 11, @@ -510,6 +520,7 @@ const WorkspacePanel: FC<{ mb: 0.5, backgroundColor: 'rgba(0,0,0,0.02)', borderBottom: `1px solid ${borderColor.divider}`, + userSelect: 'none', }}> setWorkspaceExpanded(!workspaceExpanded)} > - {workspaceExpanded ? - : - - } - - Workspace - + setWorkspaceExpanded(!workspaceExpanded)} + > + {workspaceExpanded ? + : + + } + + Workspace + + + { e.stopPropagation(); setUploadDialogOpen(true); }} + sx={{ + display: 'flex', alignItems: 'center', gap: '2px', + ml: 'auto', px: '5px', py: '2px', borderRadius: '3px', + cursor: 'pointer', + color: theme.palette.primary.textColor || theme.palette.primary.main, + '&:hover': { backgroundColor: alpha(theme.palette.primary.main, 0.08) }, + }} + > + + add data + - {workspaceExpanded && ( - - 0 ? 0 : 'calc(100% - 10px)', - width: '1px', - backgroundColor: borderColor.divider, - }, - '&::after': { - content: '""', - position: 'absolute', - left: 0, - top: '10px', - width: '8px', - height: '1px', - backgroundColor: borderColor.divider, - } - }} - > - setUploadDialogOpen(true)} - > - - - Add Data - - - + + {tables.map((table, tableIndex) => { const isTableActive = focusedTableId === table.id; const tableCharts = chartElements.filter(ce => ce.tableId === table.id); @@ -591,6 +569,7 @@ const WorkspacePanel: FC<{ const isLastTable = tableIndex === tables.length - 1; const handleTableClick = () => { + if (suppressScrollRef) suppressScrollRef.current = true; dispatch(dfActions.setFocusedTable(table.id)); if (tableCharts.length === 0) { dispatch(dfActions.setFocusedChart(undefined)); @@ -615,7 +594,7 @@ const WorkspacePanel: FC<{ top: 0, bottom: isLastTable ? 'calc(100% - 10px)' : 0, width: '1px', - backgroundColor: borderColor.divider, + backgroundColor: 'rgba(0,0,0,0.2)', }, '&::after': { content: '""', @@ -624,7 +603,7 @@ const WorkspacePanel: FC<{ top: '10px', width: '8px', height: '1px', - backgroundColor: borderColor.divider, + backgroundColor: 'rgba(0,0,0,0.2)', } }} > @@ -674,6 +653,7 @@ const WorkspacePanel: FC<{ const isLast = idx === tableCharts.length - 1; const handleChartClick = () => { + if (suppressScrollRef) suppressScrollRef.current = true; dispatch(dfActions.setFocusedTable(table.id)); dispatch(dfActions.setFocusedChart(chart.id)); }; @@ -691,7 +671,7 @@ const WorkspacePanel: FC<{ top: 0, bottom: isLast ? '50%' : 0, width: '1px', - backgroundColor: borderColor.divider, + backgroundColor: 'rgba(0,0,0,0.2)', }, '&::after': { content: '""', @@ -700,7 +680,7 @@ const WorkspacePanel: FC<{ top: '50%', width: '8px', height: '1px', - backgroundColor: borderColor.divider, + backgroundColor: 'rgba(0,0,0,0.2)', } }} > @@ -731,13 +711,17 @@ const WorkspacePanel: FC<{ ); })} - )} + setUploadDialogOpen(false)} initialTab="menu" /> + setAgentRulesOpen(false)} + /> ); }; @@ -1067,7 +1051,7 @@ let SingleThreadGroupView: FC<{ }); // Build a flat sequence of timeline items: [trigger, table, charts, trigger, table, charts, ...] - let timelineItems: { key: string; element: React.ReactNode; type: 'used-table' | 'trigger' | 'table' | 'chart' | 'leaf-trigger' | 'leaf-table'; highlighted: boolean; tableId?: string; isRunning?: boolean }[] = []; + let timelineItems: { key: string; element: React.ReactNode; type: 'used-table' | 'trigger' | 'table' | 'chart' | 'leaf-trigger' | 'leaf-table'; highlighted: boolean; tableId?: string; chartType?: string; isRunning?: boolean }[] = []; // Add used (shared) tables at the top // Only show the immediate parent + "..." for further ancestors @@ -1137,10 +1121,20 @@ let SingleThreadGroupView: FC<{ const isChart = subKey.includes('chart') || subKey.includes('agent'); const isAgent = subKey.includes('agent'); const isAgentRunning = isAgent && runningAgentTableIds.has(tableId); + // Extract chartType from the key (pattern: 'relevant-chart-{chartId}' or 'agent-status-{chartId}') + let itemChartType: string | undefined; + if (isChart) { + const cIdMatch = subKey.match(/(?:chart|agent-status)-(.+)$/); + if (cIdMatch) { + const cObj = charts.find(c => c.id === cIdMatch[1]); + itemChartType = cObj?.chartType; + } + } timelineItems.push({ key: subKey, type: isChart ? 'chart' : 'table', tableId: isChart ? undefined : tableId, + chartType: itemChartType, highlighted: isHighlighted, element: subItem, ...(isAgentRunning ? { isRunning: true } : {}), @@ -1168,10 +1162,19 @@ let SingleThreadGroupView: FC<{ const isChart = subKey.includes('chart') || subKey.includes('agent'); const isAgent = subKey.includes('agent'); const isAgentRunning = isAgent && runningAgentTableIds.has(lt.id); + let leafChartType: string | undefined; + if (isChart) { + const cIdMatch = subKey.match(/(?:chart|agent-status)-(.+)$/); + if (cIdMatch) { + const cObj = charts.find(c => c.id === cIdMatch[1]); + leafChartType = cObj?.chartType; + } + } timelineItems.push({ key: subKey, type: isChart ? 'chart' : 'leaf-table', tableId: isChart ? undefined : lt.id, + chartType: leafChartType, highlighted: highlightedTableIds.includes(lt.id), element: subItem, ...(isAgentRunning ? { isRunning: true } : {}), @@ -1181,7 +1184,8 @@ let SingleThreadGroupView: FC<{ }); // Timeline rendering helper - const TIMELINE_WIDTH = 16; + const TIMELINE_WIDTH = 14; + const TIMELINE_GAP = '4px'; // gap between timeline and card content const DOT_SIZE = 6; const CARD_PY = '4px'; // vertical padding for each timeline row @@ -1220,6 +1224,29 @@ let SingleThreadGroupView: FC<{ return ; } + // For chart items, show a chart-type-specific icon + if (item.type === 'chart' && item.chartType) { + const iconSx = { width: 14, height: 14, color }; + const ct = item.chartType.toLowerCase(); + if (ct.includes('scatter') || ct.includes('point') || ct.includes('dot') || ct.includes('boxplot')) { + return ; + } + if (ct.includes('line') || ct.includes('regression')) { + return ; + } + if (ct.includes('pie')) { + return ; + } + if (ct.includes('heatmap')) { + return ; + } + if (ct.includes('table')) { + return ; + } + // Bar, histogram, stacked, grouped, pyramid, and default + return ; + } + return - {/* Dashed connector from previous element */} - - {/* Thick solid bar — top half */} - - {/* Horizontal tick to the right */} + - + - {/* Thick solid bar — bottom half */} - - {/* Dashed connector to next element */} - + {!isLast && } + {isLast && } - + {item.element} ); } - // Charts/agents: dot on the timeline with a horizontal tick line to the chart + // Charts: chart-type icon on the timeline if (isChart) { return ( - - - + + {getTimelineDot(item)} - {!isLast && } {isLast && } - + {item.element} @@ -1338,7 +1345,7 @@ let SingleThreadGroupView: FC<{ )} {isLast && } - {item.element} @@ -1352,6 +1359,7 @@ let SingleThreadGroupView: FC<{ '& .selected-card': { boxShadow: `0 0 0 2px ${theme.palette.primary.light}`, borderColor: 'transparent', + margin: '1px 0', }, padding: '6px', }} @@ -1841,6 +1849,7 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { const scrollRef = useRef(null) const containerRef = useRef(null) + const suppressScrollRef = useRef(false); const executeScroll = (smooth: boolean = true) => { if (scrollRef.current != null) { @@ -1856,7 +1865,11 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { useEffect(() => { // load the example datasets if (focusedTableId) { - executeScroll(true); + if (suppressScrollRef.current) { + suppressScrollRef.current = false; + } else { + executeScroll(true); + } } }, [focusedTableId]); @@ -2195,6 +2208,7 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { = function DataView() { let genTableLink = (t: DictTable) => { dispatch(dfActions.setFocusedTable(t.id)) }}> + color={theme.palette.primary.textColor || theme.palette.primary.main} onClick={()=>{ dispatch(dfActions.setFocusedTable(t.id)) }}> {t.displayId || t.id} ; diff --git a/src/views/EncodingShelfCard.tsx b/src/views/EncodingShelfCard.tsx index 5cdfd3a2..10fd0c45 100644 --- a/src/views/EncodingShelfCard.tsx +++ b/src/views/EncodingShelfCard.tsx @@ -232,7 +232,6 @@ export const TriggerCard: FC<{ ...sx, }} onClick={handleClick}> - {processedPrompt} {hideFields ? "" : <>{" "}{encodingComp}} @@ -973,7 +972,7 @@ export const EncodingShelfCard: FC = function ({ chartId '0%': { }, '50%': { - color: theme.palette.derived.main, + color: theme.palette.derived.textColor || theme.palette.derived.main, }, '100%': { } diff --git a/src/views/EncodingShelfThread.tsx b/src/views/EncodingShelfThread.tsx index c082ce7e..76af21ad 100644 --- a/src/views/EncodingShelfThread.tsx +++ b/src/views/EncodingShelfThread.tsx @@ -130,7 +130,7 @@ export const EncodingShelfThread: FC = function ({ cha { dispatch(dfActions.setFocusedTable(tableId)) }}> {table?.displayId || tableId} From 157adbaac026ece74bf9d9d8ab30c6ae2f32ae97 Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Mon, 9 Feb 2026 21:04:48 -0800 Subject: [PATCH 33/50] nit --- src/views/DataThread.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/views/DataThread.tsx b/src/views/DataThread.tsx index 70dbdde4..6b80d5cb 100644 --- a/src/views/DataThread.tsx +++ b/src/views/DataThread.tsx @@ -449,7 +449,7 @@ const WorkspacePanel: FC<{ alignItems: 'center', gap: 0.5, px: 0.75, - py: '1px', + py: '3px', borderRadius: '3px', cursor: 'pointer', fontSize: 11, @@ -515,8 +515,7 @@ const WorkspacePanel: FC<{ return ( setWorkspaceExpanded(!workspaceExpanded)} > @@ -541,7 +541,7 @@ const WorkspacePanel: FC<{ : } - + Workspace @@ -555,13 +555,13 @@ const WorkspacePanel: FC<{ '&:hover': { backgroundColor: alpha(theme.palette.primary.main, 0.08) }, }} > - - add data + + add data - + {tables.map((table, tableIndex) => { const isTableActive = focusedTableId === table.id; const tableCharts = chartElements.filter(ce => ce.tableId === table.id); From 286187e00beddb2f28152b7289dc74fc6cbfb988 Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Mon, 9 Feb 2026 21:14:44 -0800 Subject: [PATCH 34/50] minor --- src/views/EncodingShelfCard.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/views/EncodingShelfCard.tsx b/src/views/EncodingShelfCard.tsx index 10fd0c45..4aeaba58 100644 --- a/src/views/EncodingShelfCard.tsx +++ b/src/views/EncodingShelfCard.tsx @@ -323,7 +323,7 @@ export const EncodingShelfCard: FC = function ({ chartId .map(([group, channelList]) => { let component = - {group} + {group && {group}} {channelList.filter(channel => Object.keys(encodingMap).includes(channel)) .map(channel => )} @@ -830,7 +830,7 @@ export const EncodingShelfCard: FC = function ({ chartId // zip multiple components together const w: any = (a: any[], b: any[]) => a.length ? [a[0], ...w(b, a.slice(1))] : b; - let formulateInputBox = + let formulateInputBox = = function ({ chartId display: 'flex', alignItems: 'center', gap: 1, - padding: '4px 8px', + padding: '4px 10px', borderBottom: `1px solid ${borderColor.component}`, backgroundColor: 'rgba(0, 0, 0, 0.02)' }}> @@ -1002,7 +1002,7 @@ export const EncodingShelfCard: FC = function ({ chartId ); let channelComponent = ( - + - {/* Template-driven config property selectors */} - {(() => { + + {/* Template-driven config property selectors */} + {(() => { const template = getChartTemplate(chart.chartType); const configProps = template?.configProperties; if (!configProps || configProps.length === 0) return null; return ( - + {configProps.map((propDef) => { if (propDef.type === 'slider') { const currentValue = chart.config?.[propDef.key] ?? propDef.defaultValue ?? propDef.min ?? 0; @@ -1112,7 +1113,7 @@ export const EncodingShelfCard: FC = function ({ chartId }}> {propDef.label} @@ -1155,7 +1156,7 @@ export const EncodingShelfCard: FC = function ({ chartId }}> {propDef.label} @@ -1189,7 +1190,6 @@ export const EncodingShelfCard: FC = function ({ chartId ); })()} - {encodingBoxGroups} @@ -1207,7 +1207,7 @@ export const EncodingShelfCard: FC = function ({ chartId }}> {ideateMode ? ( - + - ); -} - -export const ExportStateButton: React.FC<{}> = ({ }) => { - const identity = useSelector((state: DataFormulatorState) => state.identity); - const tables = useSelector((state: DataFormulatorState) => state.tables); - const fullStateJson = useSelector((state: DataFormulatorState) => { - // Fields to exclude from serialization - const excludedFields = new Set([ - 'models', - 'selectedModelId', - 'testedModels', - 'dataLoaderConnectParams', - 'identity', - 'agentRules', - 'serverConfig', - ]); - - // Build new object with only allowed fields - const stateToSerialize: any = {}; - for (const [key, value] of Object.entries(state)) { - if (!excludedFields.has(key)) { - stateToSerialize[key] = value; - } - } - - return JSON.stringify(stateToSerialize); - }); - - return - - -} - - -//type AppProps = ConnectedProps; - export const toolName = "Data Formulator" export interface AppFCProps { @@ -251,14 +151,279 @@ const TableMenu: React.FC = () => { ); }; +const SaveSessionDialog: React.FC<{open: boolean, onClose: () => void}> = ({open, onClose}) => { + const [sessionName, setSessionName] = useState(''); + const [saving, setSaving] = useState(false); + const dispatch = useDispatch(); + const tables = useSelector((state: DataFormulatorState) => state.tables); + + const fullState = useSelector((state: DataFormulatorState) => { + const excludedFields = new Set([ + 'models', 'selectedModelId', 'testedModels', + 'dataLoaderConnectParams', 'identity', 'agentRules', 'serverConfig', + ]); + const stateToSerialize: any = {}; + for (const [key, value] of Object.entries(state)) { + if (!excludedFields.has(key)) { + stateToSerialize[key] = value; + } + } + return stateToSerialize; + }); + + const handleSave = async () => { + if (!sessionName.trim()) return; + setSaving(true); + try { + const res = await fetchWithIdentity(getUrls().SESSION_SAVE, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name: sessionName.trim(), state: fullState }), + }); + const data = await res.json(); + if (data.status === 'ok') { + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "success", value: `Session "${sessionName}" saved` })); + onClose(); + } else { + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "error", value: data.message || 'Save failed' })); + } + } catch (e) { + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "error", value: 'Failed to save session' })); + } + setSaving(false); + }; + + return ( + + Save Session + + setSessionName(e.target.value)} + onKeyDown={(e) => { if (e.key === 'Enter') handleSave(); }} + helperText={`${tables.length} table(s) will be saved`} + /> + + + + + + + ); +}; + +const LoadSessionDialog: React.FC<{open: boolean, onClose: () => void}> = ({open, onClose}) => { + const [sessions, setSessions] = useState<{name: string, saved_at: string}[]>([]); + const [loading, setLoading] = useState(false); + const [confirmDelete, setConfirmDelete] = useState(null); + const dispatch = useDispatch(); + + useEffect(() => { + if (!open) return; + (async () => { + try { + const res = await fetchWithIdentity(getUrls().SESSION_LIST); + const data = await res.json(); + if (data.status === 'ok') setSessions(data.sessions); + } catch (e) { /* ignore */ } + })(); + }, [open]); + + const handleLoad = async (name: string) => { + setLoading(true); + try { + const res = await fetchWithIdentity(getUrls().SESSION_LOAD, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name }), + }); + const data = await res.json(); + if (data.status === 'ok') { + dispatch(dfActions.loadState(data.state)); + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "success", value: `Session "${name}" loaded` })); + onClose(); + } else { + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "error", value: data.message || 'Load failed' })); + } + } catch (e) { + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "error", value: 'Failed to load session' })); + } + setLoading(false); + }; + + const handleDelete = async (name: string) => { + try { + const res = await fetchWithIdentity(getUrls().SESSION_DELETE, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name }), + }); + const data = await res.json(); + if (data.status === 'ok') { + setSessions(prev => prev.filter(s => s.name !== name)); + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "success", value: `Session "${name}" deleted` })); + } + } catch (e) { /* ignore */ } + setConfirmDelete(null); + }; + + return ( + + Load Session + + {sessions.length === 0 ? ( + No saved sessions found. + ) : ( + sessions.map(s => ( + handleLoad(s.name)} + > + + {s.name} + + {new Date(s.saved_at).toLocaleString()} + + + {confirmDelete === s.name ? ( + e.stopPropagation()}> + + + + ) : ( + + { e.stopPropagation(); setConfirmDelete(s.name); }}> + + + + )} + + )) + )} + + + + + + ); +}; + const SessionMenu: React.FC = () => { const [anchorEl, setAnchorEl] = useState(null); + const [saveDialogOpen, setSaveDialogOpen] = useState(false); + const [loadDialogOpen, setLoadDialogOpen] = useState(false); + const [recentSessions, setRecentSessions] = useState<{name: string, saved_at: string}[]>([]); + const [exporting, setExporting] = useState(false); + const importRef = React.useRef(null); const open = Boolean(anchorEl); - const identity = useSelector((state: DataFormulatorState) => state.identity); - const tables = useSelector((state: DataFormulatorState) => state.tables); - const theme = useTheme(); - const dispatch = useDispatch(); + + const fullState = useSelector((state: DataFormulatorState) => { + const excludedFields = new Set([ + 'models', 'selectedModelId', 'testedModels', + 'dataLoaderConnectParams', 'identity', 'agentRules', 'serverConfig', + ]); + const obj: any = {}; + for (const [key, value] of Object.entries(state)) { + if (!excludedFields.has(key)) obj[key] = value; + } + return obj; + }); + + // Fetch recent sessions when the menu opens + useEffect(() => { + if (!open) return; + (async () => { + try { + const res = await fetchWithIdentity(getUrls().SESSION_LIST); + const data = await res.json(); + if (data.status === 'ok') setRecentSessions(data.sessions.slice(0, 3)); + } catch (e) { /* ignore */ } + })(); + }, [open]); + + const closeMenu = () => setAnchorEl(null); + + const handleLoadSession = async (name: string) => { + closeMenu(); + try { + const res = await fetchWithIdentity(getUrls().SESSION_LOAD, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name }), + }); + const data = await res.json(); + if (data.status === 'ok') { + dispatch(dfActions.loadState(data.state)); + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "success", value: `Session "${name}" loaded` })); + } else { + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "error", value: data.message || 'Load failed' })); + } + } catch (e) { + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "error", value: 'Failed to load session' })); + } + }; + + const handleExport = async () => { + closeMenu(); + setExporting(true); + try { + const res = await fetchWithIdentity(getUrls().SESSION_EXPORT, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ state: fullState }), + }); + if (!res.ok) throw new Error('Export failed'); + const blob = await res.blob(); + const disposition = res.headers.get('content-disposition'); + const match = disposition?.match(/filename="?(.+?)"?$/); + const filename = match?.[1] || 'session.dfsession'; + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = filename; + a.click(); + URL.revokeObjectURL(a.href); + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "success", value: "Session exported" })); + } catch (e) { + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "error", value: 'Failed to export session' })); + } + setExporting(false); + }; + + const handleImport = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (!file) return; + closeMenu(); + try { + const formData = new FormData(); + formData.append('file', file); + const res = await fetchWithIdentity(getUrls().SESSION_IMPORT, { + method: 'POST', + body: formData, + }); + const data = await res.json(); + if (data.status === 'ok') { + dispatch(dfActions.loadState(data.state)); + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "success", value: `Session imported from ${file.name}` })); + } else { + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "error", value: data.message || 'Import failed' })); + } + } catch (e) { + dispatch(dfActions.addMessages({ timestamp: Date.now(), component: "Session", type: "error", value: 'Failed to import session' })); + } + if (importRef.current) importRef.current.value = ''; + }; + return ( <> setAnchorEl(null)} - slotProps={{ - paper: { sx: { py: '4px', px: '8px' } } - }} - aria-labelledby="session-menu-button" - sx={{ '& .MuiMenuItem-root': { padding: 0, margin: 0 } }} + onClose={closeMenu} + slotProps={{ paper: { sx: { minWidth: 200 } } }} > - {}}> - + { setSaveDialogOpen(true); closeMenu(); }} + sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }}> + Save session - {}}> - + { setLoadDialogOpen(true); closeMenu(); }} + sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }}> + Open session... - database file - {tables.some(t => t.virtual) && - - This session contains data stored in the database, export and reload the database to resume the session later. - } - t.virtual)} onClick={() => { - handleDBDownload(identity.id); - }}> - + + {recentSessions.length > 0 && [ + , + + Quick resume + , + ...recentSessions.map(s => ( + handleLoadSession(s.name)} + sx={{ pl: 4, py: 0.25, minHeight: 0, fontSize: '12px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 2 }}> + {s.name} + + {new Date(s.saved_at).toLocaleDateString()} + + + )), + ]} + + + + {exporting ? 'Exporting...' : 'Export to file'} - {}}> - + importRef.current?.click()} + sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }}> + Import from file + - + setSaveDialogOpen(false)} /> + setLoadDialogOpen(false)} /> ); }; @@ -342,13 +500,13 @@ const ResetDialog: React.FC = () => { onClick={() => setOpen(true)} endIcon={} > - Reset + Exit setOpen(false)} open={open}> - Reset Session? + Exit Session? - All unexported content (charts, derived data, concepts) will be lost upon reset. + All unsaved content (data, charts, reports) will be lost. Make sure to save your session before exiting. @@ -370,7 +528,7 @@ const ResetDialog: React.FC = () => { }} endIcon={} > - reset session + exit session diff --git a/src/app/dfSlice.tsx b/src/app/dfSlice.tsx index 46b4f079..b2541f47 100644 --- a/src/app/dfSlice.tsx +++ b/src/app/dfSlice.tsx @@ -367,41 +367,39 @@ export const dataFormulatorSlice = createSlice({ }, loadState: (state, action: PayloadAction) => { - - let savedState = action.payload; - - // models should not be loaded again, especially they may be from others - state.agentRules = state.agentRules || initialState.agentRules; - state.models = state.models || []; - state.selectedModelId = state.selectedModelId || undefined; - state.testedModels = state.testedModels || []; - state.dataLoaderConnectParams = state.dataLoaderConnectParams || {}; - state.serverConfig = initialState.serverConfig; - - //state.table = undefined; - state.tables = savedState.tables || []; - state.charts = savedState.charts || []; - - state.conceptShelfItems = savedState.conceptShelfItems || []; - - state.messages = []; - state.displayedMessageIdx = -1; - - state.focusedDataCleanBlockId = savedState.focusedDataCleanBlockId || undefined; - - state.focusedTableId = savedState.focusedTableId || undefined; - state.focusedChartId = savedState.focusedChartId || undefined; - - state.chartSynthesisInProgress = []; - - state.config = { ...initialState.config, ...(savedState.config || {}) }; - - state.dataCleanBlocks = savedState.dataCleanBlocks || []; - state.cleanInProgress = false; - - state.agentActions = savedState.agentActions || []; - - state.generatedReports = savedState.generatedReports || []; + const saved = action.payload; + + // Return a brand-new state object so Immer skips + // recursive proxy / freeze on potentially huge table rows. + return { + // Preserve local-only / sensitive fields from current state + identity: state.identity, + agentRules: state.agentRules || initialState.agentRules, + models: state.models || [], + selectedModelId: state.selectedModelId || undefined, + testedModels: state.testedModels || [], + dataLoaderConnectParams: state.dataLoaderConnectParams || {}, + serverConfig: initialState.serverConfig, + + // Restore from saved payload + tables: saved.tables || [], + charts: saved.charts || [], + conceptShelfItems: saved.conceptShelfItems || [], + focusedDataCleanBlockId: saved.focusedDataCleanBlockId || undefined, + focusedTableId: saved.focusedTableId || undefined, + focusedChartId: saved.focusedChartId || undefined, + config: { ...initialState.config, ...(saved.config || {}) }, + dataCleanBlocks: saved.dataCleanBlocks || [], + agentActions: saved.agentActions || [], + generatedReports: saved.generatedReports || [], + + // Reset transient fields + messages: [], + displayedMessageIdx: -1, + viewMode: saved.viewMode || 'editor', + chartSynthesisInProgress: [], + cleanInProgress: false, + }; }, updateAgentWorkInProgress: (state, action: PayloadAction<{actionId: string, tableId?: string, description: string, status: 'running' | 'completed' | 'warning' | 'failed', hidden: boolean}>) => { if (state.agentActions.some(a => a.actionId == action.payload.actionId)) { diff --git a/src/app/utils.tsx b/src/app/utils.tsx index a8eb7b5d..f4a550da 100644 --- a/src/app/utils.tsx +++ b/src/app/utils.tsx @@ -67,6 +67,14 @@ export function getUrls() { // Refresh data endpoint REFRESH_DERIVED_DATA: `/api/agent/refresh-derived-data`, + + // Session management + SESSION_SAVE: `/api/sessions/save`, + SESSION_LIST: `/api/sessions/list`, + SESSION_LOAD: `/api/sessions/load`, + SESSION_DELETE: `/api/sessions/delete`, + SESSION_EXPORT: `/api/sessions/export`, + SESSION_IMPORT: `/api/sessions/import`, }; } From 51c5c4cf5a56dbfa89ec9e9c3f8182647b2c6b1b Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Tue, 10 Feb 2026 18:12:27 -0800 Subject: [PATCH 38/50] fix some issues with dataloaders --- py-src/data_formulator/app.py | 33 +- .../data_loader/athena_data_loader.py | 85 +- .../data_loader/azure_blob_data_loader.py | 38 +- .../data_loader/bigquery_data_loader.py | 46 +- .../data_loader/kusto_data_loader.py | 43 +- .../data_loader/mongodb_data_loader.py | 39 +- .../data_loader/mssql_data_loader.py | 120 ++- .../data_loader/mysql_data_loader.py | 99 ++- .../data_loader/postgresql_data_loader.py | 68 +- .../data_loader/s3_data_loader.py | 35 +- py-src/data_formulator/datalake/__init__.py | 4 +- py-src/data_formulator/datalake/workspace.py | 35 +- py-src/data_formulator/session_routes.py | 99 ++- py-src/data_formulator/tables_routes.py | 27 + src/app/App.tsx | 30 +- src/app/dfSlice.tsx | 1 + src/app/tableThunks.ts | 15 + src/app/utils.tsx | 3 + src/views/DBTableManager.tsx | 768 ++++++++---------- src/views/DataFormulator.tsx | 10 +- src/views/UnifiedDataUploadDialog.tsx | 129 ++- 21 files changed, 794 insertions(+), 933 deletions(-) diff --git a/py-src/data_formulator/app.py b/py-src/data_formulator/app.py index 7e4c2638..1b032e61 100644 --- a/py-src/data_formulator/app.py +++ b/py-src/data_formulator/app.py @@ -56,6 +56,7 @@ def default(self, obj): 'disable_file_upload': os.environ.get('DISABLE_FILE_UPLOAD', 'false').lower() == 'true', 'project_front_page': os.environ.get('PROJECT_FRONT_PAGE', 'false').lower() == 'true', 'max_display_rows': int(os.environ.get('MAX_DISPLAY_ROWS', '5000')), + 'data_dir': os.environ.get('DATA_FORMULATOR_HOME', None), } # Get logger for this module (logging config moved to run_app function) @@ -79,7 +80,7 @@ def configure_logging(): app.logger.addHandler(handler) -def _register_blueprints(disable_database: bool): +def _register_blueprints(): """ Import and register blueprints. This is where heavy imports happen. Called from run_app() with progress feedback. @@ -100,8 +101,7 @@ def _register_blueprints(disable_database: bool): demo_stream_limiter.init_app(app) # Register blueprints - if not disable_database: - app.register_blueprint(tables_bp) + app.register_blueprint(tables_bp) app.register_blueprint(agent_bp) app.register_blueprint(session_bp) app.register_blueprint(demo_stream_bp) @@ -140,21 +140,12 @@ def get_app_config(): "PROJECT_FRONT_PAGE": args['project_front_page'], "MAX_DISPLAY_ROWS": args['max_display_rows'], } - return flask.jsonify(config) -@app.route('/api/tables/', methods=['GET', 'POST']) -def database_disabled_fallback(path): - """Fallback route for table endpoints when database is disabled""" - if app.config['CLI_ARGS']['disable_database']: - return flask.jsonify({ - "status": "error", - "message": "Database functionality is disabled. Use --disable-database=false to enable table operations." - }), 503 - else: - return flask.jsonify({ - "status": "error", - "message": "Table routes are not available" - }), 404 + if not args['disable_database']: + from data_formulator.datalake.workspace import get_data_formulator_home + config["DATA_FORMULATOR_HOME"] = str(get_data_formulator_home()) + + return flask.jsonify(config) def parse_args() -> argparse.Namespace: @@ -165,7 +156,8 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--disable-display-keys", action='store_true', default=False, help="Whether disable displaying keys in the frontend UI, recommended to turn on if you host the app not just for yourself.") parser.add_argument("--disable-database", action='store_true', default=False, - help="Disable database functionality and table routes. This prevents creation of local database files and disables table-related endpoints.") + help="Disable server-side data persistence. Data loaders and table routes remain available but data is not saved to disk. " + "The frontend forces local-only mode (storeOnServer=false) so all table data lives in the browser.") parser.add_argument("--disable-file-upload", action='store_true', default=False, help="Disable file upload functionality. This prevents the app from uploading files to the server.") parser.add_argument("--project-front-page", action='store_true', default=False, @@ -173,6 +165,8 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--max-display-rows", type=int, default=int(os.environ.get('MAX_DISPLAY_ROWS', '10000')), help="Maximum number of rows to send to the frontend for display (default: 10000)") + parser.add_argument("--data-dir", type=str, default=None, + help="Data Formulator home directory for workspaces and sessions (default: ~/.data_formulator)") parser.add_argument("--dev", action='store_true', default=False, help="Launch the app in development mode (prevents the app from opening the browser automatically)") return parser.parse_args() @@ -192,10 +186,11 @@ def run_app(): 'disable_file_upload': args.disable_file_upload, 'project_front_page': args.project_front_page, 'max_display_rows': args.max_display_rows, + 'data_dir': args.data_dir, } # Register blueprints (this is where heavy imports happen) - _register_blueprints(args.disable_database) + _register_blueprints() url = "http://localhost:{0}".format(args.port) print(f"Ready! Open {url} in your browser.", flush=True) diff --git a/py-src/data_formulator/data_loader/athena_data_loader.py b/py-src/data_formulator/data_loader/athena_data_loader.py index 1d546513..70d0fcc1 100644 --- a/py-src/data_formulator/data_loader/athena_data_loader.py +++ b/py-src/data_formulator/data_loader/athena_data_loader.py @@ -74,80 +74,17 @@ def list_params() -> list[dict[str, Any]]: @staticmethod def auth_instructions() -> str: - return """ -**Authentication Options (choose one):** - -**Option 1 - AWS Profile (Recommended):** -- **AWS Profile**: Profile name from ~/.aws/credentials (e.g., 'default', 'myprofile') -- Configure profiles via `aws configure --profile myprofile` -- No need to enter access key or secret when using a profile - -**Option 2 - Explicit Credentials:** -- **AWS Access Key ID**: Your AWS access key identifier -- **AWS Secret Access Key**: Your AWS secret access key -- **AWS Session Token**: Optional, for temporary credentials only - -**Common Parameters:** -- **Region Name**: AWS region (e.g., 'us-east-1', 'ap-southeast-5') -- **Workgroup**: Athena workgroup name (default: 'primary') -- **Output Location**: S3 path for query results (e.g., 's3://my-bucket/athena-results/'). If empty, uses workgroup configuration. -- **Database**: Optional default database/catalog for queries -- **Query Timeout**: Query execution timeout in seconds (default: 300 = 5 minutes) - -**Setting up AWS Profile:** -```bash -aws configure --profile myprofile -# Enter: Access Key ID, Secret Access Key, Region, Output format -``` - -**Required Permissions:** -```json -{ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "athena:StartQueryExecution", - "athena:GetQueryExecution", - "athena:GetQueryResults", - "athena:GetWorkGroup", - "athena:ListDatabases", - "athena:ListTableMetadata" - ], - "Resource": "*" - }, - { - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:ListBucket", - "s3:GetBucketLocation", - "s3:PutObject" - ], - "Resource": [ - "arn:aws:s3:::your-athena-results-bucket", - "arn:aws:s3:::your-athena-results-bucket/*", - "arn:aws:s3:::your-data-bucket", - "arn:aws:s3:::your-data-bucket/*" - ] - }, - { - "Effect": "Allow", - "Action": [ - "glue:GetDatabase", - "glue:GetDatabases", - "glue:GetTable", - "glue:GetTables" - ], - "Resource": "*" - } - ] -} -``` - -**Security:** Never share secret keys, rotate regularly, use least privilege permissions. - """ + return """**Example (profile):** aws_profile: `default` · region_name: `us-east-1` · workgroup: `primary` · database: `my_database` + +**Example (keys):** aws_access_key_id: `AKIA...` · aws_secret_access_key: `wJalr...` · region_name: `us-east-1` + +**Option 1 — AWS Profile (recommended):** +Set `aws_profile` to a profile name from `~/.aws/credentials`. Set up with `aws configure --profile `. No access key or secret needed. + +**Option 2 — Explicit Credentials:** +Enter `aws_access_key_id` and `aws_secret_access_key` directly. Add `aws_session_token` for temporary credentials. + +**Required IAM permissions:** `athena:StartQueryExecution`, `athena:GetQueryExecution`, `athena:GetQueryResults`, `athena:GetWorkGroup`, `athena:ListDatabases`, `athena:ListTableMetadata`, plus S3 and Glue permissions on your data/results buckets.""" def __init__(self, params: dict[str, Any]): self.params = params diff --git a/py-src/data_formulator/data_loader/azure_blob_data_loader.py b/py-src/data_formulator/data_loader/azure_blob_data_loader.py index 3c8bdf2d..fb078e4b 100644 --- a/py-src/data_formulator/data_loader/azure_blob_data_loader.py +++ b/py-src/data_formulator/data_loader/azure_blob_data_loader.py @@ -30,39 +30,23 @@ def list_params() -> list[dict[str, Any]]: @staticmethod def auth_instructions() -> str: - return """Authentication Options (choose one) + return """**Example (conn string):** connection_string: `DefaultEndpointsProtocol=https;AccountName=...` · container_name: `mydata` -Option 1 - Connection String (Simplest) - - Get connection string from Azure Portal > Storage Account > Access keys - - Use `connection_string` parameter with full connection string - - `account_name` can be omitted when using connection string +**Example (account key):** account_name: `mystorageacct` · container_name: `mydata` · account_key: `abc123...` -Option 2 - Account Key - - Get account key from Azure Portal > Storage Account > Access keys - - Use `account_name` + `account_key` parameters - - Provides full access to storage account +**Option 1 — Connection String (simplest):** +Get it from Azure Portal → Storage Account → Access keys. Enter in `connection_string`; `account_name` can be omitted. -Option 3 - SAS Token (Recommended for limited access) - - Generate SAS token from Azure Portal > Storage Account > Shared access signature - - Use `account_name` + `sas_token` parameters - - Can be time-limited and permission-scoped +**Option 2 — Account Key:** +From Azure Portal → Storage Account → Access keys. Use `account_name` + `account_key`. -Option 4 - Credential Chain (Most Secure) - - Use `account_name` + `container_name` only (no explicit credentials) - - Requires Azure CLI login (`az login` in terminal) or Managed Identity - - Default chain: `cli;managed_identity;env` - - Customize with `credential_chain` parameter +**Option 3 — SAS Token (recommended for limited access):** +Generate from Azure Portal → Storage Account → Shared access signature. Use `account_name` + `sas_token`. Can be time-limited and permission-scoped. -Additional Options - - `endpoint`: Custom endpoint (default: `blob.core.windows.net`) - - For Azure Government: `blob.core.usgovcloudapi.net` - - For Azure China: `blob.core.chinacloudapi.cn` +**Option 4 — Azure CLI / Managed Identity (most secure):** +Just provide `account_name` + `container_name`. Requires `az login` or Managed Identity. -Supported File Formats: - - CSV files (.csv) - - Parquet files (.parquet) - - JSON files (.json, .jsonl) -""" +**Supported formats:** CSV, Parquet, JSON, JSONL""" def __init__(self, params: dict[str, Any]): self.params = params diff --git a/py-src/data_formulator/data_loader/bigquery_data_loader.py b/py-src/data_formulator/data_loader/bigquery_data_loader.py index e9c6807b..687e95ae 100644 --- a/py-src/data_formulator/data_loader/bigquery_data_loader.py +++ b/py-src/data_formulator/data_loader/bigquery_data_loader.py @@ -24,42 +24,16 @@ def list_params() -> list[dict[str, Any]]: @staticmethod def auth_instructions() -> str: - return """BigQuery Authentication Instructions - -Authentication Options (choose one): - -Option 1 - Application Default Credentials (Recommended) - - Install Google Cloud SDK: https://cloud.google.com/sdk/docs/install - - Run `gcloud auth application-default login` in your terminal - - Leave `credentials_path` parameter empty - - Requires Google Cloud Project ID - -Option 2 - Service Account Key File - - Create a service account in Google Cloud Console - - Download the JSON key file - - Provide the full path to the JSON file in `credentials_path` parameter - - Grant the service account BigQuery Data Viewer role (or appropriate permissions) - -Option 3 - Environment Variables - - Set GOOGLE_APPLICATION_CREDENTIALS environment variable to point to your service account JSON file - - Leave `credentials_path` parameter empty - -Required Permissions: - - BigQuery Data Viewer (for reading data) - - BigQuery Job User (for running queries) - -Parameters: - - project_id: Your Google Cloud Project ID (required) - - dataset_id: Specific dataset to browse (optional - leave empty to see all datasets) - - location: BigQuery location/region (default: US) - - credentials_path: Path to service account JSON file (optional) - -Supported Operations: - - Browse datasets and tables - - Preview table schemas and data - - Import data from tables - - Execute custom SQL queries -""" + return """**Example:** project_id: `my-gcp-project` · dataset_id: `analytics` · credentials_path: `/path/to/key.json` · location: `US` + +**Option 1 — Application Default Credentials (recommended):** +Install [Google Cloud SDK](https://cloud.google.com/sdk/docs/install), then run `gcloud auth application-default login`. Leave `credentials_path` empty. + +**Option 2 — Service Account Key File:** +Create a service account in Google Cloud Console, download the JSON key, and enter the full path in `credentials_path`. Grant the account **BigQuery Data Viewer** and **BigQuery Job User** roles. + +**Option 3 — Environment Variable:** +Set `GOOGLE_APPLICATION_CREDENTIALS` to your service account JSON file path. Leave `credentials_path` empty.""" def __init__(self, params: dict[str, Any]): self.params = params diff --git a/py-src/data_formulator/data_loader/kusto_data_loader.py b/py-src/data_formulator/data_loader/kusto_data_loader.py index ae893f68..ace6dbf6 100644 --- a/py-src/data_formulator/data_loader/kusto_data_loader.py +++ b/py-src/data_formulator/data_loader/kusto_data_loader.py @@ -16,40 +16,25 @@ class KustoDataLoader(ExternalDataLoader): @staticmethod def list_params() -> list[dict[str, Any]]: params_list = [ - {"name": "kusto_cluster", "type": "string", "required": True, "description": ""}, - {"name": "kusto_database", "type": "string", "required": True, "description": ""}, - {"name": "client_id", "type": "string", "required": False, "description": "only necessary for AppKey auth"}, - {"name": "client_secret", "type": "string", "required": False, "description": "only necessary for AppKey auth"}, - {"name": "tenant_id", "type": "string", "required": False, "description": "only necessary for AppKey auth"} + {"name": "kusto_cluster", "type": "string", "required": True, "description": "e.g., https://mycluster.region.kusto.windows.net"}, + {"name": "kusto_database", "type": "string", "required": True, "description": "database name"}, + {"name": "client_id", "type": "string", "required": False, "description": "only for App Key auth"}, + {"name": "client_secret", "type": "string", "required": False, "description": "only for App Key auth"}, + {"name": "tenant_id", "type": "string", "required": False, "description": "only for App Key auth"} ] return params_list @staticmethod def auth_instructions() -> str: - return """Azure Kusto Authentication Instructions - -Method 1: Azure CLI Authentication - 1. Install Azure CLI: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli - 2. Run `az login` in your terminal to authenticate - 3. Ensure you have access to the specified Kusto cluster and database - 4. Leave client_id, client_secret, and tenant_id parameters empty - -Method 2: Application Key Authentication - 1. Register an Azure AD application in your tenant - 2. Generate a client secret for the application - 3. Grant the application appropriate permissions to your Kusto cluster: - - Go to your Kusto cluster in Azure Portal - - Navigate to Permissions > Add - - Add your application as a user with appropriate role (e.g., "AllDatabasesViewer" for read access) - 4. Provide the following parameters: - - client_id: Application (client) ID from your Azure AD app registration - - client_secret: Client secret value you generated - - tenant_id: Directory (tenant) ID from your Azure AD - -Required Parameters: - - kusto_cluster: Your Kusto cluster URI (e.g., "https://mycluster.region.kusto.windows.net") - - kusto_database: Name of the database you want to access -""" + return """**Example (CLI):** kusto_cluster: `https://mycluster.westus.kusto.windows.net` · kusto_database: `mydb` + +**Example (App Key):** kusto_cluster: `https://mycluster.westus.kusto.windows.net` · kusto_database: `mydb` · client_id: `abc-123...` · client_secret: `xyz...` · tenant_id: `def-456...` + +**Option 1 — Azure CLI (recommended):** +Run `az login` in your terminal. Leave `client_id`, `client_secret`, and `tenant_id` empty. + +**Option 2 — App Key Authentication:** +Register an Azure AD application, generate a client secret, and grant it access to your Kusto cluster (e.g., "AllDatabasesViewer" role via Azure Portal → Kusto cluster → Permissions). Provide `client_id`, `client_secret`, and `tenant_id`.""" def __init__(self, params: dict[str, Any]): self.params = params diff --git a/py-src/data_formulator/data_loader/mongodb_data_loader.py b/py-src/data_formulator/data_loader/mongodb_data_loader.py index cf8d84e6..7d31327a 100644 --- a/py-src/data_formulator/data_loader/mongodb_data_loader.py +++ b/py-src/data_formulator/data_loader/mongodb_data_loader.py @@ -18,42 +18,25 @@ class MongoDBDataLoader(ExternalDataLoader): @staticmethod def list_params() -> list[dict[str, Any]]: params_list = [ - {"name": "host", "type": "string", "required": True, "default": "localhost", "description": ""}, - {"name": "port", "type": "int", "required": False, "default": 27017, "description": "MongoDB server port (default 27017)"}, - {"name": "username", "type": "string", "required": False, "default": "", "description": ""}, - {"name": "password", "type": "string", "required": False, "default": "", "description": ""}, - {"name": "database", "type": "string", "required": True, "default": "", "description": ""}, - {"name": "collection", "type": "string", "required": False, "default": "", "description": "If specified, only this collection will be accessed"}, - {"name": "authSource", "type": "string", "required": False, "default": "", "description": "Authentication database (defaults to target database if empty)"} + {"name": "host", "type": "string", "required": True, "default": "localhost", "description": "server address"}, + {"name": "port", "type": "int", "required": False, "default": 27017, "description": "server port"}, + {"name": "username", "type": "string", "required": False, "default": "", "description": "leave blank if no auth"}, + {"name": "password", "type": "string", "required": False, "default": "", "description": "leave blank if no auth"}, + {"name": "database", "type": "string", "required": True, "default": "", "description": "database name"}, + {"name": "collection", "type": "string", "required": False, "default": "", "description": "leave empty to list all collections"}, + {"name": "authSource", "type": "string", "required": False, "default": "", "description": "auth database (defaults to target database)"} ] return params_list @staticmethod def auth_instructions() -> str: - return """ -MongoDB Connection Instructions: + return """**Example:** host: `localhost` · port: `27017` · database: `mydb` · collection: `users` -1. Local MongoDB Setup: - - Ensure MongoDB server is running on your machine - - Default connection: host='localhost', port=27017 - - If authentication is not enabled, leave username and password empty +**Local setup:** Ensure MongoDB is running. Leave username and password blank if authentication is not enabled. -2. Remote MongoDB Connection: - - Obtain host address, port, username, and password from your database administrator - - Ensure the MongoDB server allows remote connections +**Remote setup:** Get host, port, username, and password from your database administrator. -3. Common Connection Parameters: - - host: MongoDB server address (default: 'localhost') - - port: MongoDB server port (default: 27017) - - username: Your MongoDB username (leave empty if no auth) - - password: Your MongoDB password (leave empty if no auth) - - database: Target database name to connect to - - collection: (Optional) Specific collection to access, leave empty to list all collections - -4. Troubleshooting: - - Verify MongoDB service is running: `mongod --version` - - Test connection: `mongosh --host [host] --port [port]` -""" +**Troubleshooting:** Test with `mongosh --host --port `""" def __init__(self, params: dict[str, Any]): self.params = params diff --git a/py-src/data_formulator/data_loader/mssql_data_loader.py b/py-src/data_formulator/data_loader/mssql_data_loader.py index c73e93a8..9bec55d0 100644 --- a/py-src/data_formulator/data_loader/mssql_data_loader.py +++ b/py-src/data_formulator/data_loader/mssql_data_loader.py @@ -83,60 +83,18 @@ def list_params() -> list[dict[str, Any]]: @staticmethod def auth_instructions() -> str: - return """ -SQL Server Connection Instructions: - -1. Prerequisites: - - Install connectorx: pip install connectorx (used for fast Arrow-native data access) - - Install ODBC stack for connectorx: - * macOS: brew install unixodbc - * Linux: sudo apt-get install unixodbc-dev (Ubuntu/Debian) or sudo yum install unixODBC-devel (CentOS/RHEL) - * Windows: Usually included with ODBC driver installation - - Install Microsoft ODBC Driver for SQL Server: - * Windows: Usually pre-installed with SQL Server - * macOS: brew tap microsoft/mssql-release && brew install msodbcsql17 - * Linux: Install via package manager (msodbcsql17 or msodbcsql18) - -2. Local SQL Server Setup: - - Ensure SQL Server is running on your machine - - Enable SQL Server Authentication if using username/password - - Default connection: server='localhost' or '.' or '(local)' - -3. Connection Parameters: - - server: SQL Server instance (localhost, server\\instance, or IP address) - - database: Target database name (default: master) - - user: SQL Server username (leave empty for Windows Authentication) - - password: SQL Server password (leave empty for Windows Authentication) - - port: SQL Server port (default: 1433) - - driver: ODBC driver name (default: 'ODBC Driver 17 for SQL Server') - -4. Authentication Methods: - - Windows Authentication: Leave user/password empty (recommended for local development) - - SQL Server Authentication: Provide username and password - - Azure AD Authentication: Use appropriate connection parameters - -5. Connection Examples: - - Local default instance: server='localhost' or server='.' - - Named instance: server='localhost\\SQLEXPRESS' - - Remote server: server='192.168.1.100' or server='sql-server.company.com' - - Custom port: server='localhost,1434' (note the comma, not colon) - -6. Common Issues & Troubleshooting: - - If connectorx fails: Install unixodbc first (macOS/Linux) - - Ensure SQL Server service is running - - Check SQL Server Browser service for named instances - - Verify TCP/IP protocol is enabled in SQL Server Configuration Manager - - Check Windows Firewall settings for SQL Server port - - Test connection: sqlcmd -S server -d database -U username -P password - - For named instances, ensure SQL Server Browser service is running - - Check ODBC drivers: odbcinst -q -d (on Unix/Linux) - -7. Driver Installation: - - macOS: brew install msodbcsql17 or download from Microsoft - - Ubuntu/Debian: sudo apt-get install msodbcsql17 - - CentOS/RHEL: sudo yum install msodbcsql17 - - Windows: Install SQL Server or download ODBC driver separately -""" + return """**Example (SQL auth):** server: `localhost` · database: `mydb` · user: `sa` · password: `MyP@ss` · port: `1433` + +**Example (Windows auth):** server: `localhost\\SQLEXPRESS` · database: `mydb` (leave user/password empty) + +**Prerequisites (macOS/Linux only):** +Install ODBC driver: `brew install unixodbc msodbcsql17` (macOS) or `sudo apt-get install unixodbc-dev msodbcsql17` (Ubuntu/Debian). Windows usually has these pre-installed. + +**Authentication:** +- **Windows Auth:** Leave user/password empty (recommended for local dev) +- **SQL Server Auth:** Provide username and password + +**Troubleshooting:** Ensure SQL Server service is running. Verify TCP/IP is enabled in SQL Server Configuration Manager. Test with `sqlcmd -S -d -U -P `.""" def __init__(self, params: dict[str, Any]): log.info(f"Initializing MSSQL DataLoader with parameters: {params}") @@ -165,6 +123,43 @@ def __init__(self, params: dict[str, Any]): log.error(f"Failed to connect to SQL Server: {e}") raise ValueError(f"Failed to connect to SQL Server '{self.server}': {e}") from e + # SQL Server types that connectorx cannot handle natively + _CX_SPATIAL_TYPES = {'geometry', 'geography'} # use .STAsText() + _CX_OTHER_UNSUPPORTED = {'hierarchyid', 'xml', 'sql_variant', 'image', 'timestamp'} + _CX_UNSUPPORTED_TYPES = _CX_SPATIAL_TYPES | _CX_OTHER_UNSUPPORTED + + def _safe_select_list(self, schema: str, table_name: str) -> str: + """Build a SELECT column list that converts unsupported types to text. + Uses .STAsText() for spatial types, CAST(... AS NVARCHAR(MAX)) for others. + Returns '*' if no unsupported columns are found.""" + try: + columns_query = f""" + SELECT COLUMN_NAME, DATA_TYPE + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = '{schema}' AND TABLE_NAME = '{table_name}' + ORDER BY ORDINAL_POSITION + """ + cols_df = self._execute_query_raw(columns_query).to_pandas() + has_unsupported = any(r['DATA_TYPE'].lower() in self._CX_UNSUPPORTED_TYPES for _, r in cols_df.iterrows()) + if not has_unsupported: + return "*" + parts = [] + for _, r in cols_df.iterrows(): + col, dtype = r['COLUMN_NAME'], r['DATA_TYPE'].lower() + if dtype in self._CX_SPATIAL_TYPES: + parts.append(f"[{col}].STAsText() AS [{col}]") + elif dtype in self._CX_OTHER_UNSUPPORTED: + parts.append(f"CAST([{col}] AS NVARCHAR(MAX)) AS [{col}]") + else: + parts.append(f"[{col}]") + return ', '.join(parts) + except Exception: + return "*" + + def _execute_query_raw(self, query: str) -> pa.Table: + """Execute a query via connectorx (no error wrapping).""" + return cx.read_sql(self.connection_url, query, return_type="arrow") + def _execute_query(self, query: str) -> pa.Table: """Execute a query and return results as a PyArrow Table (via connectorx).""" try: @@ -193,7 +188,8 @@ def fetch_data_as_arrow( schema = "dbo" table = source_table - base_query = f"SELECT * FROM [{schema}].[{table}]" + col_list = self._safe_select_list(schema.strip('[]'), table.strip('[]')) + base_query = f"SELECT {col_list} FROM [{schema}].[{table}]" # Add ORDER BY if sort columns specified order_by_clause = "" @@ -294,13 +290,14 @@ def list_tables(self, table_filter: str | None = None) -> list[dict[str, Any]]: columns.append(col_info) - # Get sample data (first 10 rows) - sample_query = f"SELECT TOP 10 * FROM [{schema}].[{table_name}]" - sample_df = self._execute_query(sample_query).to_pandas() + # Build safe column list (casts unsupported types to NVARCHAR) + col_list = self._safe_select_list(schema, table_name) - # Handle NaN values in sample data for JSON serialization + # Get sample data (first 10 rows) + sample_rows = [] + sample_query = f"SELECT TOP 10 {col_list} FROM [{schema}].[{table_name}]" try: - # Replace NaN with None for proper JSON serialization + sample_df = self._execute_query(sample_query).to_pandas() sample_df_clean = sample_df.fillna(value=None) sample_rows = json.loads( sample_df_clean.to_json( @@ -309,9 +306,8 @@ def list_tables(self, table_filter: str | None = None) -> list[dict[str, Any]]: ) except Exception as e: log.warning( - f"Failed to serialize sample data for table {schema}.{table_name}: {e}" + f"Failed to sample table {schema}.{table_name}: {e}" ) - sample_rows = [] # Get row count count_query = f"SELECT COUNT(*) as row_count FROM [{schema}].[{table_name}]" diff --git a/py-src/data_formulator/data_loader/mysql_data_loader.py b/py-src/data_formulator/data_loader/mysql_data_loader.py index f10180c9..5909a2d1 100644 --- a/py-src/data_formulator/data_loader/mysql_data_loader.py +++ b/py-src/data_formulator/data_loader/mysql_data_loader.py @@ -16,40 +16,23 @@ class MySQLDataLoader(ExternalDataLoader): @staticmethod def list_params() -> list[dict[str, Any]]: params_list = [ - {"name": "user", "type": "string", "required": True, "default": "root", "description": ""}, + {"name": "user", "type": "string", "required": True, "default": "root", "description": "MySQL username"}, {"name": "password", "type": "string", "required": False, "default": "", "description": "leave blank for no password"}, - {"name": "host", "type": "string", "required": True, "default": "localhost", "description": ""}, - {"name": "port", "type": "int", "required": False, "default": 3306, "description": "MySQL server port (default 3306)"}, - {"name": "database", "type": "string", "required": True, "default": "mysql", "description": ""} + {"name": "host", "type": "string", "required": True, "default": "localhost", "description": "server address"}, + {"name": "port", "type": "int", "required": False, "default": 3306, "description": "server port"}, + {"name": "database", "type": "string", "required": True, "default": "mysql", "description": "database name"} ] return params_list @staticmethod def auth_instructions() -> str: - return """ -MySQL Connection Instructions: - -1. Local MySQL Setup: - - Ensure MySQL server is running on your machine - - Default connection: host='localhost', user='root', port=3306 - - If you haven't set a root password, leave password field empty - -2. Remote MySQL Connection: - - Obtain host address, port, username, and password from your database administrator - - Ensure the MySQL server allows remote connections - - Check that your IP is whitelisted in MySQL's user permissions - -3. Common Connection Parameters: - - user: Your MySQL username (default: 'root') - - password: Your MySQL password (leave empty if no password set) - - host: MySQL server address (default: 'localhost') - - port: MySQL server port (default: 3306) - - database: Target database name to connect to - -4. Troubleshooting: - - Verify MySQL service is running: `brew services list` (macOS) or `sudo systemctl status mysql` (Linux) - - Test connection: `mysql -u [username] -p -h [host] -P [port] [database]` -""" + return """**Example:** user: `root` · host: `localhost` · port: `3306` · database: `mydb` + +**Local setup:** Ensure MySQL is running — `brew services list` (macOS) or `systemctl status mysql` (Linux). Leave password blank if none is set. + +**Remote setup:** Get host, port, username, and password from your database administrator. Ensure the server allows remote connections and your IP is whitelisted. + +**Troubleshooting:** Test with `mysql -u -p -h -P `""" def __init__(self, params: dict[str, Any]): self.params = params @@ -94,6 +77,42 @@ def __init__(self, params: dict[str, Any]): raise ValueError(f"Failed to connect to MySQL database '{self.database}' on host '{self.host}': {e}") from e logger.info(f"Successfully connected to MySQL: mysql://{self.user}:***@{self.host}:{self.port}/{self.database}") + # MySQL types that connectorx cannot handle natively + _CX_GEOMETRY_TYPES = {'geometry', 'point', 'linestring', 'polygon', + 'multipoint', 'multilinestring', 'multipolygon', + 'geometrycollection'} + _CX_OTHER_UNSUPPORTED = {'bit'} + _CX_UNSUPPORTED_TYPES = _CX_GEOMETRY_TYPES | _CX_OTHER_UNSUPPORTED + + def _safe_select_list(self, schema: str, table_name: str) -> str: + """Build a SELECT column list that converts unsupported types to text. + Uses ST_AsText() for geometry types, CAST(... AS CHAR) for others. + Returns '*' if no unsupported columns are found.""" + try: + columns_query = f""" + SELECT COLUMN_NAME, DATA_TYPE + FROM information_schema.columns + WHERE TABLE_SCHEMA = '{schema}' AND TABLE_NAME = '{table_name}' + ORDER BY ORDINAL_POSITION + """ + cols_arrow = cx.read_sql(self.connection_url, columns_query, return_type="arrow") + cols_df = cols_arrow.to_pandas() + has_unsupported = any(r['DATA_TYPE'].lower() in self._CX_UNSUPPORTED_TYPES for _, r in cols_df.iterrows()) + if not has_unsupported: + return "*" + parts = [] + for _, r in cols_df.iterrows(): + col, dtype = r['COLUMN_NAME'], r['DATA_TYPE'].lower() + if dtype in self._CX_GEOMETRY_TYPES: + parts.append(f"ST_AsText(`{col}`) AS `{col}`") + elif dtype in self._CX_OTHER_UNSUPPORTED: + parts.append(f"CAST(`{col}` AS CHAR) AS `{col}`") + else: + parts.append(f"`{col}`") + return ', '.join(parts) + except Exception: + return "*" + def fetch_data_as_arrow( self, source_table: str, @@ -109,11 +128,14 @@ def fetch_data_as_arrow( if not source_table: raise ValueError("source_table must be provided") - # Handle table names + # Handle table names and build safe column list if '.' in source_table: - base_query = f"SELECT * FROM {source_table}" + parts = source_table.split('.', 1) + col_list = self._safe_select_list(parts[0].strip('`'), parts[1].strip('`')) + base_query = f"SELECT {col_list} FROM {source_table}" else: - base_query = f"SELECT * FROM `{source_table}`" + col_list = self._safe_select_list(self.database, source_table.strip('`')) + base_query = f"SELECT {col_list} FROM `{source_table}`" # Add ORDER BY if sort columns specified order_by_clause = "" @@ -177,11 +199,18 @@ def _list_tables_connectorx(self, table_filter: str | None = None) -> list[dict[ 'type': col_row['DATA_TYPE'] } for _, col_row in columns_df.iterrows()] + # Build safe column list (casts unsupported types to CHAR) + col_list = self._safe_select_list(schema, table_name) + # Get sample data - sample_query = f"SELECT * FROM `{schema}`.`{table_name}` LIMIT 10" - sample_arrow = cx.read_sql(self.connection_url, sample_query, return_type="arrow") - sample_df = sample_arrow.to_pandas() - sample_rows = json.loads(sample_df.to_json(orient="records", date_format='iso')) + sample_rows = [] + sample_query = f"SELECT {col_list} FROM `{schema}`.`{table_name}` LIMIT 10" + try: + sample_arrow = cx.read_sql(self.connection_url, sample_query, return_type="arrow") + sample_df = sample_arrow.to_pandas() + sample_rows = json.loads(sample_df.to_json(orient="records", date_format='iso')) + except Exception as sample_err: + logger.warning(f"Could not sample {full_table_name}: {sample_err}") # Get row count count_query = f"SELECT COUNT(*) as cnt FROM `{schema}`.`{table_name}`" diff --git a/py-src/data_formulator/data_loader/postgresql_data_loader.py b/py-src/data_formulator/data_loader/postgresql_data_loader.py index 779aaf84..3bc6b63a 100644 --- a/py-src/data_formulator/data_loader/postgresql_data_loader.py +++ b/py-src/data_formulator/data_loader/postgresql_data_loader.py @@ -26,7 +26,13 @@ def list_params() -> list[dict[str, Any]]: @staticmethod def auth_instructions() -> str: - return "Provide your PostgreSQL connection details. The user must have SELECT permissions on the tables you want to access. Uses connectorx for fast Arrow-native data access." + return """**Example:** user: `postgres` · host: `localhost` · port: `5432` · database: `mydb` + +**Local setup:** Ensure PostgreSQL is running — `brew services list` (macOS) or `systemctl status postgresql` (Linux). Leave password blank if none is set. + +**Remote setup:** Get host, port, username, and password from your database administrator. The user must have SELECT permissions on the tables you want to access. + +**Troubleshooting:** Test with `psql -U -h -p -d `""" def __init__(self, params: dict[str, Any]): self.params = params @@ -60,6 +66,41 @@ def __init__(self, params: dict[str, Any]): raise ValueError(f"Failed to connect to PostgreSQL database '{self.database}' on host '{self.host}': {e}") from e logger.info(f"Successfully connected to PostgreSQL: postgresql://{self.user}:***@{self.host}:{self.port}/{self.database}") + # PostgreSQL types that connectorx cannot handle natively + _CX_SPATIAL_TYPES = {'geometry', 'geography'} # PostGIS types → ST_AsText() + _CX_OTHER_UNSUPPORTED = {'box', 'circle', 'line', 'lseg', 'path', 'point', + 'polygon', 'bit', 'bit varying', 'xml', 'tsvector', 'tsquery'} + _CX_UNSUPPORTED_TYPES = _CX_SPATIAL_TYPES | _CX_OTHER_UNSUPPORTED + + def _safe_select_list(self, schema: str, table_name: str) -> str: + """Build a SELECT column list that converts unsupported types to text. + Uses ST_AsText() for PostGIS types, ::text for others. + Returns '*' if no unsupported columns are found.""" + try: + columns_query = f""" + SELECT column_name, udt_name + FROM information_schema.columns + WHERE table_schema = '{schema}' AND table_name = '{table_name}' + ORDER BY ordinal_position + """ + cols_arrow = cx.read_sql(self.connection_url, columns_query, return_type="arrow") + cols_df = cols_arrow.to_pandas() + has_unsupported = any(r['udt_name'].lower() in self._CX_UNSUPPORTED_TYPES for _, r in cols_df.iterrows()) + if not has_unsupported: + return "*" + parts = [] + for _, r in cols_df.iterrows(): + col, dtype = r['column_name'], r['udt_name'].lower() + if dtype in self._CX_SPATIAL_TYPES: + parts.append(f'ST_AsText("{col}") AS "{col}"') + elif dtype in self._CX_OTHER_UNSUPPORTED: + parts.append(f'"{col}"::text AS "{col}"') + else: + parts.append(f'"{col}"') + return ', '.join(parts) + except Exception: + return "*" + def fetch_data_as_arrow( self, source_table: str, @@ -70,7 +111,7 @@ def fetch_data_as_arrow( """ Fetch data from PostgreSQL as a PyArrow Table using connectorx. - connectorx provides extremely fast Arrow-native database access, + connectorx provides extremely fast Arrow-native data access, typically 2-10x faster than pandas-based approaches. """ if not source_table: @@ -80,7 +121,13 @@ def fetch_data_as_arrow( table_ref = source_table if source_table.startswith("mypostgresdb."): table_ref = source_table[len("mypostgresdb."):] - base_query = f"SELECT * FROM {table_ref}" + # Build safe column list for the resolved schema.table + if '.' in table_ref: + s, t = table_ref.split('.', 1) + col_list = self._safe_select_list(s.strip('"'), t.strip('"')) + else: + col_list = self._safe_select_list('public', table_ref.strip('"')) + base_query = f"SELECT {col_list} FROM {table_ref}" # Add ORDER BY if sort columns specified order_by_clause = "" @@ -150,11 +197,18 @@ def _list_tables_connectorx(self, table_filter: str | None = None) -> list[dict[ 'type': col_row['data_type'] } for _, col_row in columns_df.iterrows()] + # Build safe column list (casts unsupported types to TEXT) + col_list = self._safe_select_list(schema, table_name) + # Get sample data - sample_query = f'SELECT * FROM "{schema}"."{table_name}" LIMIT 10' - sample_arrow = cx.read_sql(self.connection_url, sample_query, return_type="arrow") - sample_df = sample_arrow.to_pandas() - sample_rows = json.loads(sample_df.to_json(orient="records")) + sample_rows = [] + sample_query = f'SELECT {col_list} FROM "{schema}"."{table_name}" LIMIT 10' + try: + sample_arrow = cx.read_sql(self.connection_url, sample_query, return_type="arrow") + sample_df = sample_arrow.to_pandas() + sample_rows = json.loads(sample_df.to_json(orient="records")) + except Exception as sample_err: + logger.warning(f"Could not sample {full_table_name}: {sample_err}") # Get row count count_query = f'SELECT COUNT(*) as cnt FROM "{schema}"."{table_name}"' diff --git a/py-src/data_formulator/data_loader/s3_data_loader.py b/py-src/data_formulator/data_loader/s3_data_loader.py index 7e703be1..82fa8eb1 100644 --- a/py-src/data_formulator/data_loader/s3_data_loader.py +++ b/py-src/data_formulator/data_loader/s3_data_loader.py @@ -29,40 +29,13 @@ def list_params() -> list[dict[str, Any]]: @staticmethod def auth_instructions() -> str: - return """ -**Required AWS Credentials:** -- **AWS Access Key ID**: Your AWS access key identifier -- **AWS Secret Access Key**: Your AWS secret access key -- **Region Name**: AWS region (e.g., 'us-east-1', 'us-west-2') -- **Bucket**: S3 bucket name -- **AWS Session Token**: Optional, for temporary credentials only + return """**Example:** aws_access_key_id: `AKIA...` · aws_secret_access_key: `wJalr...` · region_name: `us-east-1` · bucket: `my-data-bucket` -**Getting Credentials:** -1. AWS Console → IAM → Users → Select user → Security credentials → Create access key -2. Choose "Application running outside AWS" +**Getting credentials:** AWS Console → IAM → Users → Security credentials → Create access key → choose "Application running outside AWS". -**Required S3 Permissions:** -```json -{ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Action": ["s3:GetObject", "s3:ListBucket"], - "Resource": [ - "arn:aws:s3:::your-bucket-name", - "arn:aws:s3:::your-bucket-name/*" - ] - }] -} -``` +**Required permissions:** `s3:GetObject` and `s3:ListBucket` on your bucket. -**Supported File Formats:** -- CSV files (.csv) -- Parquet files (.parquet) -- JSON files (.json, .jsonl) - -**Security:** Never share secret keys, rotate regularly, use least privilege permissions. - """ +**Supported formats:** CSV, Parquet, JSON, JSONL""" def __init__(self, params: dict[str, Any]): self.params = params diff --git a/py-src/data_formulator/datalake/__init__.py b/py-src/data_formulator/datalake/__init__.py index c81f9f6c..7189794e 100644 --- a/py-src/data_formulator/datalake/__init__.py +++ b/py-src/data_formulator/datalake/__init__.py @@ -37,8 +37,8 @@ from data_formulator.datalake.workspace import ( Workspace, WorkspaceWithTempData, + get_data_formulator_home, get_default_workspace_root, - DATALAKE_ROOT_ENV, ) # Metadata types and operations @@ -73,8 +73,8 @@ # Workspace "Workspace", "WorkspaceWithTempData", + "get_data_formulator_home", "get_default_workspace_root", - "DATALAKE_ROOT_ENV", # Metadata "TableMetadata", "ColumnInfo", diff --git a/py-src/data_formulator/datalake/workspace.py b/py-src/data_formulator/datalake/workspace.py index de0cadee..76b2e040 100644 --- a/py-src/data_formulator/datalake/workspace.py +++ b/py-src/data_formulator/datalake/workspace.py @@ -11,7 +11,6 @@ import os import shutil -import tempfile import logging import time from datetime import datetime, timezone @@ -41,23 +40,39 @@ logger = logging.getLogger(__name__) -# Environment variable for configuring workspace root -DATALAKE_ROOT_ENV = "DATALAKE_ROOT" -# Default subdirectory name under temp for workspaces -DEFAULT_WORKSPACE_SUBDIR = "data_formulator_workspaces" +def get_data_formulator_home() -> Path: + """ + Get the Data Formulator home directory. + + Resolution order: + 1. Flask app.config['CLI_ARGS']['data_dir'] (set via --data-dir CLI flag) + 2. DATA_FORMULATOR_HOME environment variable + 3. Default: ~/.data_formulator + """ + # Try Flask app config first (set by --data-dir CLI arg) + try: + from flask import current_app + data_dir = current_app.config.get('CLI_ARGS', {}).get('data_dir') + if data_dir: + return Path(data_dir) + except (RuntimeError, ImportError): + pass + + env_home = os.getenv("DATA_FORMULATOR_HOME") + if env_home: + return Path(env_home) + + return Path.home() / ".data_formulator" def get_default_workspace_root() -> Path: """ Get the default workspace root directory. - Uses DATALAKE_ROOT env variable if set, otherwise uses system temp directory. + Returns DATA_FORMULATOR_HOME / "workspaces". """ - env_root = os.getenv(DATALAKE_ROOT_ENV) - if env_root: - return Path(env_root) - return Path(tempfile.gettempdir()) / DEFAULT_WORKSPACE_SUBDIR + return get_data_formulator_home() / "workspaces" def cleanup_stale_temp_files(workspace_path: Path, max_age_hours: int = 24) -> int: diff --git a/py-src/data_formulator/session_routes.py b/py-src/data_formulator/session_routes.py index 325faccb..bbd4aa4e 100644 --- a/py-src/data_formulator/session_routes.py +++ b/py-src/data_formulator/session_routes.py @@ -27,27 +27,30 @@ from datetime import datetime, timezone from pathlib import Path -from flask import Blueprint, request, jsonify, send_file +from flask import Blueprint, request, jsonify, send_file, current_app from data_formulator.auth import get_identity_id -from data_formulator.datalake.workspace import Workspace +from data_formulator.datalake.workspace import Workspace, get_data_formulator_home logger = logging.getLogger(__name__) + +def _disk_persistence_enabled() -> bool: + """Return True unless --disable-database was passed (no disk persistence).""" + try: + return not current_app.config.get('CLI_ARGS', {}).get('disable_database', False) + except RuntimeError: + return True + session_bp = Blueprint("sessions", __name__, url_prefix="/api/sessions") # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- -_SESSIONS_ROOT_ENV = "SESSIONS_ROOT" - def _get_sessions_root() -> Path: - env = os.getenv(_SESSIONS_ROOT_ENV) - if env: - return Path(env) - return Path.home() / ".data_formulator" / "sessions" + return get_data_formulator_home() / "sessions" def _sanitize(name: str) -> str: @@ -106,6 +109,9 @@ def save_session(): name: str – human-readable session name state: dict – Redux UI state (saved as-is minus sensitive fields) """ + if not _disk_persistence_enabled(): + return jsonify(status="error", message="Session save is disabled (no disk persistence)"), 403 + data = request.get_json(force=True) name: str = data.get("name", "").strip() state: dict = data.get("state") @@ -123,10 +129,11 @@ def save_session(): shutil.rmtree(sess_dir) sess_dir.mkdir(parents=True) - # 1. Copy workspace directory as-is (skip if empty / missing) - ws_path = _get_workspace_path(identity_id) - if ws_path.exists() and any(ws_path.iterdir()): - shutil.copytree(ws_path, sess_dir / "workspace", dirs_exist_ok=True) + # 1. Copy workspace directory as-is (skip if persistence disabled or empty) + if _disk_persistence_enabled(): + ws_path = _get_workspace_path(identity_id) + if ws_path.exists() and any(ws_path.iterdir()): + shutil.copytree(ws_path, sess_dir / "workspace", dirs_exist_ok=True) # 2. Save UI state (strip secrets, keep everything else including rows) clean_state = _strip_sensitive(state) @@ -141,6 +148,9 @@ def save_session(): @session_bp.route("/list", methods=["GET"]) def list_sessions(): """List saved sessions for the current user (sorted newest first).""" + if not _disk_persistence_enabled(): + return jsonify(status="ok", sessions=[]) + identity_id = get_identity_id() user_dir = _identity_dir(identity_id) @@ -171,6 +181,9 @@ def load_session(): Restores the workspace directory and returns the UI state. """ + if not _disk_persistence_enabled(): + return jsonify(status="error", message="Session load is disabled (no disk persistence)"), 403 + data = request.get_json(force=True) name: str = data.get("name", "").strip() if not name: @@ -183,13 +196,14 @@ def load_session(): if not state_file.exists(): return jsonify(status="error", message=f"Session '{name}' not found"), 404 - # 1. Restore workspace (only if the saved session has one) - ws_saved = sess_dir / "workspace" - if ws_saved.exists(): - ws_path = _get_workspace_path(identity_id) - if ws_path.exists(): - shutil.rmtree(ws_path) - shutil.copytree(ws_saved, ws_path, dirs_exist_ok=True) + # 1. Restore workspace (only if persistence enabled and session has one) + if _disk_persistence_enabled(): + ws_saved = sess_dir / "workspace" + if ws_saved.exists(): + ws_path = _get_workspace_path(identity_id) + if ws_path.exists(): + shutil.rmtree(ws_path) + shutil.copytree(ws_saved, ws_path, dirs_exist_ok=True) # 2. Return UI state state = json.loads(state_file.read_text(encoding="utf-8")) @@ -204,6 +218,9 @@ def delete_session(): Request JSON body: name: str – session name to delete """ + if not _disk_persistence_enabled(): + return jsonify(status="error", message="Session delete is disabled (no disk persistence)"), 403 + data = request.get_json(force=True) name: str = data.get("name", "").strip() if not name: @@ -240,13 +257,14 @@ def export_session(): # UI state zf.writestr("state.json", json.dumps(clean_state, default=str)) - # Workspace files - ws_path = _get_workspace_path(identity_id) - if ws_path.exists(): - for ws_file in ws_path.rglob("*"): - if ws_file.is_file(): - arcname = "workspace/" + str(ws_file.relative_to(ws_path)) - zf.write(ws_file, arcname) + # Workspace files (skip if persistence disabled) + if _disk_persistence_enabled(): + ws_path = _get_workspace_path(identity_id) + if ws_path.exists(): + for ws_file in ws_path.rglob("*"): + if ws_file.is_file(): + arcname = "workspace/" + str(ws_file.relative_to(ws_path)) + zf.write(ws_file, arcname) buf.seek(0) filename = f"df_session_{datetime.now().strftime('%Y%m%d_%H%M%S')}.dfsession" @@ -271,20 +289,21 @@ def import_session(): state = json.loads(zf.read("state.json")) - # Restore workspace files (only if the zip contains any) - workspace_entries = [n for n in zf.namelist() - if n.startswith("workspace/") and not n.endswith("/")] - if workspace_entries: - identity_id = get_identity_id() - ws_path = _get_workspace_path(identity_id) - if ws_path.exists(): - shutil.rmtree(ws_path) - ws_path.mkdir(parents=True, exist_ok=True) - for entry in workspace_entries: - rel = entry[len("workspace/"):] - dest = ws_path / rel - dest.parent.mkdir(parents=True, exist_ok=True) - dest.write_bytes(zf.read(entry)) + # Restore workspace files (only if persistence enabled and zip contains any) + if _disk_persistence_enabled(): + workspace_entries = [n for n in zf.namelist() + if n.startswith("workspace/") and not n.endswith("/")] + if workspace_entries: + identity_id = get_identity_id() + ws_path = _get_workspace_path(identity_id) + if ws_path.exists(): + shutil.rmtree(ws_path) + ws_path.mkdir(parents=True, exist_ok=True) + for entry in workspace_entries: + rel = entry[len("workspace/"):] + dest = ws_path / rel + dest.parent.mkdir(parents=True, exist_ok=True) + dest.write_bytes(zf.read(entry)) return jsonify(status="ok", state=state) except zipfile.BadZipFile: diff --git a/py-src/data_formulator/tables_routes.py b/py-src/data_formulator/tables_routes.py index 147abf89..e1ae9b80 100644 --- a/py-src/data_formulator/tables_routes.py +++ b/py-src/data_formulator/tables_routes.py @@ -153,6 +153,33 @@ def _table_metadata_to_source_metadata(meta: DatalakeTableMetadata) -> dict | No } +@tables_bp.route('/open-workspace', methods=['POST']) +def open_workspace(): + """Open the Data Formulator home directory in the system file manager.""" + from flask import current_app + from data_formulator.datalake.workspace import get_data_formulator_home + import subprocess, platform + + if current_app.config.get('CLI_ARGS', {}).get('disable_database', False): + return jsonify(status="error", message="Workspace access is disabled"), 403 + + try: + home_path = str(get_data_formulator_home()) + # Ensure directory exists + Path(home_path).mkdir(parents=True, exist_ok=True) + system = platform.system() + if system == "Darwin": + subprocess.Popen(["open", home_path]) + elif system == "Windows": + subprocess.Popen(["explorer", home_path]) + else: + subprocess.Popen(["xdg-open", home_path]) + return jsonify(status="ok", path=home_path) + except Exception as e: + logger.error(f"Failed to open workspace: {e}") + return jsonify(status="error", message=str(e)), 500 + + @tables_bp.route('/list-tables', methods=['GET']) def list_tables(): """List all tables in the current workspace (datalake).""" diff --git a/src/app/App.tsx b/src/app/App.tsx index d688c3b3..b787c2ff 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -327,6 +327,8 @@ const SessionMenu: React.FC = () => { const importRef = React.useRef(null); const open = Boolean(anchorEl); const dispatch = useDispatch(); + const serverConfig = useSelector((state: DataFormulatorState) => state.serverConfig); + const diskPersistenceDisabled = serverConfig.DISABLE_DATABASE; const fullState = useSelector((state: DataFormulatorState) => { const excludedFields = new Set([ @@ -342,7 +344,7 @@ const SessionMenu: React.FC = () => { // Fetch recent sessions when the menu opens useEffect(() => { - if (!open) return; + if (!open || diskPersistenceDisabled) return; (async () => { try { const res = await fetchWithIdentity(getUrls().SESSION_LIST); @@ -440,16 +442,24 @@ const SessionMenu: React.FC = () => { onClose={closeMenu} slotProps={{ paper: { sx: { minWidth: 200 } } }} > - { setSaveDialogOpen(true); closeMenu(); }} - sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }}> - Save session - - { setLoadDialogOpen(true); closeMenu(); }} - sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }}> - Open session... - + + + { setSaveDialogOpen(true); closeMenu(); }} + sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }}> + Save session + + + + + + { setLoadDialogOpen(true); closeMenu(); }} + sx={{ fontSize: '12px', display: 'flex', alignItems: 'center', gap: 1 }}> + Open session... + + + - {recentSessions.length > 0 && [ + {!diskPersistenceDisabled && recentSessions.length > 0 && [ , Quick resume diff --git a/src/app/dfSlice.tsx b/src/app/dfSlice.tsx index b2541f47..3e36b1e9 100644 --- a/src/app/dfSlice.tsx +++ b/src/app/dfSlice.tsx @@ -43,6 +43,7 @@ export interface ServerConfig { DISABLE_FILE_UPLOAD: boolean; PROJECT_FRONT_PAGE: boolean; MAX_DISPLAY_ROWS: number; + DATA_FORMULATOR_HOME?: string; } export interface ModelConfig { diff --git a/src/app/tableThunks.ts b/src/app/tableThunks.ts index 4d8f3a61..09f6c5a3 100644 --- a/src/app/tableThunks.ts +++ b/src/app/tableThunks.ts @@ -276,6 +276,21 @@ export const loadTable = createAsyncThunk< dispatch(dfActions.addTableToStore(finalTable)); dispatch(fetchFieldSemanticType(finalTable)); + // Notify user about truncation + if (truncated && originalRowCount) { + const diskDisabled = state.serverConfig?.DISABLE_DATABASE; + const baseMsg = `Table "${finalTable.displayId || finalTable.id}" was truncated from ${originalRowCount.toLocaleString()} to ${frontendRowLimit.toLocaleString()} rows (browser limit).`; + const installHint = diskDisabled + ? ` To load the full dataset, install Data Formulator locally and use disk storage.` + : ` To load the full dataset, switch to "Disk" storage mode.`; + dispatch(dfActions.addMessages({ + timestamp: Date.now(), + type: 'warning', + component: 'data loader', + value: baseMsg + installHint, + })); + } + return { table: finalTable, truncated, originalRowCount, duplicate: false }; } ); diff --git a/src/app/utils.tsx b/src/app/utils.tsx index f4a550da..2e62b0c0 100644 --- a/src/app/utils.tsx +++ b/src/app/utils.tsx @@ -75,6 +75,9 @@ export function getUrls() { SESSION_DELETE: `/api/sessions/delete`, SESSION_EXPORT: `/api/sessions/export`, SESSION_IMPORT: `/api/sessions/import`, + + // Workspace + OPEN_WORKSPACE: `/api/tables/open-workspace`, }; } diff --git a/src/views/DBTableManager.tsx b/src/views/DBTableManager.tsx index 757dbc37..80457940 100644 --- a/src/views/DBTableManager.tsx +++ b/src/views/DBTableManager.tsx @@ -1,5 +1,5 @@ // TableManager.tsx -import React, { useState, useEffect, useCallback, FC, useRef } from 'react'; +import React, { useState, useEffect, useCallback, FC, useRef, useMemo } from 'react'; import { Card, CardContent, @@ -12,40 +12,24 @@ import { TextField, Divider, SxProps, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, CircularProgress, ButtonGroup, ToggleButton, ToggleButtonGroup, - Tooltip, MenuItem, Menu, Chip, - Collapse, + Checkbox, + FormControlLabel, styled, useTheme, Link, - Popover, - Switch, - Slider, - FormControlLabel + alpha, } from '@mui/material'; -import ExpandLessIcon from '@mui/icons-material/ExpandLess'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import SearchIcon from '@mui/icons-material/Search'; -import Autocomplete from '@mui/material/Autocomplete'; -// Type for table import configuration -type TableImportConfig = - | { mode: 'none' } - | { mode: 'full' } - | { mode: 'subset'; rowLimit: number; sortColumns: string[]; sortOrder: 'asc' | 'desc' }; +import Autocomplete from '@mui/material/Autocomplete'; import { getUrls, fetchWithIdentity } from '../app/utils'; import { borderColor } from '../app/tokens'; @@ -54,7 +38,6 @@ import { DataSourceConfig, DictTable } from '../components/ComponentType'; import { Type } from '../data/types'; import { useDispatch, useSelector } from 'react-redux'; import { dfActions, dfSelectors } from '../app/dfSlice'; -import { alpha } from '@mui/material'; import { DataFormulatorState } from '../app/dfSlice'; import { fetchFieldSemanticType } from '../app/dfSlice'; import { loadTable } from '../app/tableThunks'; @@ -69,7 +52,7 @@ import UploadFileIcon from '@mui/icons-material/UploadFile'; import DownloadIcon from '@mui/icons-material/Download'; import RestartAltIcon from '@mui/icons-material/RestartAlt'; import CloudUploadIcon from '@mui/icons-material/CloudUpload'; -import SettingsIcon from '@mui/icons-material/Settings'; + export const handleDBDownload = async (identityId: string) => { try { @@ -142,7 +125,8 @@ interface ColumnStatistics { export const DBManagerPane: React.FC<{ onClose?: () => void; -}> = function DBManagerPane({ onClose }) { + storeOnServer?: boolean; +}> = function DBManagerPane({ onClose, storeOnServer = true }) { const theme = useTheme(); @@ -189,7 +173,6 @@ export const DBManagerPane: React.FC<{ // Fetch list of tables const fetchTables = async (): Promise => { - if (serverConfig.DISABLE_DATABASE) return undefined; try { const response = await fetchWithIdentity(getUrls().LIST_TABLES, { method: 'GET' }); const data = await response.json(); @@ -280,13 +263,14 @@ export const DBManagerPane: React.FC<{ dataLoaderType={dataLoaderType} paramDefs={metadata.params} authInstructions={metadata.auth_instructions} + storeOnServer={storeOnServer} onImport={() => { setIsUploading(true); }} onFinish={(status, message, importedTables) => { setIsUploading(false); if (status === "success") { - onClose?.(); + setSystemMessage(message, "success"); } else { setSystemMessage(message, "error"); } @@ -354,453 +338,314 @@ export const DataLoaderForm: React.FC<{ dataLoaderType: string, paramDefs: {name: string, default: string, type: string, required: boolean, description: string}[], authInstructions: string, + storeOnServer?: boolean, onImport: () => void, onFinish: (status: "success" | "error", message: string, importedTables?: string[]) => void -}> = ({dataLoaderType, paramDefs, authInstructions, onImport, onFinish}) => { +}> = ({dataLoaderType, paramDefs, authInstructions, storeOnServer = true, onImport, onFinish}) => { const dispatch = useDispatch(); const theme = useTheme(); const params = useSelector((state: DataFormulatorState) => state.dataLoaderConnectParams[dataLoaderType] ?? {}); const frontendRowLimit = useSelector((state: DataFormulatorState) => state.config?.frontendRowLimit ?? 10000); + const workspaceTables = useSelector((state: DataFormulatorState) => state.tables); const [tableMetadata, setTableMetadata] = useState>({}); - let [displaySamples, setDisplaySamples] = useState>({}); + const [selectedPreviewTable, setSelectedPreviewTable] = useState(null); let [tableFilter, setTableFilter] = useState(""); - const [tableImportConfigs, setTableImportConfigs] = useState>({}); - const [subsetConfigAnchor, setSubsetConfigAnchor] = useState<{element: HTMLElement, tableName: string} | null>(null); - - // Store on server toggle for data loader imports - const [importStoreOnServer, setImportStoreOnServer] = useState(true); - - // Helper to get import config for a table (defaults to 'none') - const getTableConfig = (tableName: string): TableImportConfig => { - return tableImportConfigs[tableName] ?? { mode: 'none' }; - }; - - // Helper to update config for a specific table - const updateTableConfig = (tableName: string, config: TableImportConfig) => { - setTableImportConfigs(prev => ({ ...prev, [tableName]: config })); - }; - - // Get selected tables (those with mode !== 'none') - const selectedTables = Object.entries(tableImportConfigs) - .filter(([_, config]) => config.mode !== 'none') - .map(([tableName, _]) => tableName); + // Import mode for the currently selected table + const [importMode, setImportMode] = useState<'full' | 'subset'>('full'); + const [subsetConfig, setSubsetConfig] = useState<{ rowLimit: number; sortColumns: string[]; sortOrder: 'asc' | 'desc' }>({ rowLimit: 1000, sortColumns: [], sortOrder: 'asc' }); + // Track which tables have been loaded and how (persists across table selections) + const [loadedTables, setLoadedTables] = useState>({}); + + // Cross-reference workspace tables with database tables to detect already-loaded ones + const workspaceLoadedTables = useMemo(() => { + const result: Record = {}; + for (const wt of workspaceTables) { + const dbTableName = wt.source?.databaseTable; + if (dbTableName && wt.source?.type === 'database') { + // Determine if subset: if virtual exists and rowCount > loaded rows, it's a subset + const isSubset = wt.virtual ? wt.rows.length < wt.virtual.rowCount : false; + result[dbTableName] = isSubset ? 'subset' : 'full'; + } + } + return result; + }, [workspaceTables]); + + // Merge: local session loads take priority over workspace-detected loads + const effectiveLoadedTables = useMemo(() => { + return { ...workspaceLoadedTables, ...loadedTables }; + }, [workspaceLoadedTables, loadedTables]); let [isConnecting, setIsConnecting] = useState(false); - const toggleDisplaySamples = (tableName: string) => { - setDisplaySamples({...displaySamples, [tableName]: !displaySamples[tableName]}); - } + // Auto-select first table for preview when metadata loads + useEffect(() => { + const tableNames = Object.keys(tableMetadata); + if (tableNames.length > 0 && (!selectedPreviewTable || !tableMetadata[selectedPreviewTable])) { + setSelectedPreviewTable(tableNames[0]); + } + }, [tableMetadata]); + + // Reset import mode when switching tables + useEffect(() => { + if (selectedPreviewTable && tableMetadata[selectedPreviewTable]) { + setImportMode('full'); + const metadata = tableMetadata[selectedPreviewTable]; + setSubsetConfig({ rowLimit: Math.min(1000, metadata.row_count || 1000), sortColumns: [], sortOrder: 'asc' }); + } + }, [selectedPreviewTable]); + + // Build preview DictTable for the selected table + const previewTable: DictTable | null = useMemo(() => { + if (!selectedPreviewTable || !tableMetadata[selectedPreviewTable]) return null; + const metadata = tableMetadata[selectedPreviewTable]; + const sampleRows = metadata.sample_rows || []; + const columns = metadata.columns || []; + const names = columns.map((c: any) => c.name); + return { + id: selectedPreviewTable, + displayId: selectedPreviewTable, + names, + rows: sampleRows, + metadata: names.reduce((acc: Record, name: string) => ({ + ...acc, + [name]: { type: 'string' as any, semanticType: '', levels: [] } + }), {}), + anchored: true, + createdBy: 'user' as const, + attachedMetadata: '', + }; + }, [selectedPreviewTable, tableMetadata]); + + const tableNames = Object.keys(tableMetadata); let tableMetadataBox = [ - -
- - - - Table Name - Columns - Import Options - - - - {Object.entries(tableMetadata).map(([tableName, metadata]) => { - return [ - - - { - e.stopPropagation(); - toggleDisplaySamples(tableName); - }}> - {displaySamples[tableName] ? : } - - - - {tableName} - ({metadata.row_count > 0 ? `${metadata.row_count} rows × ` : ""}{metadata.columns.length} cols) - - - - {metadata.columns.map((column: any) => ( - - ))} - - - - - { - if (newMode === null) return; // Prevent deselecting all - if (newMode === 'none') { - updateTableConfig(tableName, { mode: 'none' }); - } else if (newMode === 'full') { - updateTableConfig(tableName, { mode: 'full' }); - } else if (newMode === 'subset') { - // Initialize with default values - updateTableConfig(tableName, { - mode: 'subset', - rowLimit: Math.min(1000, metadata.row_count || 1000), - sortColumns: [], - sortOrder: 'asc' - }); - } - }} - > - - - Skip - - - - - Full - - - { - e.stopPropagation(); - setSubsetConfigAnchor({ element: e.currentTarget, tableName }); - }} sx={{ - px: 1, py: 0, fontSize: 11, textTransform: 'none', - '&.Mui-selected': { backgroundColor: '#f9a825', color: 'white' }, - '&.Mui-selected:hover': { backgroundColor: '#f57f17' } - }}> - - Subset - - - - - {getTableConfig(tableName).mode === 'subset' && ( - - - - )} - - - , - - - - - { - return Object.fromEntries(Object.entries(row).map(([key, value]: [string, any]) => { - return [key, String(value)]; - })); - })} - columnDefs={metadata.columns.map((column: any) => ({id: column.name, label: column.name}))} - rowsPerPageNum={-1} - compact={false} - isIncompleteTable={metadata.row_count > 10} - /> - - - - ] - })} - -
-
, - // Subset configuration popover - setSubsetConfigAnchor(null)} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', - }} - transformOrigin={{ - vertical: 'top', - horizontal: 'left', - }} - > - {subsetConfigAnchor && (() => { - const tableName = subsetConfigAnchor.tableName; - const config = getTableConfig(tableName); - const metadata = tableMetadata[tableName]; - if (config.mode !== 'subset' || !metadata) return null; - - return ( - - - Create a subset of "{tableName}" - - - Row Limit (max: {metadata.row_count || 'unknown'} rows) - - - 0 && ( + + {/* Table chips */} + + {tableNames.map((tableName) => { + const metadata = tableMetadata[tableName]; + const isSelected = tableName === selectedPreviewTable; + const loaded = effectiveLoadedTables[tableName]; + return ( + { - const value = parseInt(e.target.value) || 1; - const maxRows = metadata.row_count || 100000; - updateTableConfig(tableName, { - ...config, - rowLimit: Math.min(Math.max(1, value), maxRows) - }); - }} - slotProps={{ - input: { - inputProps: { - min: 1, - max: metadata.row_count || 100000, - step: 100 - } - } + onClick={() => setSelectedPreviewTable(tableName)} + icon={loaded ? : undefined} + sx={{ + cursor: 'pointer', + fontSize: 11, + height: 26, + borderRadius: 1, + ...(loaded === 'full' ? { + backgroundColor: alpha(theme.palette.success.main, 0.12), + borderColor: alpha(theme.palette.success.main, 0.5), + color: theme.palette.success.dark, + '& .MuiChip-icon': { color: theme.palette.success.main }, + } : loaded === 'subset' ? { + backgroundColor: alpha('#f9a825', 0.15), + borderColor: alpha('#f9a825', 0.5), + color: '#e65100', + '& .MuiChip-icon': { color: '#f9a825' }, + } : isSelected ? { + backgroundColor: alpha(theme.palette.primary.main, 0.12), + borderColor: alpha(theme.palette.primary.main, 0.5), + color: theme.palette.primary.main, + } : {}), + border: '1px solid', + borderColor: loaded === 'full' + ? alpha(theme.palette.success.main, 0.5) + : loaded === 'subset' + ? alpha('#f9a825', 0.5) + : isSelected + ? alpha(theme.palette.primary.main, 0.5) + : 'rgba(0,0,0,0.15)', + '&:hover': { + backgroundColor: loaded === 'full' + ? alpha(theme.palette.success.main, 0.18) + : loaded === 'subset' + ? alpha('#f9a825', 0.22) + : alpha(theme.palette.primary.main, 0.08), + }, }} - fullWidth - sx={{ mb: 1, '& .MuiInputBase-root': { fontSize: 12 } }} /> - { - updateTableConfig(tableName, { - ...config, - rowLimit: value as number - }); - }} - min={1} - max={metadata.row_count || 10000} - step={Math.max(1, Math.floor((metadata.row_count || 10000) / 100))} - valueLabelDisplay="auto" + ); + })} + + + {/* Preview + load controls */} + {previewTable && selectedPreviewTable && ( + + + ({ + id: name, + label: name, + minWidth: 60, + }))} + rowsPerPageNum={-1} + compact={false} + isIncompleteTable={previewTable.rows.length > 12} + maxHeight={240} /> - - - - Sort By (optional) + + + {tableMetadata[selectedPreviewTable]?.row_count > 0 + ? `${tableMetadata[selectedPreviewTable].row_count.toLocaleString()} rows` + : `${previewTable.rows.length} sample rows` + } × {previewTable.names.length} columns - - col.name)} - value={config.sortColumns} - onChange={(_, newValue) => { - updateTableConfig(tableName, { - ...config, - sortColumns: newValue - }); + + {/* Load controls */} + + {/* Subset option */} + + setImportMode(e.target.checked ? 'subset' : 'full')} + size="small" + sx={{ p: 0.25 }} + /> + setImportMode(importMode === 'subset' ? 'full' : 'subset')} + > + Load a subset + + {importMode === 'subset' && selectedPreviewTable && tableMetadata[selectedPreviewTable] && (() => { + const metadata = tableMetadata[selectedPreviewTable]; + return ( + <> + + Rows: + { + const value = parseInt(e.target.value) || 1; + const maxRows = metadata.row_count || 100000; + setSubsetConfig(prev => ({ ...prev, rowLimit: Math.min(Math.max(1, value), maxRows) })); + }} + slotProps={{ input: { inputProps: { min: 1, max: metadata.row_count || 100000, step: 100 } } }} + sx={{ width: 90, '& .MuiInputBase-root': { fontSize: 11, height: 26 }, '& .MuiInputBase-input': { py: 0.25, px: 0.75 } }} + /> + / {(metadata.row_count || '?').toLocaleString()} + + + Sort: + col.name)} + value={subsetConfig.sortColumns} + onChange={(_, newValue) => setSubsetConfig(prev => ({ ...prev, sortColumns: newValue }))} + renderInput={(params) => ( + + )} + renderTags={(value, getTagProps) => + value.map((option, index) => ( + + )) + } + slotProps={{ paper: { sx: { fontSize: 12, '& .MuiAutocomplete-option': { fontSize: 12, py: 0.5, minHeight: 28 } } } }} + sx={{ flex: 1, minWidth: 0 }} + /> + {subsetConfig.sortColumns.length > 0 && ( + { if (v) setSubsetConfig(prev => ({ ...prev, sortOrder: v })); }} + size="small" + sx={{ height: 24 }} + > + + + + )} + + + ); + })()} + + {/* Load Table button */} + - ); - })()} - , - Object.keys(tableMetadata).length > 0 && - - setImportStoreOnServer(e.target.checked)} - size="small" - /> - } - label={ - - {importStoreOnServer ? 'Store on server' : `Local only (≤${frontendRowLimit.toLocaleString()} rows)`} - - } - /> - - - + )} +
+ ), ] const isConnected = Object.keys(tableMetadata).length > 0; return ( - - Import tables from {dataLoaderType} - {isConnecting && } {isConnected ? ( // Connected state: show connection parameters and disconnect button - + + + {dataLoaderType} + + {paramDefs.filter((paramDef) => params[paramDef.name]).length > 0 && ( + · + )} {paramDefs.filter((paramDef) => params[paramDef.name]).map((paramDef, index) => ( @@ -906,11 +757,14 @@ export const DataLoaderForm: React.FC<{ ) : ( // Not connected: show connection forms <> + + {dataLoaderType} + {paramDefs.map((paramDef) => ( @@ -1012,11 +866,27 @@ export const DataLoaderForm: React.FC<{ } {authInstructions.trim() && ( - - {authInstructions.trim()} - + ({ + mt: 2, px: 1.5, py: 1, + backgroundColor: 'rgba(0,0,0,0.02)', + borderRadius: 1, + border: '1px solid rgba(0,0,0,0.06)', + fontFamily: theme.typography.fontFamily, + fontSize: '11px', + color: 'text.secondary', + lineHeight: 1.6, + '& *': { fontFamily: theme.typography.fontFamily, fontSize: 'inherit', lineHeight: 'inherit', color: 'inherit' }, + '& p': { margin: '0 0 4px 0', '&:last-child': { marginBottom: 0 } }, + '& code': { fontSize: '10px', fontFamily: 'monospace !important', backgroundColor: 'rgba(0,0,0,0.06)', padding: '1px 4px', borderRadius: '3px' }, + '& pre': { fontSize: '10px', fontFamily: 'monospace !important', backgroundColor: 'rgba(0,0,0,0.04)', padding: '8px', borderRadius: '4px', overflow: 'auto', margin: '4px 0', '& code': { backgroundColor: 'transparent', padding: 0 } }, + '& a': { color: 'primary.main' }, + '& ul, & ol': { paddingLeft: '20px', margin: '4px 0' }, + '& li': { marginBottom: '2px' }, + '& strong': { fontWeight: 600, color: 'text.primary' }, + '& h1, & h2, & h3, & h4': { fontSize: '12px', fontWeight: 600, color: 'text.primary', margin: '4px 0' }, + })}> + {authInstructions.trim()} + )} )} diff --git a/src/views/DataFormulator.tsx b/src/views/DataFormulator.tsx index 714077ef..2099a0b4 100644 --- a/src/views/DataFormulator.tsx +++ b/src/views/DataFormulator.tsx @@ -317,11 +317,6 @@ export const DataFormulatorFC = ({ }) => { serverConfig={serverConfig} variant="page" /> - setUploadDialogOpen(false)} - initialTab={uploadDialogInitialTab} - /> @@ -352,6 +347,11 @@ export const DataFormulatorFC = ({ }) => { {tables.length > 0 ? fixedSplitPane : dataUploadRequestBox} + setUploadDialogOpen(false)} + initialTab={uploadDialogInitialTab} + /> {selectedModelId == undefined && ( void; disabled?: boolean; - disabledReason?: string; } const DataSourceCard: React.FC = ({ @@ -90,7 +94,6 @@ const DataSourceCard: React.FC = ({ description, onClick, disabled = false, - disabledReason }) => { const theme = useTheme(); @@ -156,14 +159,6 @@ const DataSourceCard: React.FC = ({ ); - if (disabled && disabledReason) { - return ( - - {card} - - ); - } - return card; }; @@ -197,32 +192,28 @@ export const DataLoadMenu: React.FC = ({ title: 'Sample Datasets', description: 'Explore and load curated example datasets', icon: , - disabled: false, - disabledReason: undefined + disabled: false }, { value: 'upload' as UploadTabType, title: 'Upload File', description: 'Upload local files (CSV, TSV, JSON, Excel)', icon: , - disabled: false, - disabledReason: undefined + disabled: false }, { value: 'paste' as UploadTabType, title: 'Paste Data', description: 'Paste tabular data directly from clipboard', icon: , - disabled: false, - disabledReason: undefined + disabled: false }, { value: 'extract' as UploadTabType, title: 'Extract Unstructured Data', description: 'Extract tables from images or text using AI', icon: , - disabled: false, - disabledReason: undefined + disabled: false }, ]; @@ -232,16 +223,14 @@ export const DataLoadMenu: React.FC = ({ title: 'Load from URL', description: 'Load data from a URL with optional auto-refresh', icon: , - disabled: false, - disabledReason: undefined + disabled: false }, { value: 'database' as UploadTabType, title: 'Database', description: 'Connect to databases or data services', icon: , - disabled: serverConfig.DISABLE_DATABASE, - disabledReason: 'Database connection is disabled in this environment' + disabled: false }, ]; @@ -333,7 +322,6 @@ export const DataLoadMenu: React.FC = ({ description={source.description} onClick={() => onSelectTab(source.value)} disabled={source.disabled} - disabledReason={source.disabledReason} /> ))} @@ -352,7 +340,6 @@ export const DataLoadMenu: React.FC = ({ description={source.description} onClick={() => onSelectTab(source.value)} disabled={source.disabled} - disabledReason={source.disabledReason} /> ))} @@ -402,7 +389,6 @@ export const DataLoadMenu: React.FC = ({ description={source.description} onClick={() => onSelectTab(source.value)} disabled={source.disabled} - disabledReason={source.disabledReason} /> ))} @@ -442,7 +428,6 @@ export const DataLoadMenu: React.FC = ({ description={source.description} onClick={() => onSelectTab(source.value)} disabled={source.disabled} - disabledReason={source.disabledReason} /> ))} @@ -473,8 +458,9 @@ export const UnifiedDataUploadDialog: React.FC = ( const fileInputRef = useRef(null); const urlInputRef = useRef(null); - // Store on server toggle (default: true, like normal browsing mode) - const [storeOnServer, setStoreOnServer] = useState(true); + // Store on server toggle (forced off when DISABLE_DATABASE) + const diskPersistenceDisabled = serverConfig.DISABLE_DATABASE; + const [storeOnServer, setStoreOnServer] = useState(!diskPersistenceDisabled); // Paste tab state const [pasteContent, setPasteContent] = useState(""); @@ -1002,29 +988,52 @@ export const UnifiedDataUploadDialog: React.FC = ( )} - {activeTab !== 'menu' && activeTab !== 'database' && ( - - setStoreOnServer(e.target.checked)} + {activeTab !== 'menu' && ( + + + Load data in + + { if (val) setStoreOnServer(val === 'disk'); }} + size="small" + sx={{ height: 26, '& .MuiToggleButton-root': { textTransform: 'none', fontSize: '0.7rem', px: 1, py: 0 } }} + > + + + + Browser + + + + + + + Disk + + + + + {storeOnServer && !diskPersistenceDisabled && serverConfig.DATA_FORMULATOR_HOME && ( + + - } - label={ - - {storeOnServer ? 'Store on server' : `Local only (≤${frontendRowLimit.toLocaleString()} rows)`} - - } - /> - + onClick={() => { + fetchWithIdentity(getUrls().OPEN_WORKSPACE, { method: 'POST' }).catch(() => {}); + }} + sx={{ p: 0.5 }} + > + + + + )} + )} = ( {/* Database Tab */} - {serverConfig.DISABLE_DATABASE ? ( - - - Database connection is disabled in this environment. - - - Install Data Formulator locally to use database features.
- - https://github.com/microsoft/data-formulator - -
-
- ) : ( - - )} +
{/* Extract Data Tab */} From 3364731a7ca5a886e75434f82d52303e1508a2f3 Mon Sep 17 00:00:00 2001 From: BAIGUANGMEI <2868653801@qq.com> Date: Thu, 12 Feb 2026 00:51:37 +0800 Subject: [PATCH 39/50] fix cross-platform file locking for Windows compatibility --- py-src/data_formulator/datalake/metadata.py | 75 +++++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/py-src/data_formulator/datalake/metadata.py b/py-src/data_formulator/datalake/metadata.py index c189ad81..00844870 100644 --- a/py-src/data_formulator/datalake/metadata.py +++ b/py-src/data_formulator/datalake/metadata.py @@ -15,10 +15,10 @@ from typing import Literal, Any import yaml import logging -import fcntl import tempfile import time import os +import sys logger = logging.getLogger(__name__) @@ -28,10 +28,74 @@ MAX_LOCK_WAIT_SECONDS = 10 +if sys.platform == 'win32': + # Windows: use LockFileEx/UnlockFileEx via ctypes for whole-file locking, + # semantically equivalent to fcntl.flock on Unix. + import ctypes + import ctypes.wintypes + import msvcrt as _msvcrt + + # use_last_error=True: ctypes saves GetLastError() per-thread immediately + # after each call, avoiding races with other threads. + _kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) + + _LOCKFILE_EXCLUSIVE_LOCK = 0x0002 + _LOCKFILE_FAIL_IMMEDIATELY = 0x0001 + + class _OVERLAPPED(ctypes.Structure): + _fields_ = [ + ('Internal', ctypes.POINTER(ctypes.c_ulong)), + ('InternalHigh', ctypes.POINTER(ctypes.c_ulong)), + ('Offset', ctypes.wintypes.DWORD), + ('OffsetHigh', ctypes.wintypes.DWORD), + ('hEvent', ctypes.wintypes.HANDLE), + ] + + def _lock_file(fd: int) -> None: + """Acquire an exclusive, non-blocking lock on the whole file (Windows).""" + handle = _msvcrt.get_osfhandle(fd) + overlapped = _OVERLAPPED() + result = _kernel32.LockFileEx( + ctypes.wintypes.HANDLE(handle), + ctypes.wintypes.DWORD(_LOCKFILE_EXCLUSIVE_LOCK | _LOCKFILE_FAIL_IMMEDIATELY), + ctypes.wintypes.DWORD(0), # reserved + ctypes.wintypes.DWORD(0xFFFFFFFF), # bytes to lock (low) + ctypes.wintypes.DWORD(0xFFFFFFFF), # bytes to lock (high) + ctypes.byref(overlapped), + ) + if not result: + raise ctypes.WinError(ctypes.get_last_error()) + + def _unlock_file(fd: int) -> None: + """Release the whole-file lock (Windows).""" + handle = _msvcrt.get_osfhandle(fd) + overlapped = _OVERLAPPED() + result = _kernel32.UnlockFileEx( + ctypes.wintypes.HANDLE(handle), + ctypes.wintypes.DWORD(0), # reserved + ctypes.wintypes.DWORD(0xFFFFFFFF), + ctypes.wintypes.DWORD(0xFFFFFFFF), + ctypes.byref(overlapped), + ) + if not result: + raise ctypes.WinError(ctypes.get_last_error()) +else: + import fcntl as _fcntl + + def _lock_file(fd: int) -> None: + """Acquire an exclusive, non-blocking lock on the whole file (Unix).""" + _fcntl.flock(fd, _fcntl.LOCK_EX | _fcntl.LOCK_NB) + + def _unlock_file(fd: int) -> None: + """Release the whole-file lock (Unix).""" + _fcntl.flock(fd, _fcntl.LOCK_UN) + + class WorkspaceLock: """ Context manager for acquiring an exclusive lock on workspace metadata. Prevents race conditions when multiple processes/threads modify metadata concurrently. + Uses LockFileEx on Windows and fcntl.flock on Unix — both provide whole-file locking. """ def __init__(self, workspace_path: Path, timeout: float = MAX_LOCK_WAIT_SECONDS): @@ -48,9 +112,10 @@ def __enter__(self): while True: try: # Open lock file (create if doesn't exist) - self.lock_fd = open(self.lock_file, 'a') - # Try to acquire exclusive lock (non-blocking) - fcntl.flock(self.lock_fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB) + # 'a+' creates the file atomically if missing and allows seek/read + self.lock_fd = open(self.lock_file, 'a+') + # Try to acquire exclusive whole-file lock (non-blocking) + _lock_file(self.lock_fd.fileno()) logger.debug(f"Acquired workspace lock: {self.lock_file}") return self except (IOError, OSError) as e: @@ -73,7 +138,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): """Release lock.""" if self.lock_fd: try: - fcntl.flock(self.lock_fd.fileno(), fcntl.LOCK_UN) + _unlock_file(self.lock_fd.fileno()) self.lock_fd.close() logger.debug(f"Released workspace lock: {self.lock_file}") except Exception as e: From 15a1960718b66bb3ce2bd2b10129cc5916ce1b36 Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Wed, 11 Feb 2026 10:30:16 -0800 Subject: [PATCH 40/50] some fixes --- .../data_formulator/agents/agent_data_rec.py | 181 +++++++++--------- src/app/utils.tsx | 22 +-- 2 files changed, 100 insertions(+), 103 deletions(-) diff --git a/py-src/data_formulator/agents/agent_data_rec.py b/py-src/data_formulator/agents/agent_data_rec.py index 862e216b..ef5e25b6 100644 --- a/py-src/data_formulator/agents/agent_data_rec.py +++ b/py-src/data_formulator/agents/agent_data_rec.py @@ -54,24 +54,102 @@ "recap": "..." // string, a short summary of the user's goal. "display_instruction": "..." // string, the even shorter verb phrase describing the users' goal. "recommendation": "..." // string, explain why this recommendation is made - "input_tables": [...] // string[], describe names of the input tables that will be used in the transformation. - "output_fields": [...] // string[], describe the desired output fields that the output data should have (i.e., the goal of transformed data), it's a good idea to preserve intermediate fields here - "chart_type": "" // string, one of "point", "bar", "line", "area", "heatmap", "group_bar", "boxplot", "worldmap", "usmap". "chart_type" should either be inferred from user instruction, or recommend if the user didn't specify any. - "chart_encodings": { - "x": "", - "y": "", - "color": "", - "size": "", - "opacity": "", - "facet": "", - "longitude": "", - "latitude": "" - } // object: map visualization channels (x, y, color, size, opacity, facet, longitude, latitude, etc.) to a subset of output fields, appropriate visual channels for different chart types are defined below. - "projection": "" // string (optional, only for worldmap/usmap): one of "mercator", "equalEarth", "naturalEarth1", "orthographic", "stereographic", "albersUsa", "conicEqualArea", "gnomonic", "azimuthalEquidistant". Default is "equalEarth" for worldmap, "albersUsa" for usmap. - "projection_center": [0, 0] // [longitude, latitude] (optional, only for worldmap): the center point of the map projection. Use to focus on specific regions, e.g., [105, 35] for China, [-98, 39] for USA, [10, 50] for Europe, [139, 36] for Japan. + "input_tables": [...] // string[], names of input tables from [CONTEXT] that will be used. + "output_fields": [...] // string[], desired output fields for the transformed data; include intermediate fields too. + "chart_type": "" // string, one of the chart types defined in [CHART TYPE REFERENCE] below. + "chart_encodings": {} // object, map visual channels to output field names. Available channels depend on chart_type (see reference below). + "config": {} // object (optional), chart styling options. Available options depend on chart_type (see reference below). Only include when there's a clear reason. + "output_variable": "" // string, descriptive snake_case Python variable name for the final DataFrame. } ``` +**[CHART TYPE REFERENCE]** + +Each chart type specifies: encodings (visual channels → field types), when to use it, data expectations, and optional config. + +| chart_type | encodings | config | +|---|---|---| +| point | x, y, color, size, opacity, facet | opacity (0.1–1.0) | +| bar | x, y, color, opacity, facet | cornerRadius (0–15) | +| group_bar | x, y, color, facet | cornerRadius (0–15) | +| histogram | x, color, facet | binCount (5–50) | +| line | x, y, color, opacity, facet | interpolate | +| area | x, y, color, facet | — | +| heatmap | x, y, color, facet | colorScheme | +| boxplot | x, y, color, facet | — | +| pie | theta, color, facet | innerRadius (0–100) | +| worldmap | longitude, latitude, color, size | projection, projectionCenter | +| usmap | longitude, latitude, color, size | — | + +**Chart type details:** + +- **point** (Scatter Plot) + - x, y: Quantitative or Categorical; color: Categorical (optional); size: Quantitative (optional, for bubble chart) + - Best for: relationships, correlations, distributions, regression analysis + - Good default when other chart types don't clearly apply + - config: `{"opacity": 0.5}` — marker opacity (default 1.0). Use lower values for dense/overlapping data. + +- **histogram** + - x: Quantitative or Categorical; color: Categorical (optional, for grouped histogram) + - Best for: distribution of a single quantitative field + - Values are auto-binned; color grouping is automatic + - config: `{"binCount": 20}` — number of bins (default 10). + +- **bar** (Bar / Stacked Bar Chart) + - x: Categorical (nominal/ordinal); y: Quantitative; color: Categorical or Quantitative (optional) + - Best for: comparisons across categories + - Multiple rows with the same x value are automatically stacked + - When stacking doesn't make sense: aggregate y values or introduce facets + - config: `{"cornerRadius": 5}` — rounded bar ends (default 0). + +- **group_bar** (Grouped Bar Chart) + - x: Categorical; y: Quantitative; color: Categorical (required, defines groups) + - Bars from different color groups are placed side by side + - config: `{"cornerRadius": 5}` — rounded bar ends (default 0). + +- **line** (Line Chart) + - x: Temporal (preferred) or ordinal; y: Quantitative; color: Categorical (optional, for multiple lines) + - Best for: trends over time, continuous data, forecasting + - Multiple rows with same x+color but different y: aggregate y or use facets + - config: `{"interpolate": "monotone"}` — options: "linear", "monotone" (smooth), "step", "step-before", "step-after", "basis" (smooth), "cardinal", "catmull-rom". + +- **area** (Area Chart) + - x: Temporal (preferred) or ordinal; y: Quantitative; color: Categorical (optional, for stacked areas) + - Best for: trends over time, part-to-whole over time + +- **heatmap** + - x, y: Categorical (convert quantitative to nominal); color: Quantitative (intensity) + - Best for: pattern discovery in matrix data + - config: `{"colorScheme": "viridis"}` — options: "viridis", "inferno", "magma", "plasma", "turbo", "blues", "reds", "greens", "oranges", "purples", "greys", "blueorange" (diverging), "redblue" (diverging). + +- **boxplot** (Box Plot) + - x: Categorical; y: Quantitative; color: Categorical (optional, for grouped boxplots) + - Best for: distribution comparison across categories + +- **pie** (Pie / Donut Chart) + - theta: Quantitative (slice size); color: Categorical (slice labels) + - Best for: part-to-whole relationships, proportions + - Avoid when >7-8 categories — use bar chart instead + - config: `{"innerRadius": 50}` — 0 = pie, >0 = donut chart. + +- **worldmap** (World Map) + - longitude: Quantitative (-180 to 180); latitude: Quantitative (-90 to 90); color: Categorical/Quantitative (optional); size: Quantitative (optional) + - Best for: geographic data with coordinates (cities, events, sales by location) + - config: `{"projection": "equalEarth", "projectionCenter": [105, 35]}` + - projection options: "mercator", "equalEarth" (default), "naturalEarth1", "orthographic", "stereographic", "conicEqualArea", "gnomonic", "azimuthalEquidistant" + - projectionCenter: [lon, lat] — e.g., [105, 35] China, [-98, 39] USA, [10, 50] Europe, [139, 36] Japan + +- **usmap** (US Map) + - longitude: Quantitative; latitude: Quantitative; color: Categorical/Quantitative (optional); size: Quantitative (optional) + - Best for: US-focused geographic data + - Uses fixed albersUsa projection (includes Alaska and Hawaii); no config options. + +**General encoding rules:** +- facet: available for all chart types; use a categorical field with small cardinality. +- opacity: available as an additional legend channel (Quantitative or Categorical). +- All fields in "chart_encodings" must also appear in "output_fields". +- Typically use 2-3 encoding channels (x, y, color/size); add facet only when needed. + Concretely: - recap what the user's goal is in a short summary in "recap". - If the user's [GOAL] is clear already, simply infer what the user mean. Set "mode" as "infer" and create "output_fields" and "chart_encodings" based off user description. @@ -96,75 +174,6 @@ - determine "input_tables", the names of a subset of input tables from [CONTEXT] section that will be used to achieve the user's goal. - **IMPORTANT** Note that the Table 1 in [CONTEXT] section is the table the user is currently viewing, it should take precedence if the user refers to insights about the "current table". - At the same time, leverage table information to determine which tables are relevant to the user's goal and should be used. - - "chart_type" must be one of "point", "bar", "line", "area", "heatmap", "group_bar", "boxplot", "worldmap", "usmap" - - "chart_encodings" should specify which fields should be used to create the visualization - - decide which visual channels should be used to create the visualization appropriate for the chart type. - - point: x, y, color, size, facet - - histogram: x, color, facet - - bar: x, y, color, facet - - line: x, y, color, facet - - area: x, y, color, facet - - heatmap: x, y, color, facet - - group_bar: x, y, color, facet - - boxplot: x, y, color, facet - - worldmap: longitude, latitude, color, size - - usmap: longitude, latitude, color, size - - note that all fields used in "chart_encodings" should be included in "output_fields". - - all fields you need for visualizations should be transformed into the output fields! - - "output_fields" should include important intermediate fields that are not used in visualization but are used for data transformation. - - typically only 2-3 fields should be used to create the visualization (x, y, color/size), facet can be added if it's a faceted visualization. - - Guidelines for choosing chart type and visualization fields: - - Consider chart types as follows: - - (point) Scatter Plots: x,y: Quantitative/Categorical, color: Categorical (optional), size: Quantitative (optional for creating bubble chart), - - best for: Relationships, correlations, distributions, forecasting, regression analysis - - scatter plots are good default way to visualize data when other chart types are not applicable. - - use color to visualize points from different categories. - - use size to visualize data points with an additional quantitative dimension of the data points. - - (histogram) Histograms: x: Quantitative/Categorical, color: Categorical (optional for creating grouped histogram), - - best for: Distribution of a quantitative field - - use x values directly if x values are categorical, and transform the data into bins if the field values are quantitative. - - when color is specified, the histogram will be grouped automatically (items with the same x values will be grouped). - - (bar) Bar Charts: x: Categorical (nominal/ordinal), y: Quantitative, color: Categorical/Quantitative (for stacked bar chart / showing additional quantitative dimension), - - best for: Comparisons across categories - - use (bar) for simple bar chart or stacked bar chart (when it makes sense to add up Y values for each category with the same X value), - - when color is specified, the bar will be stacked automatically (items with the same x values will be stacked). - - note that when there are multiple rows in the data with same x values, the bar will be stacked automatically. - - 1. consider to use an aggregated field for y values if the value is not suitable for stacking. - - 2. consider to introduce facets so that each group is visualized in a separate bar. - - (group_bar) for grouped bar chart, x: Categorical (nominal/ordinal), y: Quantitative, color: Categorical - - when color is specified, bars from different groups will be grouped automatically. - - only use facet if the cardinality of color field is small (less than 5). - - (line) Line Charts: x: Temporal (preferred) or ordinal, y: Quantitative, color: Categorical (optional for creating multiple lines), - - best for: Trends over time, continuous data, forecasting, regression analysis - - note that when there are multiple rows in the data belong to the same group (same x and color values) but different y values, the line will not look correct. - - consider to use an aggregated field for y values, or introduce facets so that each group is visualized in a separate line. - - (area) Area Charts: x: Temporal (preferred) or ordinal, y: Quantitative, color: Categorical (optional for creating stacked areas), - - best for: Trends over time, continuous data - - (heatmap) Heatmaps: x,y: Categorical (you need to convert quantitative to nominal), color: Quantitative intensity, - - best for: Pattern discovery in matrix data - - (boxplot) Box plots: x: Categorical (nominal/ordinal), y: Quantitative, color: Categorical (optional for creating grouped boxplots), - - best for: Distribution of a quantitative field - - use x values directly if x values are categorical, and transform the data into bins if the field values are quantitative. - - when color is specified, the boxplot will be grouped automatically (items with the same x values will be grouped). - - (worldmap) World Map: longitude: Quantitative (geographic longitude -180 to 180), latitude: Quantitative (geographic latitude -90 to 90), color: Categorical/Quantitative (optional), size: Quantitative (optional) - - best for: Geographic data visualization on a world map - - use when the data contains geographic coordinates (longitude, latitude) for locations around the world - - the data must have longitude and latitude fields representing geographic coordinates - - color can be used to show categories (e.g., country, region) or quantitative values (e.g., population, sales) - - size can be used to show quantitative values (e.g., magnitude, count) - - example use cases: plotting cities, earthquakes, sales by location, etc. - - projection options: "mercator", "equalEarth" (default), "naturalEarth1", "orthographic", "stereographic", "albers", "conicEqualArea" - - projection_center: set [longitude, latitude] to center the map on a specific region: - * China: [105, 35], USA: [-98, 39], Europe: [10, 50], Japan: [139, 36], India: [78, 22] - * Brazil: [-55, -10], Australia: [134, -25], Russia: [100, 60], South Africa: [25, -29] - - (usmap) US Map: longitude: Quantitative (geographic longitude), latitude: Quantitative (geographic latitude), color: Categorical/Quantitative (optional), size: Quantitative (optional) - - best for: Geographic data visualization focused on the United States - - use when the data is specifically about US locations - - uses albersUsa projection optimized for US geography (includes Alaska and Hawaii) - - the data must have longitude and latitude fields representing US geographic coordinates - - facet channel is available for all chart types, it supports a categorical field with small cardinality to visualize the data in different facets. - - if you really need additional legend fields: - - you can use opacity for legend (support Quantitative and Categorical). - visualization fields require tidy data. - similar to VegaLite and ggplot2 so that each field is mapped to a visualization axis or legend. - consider data transformations if you want to visualize multiple fields together: @@ -186,9 +195,7 @@ - when the user asks for clustering: - the output should be a long format table where actual x, y pairs with a third column "cluster_id" that indicates the cluster id of the data point. - the recommended chart should be scatter plot (quantitative x, y) - - specify "output_variable", the name of the Python variable that will contain the final DataFrame result. - The name should be descriptive and reflect the data content (e.g., "sales_by_region", "monthly_trends", "customer_segments"). - Avoid generic names like "result_df", "output", or "data". Use snake_case naming convention. + - "output_variable": descriptive snake_case name for the final DataFrame (e.g., "sales_by_region", "monthly_trends"). Avoid generic names like "result_df" or "data". 2. Then, write a Python script based on the inferred goal. The script should transform input data into the desired output table containing all "output_fields" from the refined goal. The script should be as simple as possible and easily readable. If there is no data transformation needed based on "output_fields", the script can simply load and assign the data. diff --git a/src/app/utils.tsx b/src/app/utils.tsx index 2e62b0c0..776f1c56 100644 --- a/src/app/utils.tsx +++ b/src/app/utils.tsx @@ -1151,34 +1151,24 @@ export const resolveRecommendedChart = (refinedGoal: any, allFields: FieldItem[] let chartTypeMap : any = { "line" : "Line Chart", - "histogram": "Bar Chart", + "histogram": "Histogram", "bar": "Bar Chart", "point": "Scatter Plot", "boxplot": "Boxplot", "area": "Custom Area", "heatmap": "Heatmap", "group_bar": "Grouped Bar Chart", + "pie": "Pie Chart", "worldmap": "World Map", "usmap": "US Map" } let chartType = chartTypeMap[rawChartType] || 'Scatter Plot'; let newChart = generateFreshChart(table.id, chartType) as Chart; newChart = resolveChartFields(newChart, allFields, chartEncodings, table); - if (rawChartType == "histogram") { - newChart.encodingMap.y = { aggregate: "count" }; - } - // Handle map projection settings via config - if ((rawChartType === "worldmap" || rawChartType === "usmap")) { - const config: Record = {}; - if (refinedGoal['projection']) { - config.projection = refinedGoal['projection']; - } - if (refinedGoal['projection_center']) { - config.projectionCenter = refinedGoal['projection_center']; - } - if (Object.keys(config).length > 0) { - newChart.config = config; - } + + // Apply chart config properties from agent recommendation + if (refinedGoal['config'] && typeof refinedGoal['config'] === 'object') { + newChart.config = { ...refinedGoal['config'] }; } return newChart; } From 763c7c18eeba420da4b54242137ac7c213bfdeb2 Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Wed, 11 Feb 2026 16:27:34 -0800 Subject: [PATCH 41/50] bug fixes --- py-src/data_formulator/agent_routes.py | 35 +- py-src/data_formulator/agents/__init__.py | 2 + .../agents/agent_chart_insight.py | 118 +++++++ .../agents/agent_code_explanation.py | 80 +---- .../data_formulator/agents/agent_data_rec.py | 57 ++-- .../agents/agent_data_transform.py | 50 ++- .../data_formulator/agents/semantic_types.py | 124 +++++++ py-src/data_formulator/app.py | 2 +- py-src/data_formulator/datalake/workspace.py | 6 +- py-src/data_formulator/sandbox/py_sandbox.py | 35 +- py-src/data_formulator/tables_routes.py | 2 +- .../workflows/create_vl_plots.py | 231 ++++++++++++- .../workflows/exploration_flow.py | 17 +- src/app/App.tsx | 4 +- src/app/chartCache.ts | 41 +++ src/app/dfSlice.tsx | 106 +++++- src/app/tableThunks.ts | 2 +- src/app/utils.tsx | 2 + src/components/componentType.tsx | 28 +- src/scss/ConceptShelf.scss | 222 ------------- src/scss/DraggableCard.scss | 77 +++++ src/views/ChartRecBox.tsx | 22 +- src/views/ConceptCard.tsx | 161 --------- src/views/ConceptShelf.tsx | 306 ----------------- src/views/DBTableManager.tsx | 2 +- src/views/DataFormulator.tsx | 2 - src/views/DataThread.tsx | 5 +- src/views/EncodingBox.tsx | 15 +- src/views/EncodingShelfCard.tsx | 27 +- src/views/ExplComponents.tsx | 5 +- src/views/OperatorCard.tsx | 2 +- src/views/ReactTable.tsx | 11 +- src/views/ReportView.tsx | 30 +- src/views/SelectableDataGrid.tsx | 47 ++- src/views/UnifiedDataUploadDialog.tsx | 2 +- src/views/ViewUtils.tsx | 4 +- src/views/VisualizationView.tsx | 310 ++++++++++-------- 37 files changed, 1154 insertions(+), 1038 deletions(-) create mode 100644 py-src/data_formulator/agents/agent_chart_insight.py delete mode 100644 src/scss/ConceptShelf.scss create mode 100644 src/scss/DraggableCard.scss delete mode 100644 src/views/ConceptCard.tsx delete mode 100644 src/views/ConceptShelf.tsx diff --git a/py-src/data_formulator/agent_routes.py b/py-src/data_formulator/agent_routes.py index 723c3147..0512c361 100644 --- a/py-src/data_formulator/agent_routes.py +++ b/py-src/data_formulator/agent_routes.py @@ -29,6 +29,7 @@ from data_formulator.agents.agent_data_clean import DataCleanAgent from data_formulator.agents.agent_data_clean_stream import DataCleanAgentStream from data_formulator.agents.agent_code_explanation import CodeExplanationAgent +from data_formulator.agents.agent_chart_insight import ChartInsightAgent from data_formulator.agents.agent_interactive_explore import InteractiveExploreAgent from data_formulator.agents.agent_report_gen import ReportGenAgent from data_formulator.agents.client_utils import Client @@ -446,7 +447,7 @@ def generate(): model_config=model_config, input_tables=input_tables, initial_plan=initial_plan, - session_id=identity_id, + identity_id=identity_id, exec_python_in_subprocess=exec_python_in_subprocess, max_iterations=max_iterations, max_repair_attempts=max_repair_attempts, @@ -594,6 +595,38 @@ def request_code_expl(): else: return jsonify({'error': 'Invalid request format'}), 400 +@agent_bp.route('/chart-insight', methods=['GET', 'POST']) +def request_chart_insight(): + if request.is_json: + logger.info("# chart insight request") + content = request.get_json() + client = get_client(content['model']) + + chart_image = content.get("chart_image", "") + chart_type = content.get("chart_type", "") + field_names = content.get("field_names", []) + input_tables = content.get("input_tables", []) + + # Get workspace + identity_id = get_identity_id() + workspace = Workspace(identity_id) + temp_data = get_temp_tables(workspace, input_tables) + + with WorkspaceWithTempData(workspace, temp_data) as workspace: + agent = ChartInsightAgent(client=client, workspace=workspace) + candidates = agent.run(chart_image, chart_type, field_names, input_tables) + + if candidates and len(candidates) > 0: + result = candidates[0] + if result['status'] == 'ok': + return jsonify(result) + else: + return jsonify(result), 400 + else: + return jsonify({'error': 'No insight generated'}), 400 + else: + return jsonify({'error': 'Invalid request format'}), 400 + @agent_bp.route('/get-recommendation-questions', methods=['GET', 'POST']) def get_recommendation_questions(): def generate(): diff --git a/py-src/data_formulator/agents/__init__.py b/py-src/data_formulator/agents/__init__.py index 6ced4d6a..5832c024 100644 --- a/py-src/data_formulator/agents/__init__.py +++ b/py-src/data_formulator/agents/__init__.py @@ -8,6 +8,7 @@ from data_formulator.agents.agent_sort_data import SortDataAgent from data_formulator.agents.agent_data_clean import DataCleanAgent from data_formulator.agents.agent_interactive_explore import InteractiveExploreAgent +from data_formulator.agents.agent_chart_insight import ChartInsightAgent __all__ = [ "DataTransformationAgent", @@ -16,4 +17,5 @@ "SortDataAgent", "DataCleanAgent", "InteractiveExploreAgent", + "ChartInsightAgent", ] diff --git a/py-src/data_formulator/agents/agent_chart_insight.py b/py-src/data_formulator/agents/agent_chart_insight.py new file mode 100644 index 00000000..00203538 --- /dev/null +++ b/py-src/data_formulator/agents/agent_chart_insight.py @@ -0,0 +1,118 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +from data_formulator.agents.agent_utils import generate_data_summary, extract_json_objects + +import logging + +logger = logging.getLogger(__name__) + + +SYSTEM_PROMPT = r'''You are a data analyst helping users understand their visualizations. +You are given a chart image along with metadata about the chart type, data fields used, and a summary of the underlying data (including schema, value ranges, and sample rows). + +Use both the chart image and the data summary to produce: + +1. **title**: A short, descriptive title for the chart (5-10 words). It should summarize what the chart is about — the subject, the dimensions compared, and the scope. Do not include the chart type in the title. Write it in title case. + +2. **takeaways**: A list of 1-3 key findings or insights from the chart. Each takeaway should be one sentence. Highlight notable patterns, trends, outliers, or comparisons visible in the chart. Be specific — reference actual values, categories, or trends from the data when possible. + +Respond with a JSON object in exactly this format (no markdown fences): + +{"title": "...", "takeaways": ["...", "..."]} +''' + + +class ChartInsightAgent(object): + + def __init__(self, client, workspace=None): + self.client = client + self.workspace = workspace + + def run(self, chart_image_base64, chart_type, field_names, input_tables=None, n=1): + """ + Generate insight for a chart. + + Args: + chart_image_base64: Base64-encoded PNG data URL of the chart + chart_type: The type of chart (e.g., "bar", "scatter") + field_names: List of field names used in the chart encodings + input_tables: Optional list of input table dicts for data context + n: Number of candidates to generate + """ + + # Build context about the chart + context_parts = [f"Chart type: {chart_type}"] + context_parts.append(f"Fields used: {', '.join(field_names)}") + + if input_tables and self.workspace: + data_summary = generate_data_summary( + input_tables, workspace=self.workspace, + include_data_samples=True, row_sample_size=3 + ) + context_parts.append(f"\nData summary:\n{data_summary}") + + context = "\n".join(context_parts) + + # Build the message with image + user_content = [ + { + "type": "text", + "text": f"[CHART METADATA]\n{context}\n\n[CHART IMAGE]\nHere is the chart to analyze:" + }, + { + "type": "image_url", + "image_url": { + "url": f"data:image/png;base64,{chart_image_base64}", + "detail": "low" + } + } + ] + + messages = [ + {"role": "system", "content": SYSTEM_PROMPT}, + {"role": "user", "content": user_content} + ] + + logger.info(f"ChartInsightAgent: analyzing {chart_type} chart with fields {field_names}") + + response = self.client.get_completion(messages=messages) + + candidates = [] + for choice in response.choices: + logger.info("\n=== Chart insight result ===>\n") + logger.info(choice.message.content + "\n") + + response_content = choice.message.content + title = "" + takeaways = [] + + # Parse JSON response + json_blocks = extract_json_objects(response_content + "\n") + for parsed in json_blocks: + title = parsed.get('title', '') + takeaways = parsed.get('takeaways', []) + if isinstance(takeaways, str): + takeaways = [takeaways] + if title or takeaways: + break + + if title or takeaways: + result = { + 'status': 'ok', + 'title': title, + 'takeaways': takeaways, + } + else: + logger.error(f"unable to parse insight from response: {response_content}") + result = { + 'status': 'other error', + 'content': 'unable to generate chart insight' + } + + result['dialog'] = [*messages, {"role": choice.message.role, "content": choice.message.content}] + result['agent'] = 'ChartInsightAgent' + + candidates.append(result) + + return candidates diff --git a/py-src/data_formulator/agents/agent_code_explanation.py b/py-src/data_formulator/agents/agent_code_explanation.py index f67972f2..087c6937 100644 --- a/py-src/data_formulator/agents/agent_code_explanation.py +++ b/py-src/data_formulator/agents/agent_code_explanation.py @@ -9,14 +9,11 @@ logger = logging.getLogger(__name__) -SYSTEM_PROMPT = r'''You are a data scientist to help user explain code, -so that a non-code can clearly understand what the code is doing, you are provided with a summary of the input data, and the transformation code. +SYSTEM_PROMPT = r'''You are a data scientist to help user explain derived data concepts, +so that a non-coder can clearly understand what new fields mean. You are provided with a summary of the input data, and the transformation code. Your goal: -1. You should generate a good itemized explanation of the code so that the reader can understand high-level steps of what the data transformation is doing. - - Be very concise, and stay at a high-level. The reader doesn't understand code and does not want to learn exactly what the code is doing. They just want to learn what have been done from a logical level. - - The explanation should be a markdown string that is a list of bullet points (with new lines), highlight constants, data fields, and important verbs. -2. Generate a list of explanations for new fields (fields not from the input data) that introduce metrics/concepts that are not obvious from the code. +1. Generate a list of explanations for new fields (fields not from the input data) that introduce metrics/concepts that are not obvious from the code. - provide a declarative definition that explains the new field, use a mathematical notation if applicable. - only include new fields explanation of new metrics that are involved in computation (e.g., ROI, commerical_success_score) - *DO NOT* explain trivial new fields like "Decade" or "Avg_Rating", "US_Sales" that are self-explanatory. @@ -28,24 +25,18 @@ - note: when using underscores as part of the text, you need to escape them with a backslash, e.g., `\_` - Note: don't use math notation for fields whose computation is trivial (use plain english), it will likely be confusing to the reader. Only use math notation for fields that can not be easilyexplained in plain english. Use it sparingly. -3. If there are multiple fields that have the similar computation, you can explain them together in one explanation. +2. If there are multiple fields that have the similar computation, you can explain them together in one explanation. - in "field", you can provide a list of fields in format of "field1, field2, ..." - in "explanation", you can provide a single explanation for the computation of the fields. - for example, if you have fields like "Norm_Rating", "Norm_Gross", "Critical_Commercial_Score", you can explain Norm_Rating, Norm_Gross together in one explanation and explain Critical_Commercial_Score in another explanation. -4. If the code is about statistical analysis, you should explain the statistical analysis in the explanation as a concept named "Statistical Analysis" in the [CONCEPTS EXPLANATION] section. +3. If the code is about statistical analysis, you should explain the statistical analysis in the explanation as a concept named "Statistical Analysis". - explain how you model the data, which fields are used, how data processing is done, and what models are used. - suggest some other modeling approaches that can be used to analyze the data in the explanation as well. -The focus is to explain how new fields are computed, don't generate explanation for low-level actions like "return", "load data" etc. +The focus is to explain how new fields are computed, don't generate explanation for low-level actions like "return", "load data" etc. +If there are no non-trivial new fields/concepts, return an empty list. -Provide the result in the following two sections: - - first section is the code explanation that should be a markdown block explaining the code, in the [CODE EXPLANATION] section. - - remember to highlight constants, data fields, and important verbs in the code explanation. - - second section is the concepts explanation that should be a json block (start with ```json) in the [CONCEPTS EXPLANATION] section. - -[CODE EXPLANATION] - -...(explanation of the code) +Provide the result as a JSON block (start with ```json) in the [CONCEPTS EXPLANATION] section. [CONCEPTS EXPLANATION] @@ -83,18 +74,6 @@ IMDB_Rating -- type: float64, values: 1.5, 2.5, 3.0, ..., nan, nan, nan, nan IMDB_Votes -- type: float64, values: 24578.0, nan, 18.0, ..., 364077.0, 387438.0, 411088.0, 519541.0 -table_0 (movies) sample: - -``` -|Title|US_Gross|Worldwide_Gross|US_DVD_Sales|Production_Budget|Release_Date|MPAA_Rating|Running_Time_min|Distributor|Source|Major_Genre|Creative_Type|Director|Rotten_Tomatoes_Rating|IMDB_Rating|IMDB_Votes -0|The Land Girls|146083|146083||8000000|Jun 12 1998|R||Gramercy||||||6.1|1071.0 -1|First Love, Last Rites|10876|10876||300000|Aug 07 1998|R||Strand||Drama||||6.9|207.0 -2|I Married a Strange Person|203134|203134||250000|Aug 28 1998|||Lionsgate||Comedy||||6.8|865.0 -3|Let's Talk About Sex|373615|373615||300000|Sep 11 1998|||Fine Line||Comedy|||13.0|| -4|Slam|1009819|1087521||1000000|Oct 09 1998|R||Trimark|Original Screenplay|Drama|Contemporary Fiction||62.0|3.4|165.0 -...... -``` - [CODE] ```python @@ -144,31 +123,18 @@ def extract_decade(date_str): return transformed_df ``` -[CODE EXPLANATION] - -1. **Average Rating** is calculated by averaging the **Rotten_Tomatoes_Rating** (normalized to a 0-10 scale) and **IMDB_Rating**, handling missing values. -2. **Normalized Rating** is derived by scaling the **Average Rating** to a range between 0 and 1 using min-max normalization. -3. **Worldwide Gross** is normalized by scaling the values of **Worldwide_Gross** to a range between 0 and 1 using min-max normalization. -4. **Critical-Commercial Success Score** is computed as the product of **Normalized Rating** and **Normalized Worldwide Gross**, representing a combination of critical acclaim and commercial success. -5. **Decade** is extracted from the **Release_Date** by identifying the year and grouping it into its respective decade (e.g., '1990s', '2000s'). -6. The resulting dataset includes original fields (**Title**, **Major_Genre**, **Release_Date**) and newly computed fields (**Decade**, **Avg_Rating**, **Norm_Rating**, **Norm_Gross**, **Critical_Commercial_Score**). - [CONCEPTS EXPLANATION] ```json -[ - { - "field": "Norm_Rating", - "explanation": "The normalized rating scales **Avg_Rating** between 0 and 1 using min-max normalization. Formula: -BSLASH-(-BSLASH-text{Norm-BSLASH-_Rating} = -BSLASH-frac{-BSLASH-text{Avg-BSLASH-_Rating} - -BSLASH-text{Min}(-BSLASH-text{Avg-BSLASH-_Rating})}{-BSLASH-text{Max}(-BSLASH-text{Avg-BSLASH-_Rating}) - -BSLASH-text{Min}(-BSLASH-text{Avg-BSLASH-_Rating})} -BSLASH-)" - }, +[ { - "field": "Norm_Gross", - "explanation": "The normalized worldwide gross scales **Worldwide_Gross** between 0 and 1 using min-max normalization. Formula: -BSLASH-(-BSLASH-text{Norm-BSLASH-_Gross} = -BSLASH-frac{-BSLASH-text{Worldwide-BSLASH-_Gross} - -BSLASH-text{Min}(-BSLASH-text{Worldwide-BSLASH-_Gross})}{-BSLASH-text{Max}(-BSLASH-text{Worldwide-BSLASH-_Gross}) - -BSLASH-text{Min}(-BSLASH-text{Worldwide-BSLASH-_Gross})} -BSLASH-)" + "field": "Norm_Rating, Norm_Gross", + "explanation": "Normalized values that scale the original values between 0 and 1 using min-max normalization. Formula: -BSLASH-(-BSLASH-text{Normalized} = -BSLASH-frac{-BSLASH-text{Value} - -BSLASH-text{Min}}{-BSLASH-text{Max} - -BSLASH-text{Min}} -BSLASH-)" }, { "field": "Critical_Commercial_Score", "explanation": "The critical-commercial success score combines **Norm_Rating** and **Norm_Gross** to represent a movie's critical acclaim and commercial performance. Formula: -BSLASH-(-BSLASH-text{Critical-BSLASH-_Commercial-BSLASH-_Score} = -BSLASH-text{Norm-BSLASH-_Rating} -BSLASH-times -BSLASH-text{Norm-BSLASH-_Gross} -BSLASH-)" - } + } ] ''' @@ -197,21 +163,9 @@ def run(self, input_tables, code, n=1): logger.info("\n=== Code explanation result ===>\n") logger.info(choice.message.content + "\n") - # Inline parsing of both sections + # Inline parsing of concepts section response_content = choice.message.content - code_explanation = "" concepts = [] - - # Find CODE EXPLANATION section - code_start = response_content.find('[CODE EXPLANATION]') - if code_start != -1: - code_start += len('[CODE EXPLANATION]') - # Find the end of code explanation (either CONCEPTS EXPLANATION or end of content) - concepts_start = response_content.find('[CONCEPTS EXPLANATION]', code_start) - if concepts_start != -1: - code_explanation = response_content[code_start:concepts_start].strip() - else: - code_explanation = response_content[code_start:].strip() # Find CONCEPTS EXPLANATION section concepts_start = response_content.find('[CONCEPTS EXPLANATION]') @@ -220,7 +174,6 @@ def run(self, input_tables, code, n=1): # Extract JSON from the concepts section concepts_content = response_content[concepts_start:].strip() try: - # Escape backslashes by doubling them raw_json_blocks = extract_code_from_gpt_response(concepts_content, "json") json_blocks = [json.loads(block) for block in raw_json_blocks] except Exception as e: @@ -230,15 +183,14 @@ def run(self, input_tables, code, n=1): concepts = json_blocks[0] # Build result - if code_explanation or concepts != []: + if concepts: result = { 'status': 'ok', 'concepts': concepts, - 'code': code_explanation } else: - logger.error(f"unable to extract JSON from response: {response_content}") - result = {'status': 'other error', 'content': 'unable to create code and concepts explanation'} + # No non-trivial concepts found — that's ok, return empty list + result = {'status': 'ok', 'concepts': []} # individual dialog for the agent result['dialog'] = [*messages, {"role": choice.message.role, "content": choice.message.content}] diff --git a/py-src/data_formulator/agents/agent_data_rec.py b/py-src/data_formulator/agents/agent_data_rec.py index ef5e25b6..5ad7f655 100644 --- a/py-src/data_formulator/agents/agent_data_rec.py +++ b/py-src/data_formulator/agents/agent_data_rec.py @@ -25,8 +25,14 @@ **About the execution environment:** - You can use BOTH DuckDB SQL and pandas operations in the same script -- The script will run in the workspace data directory -- Use the file path shown in the [CONTEXT] section (under "**file path:**") to load data (e.g., `read_parquet('student_exam.parquet')` or `pd.read_parquet('data/sales.parquet')`) +- The script will run in the workspace data directory (all data files are in the current directory) +- Each table in [CONTEXT] has a **file path** (e.g., `student_exam.parquet`, `sales.csv`, `report.xlsx`). Use EXACTLY that path to load data: + - `.parquet` files: `pd.read_parquet('file.parquet')` or DuckDB `read_parquet('file.parquet')` + - `.csv` files: `pd.read_csv('file.csv')` or DuckDB `read_csv_auto('file.csv')` + - `.json` files: `pd.read_json('file.json')` + - `.xlsx`/`.xls` files: `pd.read_excel('file.xlsx')` + - `.txt` files: `pd.read_csv('file.txt', sep='\t')` (or appropriate delimiter) +- **IMPORTANT:** Use the exact filename from the context — do NOT change the file extension or assume all files are parquet. - **Allowed libraries:** pandas, numpy, duckdb, math, datetime, json, statistics, collections, re, sklearn, scipy, random, itertools, functools, operator, time - **Not allowed:** matplotlib, plotly, seaborn, requests, subprocess, os, sys, io, or any other library not listed above. Do NOT import them — the sandbox will reject the import. - File system access (open, write) and network access are also forbidden. @@ -206,53 +212,52 @@ **Example data loading patterns:** -Use the **file path** shown in the [CONTEXT] section to load data: +Always use the exact **file path** from [CONTEXT] to load data. Choose the reader based on the file extension: ```python -# Option 1: Load with DuckDB SQL (use file path from context) +# Parquet files (most common for workspace-generated tables) import pandas as pd import duckdb -# If context shows: - **file path:** `student_exam.parquet` -df = duckdb.sql(""" - SELECT - student, - major, - (math + reading + writing) / 3.0 AS average_score, - RANK() OVER (ORDER BY (math + reading + writing) / 3.0 DESC) AS rank - FROM read_parquet('student_exam.parquet') - ORDER BY average_score DESC -""").df() +# pandas +df = pd.read_parquet('student_exam.parquet') -result_df = df +# DuckDB (preferred for large datasets) +df = duckdb.sql("SELECT * FROM read_parquet('student_exam.parquet')").df() ``` ```python -# Option 2: Load with pandas (use file path from context) +# CSV files import pandas as pd +import duckdb -# If context shows: - **file path:** `student_exam.parquet` -df = pd.read_parquet('student_exam.parquet') -df['average_score'] = (df['math'] + df['reading'] + df['writing']) / 3.0 -df['rank'] = df['average_score'].rank(ascending=False, method='min') -df = df.sort_values('average_score', ascending=False) +# pandas +df = pd.read_csv('sales.csv') + +# DuckDB +df = duckdb.sql("SELECT * FROM read_csv_auto('sales.csv')").df() +``` + +```python +# Excel / JSON / TXT files (use pandas) +import pandas as pd -result_df = df[['student', 'major', 'average_score', 'rank']] +df = pd.read_excel('report.xlsx') # .xlsx or .xls +df = pd.read_json('data.json') # .json +df = pd.read_csv('log.txt', sep='\t') # .txt (tab-delimited) ``` ```python -# Option 3: Hybrid - DuckDB for aggregation, pandas for reshaping +# Hybrid example: DuckDB for aggregation, pandas for reshaping import pandas as pd import duckdb -# Aggregate with DuckDB df = duckdb.sql(""" SELECT category, SUM(value) as total FROM read_parquet('data.parquet') GROUP BY category """).df() -# Reshape with pandas result_df = df.pivot(columns='category', values='total') ``` @@ -332,7 +337,7 @@ class DataRecAgent(object): - def __init__(self, client, workspace, system_prompt=None, agent_coding_rules="", max_display_rows=5000): + def __init__(self, client, workspace, system_prompt=None, agent_coding_rules="", max_display_rows=10000): self.client = client self.workspace = workspace self.max_display_rows = max_display_rows diff --git a/py-src/data_formulator/agents/agent_data_transform.py b/py-src/data_formulator/agents/agent_data_transform.py index f8d510e3..1c267e77 100644 --- a/py-src/data_formulator/agents/agent_data_transform.py +++ b/py-src/data_formulator/agents/agent_data_transform.py @@ -23,8 +23,14 @@ **About the execution environment:** - You can use BOTH DuckDB SQL and pandas operations in the same script -- The script will run in the workspace data directory -- Use the file path shown in the [CONTEXT] section (under "**file path:**") to load data (e.g., `read_parquet('student_exam.parquet')` or `pd.read_parquet('data/sales.parquet')`) +- The script will run in the workspace data directory (all data files are in the current directory) +- Each table in [CONTEXT] has a **file path** (e.g., `student_exam.parquet`, `sales.csv`, `report.xlsx`). Use EXACTLY that path to load data: + - `.parquet` files: `pd.read_parquet('file.parquet')` or DuckDB `read_parquet('file.parquet')` + - `.csv` files: `pd.read_csv('file.csv')` or DuckDB `read_csv_auto('file.csv')` + - `.json` files: `pd.read_json('file.json')` + - `.xlsx`/`.xls` files: `pd.read_excel('file.xlsx')` + - `.txt` files: `pd.read_csv('file.txt', sep='\t')` (or appropriate delimiter) +- **IMPORTANT:** Use the exact filename from the context — do NOT change the file extension or assume all files are parquet. - **Allowed libraries:** pandas, numpy, duckdb, math, datetime, json, statistics, collections, re, sklearn, scipy, random, itertools, functools, operator, time - **Not allowed:** matplotlib, plotly, seaborn, requests, subprocess, os, sys, io, or any other library not listed above. Do NOT import them — the sandbox will reject the import. - File system access (open, write) and network access are also forbidden. @@ -125,27 +131,47 @@ **Example data loading patterns:** -Use the **file path** shown in the [CONTEXT] section to load data: +Always use the exact **file path** from [CONTEXT] to load data. Choose the reader based on the file extension: ```python -# Option 1: Load with DuckDB SQL (use file path from context) +# Parquet files (most common for workspace-generated tables) import pandas as pd import duckdb -# If context shows: - **file path:** `sales_data.parquet` +# pandas +df = pd.read_parquet('sales_data.parquet') + +# DuckDB (preferred for large datasets) df = duckdb.sql(""" - SELECT - date, - SUM(sales) as total_sales + SELECT date, SUM(sales) as total_sales FROM read_parquet('sales_data.parquet') GROUP BY date """).df() +``` -# Option 2: Load with pandas (use file path from context) +```python +# CSV files import pandas as pd -df = pd.read_parquet('sales_data.parquet') +import duckdb + +# pandas +df = pd.read_csv('sales.csv') -# Option 3: Hybrid - DuckDB for aggregation, pandas for time series +# DuckDB +df = duckdb.sql("SELECT * FROM read_csv_auto('sales.csv')").df() +``` + +```python +# Excel / JSON / TXT files (use pandas) +import pandas as pd + +df = pd.read_excel('report.xlsx') # .xlsx or .xls +df = pd.read_json('data.json') # .json +df = pd.read_csv('log.txt', sep='\t') # .txt (tab-delimited) +``` + +```python +# Hybrid example: DuckDB for aggregation, pandas for time series import pandas as pd import duckdb @@ -241,7 +267,7 @@ class DataTransformationAgent(object): - def __init__(self, client, workspace, system_prompt=None, agent_coding_rules="", max_display_rows=5000): + def __init__(self, client, workspace, system_prompt=None, agent_coding_rules="", max_display_rows=10000): self.client = client self.workspace = workspace self.max_display_rows = max_display_rows diff --git a/py-src/data_formulator/agents/semantic_types.py b/py-src/data_formulator/agents/semantic_types.py index 13d9ee7b..b3a158c1 100644 --- a/py-src/data_formulator/agents/semantic_types.py +++ b/py-src/data_formulator/agents/semantic_types.py @@ -307,3 +307,127 @@ def generate_semantic_types_prompt() -> str: "Date", "Time", "DateTime", "TimeRange", "Range", "Duration", "Name", "Percentage", "String", "Number" ] + + +# --------------------------------------------------------------------------- +# Semantic Type → Vega-Lite Encoding Type +# --------------------------------------------------------------------------- + +VL_TYPE_MAP: Dict[str, str] = { + # Temporal → temporal + "DateTime": "temporal", "Date": "temporal", "Time": "temporal", + "YearMonth": "temporal", "YearQuarter": "temporal", "YearWeek": "temporal", + + # Temporal granules → ordinal + "Year": "ordinal", "Quarter": "ordinal", "Month": "ordinal", + "Week": "ordinal", "Day": "ordinal", "Hour": "ordinal", "Decade": "ordinal", + "TimeRange": "ordinal", + + # Duration → quantitative + "Duration": "quantitative", + + # Measures → quantitative + "Quantity": "quantitative", "Count": "quantitative", + "Amount": "quantitative", "Price": "quantitative", "Revenue": "quantitative", "Cost": "quantitative", + "Percentage": "quantitative", "Rate": "quantitative", "Ratio": "quantitative", + "Distance": "quantitative", "Area": "quantitative", "Volume": "quantitative", + "Weight": "quantitative", "Temperature": "quantitative", "Speed": "quantitative", + + # Discrete numerics → ordinal (except ID/Score/Rating which differ) + "Rank": "ordinal", "Index": "ordinal", "Score": "quantitative", + "Rating": "quantitative", "Level": "ordinal", + "ID": "nominal", + + # Geographic coordinates → quantitative (for lat/lon encoding) + "Latitude": "quantitative", "Longitude": "quantitative", "Coordinates": "quantitative", + + # Geographic locations → nominal + "Location": "nominal", "Country": "nominal", "State": "nominal", "City": "nominal", + "Region": "nominal", "Address": "nominal", "ZipCode": "nominal", + + # Entity names → nominal + "Name": "nominal", "PersonName": "nominal", "Username": "nominal", "Email": "nominal", + "Company": "nominal", "Brand": "nominal", "Department": "nominal", + "Product": "nominal", "SKU": "nominal", "Category": "nominal", + + # Coded → nominal + "Status": "nominal", "Type": "nominal", "Boolean": "nominal", "Binary": "nominal", "Code": "nominal", + + # Ranges → ordinal + "Range": "ordinal", "AgeGroup": "ordinal", "Bucket": "ordinal", + + # Fallbacks + "String": "nominal", "Number": "quantitative", "Unknown": "nominal", +} + + +def get_vl_type(semantic_type: str) -> Optional[str]: + """ + Get the Vega-Lite encoding type for a semantic type. + Returns 'quantitative', 'ordinal', 'nominal', or 'temporal', or None if unknown. + """ + return VL_TYPE_MAP.get(semantic_type) + + +# --------------------------------------------------------------------------- +# Name-based Heuristic Inference +# --------------------------------------------------------------------------- +# +# For derived columns (from agent code) that lack frontend semantic type +# metadata, infer a likely VL type from the column name. +# +# Pattern matching is intentionally conservative — only triggers when +# the column name strongly suggests a specific meaning. +# --------------------------------------------------------------------------- + +import re as _re + +# Patterns that strongly indicate quantitative (measures) +_QUANT_PATTERNS: list[_re.Pattern] = [ + _re.compile(r'(?:^|_)(avg|mean|average|sum|total|count|num|min|max|median|std|stdev|var|variance)(?:_|$)', _re.I), + _re.compile(r'(?:^|_)(revenue|sales|profit|income|cost|expense|price|amount|quantity|volume|weight|distance|speed|temperature|rate|ratio|pct|percent|percentage|growth|change|diff|delta)(?:_|$)', _re.I), + _re.compile(r'(?:^|_)(lat|lon|latitude|longitude)(?:_|$)', _re.I), +] + +# Patterns that indicate temporal +_TEMPORAL_PATTERNS: list[_re.Pattern] = [ + _re.compile(r'(?:^|_)(date|datetime|timestamp|time|created_at|updated_at|started_at|ended_at)(?:_|$)', _re.I), +] + +# Patterns that indicate ordinal (time granules) +_ORDINAL_PATTERNS: list[_re.Pattern] = [ + _re.compile(r'^(year|month|quarter|week|day|hour|decade|year_month|year_quarter)$', _re.I), + _re.compile(r'(?:^|_)(rank|ranking|level|tier|grade|priority)(?:_|$)', _re.I), +] + +# Patterns that indicate nominal (categorical) +_NOMINAL_PATTERNS: list[_re.Pattern] = [ + _re.compile(r'(?:^|_)(name|label|category|type|status|group|class|kind|tag|code|id|key)(?:_|$)', _re.I), + _re.compile(r'(?:^|_)(country|state|city|region|location|department|brand|company|product)(?:_|$)', _re.I), +] + + +def infer_vl_type_from_name(column_name: str) -> Optional[str]: + """ + Infer a likely Vega-Lite type from a column name using pattern matching. + Returns 'quantitative', 'ordinal', 'nominal', 'temporal', or None if + no strong signal is found. + """ + # Check patterns in priority order + for pattern in _TEMPORAL_PATTERNS: + if pattern.search(column_name): + return 'temporal' + + for pattern in _ORDINAL_PATTERNS: + if pattern.search(column_name): + return 'ordinal' + + for pattern in _QUANT_PATTERNS: + if pattern.search(column_name): + return 'quantitative' + + for pattern in _NOMINAL_PATTERNS: + if pattern.search(column_name): + return 'nominal' + + return None diff --git a/py-src/data_formulator/app.py b/py-src/data_formulator/app.py index 1b032e61..8c0268fc 100644 --- a/py-src/data_formulator/app.py +++ b/py-src/data_formulator/app.py @@ -55,7 +55,7 @@ def default(self, obj): 'disable_database': os.environ.get('DISABLE_DATABASE', 'false').lower() == 'true', 'disable_file_upload': os.environ.get('DISABLE_FILE_UPLOAD', 'false').lower() == 'true', 'project_front_page': os.environ.get('PROJECT_FRONT_PAGE', 'false').lower() == 'true', - 'max_display_rows': int(os.environ.get('MAX_DISPLAY_ROWS', '5000')), + 'max_display_rows': int(os.environ.get('MAX_DISPLAY_ROWS', '10000')), 'data_dir': os.environ.get('DATA_FORMULATOR_HOME', None), } diff --git a/py-src/data_formulator/datalake/workspace.py b/py-src/data_formulator/datalake/workspace.py index 76b2e040..7f6ccc98 100644 --- a/py-src/data_formulator/datalake/workspace.py +++ b/py-src/data_formulator/datalake/workspace.py @@ -649,18 +649,18 @@ def __enter__(self) -> "WorkspaceWithTempData": for item in self._temp_data: base_name = item.get("name", "table") safe_name = sanitize_table_name(base_name) - filename = f"{safe_name}.parquet" + filename = f"{safe_name}.csv" file_path = self._path / filename rows = item.get("rows", []) df = pd.DataFrame(rows) if rows else pd.DataFrame() - df.to_parquet(file_path) + df.to_csv(file_path, index=False) self._overlay[safe_name] = TableMetadata( name=safe_name, source_type="upload", filename=filename, - file_type="parquet", + file_type="csv", created_at=datetime.now(), row_count=len(df), ) diff --git a/py-src/data_formulator/sandbox/py_sandbox.py b/py-src/data_formulator/sandbox/py_sandbox.py index 998e9ea5..816a76d0 100644 --- a/py-src/data_formulator/sandbox/py_sandbox.py +++ b/py-src/data_formulator/sandbox/py_sandbox.py @@ -113,6 +113,12 @@ def safe_import(name, *args, **kwargs): return {'status': 'ok', 'allowed_objects': {key: restricted_globals[key] for key in allowed_objects}} +# Lock to serialize os.chdir in the main-process path. +# os.chdir is process-global, so concurrent threads would race on it. +import threading +_chdir_lock = threading.Lock() + + def run_unified_transform_in_sandbox( code: str, workspace_path: str, @@ -134,22 +140,32 @@ def run_unified_transform_in_sandbox( """ import os - # Save current directory - original_cwd = os.getcwd() + # Prepend an os.chdir() call into the executed code itself so that: + # - In subprocess mode, the child process changes its own cwd (no race). + # - In main-process mode, we still rely on os.chdir but protect it with + # a lock so concurrent requests don't stomp on each other's cwd. + workspace_path_escaped = str(workspace_path).replace("\\", "\\\\").replace("'", "\\'") + chdir_preamble = f"import os as _sandbox_os; _sandbox_os.chdir('{workspace_path_escaped}')\n" + code_with_chdir = chdir_preamble + code try: - # Change to workspace directory so script can access files directly - os.chdir(workspace_path) - allowed_objects = { output_variable: None # Will be populated by script } - # Execute the script directly (no function wrapper) if exec_python_in_subprocess: - result = run_in_subprocess(code, allowed_objects) + # Subprocess: the child gets its own process-global cwd — no race. + result = run_in_subprocess(code_with_chdir, allowed_objects) else: - result = run_in_main_process(code, allowed_objects) + # Main-process: serialise the chdir+exec to avoid cwd races + # between concurrent Flask threads. + original_cwd = os.getcwd() + with _chdir_lock: + try: + os.chdir(workspace_path) + result = run_in_main_process(code, allowed_objects) + finally: + os.chdir(original_cwd) if result['status'] == 'ok': output_df = result['allowed_objects'][output_variable] @@ -173,6 +189,3 @@ def run_unified_transform_in_sandbox( 'status': 'error', 'content': f"Error during execution setup: {type(e).__name__} - {str(e)}" } - finally: - # Always restore original directory - os.chdir(original_cwd) diff --git a/py-src/data_formulator/tables_routes.py b/py-src/data_formulator/tables_routes.py index e1ae9b80..e7fbda33 100644 --- a/py-src/data_formulator/tables_routes.py +++ b/py-src/data_formulator/tables_routes.py @@ -9,7 +9,7 @@ mimetypes.add_type('application/javascript', '.mjs') import json import traceback -from flask import request, jsonify, Blueprint +from flask import request, jsonify, Blueprint, Response import pandas as pd from pathlib import Path diff --git a/py-src/data_formulator/workflows/create_vl_plots.py b/py-src/data_formulator/workflows/create_vl_plots.py index be335fad..d4ee326c 100644 --- a/py-src/data_formulator/workflows/create_vl_plots.py +++ b/py-src/data_formulator/workflows/create_vl_plots.py @@ -3,6 +3,37 @@ from typing import Any import vl_convert as vlc import base64 +import logging + +from data_formulator.agents.semantic_types import infer_vl_type_from_name + +logger = logging.getLogger(__name__) + + +def resolve_field_type( + series: pd.Series, + field_name: str | None = None, +) -> str: + """ + Resolve the Vega-Lite type for a field. Priority: + 1. Column-name heuristic (catches derived columns like avg_revenue, year, etc.) + 2. Pandas dtype detection (fallback) + + Parameters: + - series: the pandas Series for the field + - field_name: column name (used for name heuristics) + + Returns one of: 'quantitative', 'nominal', 'ordinal', 'temporal' + """ + # 1. Try column-name heuristic (useful for derived columns) + if field_name: + inferred = infer_vl_type_from_name(field_name) + if inferred: + return inferred + + # 2. Fall back to pandas-based detection + return detect_field_type(series) + def detect_field_type(series: pd.Series) -> str: """ @@ -64,10 +95,58 @@ def detect_field_type(series: pd.Series) -> str: "chart": "boxplot", "mark": "boxplot", "channels": ["x", "y", "opacity", "column", "row"] + }, + { + "chart": "histogram", + "mark": "bar", + "channels": ["x", "color", "column", "row"] + }, + { + "chart": "pie", + "mark": "arc", + "channels": ["theta", "color", "column", "row"] + }, + { + "chart": "worldmap", + "mark": "circle", + "channels": ["longitude", "latitude", "color", "size", "opacity"] + }, + { + "chart": "usmap", + "mark": "circle", + "channels": ["longitude", "latitude", "color", "size"] } ] +# Chart-type-aware expected types per (chart_type, channel). +# When detect_field_type disagrees with what the chart semantics require, +# the value here wins. None means "keep detected". +_CHANNEL_TYPE_OVERRIDES: dict[str, dict[str, str]] = { + "histogram": {"x": "quantitative"}, + "bar": {"x": "nominal"}, + "group_bar": {"x": "nominal"}, + "heatmap": {"x": "nominal", "y": "nominal"}, + "boxplot": {"x": "nominal", "y": "quantitative"}, + "pie": {"theta": "quantitative", "color": "nominal"}, + "worldmap": {"longitude": "quantitative", "latitude": "quantitative"}, + "usmap": {"longitude": "quantitative", "latitude": "quantitative"}, +} + + +def coerce_field_type(chart_type: str, channel: str, detected_type: str) -> str: + """ + Return the Vega-Lite type that should actually be used for this + (chart_type, channel) combination. If no override is needed the + originally detected type is returned unchanged. + """ + overrides = _CHANNEL_TYPE_OVERRIDES.get(chart_type, {}) + forced = overrides.get(channel) + if forced: + return forced + return detected_type + + def get_chart_template(chart_type: str) -> dict | None: """ Find a chart template by chart type name. @@ -390,7 +469,8 @@ def assemble_vegailte_chart( df: pd.DataFrame, chart_type: str, encodings: dict[str, dict[str, str]], - max_nominal_values: int = 68 + max_nominal_values: int = 68, + config: dict | None = None, ) -> dict: """ Assemble a Vega-Lite chart specification from a dataframe, chart type, and encodings. @@ -412,11 +492,43 @@ def assemble_vegailte_chart( if not template: raise ValueError(f"Chart type '{chart_type}' not found in templates") - # Create the spec structure directly - spec = { - "mark": template["mark"], - "encoding": {} - } + # Build initial spec — some chart types need special structure + if chart_type == "histogram": + spec = { + "mark": "bar", + "encoding": { + "x": {"bin": True}, + "y": {"aggregate": "count"} + } + } + elif chart_type in ("worldmap", "usmap"): + projection_type = "albersUsa" if chart_type == "usmap" else "equalEarth" + topo_url = ( + "https://vega.github.io/vega-lite/data/us-10m.json" if chart_type == "usmap" + else "https://vega.github.io/vega-lite/data/world-110m.json" + ) + topo_feature = "states" if chart_type == "usmap" else "countries" + spec = { + "width": 500 if chart_type == "usmap" else 600, + "height": 300 if chart_type == "usmap" else 350, + "layer": [ + { + "data": {"url": topo_url, "format": {"type": "topojson", "feature": topo_feature}}, + "projection": {"type": projection_type}, + "mark": {"type": "geoshape", "fill": "lightgray", "stroke": "white"}, + }, + { + "projection": {"type": projection_type}, + "mark": "circle", + "encoding": {}, + }, + ], + } + else: + spec = { + "mark": template["mark"], + "encoding": {} + } # Remove duplicate columns before converting to records if df.columns.duplicated().any(): @@ -451,7 +563,7 @@ def assemble_vegailte_chart( encoding_obj["type"] = "quantitative" else: # Regular field encoding - field_type = detect_field_type(df[field_name]) + field_type = resolve_field_type(df[field_name], field_name) encoding_obj["field"] = field_name encoding_obj["type"] = field_type @@ -461,6 +573,10 @@ def assemble_vegailte_chart( encoding_obj["type"] = "nominal" else: encoding_obj["type"] = "temporal" + + # Chart-type-aware type coercion (e.g. histogram x must be quantitative, + # bar x must be nominal, heatmap x/y must be nominal, etc.) + encoding_obj["type"] = coerce_field_type(chart_type, channel, encoding_obj["type"]) # Scale configurations for quantitative line charts if (encoding_obj["type"] == "quantitative" and @@ -480,8 +596,23 @@ def assemble_vegailte_chart( "labelFontSize": 8 } - # Add encoding to spec - spec["encoding"][channel] = encoding_obj + # For map charts, encodings go into the second layer + if chart_type in ("worldmap", "usmap"): + spec["layer"][1]["encoding"][channel] = encoding_obj + else: + # Add encoding to spec + spec["encoding"][channel] = encoding_obj + + # Special handling for histogram: ensure x has bin:true and y has count + if chart_type == "histogram": + if "x" in spec["encoding"]: + spec["encoding"]["x"]["bin"] = True + if "y" not in spec["encoding"]: + spec["encoding"]["y"] = {"aggregate": "count"} + + # Special handling for pie: mark is 'arc' + if chart_type == "pie": + spec["mark"] = "arc" # Special handling for group_bar: add xOffset using the same field as color if chart_type == "group_bar" and "color" in spec["encoding"]: @@ -491,16 +622,27 @@ def assemble_vegailte_chart( "type": color_encoding.get("type", "nominal") } + # Apply config options + if config: + _apply_chart_config(spec, chart_type, config) + + # Handle agent "facet" channel → map to "column" so the existing column→facet logic picks it up + enc_target = spec["layer"][1]["encoding"] if chart_type in ("worldmap", "usmap") else spec.get("encoding", {}) + if "facet" in enc_target and "column" not in enc_target: + enc_target["column"] = enc_target.pop("facet") + # Handle faceting (column without row becomes facet) - if "column" in spec["encoding"] and "row" not in spec["encoding"]: - spec["encoding"]["facet"] = spec["encoding"]["column"] - spec["encoding"]["facet"]["columns"] = 6 - del spec["encoding"]["column"] + if "encoding" in spec: + if "column" in spec["encoding"] and "row" not in spec["encoding"]: + spec["encoding"]["facet"] = spec["encoding"]["column"] + spec["encoding"]["facet"]["columns"] = 6 + del spec["encoding"]["column"] # Handle nominal axes with many entries + spec_encoding = spec.get("encoding", {}) for channel in ['x', 'y', 'column', 'row']: - if channel in spec["encoding"]: - encoding = spec["encoding"][channel] + if channel in spec_encoding: + encoding = spec_encoding[channel] if encoding.get("type") == "nominal": field_name = encoding["field"] unique_values = df[field_name].unique() @@ -544,6 +686,65 @@ def assemble_vegailte_chart( return spec +def _apply_chart_config(spec: dict, chart_type: str, config: dict): + """Apply optional config overrides to a Vega-Lite spec.""" + if not config: + return + + def _ensure_mark_obj(s): + """Convert string mark to object so we can add properties.""" + if isinstance(s.get("mark"), str): + s["mark"] = {"type": s["mark"]} + + if chart_type == "histogram": + bin_count = config.get("binCount") + if bin_count and "encoding" in spec and "x" in spec["encoding"]: + spec["encoding"]["x"]["bin"] = {"maxbins": int(bin_count)} + + elif chart_type == "pie": + inner_radius = config.get("innerRadius") + if inner_radius is not None: + _ensure_mark_obj(spec) + spec["mark"]["innerRadius"] = int(inner_radius) + + elif chart_type == "heatmap": + color_scheme = config.get("colorScheme") + if color_scheme and "encoding" in spec and "color" in spec["encoding"]: + spec["encoding"]["color"].setdefault("scale", {})["scheme"] = color_scheme + + elif chart_type == "point": + opacity = config.get("opacity") + if opacity is not None: + _ensure_mark_obj(spec) + spec["mark"]["opacity"] = float(opacity) + + elif chart_type in ("bar", "group_bar"): + corner_radius = config.get("cornerRadius") + if corner_radius is not None: + _ensure_mark_obj(spec) + spec["mark"]["cornerRadiusEnd"] = int(corner_radius) + + elif chart_type == "line": + interpolate = config.get("interpolate") + if interpolate: + _ensure_mark_obj(spec) + spec["mark"]["interpolate"] = interpolate + + elif chart_type == "worldmap": + projection = config.get("projection") + projection_center = config.get("projectionCenter") + if projection and "layer" in spec: + for layer in spec["layer"]: + if "projection" in layer: + layer["projection"]["type"] = projection + if projection_center and "layer" in spec: + # projectionCenter [lon, lat] → rotate [-lon, -lat, 0] + lon, lat = projection_center + for layer in spec["layer"]: + if "projection" in layer: + layer["projection"]["rotate"] = [-lon, -lat, 0] + + def _get_top_values(df: pd.DataFrame, field_name: str, unique_values: list, channel: str, spec: dict, max_values: int) -> list: """ diff --git a/py-src/data_formulator/workflows/exploration_flow.py b/py-src/data_formulator/workflows/exploration_flow.py index 59b64dec..6bb4ebbb 100644 --- a/py-src/data_formulator/workflows/exploration_flow.py +++ b/py-src/data_formulator/workflows/exploration_flow.py @@ -9,14 +9,15 @@ from data_formulator.agents.agent_data_rec import DataRecAgent from data_formulator.agents.client_utils import Client from data_formulator.datalake.workspace import WorkspaceWithTempData, Workspace -from data_formulator.workflows.create_vl_plots import assemble_vegailte_chart, spec_to_base64, detect_field_type +from data_formulator.workflows.create_vl_plots import assemble_vegailte_chart, spec_to_base64, coerce_field_type, resolve_field_type logger = logging.getLogger(__name__) def create_chart_spec_from_data( transformed_data: dict[str, Any], chart_type: str, - chart_encodings: dict[str, str] + chart_encodings: dict[str, str], + config: dict | None = None, ) -> str: """ Create a chart from transformed data using Vega-Lite. @@ -25,6 +26,7 @@ def create_chart_spec_from_data( transformed_data: Dictionary with 'rows' key containing the data chart_type: Type of chart to create (bar, point, line, etc.) chart_encodings: Dictionary mapping channel names to field names (e.g., {"x": "field1", "y": "field2"}) + config: Optional chart configuration (binCount, innerRadius, colorScheme, etc.) Returns: Base64 encoded PNG image string @@ -41,12 +43,13 @@ def create_chart_spec_from_data( encodings = {} for channel, field in chart_encodings.items(): if field and field in df.columns: - # Determine field type for encoding - field_type = detect_field_type(df[field]) + # Resolve type using name heuristic + pandas detection, then coerce for chart + field_type = resolve_field_type(df[field], field) + field_type = coerce_field_type(chart_type, channel, field_type) encodings[channel] = {"field": field, "type": field_type} # Create Vega-Lite specification - spec = assemble_vegailte_chart(df, chart_type, encodings) + spec = assemble_vegailte_chart(df, chart_type, encodings, config=config) return spec @@ -215,11 +218,13 @@ def run_exploration_flow_streaming( # Step 2: Create visualization to help generate followup question chart_type = refined_goal.get('chart_type', 'bar') chart_encodings = refined_goal.get('chart_encodings', {}) + chart_config = refined_goal.get('config', {}) chart_spec = create_chart_spec_from_data( transformed_data, chart_type, - chart_encodings + chart_encodings, + config=chart_config, ) current_visualization = spec_to_base64(chart_spec) if chart_spec else None diff --git a/src/app/App.tsx b/src/app/App.tsx index b787c2ff..ed4c6190 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -557,7 +557,7 @@ const ConfigDialog: React.FC = () => { const [defaultChartWidth, setDefaultChartWidth] = useState(config.defaultChartWidth ?? 300); const [defaultChartHeight, setDefaultChartHeight] = useState(config.defaultChartHeight ?? 300); - const [frontendRowLimit, setFrontendRowLimit] = useState(config.frontendRowLimit ?? 10000); + const [frontendRowLimit, setFrontendRowLimit] = useState(config.frontendRowLimit ?? 50000); const [paletteKey, setPaletteKey] = useState( (config.paletteKey && palettes[config.paletteKey]) ? config.paletteKey : defaultPaletteKey ); @@ -732,7 +732,7 @@ const ConfigDialog: React.FC = () => { setFormulateTimeoutSeconds(30); setDefaultChartWidth(300); setDefaultChartHeight(300); - setFrontendRowLimit(10000); + setFrontendRowLimit(50000); setPaletteKey(defaultPaletteKey); }}>Reset to default diff --git a/src/app/chartCache.ts b/src/app/chartCache.ts index e9659173..0ded5ec6 100644 --- a/src/app/chartCache.ts +++ b/src/app/chartCache.ts @@ -42,6 +42,47 @@ export function clearCache(): void { cache.clear(); } +/** + * Get a higher-resolution PNG data URL from the cached SVG. + * Renders the SVG onto a canvas at specified dimensions and + * exports as a PNG data URL suitable for sending to a vision model. + * Falls back to the thumbnail if SVG rendering fails. + */ +export async function getChartPngDataUrl( + chartId: string, + width: number = 400, + height: number = 400, +): Promise { + const entry = cache.get(chartId); + if (!entry) return undefined; + + try { + const svgBlob = new Blob([entry.svg], { type: 'image/svg+xml;charset=utf-8' }); + const url = URL.createObjectURL(svgBlob); + + const img = new Image(); + await new Promise((resolve, reject) => { + img.onload = () => resolve(); + img.onerror = reject; + img.src = url; + }); + + const canvas = document.createElement('canvas'); + canvas.width = width * 2; // 2x for clarity + canvas.height = height * 2; + const ctx = canvas.getContext('2d')!; + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + + URL.revokeObjectURL(url); + return canvas.toDataURL('image/png'); + } catch (err) { + console.warn('getChartPngDataUrl: SVG render failed, falling back to thumbnail', err); + return entry.thumbnailDataUrl || undefined; + } +} + /** * Compute a deterministic cache key from chart rendering inputs. * This is used to detect when a chart needs re-rendering. diff --git a/src/app/dfSlice.tsx b/src/app/dfSlice.tsx index 3e36b1e9..fd674157 100644 --- a/src/app/dfSlice.tsx +++ b/src/app/dfSlice.tsx @@ -2,7 +2,7 @@ // Licensed under the MIT License. import { createAsyncThunk, createSlice, PayloadAction, createSelector } from '@reduxjs/toolkit' -import { Channel, Chart, ChartTemplate, DataCleanBlock, DataSourceConfig, EncodingItem, EncodingMap, FieldItem, Trigger } from '../components/ComponentType' +import { Channel, Chart, ChartTemplate, DataCleanBlock, DataSourceConfig, EncodingItem, EncodingMap, FieldItem, Trigger, computeInsightKey, ChartInsight } from '../components/ComponentType' import { enableMapSet } from 'immer'; import { DictTable } from "../components/ComponentType"; import { Message } from '../views/MessageSnackbar'; @@ -10,6 +10,7 @@ import { getChartTemplate, getChartChannels } from "../components/ChartTemplates import { recommendEncodings } from '../components/chartUtils'; import { getDataTable } from '../views/VisualizationView'; import { adaptChart, getTriggers, getUrls, computeContentHash, fetchWithIdentity } from './utils'; +import { getChartPngDataUrl } from './chartCache'; import { Type } from '../data/types'; import { createTableFromFromObjectArray, inferTypeFromValueArray } from '../data/utils'; import { Identity, IdentityType, getBrowserId } from './identity'; @@ -103,6 +104,7 @@ export interface DataFormulatorState { viewMode: 'editor' | 'report'; chartSynthesisInProgress: string[]; + chartInsightInProgress: string[]; serverConfig: ServerConfig; @@ -156,20 +158,21 @@ const initialState: DataFormulatorState = { viewMode: 'editor', chartSynthesisInProgress: [], + chartInsightInProgress: [], serverConfig: { DISABLE_DISPLAY_KEYS: false, DISABLE_DATABASE: true, // disable database by default DISABLE_FILE_UPLOAD: false, PROJECT_FRONT_PAGE: false, - MAX_DISPLAY_ROWS: 5000, + MAX_DISPLAY_ROWS: 10000, }, config: { formulateTimeoutSeconds: 60, defaultChartWidth: 300, defaultChartHeight: 300, - frontendRowLimit: 10000, + frontendRowLimit: 50000, paletteKey: 'fluent', }, @@ -299,6 +302,70 @@ export const fetchCodeExpl = createAsyncThunk( } ); +export const fetchChartInsight = createAsyncThunk( + "dataFormulatorSlice/fetchChartInsight", + async (args: { chartId: string; tableId: string }, { getState }) => { + console.log(">>> call agent to generate chart insight <<<"); + + let state = getState() as DataFormulatorState; + let chart = dfSelectors.getAllCharts(state).find(c => c.id === args.chartId); + if (!chart) throw new Error(`Chart not found: ${args.chartId}`); + + // Get high-res PNG from the rendered chart + let chartImage = await getChartPngDataUrl(args.chartId); + if (!chartImage) throw new Error(`No rendered chart image for: ${args.chartId}`); + + // Strip the data:image/png;base64, prefix for the backend + const base64Prefix = 'data:image/png;base64,'; + if (chartImage.startsWith(base64Prefix)) { + chartImage = chartImage.substring(base64Prefix.length); + } + + // Collect field names from the encoding map + let fieldNames = Object.values(chart.encodingMap) + .map(enc => enc.fieldID) + .filter((id): id is string => !!id) + .map(id => { + let field = state.conceptShelfItems.find(f => f.id === id); + return field?.name || id; + }); + + // Collect input table info (include source tables for derived tables) + let table = state.tables.find(t => t.id === args.tableId); + let tableIds = table?.derive?.source ? [...table.derive.source, table.id] : [table?.id].filter(Boolean); + let inputTables = [...new Set(tableIds)] + .map(tId => state.tables.find(t => t.id === tId)) + .filter((t): t is DictTable => !!t) + .map(t => ({ + name: t.id, + rows: t.rows, + attached_metadata: t.attachedMetadata, + })); + + let message = { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + token: Date.now(), + chart_image: chartImage, + chart_type: chart.chartType, + field_names: fieldNames, + input_tables: inputTables, + model: dfSelectors.getActiveModel(state), + }), + }; + + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 30000); + + let response = await fetchWithIdentity(getUrls().CHART_INSIGHT_URL, { ...message, signal: controller.signal }); + clearTimeout(timeoutId); + + let result = await response.json(); + return { ...result, chartId: args.chartId, insightKey: computeInsightKey(chart) }; + } +); + export const fetchAvailableModels = createAsyncThunk( "dataFormulatorSlice/fetchAvailableModels", async () => { @@ -779,6 +846,12 @@ export const dataFormulatorSlice = createSlice({ chart.thumbnail = action.payload.thumbnail; } }, + updateChartInsight: (state, action: PayloadAction<{chartId: string, insight: ChartInsight}>) => { + let chart = dfSelectors.getAllCharts(state).find(c => c.id == action.payload.chartId); + if (chart) { + chart.insight = action.payload.insight; + } + }, updateChartEncoding: (state, action: PayloadAction<{chartId: string, channel: Channel, encoding: EncodingItem}>) => { let chartId = action.payload.chartId; let channel = action.payload.channel; @@ -1096,6 +1169,26 @@ export const dataFormulatorSlice = createSlice({ console.log("fetched codeExpl"); console.log(action.payload); }) + .addCase(fetchChartInsight.pending, (state, action) => { + let chartId = action.meta.arg.chartId; + if (!state.chartInsightInProgress.includes(chartId)) { + state.chartInsightInProgress.push(chartId); + } + }) + .addCase(fetchChartInsight.fulfilled, (state, action) => { + let { chartId, insightKey, title, takeaways } = action.payload; + let chart = dfSelectors.getAllCharts(state).find(c => c.id === chartId); + if (chart && (title || (takeaways && takeaways.length > 0))) { + chart.insight = { title, takeaways: takeaways || [], key: insightKey }; + } + state.chartInsightInProgress = state.chartInsightInProgress.filter(id => id !== chartId); + console.log("fetched chart insight", action.payload); + }) + .addCase(fetchChartInsight.rejected, (state, action) => { + let chartId = action.meta.arg.chartId; + state.chartInsightInProgress = state.chartInsightInProgress.filter(id => id !== chartId); + console.error("chart insight failed", action.error); + }) }, }) @@ -1222,12 +1315,9 @@ export const dfSelectors = { // derived field: extra all field items from the table export const getDataFieldItems = (baseTable: DictTable): FieldItem[] => { - let dataFieldItems = baseTable.names.map((name, index) => { + let dataFieldItems = baseTable.names.map((name) => { const id = `original--${baseTable.id}--${name}`; - const columnValues = baseTable.rows.map((r) => r[name]); - const type = baseTable.metadata[name].type; - const uniqueValues = Array.from(new Set(columnValues)); - return { id, name, type, source: "original", description: "", tableRef: baseTable.id } as FieldItem; + return { id, name, source: "original", tableRef: baseTable.id } as FieldItem; }) || []; return dataFieldItems; diff --git a/src/app/tableThunks.ts b/src/app/tableThunks.ts index 09f6c5a3..e7995f6f 100644 --- a/src/app/tableThunks.ts +++ b/src/app/tableThunks.ts @@ -68,7 +68,7 @@ export const loadTable = createAsyncThunk< async (payload, { dispatch, getState }) => { const { table, storeOnServer, file, dataLoaderType, dataLoaderParams, sourceTableName, importOptions } = payload; const state = getState(); - const frontendRowLimit = state.config?.frontendRowLimit ?? 10000; + const frontendRowLimit = state.config?.frontendRowLimit ?? 50000; const existingTables = state.tables; // === DUPLICATE CHECK === diff --git a/src/app/utils.tsx b/src/app/utils.tsx index 776f1c56..cb303a10 100644 --- a/src/app/utils.tsx +++ b/src/app/utils.tsx @@ -33,6 +33,7 @@ export function getUrls() { CLEAN_DATA_URL: `/api/agent/clean-data-stream`, CODE_EXPL_URL: `/api/agent/code-expl`, + CHART_INSIGHT_URL: `/api/agent/chart-insight`, SERVER_PROCESS_DATA_ON_LOAD: `/api/agent/process-data-on-load`, DERIVE_DATA: `/api/agent/derive-data`, @@ -51,6 +52,7 @@ export function getUrls() { GET_COLUMN_STATS: `/api/tables/analyze`, SAMPLE_TABLE: `/api/tables/sample-table`, SYNC_TABLE_DATA: `/api/tables/sync-table-data`, + EXPORT_TABLE_CSV: `/api/tables/export-table-csv`, DATA_LOADER_LIST_DATA_LOADERS: `/api/tables/data-loader/list-data-loaders`, DATA_LOADER_LIST_TABLES: `/api/tables/data-loader/list-tables`, diff --git a/src/components/componentType.tsx b/src/components/componentType.tsx index da45a4f0..6c8b2271 100644 --- a/src/components/componentType.tsx +++ b/src/components/componentType.tsx @@ -5,13 +5,6 @@ import { Type } from '../data/types'; import { CHANNEL_LIST } from "./ChartTemplates" import { inferTypeFromValueArray } from '../data/utils'; - -export interface ConceptTransformation { - parentIDs: string[], - description: string, - code: string -} - export type FieldSource = "custom" | "original"; export interface FieldItem { @@ -19,7 +12,7 @@ export interface FieldItem { name: string; source: FieldSource; - tableRef: string; // which table it belongs to, it matters when it's an original field or a derived field + tableRef: string; // which table it belongs to } export const duplicateField = (field: FieldItem) => { @@ -186,6 +179,12 @@ export function createDictTable( } } +export interface ChartInsight { + title: string; + takeaways: string[]; + key: string; // "chartType|sortedFieldIds" — used to detect staleness +} + export type Chart = { id: string, chartType: string, @@ -195,6 +194,16 @@ export type Chart = { source: "user" | "trigger", config?: Record, // additional chart config properties defined by the chart template's configProperties thumbnail?: string, // PNG data URL for thumbnail display (managed by ChartRenderService, not persisted) + insight?: ChartInsight, // AI-generated insight about the visualization +} + +/** Compute a string key for insight invalidation: chartType|sortedFieldIds */ +export function computeInsightKey(chart: Chart): string { + const fieldIds = Object.values(chart.encodingMap) + .map(enc => enc.fieldID) + .filter((id): id is string => !!id) + .sort(); + return `${chart.chartType}|${fieldIds.join(',')}`; } export let duplicateChart = (chart: Chart) : Chart => { @@ -254,6 +263,9 @@ export const AGGR_OP_LIST = ["count", "sum", "average"] as const export type AggrOp = typeof AGGR_OP_LIST[number]; export type Channel = typeof CHANNEL_LIST[number]; +export interface EncodingDropResult { + channel: Channel +} // export const markToChannels = (mark: string) => { // let channels = []; diff --git a/src/scss/ConceptShelf.scss b/src/scss/ConceptShelf.scss deleted file mode 100644 index 5464298c..00000000 --- a/src/scss/ConceptShelf.scss +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -.concept-shelf { - width: 100%; - display: flex; - flex-direction: column; - overflow-y: hidden; - margin: 8px 8px 8px 0px; - - .data-fields-group { - //height: calc(100% - 30px); - flex-grow: 1; - overflow-y: auto; - display: flex; - flex-direction: column; - font-size: small; - - .data-fields-list { - flex: 1 1 auto; - display: block; - flex-direction: column; - flex-wrap: wrap; - - .data-field-list-item { - justify-content: flex-start; - - //width: 98%; - .MuiChip-deleteIcon { - margin: 0 5px 0 auto !important; - } - - //min-height: 120px; - margin: 3px; - } - - // .concept-card-original { - // background-color: rgb(2 136 209); - // } - - // .concept-card-custom { - // background-color: lightsalmon; - // } - - // .concept-card-derived { - // background-color: gold; - // margin-left: 10px; - // } - } - } - - // .data-fields-group { - // -ms-overflow-style: none; /* Internet Explorer 10+ */ - // scrollbar-width: none; /* Firefox */ - // } - // .data-fields-group::-webkit-scrollbar { - // display: none; /* Safari and Chrome */ - // } -} - -.concept-examples-option-container { - margin: 6px; - padding: 4px; - border-radius: 6px; - cursor: pointer; - background-color: rgba(249, 105, 14, 0.05); - - &:hover { - background-color: rgba(249, 105, 14, 0.2); - - .concept-examples-option-chip { - background-color: rgb(202, 202, 202); - } - } - - .concept-examples-option-chip { - height: 100%; - background-color: rgb(235, 235, 235); - margin: 2px; - cursor: pointer; - font-size: 12px; - - .MuiChip-label { - padding: 4px 8px; - white-space: normal; - } - } -} - - - - -.draggable-card { - - .draggable-card-header { - display: flex; - flex-direction: row; - justify-content: space-between; - height: auto; - padding: 1px 8px 1px 0px; - margin-left: 8px; - position: relative; - - .draggable-card-title { - margin: auto; - margin-left: 3px; - display: flex; - align-items: center; - } - } - - .draggable-card-header.uninstantiated { - font-style: italic; - color: rgab(80, 80, 80); - } - - .draggable-card-header.uninstantiated.custom { - border: 0.5px dashed lightsalmon; - border-left: none; - } - - .draggable-card-header.uninstantiated.derived { - border: 0.5px dashed gold; - border-left: none; - } - - & .draggable-card-action-button { - visibility: hidden; - opacity: 0; - transition: visibility 0s, opacity 0.3s linear; - } - - &:hover .draggable-card-action-button { - visibility: visible; - opacity: 1; - } - - .draggable-card-body { - padding: 0px 8px 8px 4px !important; - margin-left: 8px; - background-color: rgba(255, 255, 255, 0.93); - } - - .draggable-card-body-edit-mode { - background-color: rgba(255, 255, 255, 0.98); - padding: 8px 8px 8px 4px !important; - margin-left: 8px; - } - - .draggable-card-example-values { - display: block; - text-overflow: ellipsis; - margin-left: 8px; - max-width: calc(100% - 96px); - font-size: smaller; - font-style: italic; - //white-space: nowrap; - color: rebeccapurple; - - .draggable-card-example-values-chip { - height: auto; - max-width: 120px; - margin: 3px; - border-radius: 0px; - } - } -} - - - -.concept-creator { - - font-size: small; - - .concept-creator-title { - min-height: 0px; - } - - .concept-creator-title.Mui-expanded { - min-height: 0px; - background-color: rgb(0 0 0 / 5%); - } - - .concept-creator-title>.MuiAccordionSummary-content { - margin: 6px; - } - - .concept-creator-field-chip { - font-size: smaller; - padding: 0px 4px; - margin: 2px; - height: auto; - } - - .MuiAccordionDetails-root { - margin: 4px 8px; - padding: 0px 0px; - } -} - -.concept-creator.Mui-expanded { - margin: 0px !important; -} - -.concept-form { - - .MuiInputBase-root, - .MuiButton-root, - .MuiInputLabel-root { - font-size: inherit; - } -} - -@keyframes color { - 0% { background: white; } - 50% { background: rgb(237, 247, 252); } - 100% { background: white; } - } - -.background-highlight { - animation: color 2s infinite linear; -} \ No newline at end of file diff --git a/src/scss/DraggableCard.scss b/src/scss/DraggableCard.scss new file mode 100644 index 00000000..521c22ce --- /dev/null +++ b/src/scss/DraggableCard.scss @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +.draggable-card { + + .draggable-card-header { + display: flex; + flex-direction: row; + justify-content: space-between; + height: auto; + padding: 1px 8px 1px 0px; + margin-left: 8px; + position: relative; + + .draggable-card-title { + margin: auto; + margin-left: 3px; + display: flex; + align-items: center; + } + } + + .draggable-card-header.uninstantiated { + font-style: italic; + color: rgab(80, 80, 80); + } + + .draggable-card-header.uninstantiated.custom { + border: 0.5px dashed lightsalmon; + border-left: none; + } + + .draggable-card-header.uninstantiated.derived { + border: 0.5px dashed gold; + border-left: none; + } + + & .draggable-card-action-button { + visibility: hidden; + opacity: 0; + transition: visibility 0s, opacity 0.3s linear; + } + + &:hover .draggable-card-action-button { + visibility: visible; + opacity: 1; + } + + .draggable-card-body { + padding: 0px 8px 8px 4px !important; + margin-left: 8px; + background-color: rgba(255, 255, 255, 0.93); + } + + .draggable-card-body-edit-mode { + background-color: rgba(255, 255, 255, 0.98); + padding: 8px 8px 8px 4px !important; + margin-left: 8px; + } + + .draggable-card-example-values { + display: block; + text-overflow: ellipsis; + margin-left: 8px; + max-width: calc(100% - 96px); + font-size: smaller; + font-style: italic; + color: rebeccapurple; + + .draggable-card-example-values-chip { + height: auto; + max-width: 120px; + margin: 3px; + border-radius: 0px; + } + } +} diff --git a/src/views/ChartRecBox.tsx b/src/views/ChartRecBox.tsx index d4cbb3b4..9b147c9e 100644 --- a/src/views/ChartRecBox.tsx +++ b/src/views/ChartRecBox.tsx @@ -4,7 +4,7 @@ import { FC, useEffect, useState, useRef } from 'react' import { transition } from '../app/tokens'; import { useSelector, useDispatch } from 'react-redux' -import { DataFormulatorState, dfActions, dfSelectors, fetchCodeExpl, fetchFieldSemanticType, generateFreshChart } from '../app/dfSlice'; +import { DataFormulatorState, dfActions, dfSelectors, fetchCodeExpl, fetchChartInsight, fetchFieldSemanticType, generateFreshChart } from '../app/dfSlice'; import { AppDispatch } from '../app/store'; @@ -50,7 +50,6 @@ import AddIcon from '@mui/icons-material/Add'; import { AgentIcon as PrecisionManufacturing } from '../icons'; import SmartToyIcon from '@mui/icons-material/SmartToy'; import TouchAppIcon from '@mui/icons-material/TouchApp'; -import { Type } from '../data/types'; import CloseIcon from '@mui/icons-material/Close'; import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; import TipsAndUpdatesIcon from '@mui/icons-material/TipsAndUpdates'; @@ -647,11 +646,8 @@ export const ChartRecBox: FC = function ({ tableId, placeHolde const conceptsToAdd = missingNames.map((name) => ({ id: `concept-${name}-${Date.now()}`, name: name, - type: "auto" as Type, - description: "", source: "custom", tableRef: "custom", - temporary: true, } as FieldItem)); dispatch(dfActions.addConceptItems(conceptsToAdd)); @@ -671,6 +667,11 @@ export const ChartRecBox: FC = function ({ tableId, placeHolde dispatch(dfActions.setFocusedTable(candidateTable.id)); } + // Auto-generate chart insight after rendering + setTimeout(() => { + dispatch(fetchChartInsight({ chartId: newChart.id, tableId: candidateTable.id }) as any); + }, 1500); + dispatch(dfActions.addMessages({ "timestamp": Date.now(), "component": "chart builder", @@ -852,11 +853,8 @@ export const ChartRecBox: FC = function ({ tableId, placeHolde const conceptsToAdd = missingNames.map((name) => ({ id: `concept-${name}-${Date.now()}-${Math.random()}`, name: name, - type: "auto" as Type, - description: "", source: "custom", tableRef: "custom", - temporary: true, } as FieldItem)); allNewConcepts.push(...conceptsToAdd); @@ -893,6 +891,14 @@ export const ChartRecBox: FC = function ({ tableId, placeHolde dispatch(fetchFieldSemanticType(candidateTable)); dispatch(fetchCodeExpl(candidateTable)); + // Auto-generate chart insight after rendering + if (createdCharts.length > 0) { + const lastChart = createdCharts[createdCharts.length - 1]; + setTimeout(() => { + dispatch(fetchChartInsight({ chartId: lastChart.id, tableId: candidateTable.id }) as any); + }, 1500); + } + // Show progress message dispatch(dfActions.addMessages({ "timestamp": Date.now(), diff --git a/src/views/ConceptCard.tsx b/src/views/ConceptCard.tsx deleted file mode 100644 index 82729bb6..00000000 --- a/src/views/ConceptCard.tsx +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { FC, useEffect, useState } from 'react' -import { useDrag } from 'react-dnd' -import { useSelector, useDispatch } from 'react-redux' - -import '../scss/ConceptShelf.scss'; - -import 'prismjs/components/prism-python' // Language -import 'prismjs/themes/prism.css'; //Example style, you can use another -import { useTheme } from '@mui/material/styles'; - -import { - Card, - Box, - Typography, - IconButton, - TextField, - Tooltip, - LinearProgress, - SxProps, -} from '@mui/material'; - -import EditIcon from '@mui/icons-material/Edit'; -import DeleteIcon from '@mui/icons-material/Delete'; -import ForkRightIcon from '@mui/icons-material/ForkRight'; -import ZoomInIcon from '@mui/icons-material/ZoomIn'; -import HideSourceIcon from '@mui/icons-material/HideSource'; -import ArrowRightIcon from '@mui/icons-material/ArrowRight'; -import AnimateHeight from 'react-animate-height'; - -import { FieldItem, ConceptTransformation, duplicateField, FieldSource } from '../components/ComponentType'; - -import { testType, Type, TypeList } from "../data/types"; -import React from 'react'; -import { DataFormulatorState, dfActions, dfSelectors } from '../app/dfSlice'; - -import { getUrls } from '../app/utils'; -import { getIconFromType } from './ViewUtils'; - - -import _ from 'lodash'; -import { DictTable } from '../components/ComponentType'; -import { CodeBox } from './VisualizationView'; -import { CustomReactTable } from './ReactTable'; -import { alpha } from '@mui/material/styles'; - -export interface ConceptCardProps { - field: FieldItem, - sx?: SxProps -} - - - -export const ConceptCard: FC = function ConceptCard({ field, sx }) { - // concept cards are draggable cards that can be dropped into encoding shelf - let theme = useTheme(); - - const conceptShelfItems = useSelector((state: DataFormulatorState) => state.conceptShelfItems); - const tables = useSelector((state: DataFormulatorState) => state.tables); - let focusedTableId = useSelector((state: DataFormulatorState) => state.focusedTableId); - - let focusedTable = tables.find(t => t.id == focusedTableId); - - const [editMode, setEditMode] = useState(field.name == "" ? true : false); - - const dispatch = useDispatch(); - let handleDeleteConcept = (conceptID: string) => dispatch(dfActions.deleteConceptItemByID(conceptID)); - let handleUpdateConcept = (concept: FieldItem) => dispatch(dfActions.updateConceptItems(concept)); - - const [{ isDragging }, drag] = useDrag(() => ({ - type: "concept-card", - item: { type: 'concept-card', fieldID: field.id, source: "conceptShelf" }, - collect: (monitor) => ({ - isDragging: monitor.isDragging(), - handlerId: monitor.getHandlerId(), - }), - })); - - let [isLoading, setIsLoading] = useState(false); - let handleLoading = (loading: boolean) => { - setIsLoading(loading); - } - - let opacity = isDragging ? 0.3 : 1; - let fontStyle = "inherit"; - let border = "hidden"; - - const cursorStyle = isDragging ? "grabbing" : "grab"; - - let deleteOption = !(field.source == "original") && { handleDeleteConcept(field.id); }}> - - ; - - let cardHeaderOptions = [ - deleteOption, - ] - - let typeIcon = ( - - {getIconFromType(focusedTable?.metadata[field.name]?.type)} - - ) - - let fieldNameEntry = field.name != "" ? {field.name} - : new concept; - - let backgroundColor = theme.palette.primary.main; - if (field.source == "original") { - backgroundColor = theme.palette.primary.light; - } else if (field.source == "custom") { - backgroundColor = theme.palette.custom.main; - } else if (field.source == "derived") { - backgroundColor = theme.palette.derived.main; - } - - let draggleCardHeaderBgOverlay = 'rgba(255, 255, 255, 0.9)'; - - // Add subtle tint for non-focused fields - if (focusedTable && !focusedTable.names.includes(field.name)) { - draggleCardHeaderBgOverlay = 'rgba(255, 255, 255, 1)'; - } - - let boxShadow = editMode ? "0 2px 4px 0 rgb(0 0 0 / 20%), 0 2px 4px 0 rgb(0 0 0 / 19%)" : ""; - - let cardComponent = ( - - {isLoading ? - - : ""} - - - {typeIcon} - {fieldNameEntry} - {focusedTable?.metadata[field.name]?.semanticType ? - - {focusedTable?.metadata[field.name].semanticType} : ""} - - - - {cardHeaderOptions} - - - - ) - - return cardComponent; -} \ No newline at end of file diff --git a/src/views/ConceptShelf.tsx b/src/views/ConceptShelf.tsx deleted file mode 100644 index 6272d98e..00000000 --- a/src/views/ConceptShelf.tsx +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import { FC, useEffect, useState } from 'react' -import { useSelector, useDispatch } from 'react-redux' -import { useTheme } from '@mui/material/styles'; -import { alpha } from "@mui/material"; - -import '../scss/ConceptShelf.scss'; - -import { - Box, - Typography, - Tooltip, - Button, - Divider, - IconButton, -} from '@mui/material'; - -import CleaningServicesIcon from '@mui/icons-material/CleaningServices'; - -import { FieldItem, Channel } from '../components/ComponentType'; - -import React from 'react'; -import { DataFormulatorState, dfActions, dfSelectors } from '../app/dfSlice'; -import { ConceptCard } from './ConceptCard'; -import { Type } from '../data/types'; -import { groupConceptItems } from './ViewUtils'; -import { OperatorCard } from './OperatorCard'; -import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; -import ChevronRightIcon from '@mui/icons-material/ChevronRight'; -import ExpandLessIcon from '@mui/icons-material/ExpandLess'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import ListIcon from '@mui/icons-material/List'; - -export const genFreshCustomConcept : () => FieldItem = () => { - return { - id: `concept-${Date.now()}`, name: "", type: "auto" as Type, - description: "", source: "custom", tableRef: "custom", - } -} - -export interface EncodingDropResult { - channel: Channel -} - -export interface ConceptShelfProps { - -} - -export const ConceptGroup: FC<{groupName: string, fields: FieldItem[]}> = function ConceptGroup({groupName, fields}) { - - const [expanded, setExpanded] = useState(true); - const dispatch = useDispatch(); - const theme = useTheme(); - const handleCleanUnusedConcepts = () => { - dispatch(dfActions.clearUnReferencedCustomConcepts()); - }; - - // Separate fields for display logic - const displayFields = expanded ? fields : fields.slice(0, 6); - const hasMoreFields = fields.length > 6; - - return - - - setExpanded(!expanded)}> - - {groupName} - {fields.length > 6 && - {expanded ? : } - } - - {groupName === "new fields" && ( - - { - e.stopPropagation(); - handleCleanUnusedConcepts(); - }} - sx={{ - fontSize: "8px", - minWidth: "auto", - px: 0.5, - py: 0.25, - height: "16px", - ml: '0', - '&:hover': { - color: theme.palette.warning.main, - backgroundColor: alpha(theme.palette.warning.light, 0.1), - }, - '&:hover .cleaning-icon': { - animation: 'spin 0.5s cubic-bezier(0.4, 0, 0.2, 1)', - transform: 'rotate(360deg)', - }, - '@keyframes spin': { - '0%': { - transform: 'rotate(0deg)' - }, - '100%': { - transform: 'rotate(360deg)' - } - } - }} - > - - - - )} - - - - - {/* Always show first 6 fields */} - - {displayFields.map((field) => ( - - ))} - - - {/* Collapsible section for additional fields */} - {hasMoreFields && !expanded && ( - - )} - ; -} - - -export const ConceptShelf: FC = function ConceptShelf() { - - const [conceptPanelOpen, setConceptPanelOpen] = useState(false); - const theme = useTheme(); - - // reference to states - const conceptShelfItems = useSelector((state: DataFormulatorState) => state.conceptShelfItems); - const tables = useSelector((state: DataFormulatorState) => state.tables); - const focusedTableId = useSelector((state: DataFormulatorState) => state.focusedTableId); - const focusedTable = tables.find(t => t.id == focusedTableId); - - // group concepts based on types - let conceptItemGroups = groupConceptItems(conceptShelfItems, tables); - let groupNames = [...new Set(conceptItemGroups.map(g => g.group))] - - let conceptShelf = ( - - - - Data Fields - - - - - - - - field operators - - - - - - - - - - - - {groupNames.map(groupName => { - let fields = conceptItemGroups.filter(g => g.group == groupName).map(g => g.field); - fields = fields.sort((a, b) => { - if (focusedTable && focusedTable.names.includes(a.name) && !focusedTable.names.includes(b.name)) { - return -1; - } else if (focusedTable && !focusedTable.names.includes(a.name) && focusedTable.names.includes(b.name)) { - return 1; - } else { - return 0; - } - }); - return - })} - - - - - ); - - return - - setConceptPanelOpen(!conceptPanelOpen)} - > - - {conceptPanelOpen ? - : } - - - - !conceptPanelOpen && setConceptPanelOpen(!conceptPanelOpen)} - sx={{ - overflow: 'hidden', - '&::after': conceptPanelOpen ? undefined : { - content: '""', - position: 'absolute', - top: 0, - right: 0, - width: '100%', - height: '100%', - background: 'rgba(255,255,255,0.95)', - pointerEvents: 'none', - zIndex: 1 - }, - }}> - {conceptShelf} - - -} \ No newline at end of file diff --git a/src/views/DBTableManager.tsx b/src/views/DBTableManager.tsx index 80457940..59ceac71 100644 --- a/src/views/DBTableManager.tsx +++ b/src/views/DBTableManager.tsx @@ -346,7 +346,7 @@ export const DataLoaderForm: React.FC<{ const dispatch = useDispatch(); const theme = useTheme(); const params = useSelector((state: DataFormulatorState) => state.dataLoaderConnectParams[dataLoaderType] ?? {}); - const frontendRowLimit = useSelector((state: DataFormulatorState) => state.config?.frontendRowLimit ?? 10000); + const frontendRowLimit = useSelector((state: DataFormulatorState) => state.config?.frontendRowLimit ?? 50000); const workspaceTables = useSelector((state: DataFormulatorState) => state.tables); const [tableMetadata, setTableMetadata] = useState>({}); diff --git a/src/views/DataFormulator.tsx b/src/views/DataFormulator.tsx index 2099a0b4..11bacaec 100644 --- a/src/views/DataFormulator.tsx +++ b/src/views/DataFormulator.tsx @@ -31,7 +31,6 @@ import { borderColor, shadow, radius } from '../app/tokens'; import { FreeDataViewFC } from './DataView'; import { VisualizationViewFC } from './VisualizationView'; -import { ConceptShelf } from './ConceptShelf'; import { DndProvider } from 'react-dnd' import { HTML5Backend } from 'react-dnd-html5-backend' import { toolName } from '../app/App'; @@ -263,7 +262,6 @@ export const DataFormulatorFC = ({ }) => { {viewMode === 'editor' ? ( <> {visPane} - {/* */} ) : ( diff --git a/src/views/DataThread.tsx b/src/views/DataThread.tsx index 4e164545..146a13bd 100644 --- a/src/views/DataThread.tsx +++ b/src/views/DataThread.tsx @@ -1567,8 +1567,9 @@ const ChartThumbnail: FC<{
const pendingOverlay = status == 'pending' ? : null; diff --git a/src/views/EncodingBox.tsx b/src/views/EncodingBox.tsx index 03fd500e..0ad7bc1e 100644 --- a/src/views/EncodingBox.tsx +++ b/src/views/EncodingBox.tsx @@ -41,9 +41,7 @@ import CategoryIcon from '@mui/icons-material/Category'; import CalendarMonthIcon from '@mui/icons-material/CalendarMonth'; import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; -import { FieldItem, Channel, EncodingItem, AggrOp, AGGR_OP_LIST, - ConceptTransformation, Chart, duplicateField } from "../components/ComponentType"; -import { EncodingDropResult } from "../views/ConceptShelf"; +import { FieldItem, Channel, EncodingItem, AggrOp, Chart, EncodingDropResult } from "../components/ComponentType"; import _ from 'lodash'; @@ -94,14 +92,9 @@ export const LittleConceptCard: FC = function LittleConc let backgroundColor = alpha(theme.palette.primary.main, 0.05); if (field.source == "original") { - //fieldClass += "encoding-active-item-original" backgroundColor = alpha(theme.palette.primary.main, 0.05); } else if (field.source == "custom") { - //fieldClass += "encoding-active-item-custom" backgroundColor = alpha(theme.palette.custom.main, 0.05); - } else if (field.source == "derived") { - //fieldClass += "encoding-active-item-derived"; - backgroundColor = alpha(theme.palette.derived.main, 0.05); } return ( @@ -554,8 +547,8 @@ export const EncodingBox: FC = function EncodingBox({ channel, console.log("nothing happens") } else { let newConept = { - id: `concept-${Date.now()}`, name: option, type: "auto" as Type, - description: "", source: "custom", tableRef: "custom", + id: `concept-${Date.now()}`, name: option, + source: "custom", tableRef: "custom", } as FieldItem; dispatch(dfActions.updateConceptItems(newConept)); updateEncProp("fieldID", newConept.id); @@ -689,8 +682,6 @@ export const EncodingBox: FC = function EncodingBox({ channel, backgroundColor = theme.palette.primary.light; } else if (fieldItem.source == "custom") { backgroundColor = theme.palette.custom.main; - } else if (fieldItem.source == "derived") { - backgroundColor = theme.palette.derived.main; } // Add overlay logic similar to ConceptCard - make fields not in focused table more transparent diff --git a/src/views/EncodingShelfCard.tsx b/src/views/EncodingShelfCard.tsx index 282cc8b6..b2fa3cb1 100644 --- a/src/views/EncodingShelfCard.tsx +++ b/src/views/EncodingShelfCard.tsx @@ -3,7 +3,7 @@ import { FC, useEffect, useState } from 'react' import { useSelector, useDispatch } from 'react-redux' -import { DataFormulatorState, dfActions, dfSelectors, fetchCodeExpl, fetchFieldSemanticType, generateFreshChart } from '../app/dfSlice'; +import { DataFormulatorState, dfActions, dfSelectors, fetchCodeExpl, fetchChartInsight, fetchFieldSemanticType, generateFreshChart } from '../app/dfSlice'; import embed from 'vega-embed'; @@ -60,7 +60,6 @@ import { ThinkingBanner } from './DataThread'; import { AppDispatch } from '../app/store'; import { borderColor, transition, radius } from '../app/tokens'; -import { Type } from '../data/types'; import DeleteIcon from '@mui/icons-material/Delete'; import CloseIcon from '@mui/icons-material/Close'; import LightbulbOutlinedIcon from '@mui/icons-material/LightbulbOutlined'; @@ -391,7 +390,7 @@ export const EncodingShelfCard: FC = function ({ chartId input_tables: actionTables.map(t => ({ name: t.virtual?.tableId || t.id.replace(/\.[^/.]+$/, ""), rows: t.rows, - attached_metadata: t.attachedMetadata + attached_metadata: t.attachedMetadata, })), exploration_thread: explorationThread, current_data_sample: currentTable.rows.slice(0, 10), @@ -568,7 +567,7 @@ export const EncodingShelfCard: FC = function ({ chartId return { name: t.virtual?.tableId || t.id.replace(/\.[^/.]+$/ , ""), rows: t.rows, - attached_metadata: t.attachedMetadata + attached_metadata: t.attachedMetadata, }}), chart_type: chartType, chart_encodings: mode == 'formulate' ? activeSimpleEncodings : {}, @@ -602,7 +601,7 @@ export const EncodingShelfCard: FC = function ({ chartId return { name: t.virtual?.tableId || t.id.replace(/\.[^/.]+$/ , ""), rows: t.rows, - attached_metadata: t.attachedMetadata + attached_metadata: t.attachedMetadata, }}), chart_type: chartType, chart_encodings: mode == 'formulate' ? activeSimpleEncodings : {}, @@ -622,7 +621,7 @@ export const EncodingShelfCard: FC = function ({ chartId return { name: t.virtual?.tableId || t.id.replace(/\.[^/.]+$/ , ""), rows: t.rows, - attached_metadata: t.attachedMetadata + attached_metadata: t.attachedMetadata, }}), chart_type: chartType, chart_encodings: mode == 'formulate' ? activeSimpleEncodings : {}, @@ -751,11 +750,8 @@ export const EncodingShelfCard: FC = function ({ chartId return { id: `concept-${name}-${Date.now()}`, name: name, - type: "auto" as Type, - description: "", source: "custom", tableRef: "custom", - temporary: true, } as FieldItem }) dispatch(dfActions.addConceptItems(conceptsToAdd)); @@ -768,6 +764,7 @@ export const EncodingShelfCard: FC = function ({ chartId // PART 3: create new charts if necessary let needToCreateNewChart = true; + let focusedChartId: string | undefined; // different override strategy -- only override if there exists a chart that share the exact same encoding fields as the planned new chart. if (mode != "ideate" && chart.chartType != "Auto" && overrideTableId != undefined && allCharts.filter(c => c.source == "user").find(c => c.tableRef == overrideTableId)) { @@ -782,7 +779,8 @@ export const EncodingShelfCard: FC = function ({ chartId }); if (chartsWithSameEncoding.length > 0) { // find the chart to set as focus - dispatch(dfActions.setFocusedChart(chartsWithSameEncoding[0].id)); + focusedChartId = chartsWithSameEncoding[0].id; + dispatch(dfActions.setFocusedChart(focusedChartId)); needToCreateNewChart = false; } } @@ -803,9 +801,18 @@ export const EncodingShelfCard: FC = function ({ chartId newChart = resolveChartFields(newChart, currentConcepts, refinedGoal['chart_encodings'], candidateTable); } + focusedChartId = newChart.id; dispatch(dfActions.addAndFocusChart(newChart)); } + // Auto-generate chart insight after rendering + if (focusedChartId) { + const insightChartId = focusedChartId; + setTimeout(() => { + dispatch(fetchChartInsight({ chartId: insightChartId, tableId: candidateTable.id }) as any); + }, 1500); + } + // PART 4: clean up if (chart.chartType == "Table" || chart.chartType == "Auto" || (existsWorkingTable == false)) { dispatch(dfActions.deleteChartById(chartId)); diff --git a/src/views/ExplComponents.tsx b/src/views/ExplComponents.tsx index e6a91b55..38f12791 100644 --- a/src/views/ExplComponents.tsx +++ b/src/views/ExplComponents.tsx @@ -271,8 +271,7 @@ export const CodeExplanationCard: FC<{ title: string; icon: React.ReactNode; children: React.ReactNode; - transformationIndicatorText: string; -}> = ({ title, icon, children, transformationIndicatorText }) => ( +}> = ({ title, icon, children }) => ( {icon} - {title} ({transformationIndicatorText}) + {title} string; } @@ -65,10 +65,7 @@ export const CustomReactTable: React.FC = ({ {columnDefs.map((column, i) => { let backgroundColor = "none"; let borderBottomColor = theme.palette.primary.main; - if (column.source == "derived") { - backgroundColor = alpha(theme.palette.derived.main, 0.05); - borderBottomColor = theme.palette.derived.main; - } else if (column.source == "custom") { + if (column.source == "custom") { backgroundColor = alpha(theme.palette.custom.main, 0.05); borderBottomColor = theme.palette.custom.main; } @@ -95,9 +92,7 @@ export const CustomReactTable: React.FC = ({ {columnDefs.map((column, j) => { const value = row[column.id]; let backgroundColor = "none"; - if (column.source == "derived") { - backgroundColor = alpha(theme.palette.derived.main, 0.05); - } else if (column.source == "custom") { + if (column.source == "custom") { backgroundColor = alpha(theme.palette.custom.main, 0.05); } return ( diff --git a/src/views/ReportView.tsx b/src/views/ReportView.tsx index 9477dd5c..d487198d 100644 --- a/src/views/ReportView.tsx +++ b/src/views/ReportView.tsx @@ -224,6 +224,7 @@ export const ReportView: FC = () => { const conceptShelfItems = useSelector((state: DataFormulatorState) => state.conceptShelfItems); const config = useSelector((state: DataFormulatorState) => state.config); const allGeneratedReports = useSelector(dfSelectors.getAllGeneratedReports); + const serverConfig = useSelector((state: DataFormulatorState) => state.serverConfig); const focusedChartId = useSelector((state: DataFormulatorState) => state.focusedChartId); const theme = useTheme(); @@ -662,11 +663,26 @@ export const ReportView: FC = () => { throw new Error('No model selected'); } - const inputTables = tables.filter(t => t.anchored).map(table => ({ - name: table.id, - rows: table.rows, - attached_metadata: table.attachedMetadata - })); + const maxRows = serverConfig.MAX_DISPLAY_ROWS; + + const inputTables = tables.filter(t => t.anchored).map(table => { + const rows = table.rows.length > maxRows ? table.rows.slice(0, maxRows) : table.rows; + return { + name: table.id, + rows, + attached_metadata: table.attachedMetadata + }; + }); + + // Check if any table data was truncated + const truncatedTables = tables.filter(t => t.anchored).filter(t => { + const totalRows = t.virtual?.rowCount || t.rows.length; + return totalRows > maxRows; + }); + const truncationNote = truncatedTables.length > 0 + ? `\n\nNote: Some tables were truncated to ${maxRows.toLocaleString()} rows for this report. ` + + `Tables affected: ${truncatedTables.map(t => `"${t.displayId || t.id}" (${(t.virtual?.rowCount || t.rows.length).toLocaleString()} total rows)`).join(', ')}.` + : ''; const selectedCharts = await Promise.all( @@ -693,7 +709,7 @@ export const ReportView: FC = () => { code: chartTable.derive?.code || '', chart_data: { name: chartTable.id, - rows: chartTable.rows + rows: chartTable.rows.length > maxRows ? chartTable.rows.slice(0, maxRows) : chartTable.rows }, chart_url: dataUrl // use data_url to send to the agent }; @@ -706,7 +722,7 @@ export const ReportView: FC = () => { model: model, input_tables: inputTables, charts: validCharts, - style: style + style: style + truncationNote }; const response = await fetchWithIdentity(getUrls().GENERATE_REPORT_STREAM, { diff --git a/src/views/SelectableDataGrid.tsx b/src/views/SelectableDataGrid.tsx index 24b86667..cb2cf9a3 100644 --- a/src/views/SelectableDataGrid.tsx +++ b/src/views/SelectableDataGrid.tsx @@ -21,6 +21,7 @@ import { getIconFromType } from './ViewUtils'; import { IconButton, TableSortLabel, Typography } from '@mui/material'; import _ from 'lodash'; +import * as d3 from 'd3-dsv'; import { FieldSource, FieldItem } from '../components/ComponentType'; import FileDownloadIcon from '@mui/icons-material/FileDownload'; @@ -83,8 +84,6 @@ function getColorForFieldSource(source: string | undefined, theme: any): string } switch (source) { - case "derived": - return theme.palette.derived.main; // Yellow/gold for derived fields case "custom": return theme.palette.custom.main; // Orange for custom fields case "original": @@ -283,6 +282,41 @@ export const SelectableDataGrid: React.FC = ({ )), } + const handleDownload = async (format: 'csv' | 'tsv') => { + const delimiter = format === 'tsv' ? '\t' : ','; + const ext = format === 'tsv' ? 'tsv' : 'csv'; + const mime = format === 'tsv' ? 'text/tab-separated-values' : 'text/csv'; + + if (virtual) { + // Virtual table: fetch full data from server + try { + const response = await fetchWithIdentity(getUrls().EXPORT_TABLE_CSV, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ table_name: tableId, delimiter }), + }); + if (!response.ok) throw new Error('Export failed'); + const blob = await response.blob(); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = `${tableName}.${ext}`; + a.click(); + URL.revokeObjectURL(a.href); + } catch (error) { + console.error('Error downloading table:', error); + } + } else { + // Local table: export from in-memory rows + const csvContent = d3.dsvFormat(delimiter).format(rows); + const blob = new Blob([csvContent], { type: mime }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = `${tableName}.${ext}`; + a.click(); + URL.revokeObjectURL(a.href); + } + }; + const fetchVirtualData = (sortByColumnIds: string[], sortOrder: 'asc' | 'desc') => { // Set loading to true when starting the fetch setIsLoading(true); @@ -454,6 +488,15 @@ export const SelectableDataGrid: React.FC = ({ )} + + handleDownload('csv')} + > + + +
diff --git a/src/views/UnifiedDataUploadDialog.tsx b/src/views/UnifiedDataUploadDialog.tsx index 46200feb..1fff6e5d 100644 --- a/src/views/UnifiedDataUploadDialog.tsx +++ b/src/views/UnifiedDataUploadDialog.tsx @@ -451,7 +451,7 @@ export const UnifiedDataUploadDialog: React.FC = ( const existingTables = useSelector((state: DataFormulatorState) => state.tables); const serverConfig = useSelector((state: DataFormulatorState) => state.serverConfig); const dataCleanBlocks = useSelector((state: DataFormulatorState) => state.dataCleanBlocks); - const frontendRowLimit = useSelector((state: DataFormulatorState) => state.config?.frontendRowLimit ?? 10000); + const frontendRowLimit = useSelector((state: DataFormulatorState) => state.config?.frontendRowLimit ?? 50000); const existingNames = new Set(existingTables.map(t => t.id)); const [activeTab, setActiveTab] = useState(initialTab === 'menu' ? 'menu' : initialTab); diff --git a/src/views/ViewUtils.tsx b/src/views/ViewUtils.tsx index 3d654d32..80186c57 100644 --- a/src/views/ViewUtils.tsx +++ b/src/views/ViewUtils.tsx @@ -4,7 +4,7 @@ import React from "react"; import ts from "typescript"; import { runCodeOnInputListsInVM } from "../app/utils"; -import { ConceptTransformation, FieldItem } from "../components/ComponentType"; +import { FieldItem } from "../components/ComponentType"; import { Type } from "../data/types"; import { BooleanIcon, NumericalIcon, StringIcon, DateIcon, UnknownIcon } from '../icons'; @@ -22,8 +22,6 @@ export const groupConceptItems = (conceptShelfItems: FieldItem[], tables: DictTa group = tables.find(t => t.id == f.tableRef)?.displayId || f.tableRef; } else if (f.source == "custom") { group = "new fields" - } else if (f.source == "derived") { - group = tables.find(t => t.id == f.tableRef)?.displayId || f.tableRef; } return {group, field: f} }); diff --git a/src/views/VisualizationView.tsx b/src/views/VisualizationView.tsx index da0a67f0..f29e106c 100644 --- a/src/views/VisualizationView.tsx +++ b/src/views/VisualizationView.tsx @@ -42,9 +42,9 @@ import ButtonGroup from '@mui/material/ButtonGroup'; import '../scss/VisualizationView.scss'; import { useDispatch, useSelector } from 'react-redux'; -import { DataFormulatorState, dfActions } from '../app/dfSlice'; +import { DataFormulatorState, dfActions, fetchChartInsight } from '../app/dfSlice'; import { assembleVegaChart, extractFieldsFromEncodingMap, getUrls, prepVisTable, fetchWithIdentity } from '../app/utils'; -import { Chart, EncodingItem, EncodingMap, FieldItem } from '../components/ComponentType'; +import { Chart, EncodingItem, EncodingMap, FieldItem, computeInsightKey } from '../components/ComponentType'; import { DictTable } from "../components/ComponentType"; import AddchartIcon from '@mui/icons-material/Addchart'; @@ -57,7 +57,6 @@ import CloseIcon from '@mui/icons-material/Close'; import ContentCopyIcon from '@mui/icons-material/ContentCopy'; import ZoomInIcon from '@mui/icons-material/ZoomIn'; import ZoomOutIcon from '@mui/icons-material/ZoomOut'; -import TextSnippetIcon from '@mui/icons-material/TextSnippet'; import InfoIcon from '@mui/icons-material/Info'; import CasinoIcon from '@mui/icons-material/Casino'; import SaveAltIcon from '@mui/icons-material/SaveAlt'; @@ -77,8 +76,6 @@ import { EncodingShelfThread } from './EncodingShelfThread'; import { CustomReactTable } from './ReactTable'; import { InsightIcon } from '../icons'; -import { MuiMarkdown, getOverrides } from 'mui-markdown'; - import { dfSelectors } from '../app/dfSlice'; import { ChartRecBox } from './ChartRecBox'; import { CodeExplanationCard, ConceptExplCards, extractConceptExplanations } from './ExplComponents'; @@ -298,7 +295,8 @@ const VegaChartRenderer: FC<{ chartHeight: number; scaleFactor: number; chartUnavailable: boolean; -}> = React.memo(({ chart, conceptShelfItems, visTableRows, tableMetadata, chartWidth, chartHeight, scaleFactor, chartUnavailable }) => { + onSpecReady?: (spec: any | null) => void; +}> = React.memo(({ chart, conceptShelfItems, visTableRows, tableMetadata, chartWidth, chartHeight, scaleFactor, chartUnavailable, onSpecReady }) => { // Initialize from display SVG cache for instant display on chart switch const svgCached = displaySvgCache.get(chart.id); @@ -336,6 +334,7 @@ const VegaChartRenderer: FC<{ if (!spec || spec === "Table") { setSvgContent(null); setAssembledSpec(null); + onSpecReady?.(null); return; } @@ -351,6 +350,7 @@ const VegaChartRenderer: FC<{ } setAssembledSpec(spec); + onSpecReady?.(spec); // Headless render via Vega: compile VL → parse → View → toSVG() let cancelled = false; @@ -474,20 +474,7 @@ const VegaChartRenderer: FC<{ {generateChartSkeleton(chartTemplate?.icon, 48, 48, 0.3)}
)} - {svgContent && ( - - - - - - - - - - - - - )} +
); }); @@ -511,6 +498,35 @@ export const ChartEditorFC: FC<{}> = function ChartEditorFC({}) { let synthesisRunning = focusedChartId ? chartSynthesisInProgress.includes(focusedChartId) : false; let handleDeleteChart = () => { focusedChartId && dispatch(dfActions.deleteChartById(focusedChartId)) } + // Track the assembled Vega-Lite spec from the renderer so we can open it in the Vega Editor + const [renderedSpec, setRenderedSpec] = useState(null); + const handleSpecReady = useCallback((spec: any | null) => { setRenderedSpec(spec); }, []); + + const handleOpenInVegaEditor = useCallback(() => { + if (!renderedSpec) return; + const editorUrl = 'https://vega.github.io/editor/'; + const editor = window.open(editorUrl); + if (!editor) return; + const wait = 10_000; + const step = 250; + const { origin } = new URL(editorUrl); + let count = Math.floor(wait / step); + function listen(evt: MessageEvent) { + if (evt.source === editor) { + count = 0; + window.removeEventListener('message', listen, false); + } + } + window.addEventListener('message', listen, false); + function send() { + if (count <= 0) return; + editor!.postMessage({ spec: JSON.stringify(renderedSpec, null, 2), mode: 'vega-lite' }, origin); + setTimeout(send, step); + count -= 1; + } + setTimeout(send, step); + }, [renderedSpec]); + let focusedChart = charts.find(c => c.id == focusedChartId) as Chart; let trigger = focusedChart.source == "trigger" ? tables.find(t => t.derive?.trigger?.chart?.id == focusedChartId)?.derive?.trigger : undefined; @@ -519,8 +535,8 @@ export const ChartEditorFC: FC<{}> = function ChartEditorFC({}) { const conceptShelfItems = useSelector((state: DataFormulatorState) => state.conceptShelfItems); const [codeViewOpen, setCodeViewOpen] = useState(false); - const [codeExplViewOpen, setCodeExplViewOpen] = useState(false); const [conceptExplanationsOpen, setConceptExplanationsOpen] = useState(false); + const [insightViewOpen, setInsightViewOpen] = useState(false); const [chatDialogOpen, setChatDialogOpen] = useState(false); const [localScaleFactor, setLocalScaleFactor] = useState(1); @@ -528,15 +544,15 @@ export const ChartEditorFC: FC<{}> = function ChartEditorFC({}) { // Reset local UI state when focused chart changes useEffect(() => { setCodeViewOpen(false); - setCodeExplViewOpen(false); setConceptExplanationsOpen(false); + setInsightViewOpen(false); setChatDialogOpen(false); setLocalScaleFactor(1); }, [focusedChartId]); // Combined useEffect to scroll to exploration components when any of them open useEffect(() => { - if ((conceptExplanationsOpen || codeViewOpen || codeExplViewOpen) && explanationComponentsRef.current) { + if ((conceptExplanationsOpen || codeViewOpen || insightViewOpen) && explanationComponentsRef.current) { setTimeout(() => { explanationComponentsRef.current?.scrollIntoView({ behavior: 'smooth', @@ -544,7 +560,7 @@ export const ChartEditorFC: FC<{}> = function ChartEditorFC({}) { }); }, 200); // Small delay to ensure the component is rendered } - }, [conceptExplanationsOpen, codeViewOpen, codeExplViewOpen]); + }, [conceptExplanationsOpen, codeViewOpen, insightViewOpen]); let table = getDataTable(focusedChart, tables, charts, conceptShelfItems); @@ -728,42 +744,62 @@ export const ChartEditorFC: FC<{}> = function ChartEditorFC({}) { let resultTable = tables.find(t => t.id == trigger?.resultTableId); - let codeExpl = table.derive?.explanation?.code || ""; + // Chart insight + const chartInsightInProgress = useSelector((state: DataFormulatorState) => state.chartInsightInProgress); + const insightLoading = chartInsightInProgress.includes(focusedChart.id); + const currentInsightKey = computeInsightKey(focusedChart); + const insightFresh = focusedChart.insight?.key === currentInsightKey; + const actionBtnSx = { + padding: '4px', + borderRadius: '6px', + color: 'text.secondary', + transition: 'all 0.15s ease', + '&:hover': { + backgroundColor: 'rgba(25, 118, 210, 0.08)', + color: 'primary.main', + }, + '&.Mui-disabled': { + color: 'action.disabled', + }, + }; + let saveButton = ( - + - { if (!chartUnavailable) { dispatch(dfActions.saveUnsaveChart(focusedChart.id)); } }}> - {focusedChart.saved ? : } + {focusedChart.saved ? : } ); - let duplicateButton = - - { - dispatch(dfActions.duplicateChart(focusedChart.id)); - }}> - - - - - + let duplicateButton = ( + + + { + dispatch(dfActions.duplicateChart(focusedChart.id)); + }}> + + + + + ); let deleteButton = ( - + - { handleDeleteChart() }}> - + { handleDeleteChart() }}> + @@ -829,8 +865,8 @@ export const ChartEditorFC: FC<{}> = function ChartEditorFC({}) { setCodeViewOpen(false); } else { setCodeViewOpen(true); - setCodeExplViewOpen(false); setConceptExplanationsOpen(false); + setInsightViewOpen(false); } }} sx={{ @@ -845,29 +881,6 @@ export const ChartEditorFC: FC<{}> = function ChartEditorFC({}) { code - {codeExpl != "" && } {hasConcepts && ( )} + {!chartUnavailable && focusedChart.chartType !== "Table" && ( + + )}
, = function ChartEditorFC({}) { dialog={resultTable?.derive?.dialog || table.derive?.dialog as any[]} /> ] : []; + let vegaEditorButton = ( + + + + + + + + ); + let chartActionButtons = [ ...derivedTableItems, saveButton, + vegaEditorButton, duplicateButton, deleteButton, ] @@ -957,55 +1012,16 @@ export const ChartEditorFC: FC<{}> = function ChartEditorFC({}) {
) - let codeExplComp = , - }, - ol: { - props: { - style: { - margin: 0 - }, - } as React.HTMLProps, - }, - li: { - props: { - style: { - fontFamily: "Arial, Roboto, Helvetica Neue, sans-serif", - fontWeight: 400, - fontSize: 12, - lineHeight: 2 - }, - } as React.HTMLProps, - }, - }}>{codeExpl} - let focusedComponent = []; - let transformationIndicatorText = table.derive?.source ? - `${table.derive.source.map(s => tables.find(t => t.id === s)?.displayId || s).join(", ")} → ${table.displayId || table.id}` : ""; - let focusedElement = + {insightFresh && focusedChart.insight?.title && ( + + {focusedChart.insight.title} + + )} = function ChartEditorFC({}) { chartHeight={config.defaultChartHeight} scaleFactor={localScaleFactor} chartUnavailable={chartUnavailable} + onSpecReady={handleSpecReady} /> {chartActionItems} @@ -1049,7 +1066,6 @@ export const ChartEditorFC: FC<{}> = function ChartEditorFC({}) { } - transformationIndicatorText={transformationIndicatorText} > = function ChartEditorFC({}) { - + { - setCodeExplViewOpen(false); - }} color='primary' aria-label="delete"> + setInsightViewOpen(false); + }} color='primary' aria-label="close"> } - transformationIndicatorText={transformationIndicatorText} + title="Chart insight" + icon={} > - - {codeExplComp} - + {insightLoading ? ( + + + Analyzing chart... + + ) : insightFresh && focusedChart.insight ? ( + + + {focusedChart.insight.title} + + {(focusedChart.insight.takeaways || []).map((t, i) => ( + + {t} + + ))} + + + ) : ( + + + No insight available. + + + + )}
- + {chartActionButtons} From 10a9ce23fc91a7b9901c853327b40fe33c16ec36 Mon Sep 17 00:00:00 2001 From: Chenglong Wang Date: Wed, 11 Feb 2026 18:09:04 -0800 Subject: [PATCH 42/50] some work around editor --- src/views/DataThread.tsx | 7 +- src/views/EncodingBox.tsx | 4 +- src/views/EncodingShelfCard.tsx | 215 ++++++++++++++---------------- src/views/EncodingShelfThread.tsx | 39 +----- 4 files changed, 112 insertions(+), 153 deletions(-) diff --git a/src/views/DataThread.tsx b/src/views/DataThread.tsx index 146a13bd..4d79e742 100644 --- a/src/views/DataThread.tsx +++ b/src/views/DataThread.tsx @@ -63,6 +63,8 @@ import { RefreshDataDialog } from './RefreshDataDialog'; import { getUrls, fetchWithIdentity } from '../app/utils'; import { AppDispatch } from '../app/store'; import StopIcon from '@mui/icons-material/Stop'; +import UnfoldMoreIcon from '@mui/icons-material/UnfoldMore'; +import UnfoldLessIcon from '@mui/icons-material/UnfoldLess'; import BarChartIcon from '@mui/icons-material/BarChart'; import ShowChartIcon from '@mui/icons-material/ShowChart'; import ScatterPlotIcon from '@mui/icons-material/ScatterPlot'; @@ -1853,6 +1855,9 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { const scrollRef = useRef(null) const containerRef = useRef(null) const suppressScrollRef = useRef(false); + const [expandedColumns, setExpandedColumns] = useState(false); + + const theme = useTheme(); const executeScroll = (smooth: boolean = true) => { if (scrollRef.current != null) { @@ -2159,7 +2164,7 @@ export const DataThread: FC<{sx?: SxProps}> = function ({ sx }) { } } - // Pick the best column layout: balances scroll burden vs whitespace. + // Pick the best column la=yout: balances scroll burden vs whitespace. // Measure actual panel height from the DOM (accounts for browser zoom, panel resizing, etc.) const availableHeight = containerRef.current?.clientHeight ?? 600; const MAX_COLUMNS = 3; diff --git a/src/views/EncodingBox.tsx b/src/views/EncodingBox.tsx index 0ad7bc1e..2e58da0f 100644 --- a/src/views/EncodingBox.tsx +++ b/src/views/EncodingBox.tsx @@ -813,14 +813,14 @@ export const EncodingBox: FC = function EncodingBox({ channel, > - + { setEditMode(!editMode) }} color="default" aria-label="axis settings" component="span" size="small" sx={{ padding: "0px", borderRadius: 0, textAlign: "left", fontSize: "inherit", height: "auto", - position: "relative", borderRight: "1px solid lightgray", width: '64px', backgroundColor: "rgba(0,0,0,0.01)", + position: "relative", borderRight: "1px solid lightgray", width: '64px', display: "flex", justifyContent: "space-between" }}> {channelDisplay} diff --git a/src/views/EncodingShelfCard.tsx b/src/views/EncodingShelfCard.tsx index b2fa3cb1..a0f9d41e 100644 --- a/src/views/EncodingShelfCard.tsx +++ b/src/views/EncodingShelfCard.tsx @@ -31,11 +31,13 @@ import { Slider, CircularProgress, Button, + Collapse, Dialog, DialogTitle, DialogContent, DialogActions, } from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import React from 'react'; import { ThinkingBufferEffect } from '../components/FunComponents'; @@ -58,12 +60,13 @@ import CheckIcon from '@mui/icons-material/Check'; import { ThinkingBanner } from './DataThread'; import { AppDispatch } from '../app/store'; -import { borderColor, transition, radius } from '../app/tokens'; +import { borderColor, radius } from '../app/tokens'; import DeleteIcon from '@mui/icons-material/Delete'; import CloseIcon from '@mui/icons-material/Close'; -import LightbulbOutlinedIcon from '@mui/icons-material/LightbulbOutlined'; + import TipsAndUpdatesIcon from '@mui/icons-material/TipsAndUpdates'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import { IdeaChip } from './ChartRecBox'; // Property and state of an encoding shelf @@ -251,14 +254,11 @@ export const EncodingShelfCard: FC = function ({ chartId let chart = allCharts.find(c => c.id == chartId) as Chart; let trigger = chart.source == "trigger" ? tables.find(t => t.derive?.trigger?.chart?.id == chartId)?.derive?.trigger : undefined; - let [ideateMode, setIdeateMode] = useState(false); let [prompt, setPrompt] = useState(trigger?.instruction || ""); + let [ideateMode, setIdeateMode] = useState(false); useEffect(() => { setPrompt(trigger?.instruction || ""); - if (!(chartState[chartId] && chartState[chartId].ideas.length > 0)) { - setIdeateMode(false); - } }, [chartId]); let encodingMap = chart?.encodingMap; @@ -488,7 +488,6 @@ export const EncodingShelfCard: FC = function ({ chartId // Function to handle idea chip click const handleIdeaClick = (ideaText: string) => { - setIdeateMode(true); setPrompt(ideaText); // Automatically start the data formulation process deriveNewData(ideaText, 'ideate'); @@ -872,6 +871,8 @@ export const EncodingShelfCard: FC = function ({ chartId sx={{ "& .MuiInputLabel-root": { fontSize: '12px' }, "& .MuiInput-input": { fontSize: '12px' }, + "& .MuiInput-underline:before": { borderBottomColor: theme.palette.primary.main }, + "& .MuiInput-underline:after": { borderBottomColor: theme.palette.primary.main }, }} onChange={(event: any) => { setPrompt(event.target.value); @@ -889,9 +890,7 @@ export const EncodingShelfCard: FC = function ({ chartId }} value={prompt} label="" - placeholder={['Auto'].includes(chart.chartType) - ? (isChartAvailable ? "what do you want to visualize?" : " ✏️ what do you want to visualize?") - : (isChartAvailable ? "formulate data" : " ✏️ formulate data")} + placeholder={"what's next?"} fullWidth multiline variant="standard" @@ -962,85 +961,12 @@ export const EncodingShelfCard: FC = function ({ chartId ) : null; - // Mode toggle header component - const ModeToggleHeader = () => ( - - { - if (currentChartIdeas.length > 0) { - setIdeateMode(true); - setPrompt(""); - } else { - setIdeateMode(true); - getIdeasForVisualization(); - } - }} - > - {currentChartIdeas.length > 0 ? "Ideas" : "Get Ideas"} - - - setIdeateMode(false)} - > - Editor - - - ); + let channelComponent = ( - - + +

Try Online Demo   - Install Locally + Install Locally