From 5e5642fbc8afa377ee75137a99e82e91205ce4c1 Mon Sep 17 00:00:00 2001 From: lmcnatt <85642387+lucymcnatt@users.noreply.github.com> Date: Thu, 24 Jul 2025 11:01:31 -0400 Subject: [PATCH 1/2] [AN-12] New terra base image path updates Update base img Co-authored-by: Adam Nichols python path --- terra-base/CHANGELOG.md | 4 + terra-base/Dockerfile | 270 ++++ terra-base/README.md | 52 + terra-base/conda-env.yml | 12 + terra-base/conda_init.txt | 14 + terra-base/custom/.eslintrc.js | 32 + terra-base/custom/README.md | 6 + terra-base/custom/edit-mode.js | 447 ++++++ terra-base/custom/extension_entry_jupyter.js | 7 + .../custom/extension_entry_jupyterlab.js | 7 + terra-base/custom/google_sign_in.js | 174 +++ terra-base/custom/jupyter_delocalize.py | 148 ++ .../custom/jupyter_localize_extension.py | 149 ++ terra-base/custom/package.json | 19 + terra-base/custom/safe-mode.js | 128 ++ terra-base/custom/test/README.md | 16 + .../custom/test/jupyter_delocalize_test.py | 448 ++++++ terra-base/custom/test/test-requirements.txt | 3 + terra-base/jupyter_notebook_config.py | 46 + terra-base/pyproject.toml | 23 + .../install_jupyter_contrib_nbextensions.sh | 11 + .../jupyter_install_combined_extension.sh | 14 + .../jupyter_install_lab_extension.sh | 29 + .../jupyter_install_notebook_extension.sh | 16 + .../jupyter_install_server_extension.sh | 12 + .../jupyter_pip_install_combined_extension.sh | 11 + .../jupyter_pip_install_notebook_extension.sh | 9 + .../jupyter_pip_install_server_extension.sh | 9 + terra-base/scripts/kernel/kernel_bootstrap.sh | 24 + terra-base/scripts/kernel/kernelspec.sh | 7 + .../scripts/kernel/python_kernelspec.tmpl | 12 + terra-base/scripts/run-jupyter.sh | 16 + terra-base/tests/smoke_test.py | 32 + terra-base/uv.lock | 1198 +++++++++++++++++ terra-jupyter-gatk/Dockerfile | 2 +- terra-jupyter-hail/Dockerfile | 2 +- terra-jupyter-python/Dockerfile | 15 +- terra-jupyter-r/Dockerfile | 6 +- 38 files changed, 3420 insertions(+), 10 deletions(-) create mode 100644 terra-base/CHANGELOG.md create mode 100644 terra-base/Dockerfile create mode 100644 terra-base/README.md create mode 100644 terra-base/conda-env.yml create mode 100644 terra-base/conda_init.txt create mode 100644 terra-base/custom/.eslintrc.js create mode 100644 terra-base/custom/README.md create mode 100644 terra-base/custom/edit-mode.js create mode 100644 terra-base/custom/extension_entry_jupyter.js create mode 100644 terra-base/custom/extension_entry_jupyterlab.js create mode 100644 terra-base/custom/google_sign_in.js create mode 100644 terra-base/custom/jupyter_delocalize.py create mode 100644 terra-base/custom/jupyter_localize_extension.py create mode 100644 terra-base/custom/package.json create mode 100644 terra-base/custom/safe-mode.js create mode 100644 terra-base/custom/test/README.md create mode 100644 terra-base/custom/test/jupyter_delocalize_test.py create mode 100644 terra-base/custom/test/test-requirements.txt create mode 100755 terra-base/jupyter_notebook_config.py create mode 100644 terra-base/pyproject.toml create mode 100755 terra-base/scripts/extension/install_jupyter_contrib_nbextensions.sh create mode 100755 terra-base/scripts/extension/jupyter_install_combined_extension.sh create mode 100755 terra-base/scripts/extension/jupyter_install_lab_extension.sh create mode 100755 terra-base/scripts/extension/jupyter_install_notebook_extension.sh create mode 100755 terra-base/scripts/extension/jupyter_install_server_extension.sh create mode 100755 terra-base/scripts/extension/jupyter_pip_install_combined_extension.sh create mode 100755 terra-base/scripts/extension/jupyter_pip_install_notebook_extension.sh create mode 100755 terra-base/scripts/extension/jupyter_pip_install_server_extension.sh create mode 100644 terra-base/scripts/kernel/kernel_bootstrap.sh create mode 100644 terra-base/scripts/kernel/kernelspec.sh create mode 100644 terra-base/scripts/kernel/python_kernelspec.tmpl create mode 100644 terra-base/scripts/run-jupyter.sh create mode 100644 terra-base/tests/smoke_test.py create mode 100644 terra-base/uv.lock diff --git a/terra-base/CHANGELOG.md b/terra-base/CHANGELOG.md new file mode 100644 index 00000000..4c82cc11 --- /dev/null +++ b/terra-base/CHANGELOG.md @@ -0,0 +1,4 @@ +## 0.0.1 - 2025-08-01 +- Build off Ubuntu 22 with base image `gcr.io/deeplearning-platform-release/tf2-cu123.2-17.py310` + +Image URL: `us.gcr.io/broad-dsp-gcr-public/terra-jupyter-base:0.0.1` diff --git a/terra-base/Dockerfile b/terra-base/Dockerfile new file mode 100644 index 00000000..940ab09d --- /dev/null +++ b/terra-base/Dockerfile @@ -0,0 +1,270 @@ +# Latest gpu-enabled base image on Ubuntu 24, 313MB +FROM --platform=linux/amd64 nvidia/cuda:12.9.1-base-ubuntu24.04 + +LABEL maintainer="DSP Analysis Team " + +# want the command to fail due to an error at any stage in the pipe: https://github.com/hadolint/hadolint/wiki/DL4006 +SHELL ["/usr/bin/bash", "-o", "pipefail", "-c"] + +####################### +# General Environment Variables +####################### +ENV DEBIAN_FRONTEND=noninteractive +ENV LC_ALL=en_US.UTF-8 + +# Version of python to be installed and used +ENV PYTHON_VERSION=3.10 +# Paired conda installer +ENV CONDA_INSTALLER=https://repo.anaconda.com/miniconda/Miniconda3-py310_23.5.1-0-Linux-x86_64.sh +ENV JUPYTER_VERSION=5.7.2 +ENV NODE_MAJOR=20 + +############### +# Install Prerequisites +############### +RUN apt-get update && apt-get install -yq --no-install-recommends \ + # basic necessities + sudo \ + ca-certificates \ + curl \ + jq \ + # gnupg requirement + gnupg \ + dirmngr \ + # useful utilities for debugging within docker itself + nano \ + less \ + procps \ + lsb-release \ + # gcc compiler + build-essential \ + locales \ + # for ssh-agent and ssh-add + keychain \ + # extras \ + wget \ + bzip2 \ + git \ + # Uncomment en_US.UTF-8 for inclusion in generation + && sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen \ + # Generate locale + && locale-gen \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +############################## +# Set up Node for Jupyterlab +############################## +RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - +RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - + +# Install Node >18 (needed for jupyterlab) +RUN apt-get update && apt-get install -yq --no-install-recommends +RUN mkdir -p /etc/apt/keyrings +RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg + +RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list +RUN dpkg --remove --force-remove-reinstreq libnode-dev +RUN apt-get update && apt-get install -f -yq nodejs + + +##################### +# Create the Jupyter User (what users will see in Terra) +##################### +# Create the jupyter user and give sudo permission +ENV JUPYTER_USER=jupyter +ENV JUPYTER_UID=1002 + +# Create the jupyter user home and add the user to the users group +ENV JUPYTER_USER_HOME=/home/$JUPYTER_USER +RUN useradd -m -s /bin/bash -d $JUPYTER_USER_HOME -N -u $JUPYTER_UID -g users $JUPYTER_USER + +# We want to grant the jupyter user sudo permissions without password + # so they can install the necessary packages that they want to use on the docker container +RUN echo "$JUPYTER_USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$JUPYTER_USER \ + && chmod 0440 /etc/sudoers.d/$JUPYTER_USER + +################ +# Create Welder user +################ +# The welder uid must be consistent with the Welder docker definition here: +# https://github.com/DataBiosphere/welder/blob/master/project/Settings.scala +# Adding welder-user to the Jupyter container isn't strictly required, but it makes welder-added +# files display nicer when viewed in a terminal. +ENV WELDER_USER=welder-user +ENV WELDER_UID=1001 +RUN useradd -m -s /bin/bash -N -u $WELDER_UID $WELDER_USER + +############ +# Install R +############ +RUN apt-get update && apt-get install -y r-base + +################ +# Install Python +################ +# Install Python 3.10 and add to system python path +# Miniconda does come with it's own installation of python, but it +# is cleaner to do a proper system instalation here and set the path +#RUN apt-get update && apt-get install -y python$PYTHON_VERSION python3-pip +#RUN update-alternatives --install /usr/bin/python python /usr/bin/python$PYTHON_VERSION 1 \ +# && update-alternatives --set python /usr/bin/python$PYTHON_VERSION + +# Set up the path to the user python --> /home/jupyter/.envs/base-python3.10/bin/python +#ENV BASE_PYTHON_PATH=usr/bin/python3 +# +############### +# Install Miniconda +############### +## Note: CONDA is not used here to manage dependencies, but as a tool to manage python environments. +## We want to store the user conda environments in a directory that will be in the persistent disk +## Attention: If you change the Conda home location, please update conda_init.txt accordingly +ENV CONDA_ENV_NAME=base-python${PYTHON_VERSION} +ENV CONDA_ENV_HOME=$JUPYTER_USER_HOME/.envs/$CONDA_ENV_NAME +RUN curl -so $JUPYTER_USER_HOME/miniconda.sh ${CONDA_INSTALLER} \ + && chmod +x $JUPYTER_USER_HOME/miniconda.sh \ + && $JUPYTER_USER_HOME/miniconda.sh -b -p $CONDA_ENV_HOME \ + && rm $JUPYTER_USER_HOME/miniconda.sh +ENV PATH="${PATH}:${CONDA_ENV_HOME}/bin" + +## Set up the path to the user python --> /home/jupyter/.envs/base-python3.10/bin/python +ENV BASE_PYTHON_PATH=$CONDA_ENV_HOME/bin/python + +# Tell conda to NOT write bite code (aka these.pyc files) +ENV PYTHONDONTWRITEBYTECODE=true + +################################################### +# Set up the user to use the conda base environment +################################################### +## The user should have full access to the conda base environment, and can use it directly, or +## create new conda environments on top of it. The important part is that jupyter IS NOT installed +## in the base environment to provide isolation between the user environment, and the jupyter server +## to avoid cross-contamination +COPY conda-env.yml . +RUN conda env update --prefix $CONDA_ENV_HOME --file conda-env.yml --prune \ + # Remove packages tarballs and python bytecode files from the image + && conda clean -afy \ + && rm conda-env.yml \ + # Make sure the JUPYTER_USER is the owner of the folder where + # the base conda is installed + && chown -R $JUPYTER_USER:users $JUPYTER_USER_HOME \ + # enable conda libmamba: https://www.anaconda.com/blog/a-faster-conda-for-a-growing-community \ + && conda install -n base conda-libmamba-solver \ + && conda config --set solver libmamba + +# Add the user base conda environment as a jupyter kernel - this should be the default now +# This commands activates the conda environment and then calls ipykernel from within +# to install it as a kernel under the same name +RUN conda run -p $CONDA_ENV_HOME python -m ipykernel install --name=$CONDA_ENV_NAME + +# Prep the jupyter terminal to conda init and make sure the base conda environment is +# activated and the name is displayed in the terminal prompt +COPY conda_init.txt . +RUN cat conda_init.txt >> $JUPYTER_USER_HOME/.bashrc && \ + printf "\nconda activate ${CONDA_ENV_HOME}" >> $JUPYTER_USER_HOME/.bashrc && \ + conda config --set env_prompt '({name})' && \ + source $JUPYTER_USER_HOME/.bashrc && \ + rm conda_init.txt + + +########## +# Setup UV +########## +# - Silence uv complaining about not being able to use hard links, +# - tell uv to byte-compile packages for faster application startups, +# - prevent uv from accidentally downloading isolated Python builds, +# - specify Python version +# - don't seed venv with wheel and setuptools, we need to install specific versions +# - don't cache to keep the image size small +# - use system python to avoid installing a new python version +ENV UV_LINK_MODE=copy \ + UV_COMPILE_BYTECODE=1 \ + UV_PYTHON_DOWNLOADS=never \ + UV_PYTHON=$PYTHON_VERSION \ + UV_VENV_SEED=false \ + UV_NO_CACHE=true \ + UV_SYSTEM_PYTHON=1 + +############### +# Setup virtualenv +############### +# Using UV (Universal Virtualenv) to create a virtual environment +# UV is used in place of poetry for speed and simplicity. +# NOTE: this is separate from the jupyter user +ENV JUPYTER_HOME=/usr/jupytervenv +ENV VIRTUAL_ENV=$JUPYTER_HOME + +COPY uv.lock . +COPY pyproject.toml . + +# Add jupyter virtual environment to PATH, +# but make sure to add it at the end so that the +# Conda base python takes precedence +# (aka the ! operator in iPython shells should NOT access the jupyter virtualenvironment) +ENV PATH "${PATH}:${JUPYTER_HOME}/bin" +ENV PATH="/root/.local/bin/:$PATH" + +# Download the latest installer +ADD https://astral.sh/uv/install.sh /uv-installer.sh +#--python $BASE_PYTHON_PATH +RUN sh /uv-installer.sh && rm /uv-installer.sh \ + # Create a virtual environment and activate it for UV to use + && uv venv $JUPYTER_HOME --python $BASE_PYTHON_PATH \ + && source $JUPYTER_HOME/bin/activate \ + && PYTHONEXECUTABLE=/usr/bin/env python3 \ + # Install the python dependencies using uv + && uv pip install wheel \ + && uv pip install 'setuptools==59.8.0' \ + && uv pip install -r pyproject.toml --no-cache --no-build-isolation \ + # Cleanup + && rm uv.lock && rm pyproject.toml + # Uninstall uv +# && uv cache clean \ +# && rm ~/.local/bin/uv ~/.local/bin/uvx + +# Install nbstripout +RUN nbstripout --install --global + +# ################################## +# # Terra-specific Jupyter Utilities +# ################################## +# Ensure this matches c.NotebookApp.port in 'jupyter_notebook_config.py' +ENV JUPYTER_PORT=8000 +EXPOSE $JUPYTER_PORT +ENV JUPYTER_KERNELSPEC_DIR=/usr/local/share/jupyter + +# Install nbstripout +RUN nbstripout --install --global + +# copy workspace_cromwell.py script and make it runnable by all users +RUN curl -o /usr/local/bin/workspace_cromwell.py https://raw.githubusercontent.com/broadinstitute/cromwhelm/1ceedf89587cffd355f37401b179001f029f77ed/scripts/workspace_cromwell.py \ + && chmod +x /usr/local/bin/workspace_cromwell.py + +# Copy over custom extensions +COPY scripts $JUPYTER_HOME/scripts +COPY custom $JUPYTER_HOME/custom +COPY jupyter_notebook_config.py $JUPYTER_HOME +RUN chown -R $JUPYTER_USER:users $JUPYTER_HOME + +# Remove the jupyter environment from the list of available kernels so it is hidden from the user +# NOTE: This depends on setting the c.KernelSpecManager.ensure_native_kernel flag +# to False in 'jupyter_server_config.py' +#RUN $JUPYTER_HOME/bin/jupyter kernelspec remove python3 -y + + # setup the jupyter kernel +RUN chown -R $JUPYTER_USER:users $JUPYTER_KERNELSPEC_DIR \ + && find $JUPYTER_HOME/scripts -name '*.sh' -type f | xargs chmod +x \ + # You can get kernel directory by running `jupyter kernelspec list` + && $JUPYTER_HOME/scripts/kernel/kernelspec.sh $JUPYTER_HOME/scripts/kernel $JUPYTER_KERNELSPEC_DIR/kernels + +# Set up the user and working directory, which is where the persistent disk will be mounted +# this is different from where Jupyter is installed +USER $JUPYTER_USER +WORKDIR $JUPYTER_USER_HOME + +# Note: this entrypoint is provided for running Jupyter independently of Leonardo. +# When Leonardo deploys this image onto a cluster, the entrypoint is overwritten to enable +# additional setup inside the container before execution. Jupyter execution occurs when the +# init-actions.sh script uses 'docker exec' to call run-jupyter.sh. +# .venv/bin/jupyter lab +#ENTRYPOINT ["/usr/jupytervenv/bin/jupyter", "lab"] \ No newline at end of file diff --git a/terra-base/README.md b/terra-base/README.md new file mode 100644 index 00000000..1858c3ea --- /dev/null +++ b/terra-base/README.md @@ -0,0 +1,52 @@ +# terra-jupyter-base image + +This repo contains the terra-jupyter-base image that is compatible with notebook service in [Terra]("https://app.terra.bio/") called Leonardo. For example, use `us.gcr.io/broad-dsp-gcr-public/terra-jupyter-base:{version}` in terra. + +## Image contents + +The terra-jupyter-base extends the [Ubuntu base image]() TODO by including the following: + +- OS prerequisites +- google-cloud-sdk +- Python 3.10 +- conda +- Jupyter & JupyterLab +- Leonardo customizations/extensions +- Terra notebook utils + +To see the complete contents of this image please see the [Dockerfile](./Dockerfile). + +## Selecting prior versions of this image + +To select an older version this image, you can search the [CHANGELOG.md](./CHANGELOG.md) for a specific package version you need. + +Once you find an image version that you want, simply copy and paste the image url from the changelog into the corresponding custom docker field in the Terra notebook runtime widget. + +## Updating the UV packages + To update UV packages, first cd into the`terra-base` directory, then either: + - modify the pyproject.toml file, then run `uv lock` + - run `uv update` to update all packages in the project + - run `uv add ` or remove to add or remove a specific package in the project + +## Building the image +To build the image locally, run the following command in the root of the repo: + +```bash + docker build terra-base -t us.gcr.io/broad-dsp-gcr-public/terra-base:{version} + docker push us.gcr.io/broad-dsp-gcr-public/terra-base:{version} +``` + +## NOTE: Changing paths +If you change the following paths for the: +- `JUPYTER_HOME` +- `JUPYTER_USER` +- `CONDA_HOME` + +You may need to update the following files appropriately: +- terra-docker + - `conda_init.txt` + - `run_jupyter.sh` + - the notebook extension scripts +- leonardo + - `gce-init.sh` + - the `jupyterUserhome` in RuntimeTemplateValues \ No newline at end of file diff --git a/terra-base/conda-env.yml b/terra-base/conda-env.yml new file mode 100644 index 00000000..6fead3b5 --- /dev/null +++ b/terra-base/conda-env.yml @@ -0,0 +1,12 @@ +name: terra-base +channels: + - defaults +dependencies: + - pip=23.1.2 + - python=3.10.12 + - pip: + - bgzip==0.3.5 + - firecloud>=0.16.38 + - ipykernel==6.29.4 + - terra-notebook-utils>=0.15.0 + - cromshell>=2.1.1 diff --git a/terra-base/conda_init.txt b/terra-base/conda_init.txt new file mode 100644 index 00000000..e9e5acfa --- /dev/null +++ b/terra-base/conda_init.txt @@ -0,0 +1,14 @@ +# >>> conda initialize >>> +# !! Contents within this block are managed by 'conda init' !! +__conda_setup="$('/home/jupyter/.envs/base-python3.10/bin/conda' 'shell.bash' 'hook' 2> /dev/null)" +if [ $? -eq 0 ]; then +eval "$__conda_setup" +else +if [ -f "/home/jupyter/.envs/base-python3.10/etc/profile.d/conda.sh" ]; then +. "/home/jupyter/.envs/base-python3.10/etc/profile.d/conda.sh" +else +export PATH="/home/jupyter/.envs/base-python3.10/bin:$PATH" +fi +fi +unset __conda_setup +# <<< conda initialize <<< \ No newline at end of file diff --git a/terra-base/custom/.eslintrc.js b/terra-base/custom/.eslintrc.js new file mode 100644 index 00000000..a1781ad1 --- /dev/null +++ b/terra-base/custom/.eslintrc.js @@ -0,0 +1,32 @@ +module.exports = { + 'env': { + 'browser': true, + 'es6': true, + }, + 'extends': [ + 'google', + ], + 'globals': { + 'Atomics': 'readonly', + 'SharedArrayBuffer': 'readonly', + }, + 'parserOptions': { + 'ecmaVersion': 2018, + 'sourceType': 'module', + }, + 'rules': { + 'comma-dangle': ['error', 'never'], + //80 is the standard, but this required the least refactoring. + 'max-len': ['error', { 'code': 150 }], + //TODO: remove the following rules and fix the offenses + 'require-jsdoc': ['error', { + 'require': { + 'FunctionDeclaration': false, + 'MethodDefinition': false, + 'ClassDeclaration': false, + 'ArrowFunctionExpression': false, + 'FunctionExpression': false + } + }] + }, +}; \ No newline at end of file diff --git a/terra-base/custom/README.md b/terra-base/custom/README.md new file mode 100644 index 00000000..54a6e25e --- /dev/null +++ b/terra-base/custom/README.md @@ -0,0 +1,6 @@ +#Before you merge, lint the files with eslint. + +# `brew install eslint` +# `brew install npm` +# `npm i` +# `eslint --fix *.js` diff --git a/terra-base/custom/edit-mode.js b/terra-base/custom/edit-mode.js new file mode 100644 index 00000000..3670fb1f --- /dev/null +++ b/terra-base/custom/edit-mode.js @@ -0,0 +1,447 @@ +define(() => { + // define default values for config parameters + const params = { + googleProject: '', + clusterName: '', + welderEnabled: 'false' + }; + + // update params with any specified in the server's config file + function updateParams() { + const config = Jupyter.notebook.config; + for (const key in params) { + if (config.data.hasOwnProperty(key)) { + params[key] = config.data[key]; + } + } + + // generate URLs based on params + const leoUrl = ''; // we are choosing to use a relative path here + const welderUrl = leoUrl + `/proxy/${params.googleProject}/${params.clusterName}/welder`; + + // URLS for local testing + // let leoUrl = 'http://localhost:8080' //for testing against local server + // let welderUrl = leoUrl + // params.jupyterServerApi = '/api/contents/' + // params.jupyterFsHref = '/notebooks/' + + params.jupyterServerApi = leoUrl + `/notebooks/${params.googleProject}/${params.clusterName}` + '/api/contents/'; + params.jupyterFsHref = leoUrl + `/notebooks/${params.googleProject}/${params.clusterName}/notebooks/`; + params.localizeUrl = welderUrl + '/objects'; + params.checkMetaUrl = welderUrl + '/objects/metadata'; + params.lockUrl = welderUrl + '/objects/lock'; + } + + let modalOpen = false; + // this needs to be available so the loop can be cancelled where needed + let syncMaintainer; + let shouldExit = false; + + const syncIssueButtons = (res) => { + return { + 'Make a Copy': { + 'click': () => notebookSaveAs(), + 'class': 'btn-primary', + 'id': 'modal-copy-1' + }, + 'Reload the workspace version and discard your changes': { + 'click': () => updateLocalCopyWithRemote(res), + 'id': 'modal-reload' + } + }; + }; + + const lockIssueButtons = (res) => { + return { + 'Run in Playground Mode': { + 'click': () => openPlaygroundMode(res), + 'class': 'btn-primary', + 'id': 'modal-playground' + }, + 'Make a Copy': { + 'click': () => notebookSaveAs(), + 'id': 'modal-copy-2' + } + }; + }; + + const noRemoteFileButtons = { + 'Continue working': { + 'click': () => {}, + 'class': 'btn-primary' + } + }; + + const modeBannerId = 'notification_mode'; + const lockConflictTitle = 'File is in use'; + const syncIssueTitle = 'File versions out of sync'; + const syncIssueBody = 'Your version of this file does not match the version in the workspace. What would you like to do?'; + const syncIssueNotFoundBody = 'This file was either deleted from or was never saved to the workspace.'; + const lastLockedTimer = 60000; + + const headers = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Headers': '*' + }; + + const basePayload = { + mode: 'no-cors', + headers: headers + }; + + function init() { + console.info('edit mode plugin initialized'); + + if (!Jupyter.notebook) { + return; // exit, they are in list view + } + + updateParams(); + + if (!(params.welderEnabled == 'true')) { + console.info('welder is not enabled'); + return; + } + + checkMeta(); + + const initialSyncDelay = 27000; + // we want subsequent checkMeta + lock calls to be off-cycle of autosave + setTimeout(() => initSyncMaintainer(), initialSyncDelay); + } + + function initSyncMaintainer() { + syncMaintainer = setInterval(() => { + checkMeta(); + }, lastLockedTimer); + + window.onbeforeunload(function() { + clearInterval(syncMaintainer); + }); + } + + function checkMeta() { + const localPath = { + localPath: Jupyter.notebook.notebook_path + }; + + console.info('calling /objects/metadata/ with payload: ', JSON.stringify(localPath)); + + const payload = { + ...basePayload, + body: JSON.stringify(localPath), + method: 'POST' + }; + + return fetch(params.checkMetaUrl, payload) + .then((res) => { + processInitialCheckMeta(res); + return res.json(); + }) + .then((res) => { + handleMetaSuccess(res); + return res; + }) + .catch((err) => { + handleMetaFailure(err); + }); + } + + function renderFileNotTrackedBanner() { + removeElementById(modeBannerId); + + const toolTipText = '

Your changes are not being saved to the workspace.

'; + + $('#notification_area').append( + $('
').attr({ + 'id': 'notification_not_saving', + 'class': 'btn-warning btn btn-xs navbar-btn', + 'data-toggle': 'tooltip', + 'data-html': 'true', + 'title': toolTipText + }) + .tooltip({ + 'content': function() { + return $(this).prop('title'); + }, + 'placement': 'bottom' + }) + .append( + $('').html('Remote Save Disabled') + .append(' ') + ) + ); + } + + function processInitialCheckMeta(res) { + if (!res.ok) { + if (res.status == 412) { + console.warn('detected 412 from /objects/metadata. stopping loop'); + renderFileNotTrackedBanner(); + shouldExit = true; + clearInterval(syncMaintainer); + } + + throw Error(res.statusText); + } + } + + function handleMetaSuccess(res) { + toggleMetaFailureBanner(false); // sets banner for meta status + + const isEditMode = res.syncMode == 'EDIT'; + + if (isEditMode) { + handleCheckMetaResp(res); // displays modal if theres an issue in the payload + getLock(res); // gets lock if in edit mode + } + renderModeBanner(isEditMode); // sets edit/safe mode banner + } + + function handleMetaFailure(err) { + console.error(err); + if (!shouldExit) { + removeElementById(modeBannerId); + toggleMetaFailureBanner(true); + } + } + + // this function assumes any status not included in these lists represents a notebook out of sync + // this is done to defend against future fields being added being auto-categorized as failures + function handleCheckMetaResp(res) { + const healthySyncStatuses = ['LIVE']; + // below is included for reference, but commented out so it does not cause errors with linting + // const outOfSyncStatuses = ['DESYNCHRONIZED', 'REMOTE_CHANGED']; + const notFoundStatus = ['REMOTE_NOT_FOUND']; + const saveNeededStatus = ['LOCAL_CHANGED']; + + if (healthySyncStatuses.includes(res.syncStatus)) { + console.info('healthy sync status detected: ', res.syncStatus); + } else if (notFoundStatus.includes(res.syncStatus)) { + promptUserWithModal(syncIssueTitle, noRemoteFileButtons, syncIssueNotFoundBody); + } else if (saveNeededStatus.includes(res.syncStatus)) { + console.info('detected that we have changes that have not been delocalized.'); + // It is possible saving is the right call here (aka $("#save-notbook > button").click()), but we already do that on a periodic tick + // adding it here could possibly cause confusion + } else { + promptUserWithModal(syncIssueTitle, syncIssueButtons(res), syncIssueBody); + } + } + + function getLock(metaRes) { + const payload = { + ...basePayload, + method: 'POST', + body: JSON.stringify({localPath: Jupyter.notebook.notebook_path}) + }; + + fetch(params.lockUrl, payload) + .then((res) => { + handleLockStatus(res, metaRes); + }) + .catch((err) => { + console.error(err); + }); + } + + function toggleMetaFailureBanner(shouldShow) { + const bannerId = 'notification_metaFailure'; + const bannerText = 'Failed to check notebook status, changes may not be saved to workspace. Retrying...'; + + removeElementById(bannerId); + + if (shouldShow) { + const bannerStyling = 'btn btn-xs navbar-btn btn-danger'; + + $('#notification_area').append( + $('
').attr({ + 'id': bannerId, + 'class': bannerStyling + }) + .append($('').html(' ' + bannerText)) + ); + } + } + + const lockConflictBody = `

This file is currently being edited by another user. ` + + `Please allow 2-3 minutes after this user has closed the file for it to become available for editing.

` + + `

You can make a copy, or run it in Playground Mode to explore and execute its contents without saving any changes.`; + + function handleLockStatus(res, metaRes) { + if (!res.ok) { + const status = res.status; + const errorText = res.statusText; + + if (status == 409) { + res.json().then((res) => { + promptUserWithModal(lockConflictTitle, lockIssueButtons(metaRes), lockConflictBody); + }); + } + // for the lock endpoint, we consider all non 'ok' statuses an error + throw new Error(errorText); + } + } + + + function promptUserWithModal(title, buttons, htmlBody) { + if (modalOpen) return; + + modalOpen = true; + + // we need to require here because otherwise we haven't checked if the module is loaded + // (i.e. if we are in terminal view) + const dialog = require('base/js/dialog'); + + dialog.modal({ + body: $('

').html(htmlBody), + title: title, + buttons: buttons, + notebook: Jupyter.notebook, + keyboard_manager: Jupyter.notebook.keyboard_manager + }) + .on('hidden.bs.modal', () => modalOpen = false) + .attr('id', 'leoUserModal') + .find('.close').remove(); // TODO: test going back + } + + async function openPlaygroundMode(metaRes) { + const originalNotebookName = Jupyter.notebook.notebook_name; + + const safeModeDir = metaRes.storageLink.localSafeModeBaseDirectory; + + if (Jupyter.notebook.notebook_path.includes(safeModeDir)) { + console.warn('Attempted to navigate to enter safe mode while already in safe mode. Exitting.'); + return; // we're here already + } + + // create a new file with the contents + const postPayload = { + ...basePayload, + method: 'POST', + body: JSON.stringify({ + copy_from: Jupyter.notebook.notebook_path + }) + }; + + const patchPayload = { + headers: headers, + method: 'PATCH', + body: JSON.stringify({ + path: safeModeDir + '/' + originalNotebookName + }) + }; + + fetch(params.jupyterServerApi + safeModeDir, postPayload) + .then((res) => handleJupyterServerResponse(res)) + .then((res) => { + // then we rename the file, as POST does not allow us to specify the file name + fetch(params.jupyterServerApi + res.path, patchPayload) + .then((res) => { + // navigate to new file + window.location.href = params.jupyterFsHref + safeModeDir + '/' + originalNotebookName; + }); + }); + } + + function notebookSaveAs() { + // we need to require here because otherwise we haven't checked if the module is loaded + // (i.e. if we are in terminal view) + const utils = require('base/js/utils'); + // guarantees a path in [0] and file name in [1]. [0] is "" if just a file is passed + const originalPathSplit = utils.url_path_split(Jupyter.notebook.notebook_path); + const newNotebookPath = originalPathSplit[0]; + + // create a new file with the contents + const payload = { + ...basePayload, + method: 'POST', + body: JSON.stringify({ + copy_from: Jupyter.notebook.notebook_path + }) + }; + + fetch(params.jupyterServerApi + newNotebookPath, payload) + .then((res) => handleJupyterServerResponse(res)) + .then((res) => { + // navigate to new file. we rely on the jupyter post api to supply the name of the file we have created as it ensures it does not exist + // POST also does not allow for the specification of a file name + window.location.href = params.jupyterFsHref + res.path; + }); + } + + function handleJupyterServerResponse(res) { + if (!res.ok) { + throw new Error('failed to perform requested action, the jupyter server is unavailable'); + } + return res.json(); + } + + function removeElementById(id) { + if (!$('#' + id).length == 0) { + $('#' + id).remove(); + } + } + + // shows the user whether they are in playground mode or edit mode + function renderModeBanner(isEditMode) { + removeElementById(modeBannerId); // we always remove the banner because we re-render each loop + + let bannerText; + let toolTipText; + let bannerStyling; + + const baseStyling = 'btn btn-xs navbar-btn'; + + if (isEditMode) { + bannerText = 'Edit Mode'; + toolTipText = '

You have locked this file for editing and your changes are being automatically saved to the workspace.

'; + bannerStyling = 'notification_widget ' + baseStyling; + } else { + bannerText = 'Playground Mode (Edits not saved)'; + toolTipText = '

Playground mode allows you to explore, change, and run the notebook, but changes are not saved to the workspace.

'; + bannerStyling = 'btn-warning ' + baseStyling; + } + + $('#notification_area').append( + $('
').attr({ + 'id': modeBannerId, + 'class': bannerStyling, + 'data-toggle': 'tooltip', + 'data-html': 'true', + 'title': toolTipText + }) + .tooltip({ + 'content': function() { + return $(this).prop('title'); + }, + 'placement': 'bottom' + }) + .append( + $('').html(bannerText) + .append(' ') + ) + ); + } + + async function updateLocalCopyWithRemote(meta) { + const entries = { + action: 'localize', + entries: [{ + sourceUri: meta.storageLink.cloudStorageDirectory + '/' + Jupyter.notebook.notebook_name, + localDestinationPath: Jupyter.notebook.notebook_path + }] + }; + + const payload = { + ...basePayload, + method: 'POST', + body: JSON.stringify(entries) + }; + + await fetch(params.localizeUrl, payload); + + location.reload(true); + } + + init(); +}); diff --git a/terra-base/custom/extension_entry_jupyter.js b/terra-base/custom/extension_entry_jupyter.js new file mode 100644 index 00000000..0515b726 --- /dev/null +++ b/terra-base/custom/extension_entry_jupyter.js @@ -0,0 +1,7 @@ +define([ + 'base/js/events' +], function(events) { + require(['custom/google_sign_in']); + require(['custom/edit-mode']); + require(['custom/safe-mode']); +}); diff --git a/terra-base/custom/extension_entry_jupyterlab.js b/terra-base/custom/extension_entry_jupyterlab.js new file mode 100644 index 00000000..333ccad9 --- /dev/null +++ b/terra-base/custom/extension_entry_jupyterlab.js @@ -0,0 +1,7 @@ +module.exports = [{ + id: 'google_plugin_jupyterlab', + autoStart: true, + activate: function(app) { + require('/home/jupyter/.jupyter/custom/google_sign_in'); + } +}]; diff --git a/terra-base/custom/google_sign_in.js b/terra-base/custom/google_sign_in.js new file mode 100644 index 00000000..c63e58bf --- /dev/null +++ b/terra-base/custom/google_sign_in.js @@ -0,0 +1,174 @@ +/* + * This library is designed to run as a Jupyter/JupyterLab extension to refresh the user's + * Google credentials while using a notebook. This flow is described in more detail here: + * https://github.com/DataBiosphere/leonardo/wiki/Connecting-to-a-Leo-Notebook#token-refresh + * + * Note since this runs inside both Jupyter and JupyterLab, it should not use any + * libraries/functionality that exists in one but not the other. Examples: node, requireJS. + */ + + +// define default values for config parameters +const params = { + loginHint: '', + googleClientId: '', + googleProject: '', + clusterName: '' +}; + +// update params with any specified in the server's config file, +// or retrieve that info from the url if we cannot access it (as is the case in terminal view) +function updateParams() { + if (!Jupyter.notebook || !Jupyter.notebook.config || !Jupyter.notebook.config.data) { + console.warn('Unable to read notebook config. This is expected in terminal view, but not elsewhere. Attempting to read fallback config.'); + readFallbackConfig(); + } else { + readNotebookConfig(Jupyter.notebook.config.data); + } +} + +function readNotebookConfig(config) { + for (const key in params) { + if (config.hasOwnProperty(key)) { + params[key] = config[key]; + } + } +} + +// here we attempt to parse the url for the googleProject and the clusterName +function readFallbackConfig() { + const url = window.location.href; + const initialSearch = 'proxy/'; + + const projectSubstringStartLocation = url.search(initialSearch) + initialSearch.length; + const projectSubstring = url.substring(projectSubstringStartLocation, url.length); + const projectEndLocation = projectSubstring.search('/'); + const googleProject = projectSubstring.substring(0, projectEndLocation); + + // we add 1 for the slash between project and cluster + const clusterSubstring = projectSubstring.substring(projectEndLocation + 1, projectSubstring.length); + const clusterEndLocation = clusterSubstring.search('/'); + const clusterName = clusterSubstring.substring(0, clusterEndLocation); + + console.info(`Attempted to parse the url for a fallback configuration. + Found googleProject: '${googleProject}' and clusterName '${clusterName}'`); + + params.googleProject = googleProject; + params.clusterName = clusterName; + + fallbackReadNotebookConfig(googleProject, clusterName); +} + +function fallbackReadNotebookConfig(googleProject, clusterName) { + const url = `/proxy/${googleProject}/${clusterName}/jupyter/api/config/notebook`; + xhttpGet(url, (res) => { + readNotebookConfig(res); + }); +} + +function receive(event) { + if (event.data.type == 'bootstrap-auth.response') { + if (event.source !== window.opener) { + return; + } + params.googleClientId = event.data.body.googleClientId; + } else if (event.data.type == 'bootstrap-auth.request') { + if (event.origin !== window.origin) { + return; + } + if (!params.googleClientId) { + return; + } + event.source.postMessage({ + 'type': 'bootstrap-auth.response', + 'body': { + 'googleClientId': params.googleClientId + } + }, event.origin); + } +} + +// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/response +function xhttpGet(url, callback) { + const xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function() { + if (xhr.readyState === 4 && xhr.status == 200) { + const jsonResp = JSON.parse(xhr.responseText); + callback(jsonResp); + } + }; + + xhr.open('GET', url, true); + xhr.send(''); +} + +function startTimer() { + loadGapi('auth2', function() { + function doAuth() { + if (params.googleClientId) { + gapi.auth2.authorize({ + 'client_id': params.googleClientId, + 'scope': 'openid profile email', + 'login_hint': params.loginHint, + 'prompt': 'none' + }, function(result) { + if (result.error) { + console.error('Error occurred authorizing with Google: ' + result.error); + return; + } + setCookie(result.access_token, result.expires_in); + }); + } + } + + // refresh token every 3 minutes + console.log('Starting token refresh timer'); + setInterval(doAuth, 180000); + }); + + function statusCheck() { + const url = `/proxy/${params.googleProject}/${params.clusterName}/jupyter/api/status`; + // logging the statusCheck can be noisy, and it will tell us in the console if it fails + xhttpGet(url, () => {}); + } + + setInterval(statusCheck, 60000); +} + +// Note: this should match +// https://github.com/DataBiosphere/leonardo/blob/develop/http/src/main/scala/org/broadinstitute/dsde/workbench/leonardo/util/CookieHelper.scala +function setCookie(token, expiresIn) { + document.cookie = 'LeoToken=' + token + '; Max-Age=' + expiresIn + '; Path=/; Secure; SameSite=None'; +} + +function loadGapi(googleLib, continuation) { + console.log('Loading Google APIs'); + // Get the gapi script from Google. + const gapiScript = document.createElement('script'); + gapiScript.src = 'https://apis.google.com/js/api.js'; + gapiScript.type = 'text/javascript'; + gapiScript.async = true; + + // Load requested API scripts onto the page. + gapiScript.onload = function() { + console.log('Loading Google library \'' + googleLib + '\''); + gapi.load(googleLib, continuation); + }; + gapiScript.onerror = function() { + console.error('Unable to load Google APIs'); + }; + document.head.appendChild(gapiScript); +} + +function init() { + console.log('Starting google_sign_in extension'); + updateParams(); + startTimer(); + window.addEventListener('message', receive); + if (!params.googleClientId && window.opener) { + window.opener.postMessage({'type': 'bootstrap-auth.request'}, '*'); + } +} + +init(); diff --git a/terra-base/custom/jupyter_delocalize.py b/terra-base/custom/jupyter_delocalize.py new file mode 100644 index 00000000..e0e77bc9 --- /dev/null +++ b/terra-base/custom/jupyter_delocalize.py @@ -0,0 +1,148 @@ +from datetime import timedelta +import json +import os +import requests +import tornado +from notebook.services.contents.largefilemanager import LargeFileManager + +METADATA_TTL = timedelta(minutes=5) + +class WelderContentsManager(LargeFileManager): + """ + A contents manager which integrates with the Leo Welder service. + + Blocking Welder API calls are made before files are persisted. After a + successful call to Welder, files are persisted to the local Jupyter file + system as usual. + """ + + def __init__(self, *args, **kwargs): + # This log line shouldn't be necessary, but Jupyter's built-in logging is + # lacking and its configuration can be complex. Having this in the server + # logs is useful for confirming which ContentsManager is in use. + self.log.info('initializing WelderContentsManager') + self.welder_base_url = 'http://welder:8080' + super(WelderContentsManager, self).__init__(*args, **kwargs) + + def _extract_welder_error(self, resp): + try: + return json.dumps(resp.json()) + except: + return resp.reason or 'unknown Welder error' + + def _is_nonempty_dir(self, path): + os_path = self._get_os_path(path) + return os.path.isdir(os_path) and len(os.listdir(os_path)) > 0 + + + def _check_welder_edit_mode(self, path): + resp = requests.post(self.welder_base_url + '/objects/metadata', data=json.dumps({ + # Sometimes the Jupyter UI provided "path" contains a leading /, sometimes + # not; strip for Welder. + 'localPath': path.lstrip('/') + })) + if resp.status_code == 412: + return False + + if not resp.ok: + raise IOError("checkMetadata failed: '{}'".format(self._extract_welder_error(resp))) + + return resp.json().get("syncMode") == "EDIT" + + + def _post_welder(self, action, path): + # Ignore storage link failure, throw other errors. + resp = requests.post(self.welder_base_url + '/objects', data=json.dumps({ + 'action': action, + # Sometimes the Jupyter UI provided "path" contains a leading /, sometimes + # not; strip for Welder. + 'localPath': path.lstrip('/') + })) + if not resp.ok: + error_json = {} + try: + error_json = resp.json() + except: + pass + + # See https://github.com/DataBiosphere/welder/blob/cd39caba30989e9f2b1c76986abccf22d8e8a1c5/server/src/main/resources/api-docs.yaml#L197 + ignore_codes = set([ + 1, # Storage Link not found; expected for unmanaged files. + 2, 3 # Delocalize/delete safe mode file; expected in safe mode directories. + ]) + if resp.status_code == 412 and error_json.get('errorCode', -1) in ignore_codes: + return + + raise IOError("welder action '{}' failed: '{}'".format(action, self._extract_welder_error(resp))) + + def save(self, model, path=''): + # Don't intefere with intermediate chunks during multipart upload: + # https://jupyter-notebook.readthedocs.io/en/stable/extending/contents.html#chunked-saving + if model.get("chunk", -1) >= 0: + return super(WelderContentsManager, self).save(model, path) + + # Capture the pre-save file so we can revert if Welder fails. + orig_model = None + try: + orig_model = self.get(path) + except tornado.web.HTTPError as err: + if err.status_code != 404: + self.log.warn('failed to get file "{}", cannot revert: {}'.format(path, err.log_message)) + + # Welder reads the file from local disk, so we need to write the updated file + # before calling Welder. + # TODO(calbach): Consider changing the safeDelocalize API to support either + # direct passing of contents, or passing a file via a temporary transfer file. + ret = super(WelderContentsManager, self).save(model, path) + if not path or model['type'] == 'directory': + return ret + + try: + self._post_welder('safeDelocalize', path) + except IOError as werr: + self.log.warn("welder save failed, attempting to revert local file: " + str(werr)) + try: + if orig_model: + super(WelderContentsManager, self).save(orig_model, path) + else: + super(WelderContentsManager, self).delete_file(path) + except Exception as rerr: + self.log.error("failed to revert after Welder error, local disk is in an inconsistent state: " + str(rerr)) + raise werr + return ret + + def rename_file(self, old_path, new_path): + from_edit_mode = self._check_welder_edit_mode(old_path) + to_edit_mode = self._check_welder_edit_mode(new_path) + if not from_edit_mode and not to_edit_mode: + # If we're not touching any edit mode files, just do a normal move. + return super(WelderContentsManager, self).rename_file(old_path, new_path) + + if self._is_nonempty_dir(old_path): + raise NotImplementedError("renaming of non-empty edit mode directories is not supported") + + # These methods already properly handle edit mode semantics. + self.save(self.get(old_path), new_path) + try: + self.delete_file(old_path, from_edit_mode) + except Exception as err: + self.log.error("failed to delete old file during two-phase rename, " + + "attempting to revert save from the first phase: " + str(err)) + try: + self.delete_file(new_path, to_edit_mode) + except Exception as rerr: + self.log.error("failed to revert first phase of rename via delete, " + + "extra file will remain on disk and/or GCS: " + str(rerr)) + raise rerr + raise err + + def delete_file(self, path, edit_mode=None): + if edit_mode is None: + edit_mode = self._check_welder_edit_mode(path) + + if edit_mode: + if self._is_nonempty_dir(path): + raise NotImplementedError("deletion of non-empty edit mode directories is not supported") + self._post_welder('delete', path) + + super(WelderContentsManager, self).delete_file(path) diff --git a/terra-base/custom/jupyter_localize_extension.py b/terra-base/custom/jupyter_localize_extension.py new file mode 100644 index 00000000..bd7221ee --- /dev/null +++ b/terra-base/custom/jupyter_localize_extension.py @@ -0,0 +1,149 @@ +import distutils.util +import subprocess +import os +import sys +import tornado +from tornado import gen +from tornado.web import HTTPError +from notebook.base.handlers import IPythonHandler +from notebook.utils import url_path_join +from datauri import DataURI + +class LocalizeHandler(IPythonHandler): + def _sanitize(self, pathstr): + """Sanitizes paths. Handles local paths, gs: URIs, and data: URIs.""" + # return gs or data uris as is + if pathstr.startswith("gs:") or pathstr.startswith("data:"): + return pathstr + # expand user directories and make intermediate directories + else: + expanded = os.path.expanduser(pathstr) + try: + os.makedirs(os.path.dirname(expanded)) + except OSError: #thrown if dirs already exist + pass + return expanded + + def _localize_gcs_uri(self, locout, source, dest): + """Localizes an entry where either the source or destination is a gs: path. + Simply invokes gsutil in a subprocess.""" + + # Use a sequence of arguments with Shell=False. The subprocess module takes care + # of quoting/escaping arguments. See: + # https://docs.python.org/2/library/subprocess.html#subprocess.call + # https://docs.python.org/2/library/subprocess.html#frequently-used-arguments + cmd = ['gsutil', '-m', '-q', 'cp', '-R', '-c', '-e', source, dest] + locout.write(' '.join(cmd) + '\n') + result = subprocess.call(cmd, stderr=locout) + return result == 0 + + def check_gcs_object_status(self, locout, source): + if source.startswith("gs:"): + source_check = ['gsutil', '-m', '-q', 'ls', source] + locout.write(' '.join(source_check) + '\n') + source_status = subprocess.call(source_check, stderr=locout) + else: + source_status = 0 + return source_status == 0 + + def _localize_data_uri(self, locout, source, dest): + """Localizes an entry where the source is a data: URI""" + try: + uri = DataURI(source) + except ValueError: + locout.write('Could not parse "{}" as a data URI: {}\n'.format(source, str(e))) + return False + + try: + with open(dest, 'w+', buffering=1) as destout: + try: + uri_data = uri.data.decode() + except AttributeError: + uri_data = uri.data + destout.write(uri_data) + locout.write('{}: wrote {} bytes\n'.format(dest, len(uri_data))) + except IOError as e: + locout.write('{}: I/O error({0}): {1}\n'.format(dest, e.errno, e.strerror)) + return False + except: + locout.write('{}: unexpected error: {}\n'.format(sys.exc_info()[0])) + return False + + return True + + @gen.coroutine + def localize(self, pathdict): + """Treats the given dict as a string/string map and localizes each entry one by one. + Returns a list of any failed entries.""" + failures = [] + #This gets dropped inside the user's notebook working directory + with open("localization.log", 'a', buffering=1) as locout: + for key in pathdict: + #NOTE: keys are destinations, values are sources + source = self._sanitize(pathdict[key]) + dest = self._sanitize(key) + + if source.startswith('gs:') or dest.startswith('gs:'): + status = self.check_gcs_object_status(locout, source) + if status: + success = self._localize_gcs_uri(locout, source, dest) + else: + locout.write('Could not validate source or destination: {} -> {}.\n'.format(source, dest)) + success = False + elif source.startswith('data:'): + success = self._localize_data_uri(locout, source, dest) + else: + locout.write('Unhandled localization entry: {} -> {}. Required gs: or data: URIs.\n'.format(source, dest)) + success = False + + if not success: + failures.append((dest, source)) + + return failures + + def post(self): + try: + pathdict = tornado.escape.json_decode(self.request.body) + except ValueError: + raise HTTPError(400, "Body must be JSON object of type string/string") + + if type(pathdict) is not dict: + raise HTTPError(400, "Body must be JSON object of type string/string") + + if not all(map(lambda v: type(v) is str, pathdict.values())): + raise HTTPError(400, "Body must be JSON object of type string/string") + + try: + raw = self.get_query_argument('async', 'false') + isAsync = distutils.util.strtobool(raw) + except ValueError: + raise HTTPError(400, "Could not parse isAsync parameter as a boolean: '{}'".format(raw)) + + if isAsync: + #complete the request HERE, without waiting for the localize to run + self.set_status(200) + self.finish() + + #fire and forget the actual work -- it'll log to a file in the user's homedir + tornado.ioloop.IOLoop.current().spawn_callback(self.localize, pathdict) + + else: + #run localize synchronous to the HTTP request + #run_sync() doesn't take arguments, so we must wrap the call in a lambda. + failures = tornado.ioloop.IOLoop().run_sync(lambda: self.localize(pathdict)) + + #complete the request only after localize completes + if failures: + raise HTTPError(500, "Error occurred localizing the following {} entries: {}. See localization.log for details.".format( + len(failures), str(failures))) + else: + self.set_status(200) + self.finish() + +def load_jupyter_server_extension(nb_server_app): + """Entrypoint for the Jupyter extension.""" + web_app = nb_server_app.web_app + host_pattern = '.*$' + route_pattern = url_path_join(web_app.settings['base_url'], '/api/localize') + web_app.add_handlers(host_pattern, [(route_pattern, LocalizeHandler)]) + nb_server_app.log.info('initialized jupyter_localize_extension') diff --git a/terra-base/custom/package.json b/terra-base/custom/package.json new file mode 100644 index 00000000..1c1a6692 --- /dev/null +++ b/terra-base/custom/package.json @@ -0,0 +1,19 @@ +{ + "name": "jupyter-nbextension", + "version": "1.0.0", + "description": "Used exclusively for linting the nbextension files. See the README for instructions.", + "main": "edit-mode.js", + "directories": { + "test": "test" + }, + "dependencies": {}, + "devDependencies": { + "eslint": "^6.1.0", + "eslint-config-google": "^0.13.0" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/terra-base/custom/safe-mode.js b/terra-base/custom/safe-mode.js new file mode 100644 index 00000000..6adda657 --- /dev/null +++ b/terra-base/custom/safe-mode.js @@ -0,0 +1,128 @@ +// Adapted from the All of Us Researcher Workbench "Playground Mode" +// https://github.com/all-of-us/workbench/blob/master/api/cluster-resources/playground-extension.js + +// In "Safe Mode", changes are not saved back to GCS. This extension makes +// minor UI tweaks to differentiate this mode from normal Jupyter usage, and +// also removes/hides controls relating to persistence. Technically +// this does not stop autosave from continuing to happen in the background, but +// the intended use of this plugin is in a separate space from normal operation +// which does not support localization features. + +// const namespace = require('base/js/namespace') + +define(() => { + // define default values for config parameters + const params = { + googleProject: '', + clusterName: '', + welderEnabled: 'false' + }; + + // update params with any specified in the server's config file + function updateParams() { + const config = Jupyter.notebook.config; + for (const key in params) { + if (config.data.hasOwnProperty(key)) { + params[key] = config.data[key]; + } + } + + // generate URLs based on params + const welderUrl = `/proxy/${params.googleProject}/${params.clusterName}/welder`; + params.checkMetaUrl = welderUrl + '/objects/metadata'; + } + + const headers = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Headers': '*' + }; + + const basePayload = { + mode: 'no-cors', + headers: headers + }; + + function load() { + console.info('safe mode plugin initialized'); + + if (!Jupyter.notebook) { + return; // exit, they are in list view + } + + updateParams(); + + if (!(params.welderEnabled == 'true')) { + console.info('welder is not enabled'); + return; + } + + checkMetaLoop(); + } + + function checkMetaLoop() { + triggerUIToggle(); + + const interval = setInterval(() => { + triggerUIToggle(); + }, 60000); + + window.onbeforeunload(() => { + clearInterval(interval); + }); + } + + async function triggerUIToggle() { + checkMeta() + .then((res) => { + if (res.syncMode == 'EDIT') { + toggleUIControls(false); + } else { + // there is an icon in the jupyter UI that has a tool tip that says 'edit mode'. + // this shows up whenever a user types, so we change the tool-tip to avoid confusion + $('#modal_indicator').tooltip({'content': 'Adding code'}); + toggleUIControls(true); + } + }) + .catch((err) => { + console.error(err); + toggleUIControls(false); // we always assume safe mode if the check meta call fails + }); + } + + function checkMeta() { + const payload = { + ...basePayload, + body: JSON.stringify({localPath: Jupyter.notebook.notebook_path}), + method: 'POST' + }; + + return fetch(params.checkMetaUrl, payload) + .then((res) => { + if (!res.ok) { + throw Error('check metadata call failed due to status code'); + } + return res.json(); + }); + } + + function toggleUIControls(shouldHide) { + // these are the jquery selectors for the elements we will toggle + // "notbook" is an intentional typo to match the Jupyter UI HTML. + const selectorsToHide = ['#save-notbook', '#new_notebook', + '#open_notebook', '#copy_notebook', '#save_notebook_as', + '#save_checkpoint', '#restore_checkpoint', '.checkpoint_status', + '.autosave_status', '#notification_notebook', '#file_menu > li.divider:eq(0)', + '#file_menu > li.divider:eq(2)' + ]; + + selectorsToHide.forEach((selector) => { + if (shouldHide) { + $(selector).hide(); + } else { + $(selector).show(); + } + }); + } + + load(); +}); diff --git a/terra-base/custom/test/README.md b/terra-base/custom/test/README.md new file mode 100644 index 00000000..985b09cf --- /dev/null +++ b/terra-base/custom/test/README.md @@ -0,0 +1,16 @@ +## Python testing + +TODO: Integrate this into automated testing processes. + +- Initialize and activate a virtualenv +- Install requirements: + + ``` + jupyter-server$ pip3 install -r test-requirements.txt + ``` +- Run the tests: + + ``` + jupyter-server$ PYTHONPATH=$PYTHONPATH:. python3 -m nose + ``` + diff --git a/terra-base/custom/test/jupyter_delocalize_test.py b/terra-base/custom/test/jupyter_delocalize_test.py new file mode 100644 index 00000000..377a0274 --- /dev/null +++ b/terra-base/custom/test/jupyter_delocalize_test.py @@ -0,0 +1,448 @@ +import copy +import datetime +import json +import os +import requests_mock +import shutil +import tempfile +import unittest +from datetime import timedelta +from nbformat.v4 import new_notebook +from tornado.testing import AsyncTestCase, gen_test +from unittest.mock import patch + +# Import this first; see https://github.com/jupyter/notebook/issues/2798 +import notebook.transutils +import jupyter_delocalize + +class TestDelocalizingContentsManager(AsyncTestCase): + """DelocalizingContentsManager tests""" + + def setUp(self): + super(TestDelocalizingContentsManager, self).setUp() + self.orig_ttl = jupyter_delocalize.METADATA_TTL + jupyter_delocalize.METADATA_TTL = timedelta() + self.manager = jupyter_delocalize.DelocalizingContentsManager( + root_dir=tempfile.mkdtemp(), + delete_to_trash=False + ) + # Replaces gsutil with normal file commands. + self.manager.file_cmd = [] + self.manager.new(model={'type': 'directory'}, path='dir') + self.out_dir = tempfile.mkdtemp() + + def tearDown(self): + jupyter_delocalize.METADATA_TTL = self.orig_ttl + shutil.rmtree(self.manager.root_dir) + shutil.rmtree(self.out_dir) + super(TestDelocalizingContentsManager, self).tearDown() + + def _await_tornado(self): + # We spawn the delocalize processes in a Tornado callback, which executes + # asynchronously. + self.io_loop.add_callback(self.stop) + self.wait() + + def _save_new_notebook(self, path): + content = new_notebook() + self.manager.save({ + 'type': 'notebook', + 'content': content, + 'format': 'text' + }, path=path) + self._await_tornado() + return content.dict() + + def _save_delocalize_config(self, dir_path, config=None): + if not config: + config = { + 'destination': self.out_dir + } + self.manager.save({ + 'type': 'file', + 'content': json.dumps(config), + 'format': 'text' + }, path=dir_path + '/.delocalize.json') + self._await_tornado() + + def _rename_file(self, from_path, to_path): + self.manager.rename_file(from_path, to_path) + self._await_tornado() + + def _delete_file(self, path): + self.manager.delete_file(path) + self._await_tornado() + + def test_save_normal(self): + want = self._save_new_notebook('dir/foo.ipynb') + self.assertEqual(os.listdir(self.out_dir), []) + with open(self.manager.root_dir + '/dir/foo.ipynb', 'r') as got: + self.assertEqual(json.load(got), want) + + def test_save_delocalize(self): + self._save_delocalize_config('dir') + want = self._save_new_notebook('dir/foo.ipynb') + + with open(self.out_dir + '/foo.ipynb', 'r') as got: + self.assertEqual(json.load(got), want) + with open(self.manager.root_dir + '/dir/foo.ipynb', 'r') as got: + self.assertEqual(json.load(got), want) + + def test_save_delocalize_other_dirs(self): + self.manager.new(model={'type': 'directory'}, path='dirA') + self.manager.new(model={'type': 'directory'}, path='dir/dirB') + self._save_delocalize_config('dir') + + self._save_new_notebook('foo.ipynb') + self._save_new_notebook('dirA/fizz.ipynb') + self._save_new_notebook('dir/dirB/bar.ipynb') + self.assertEqual(os.listdir(self.out_dir), []) + + def test_save_delocalize_with_pattern(self): + self._save_delocalize_config('.', config={ + 'destination': self.out_dir, + 'pattern': '.*\.ipynb$' + }) + + self._save_new_notebook('foo.ipynb') + self._save_new_notebook('falco.jpg') + self._save_new_notebook('lombardi.pdf') + self.assertEqual(os.listdir(self.out_dir), ['foo.ipynb']) + + def test_rename_normal(self): + want = self._save_new_notebook('dir/foo.ipynb') + self._rename_file('dir/foo.ipynb', 'dir/bar.ipynb') + + self.assertFalse(os.path.isfile(self.manager.root_dir + '/dir/foo.ipynb')) + with open(self.manager.root_dir + '/dir/bar.ipynb', 'r') as got: + self.assertEqual(json.load(got), want) + + def test_rename_delocalize(self): + self._save_delocalize_config('dir') + want = self._save_new_notebook('dir/foo.ipynb') + self._rename_file('dir/foo.ipynb', 'dir/bar.ipynb') + + self.assertFalse(os.path.isfile(self.out_dir + '/foo.ipynb')) + self.assertFalse(os.path.isfile(self.manager.root_dir + '/foo.ipynb')) + with open(self.out_dir + '/bar.ipynb', 'r') as got: + self.assertEqual(json.load(got), want) + with open(self.manager.root_dir + '/dir/bar.ipynb', 'r') as got: + self.assertEqual(json.load(got), want) + + def test_rename_delocalize_with_pattern(self): + self._save_delocalize_config('dir', config={ + 'destination': self.out_dir, + 'pattern': 'foo' + }) + want = self._save_new_notebook('dir/foo.ipynb') + self._rename_file('dir/foo.ipynb', 'dir/bar.ipynb') + + # The delocalization behavior for foo.ipynb in this case is unspecified; + # currently it won't delete the file, but would be better if it did. + self.assertFalse(os.path.isfile(self.out_dir + '/bar.ipynb')) + self.assertFalse(os.path.isfile(self.manager.root_dir + '/foo.ipynb')) + with open(self.manager.root_dir + '/dir/bar.ipynb', 'r') as got: + self.assertEqual(json.load(got), want) + + def test_delete_normal(self): + self._save_new_notebook('foo.ipynb') + self._delete_file('foo.ipynb') + + self.assertFalse(os.path.isfile(self.manager.root_dir + '/foo.ipynb')) + + def test_delete_delocalize(self): + self._save_delocalize_config('.') + self._save_new_notebook('foo.ipynb') + self._delete_file('foo.ipynb') + + self.assertFalse(os.path.isfile(self.out_dir + '/foo.ipynb')) + self.assertFalse(os.path.isfile(self.manager.root_dir + '/foo.ipynb')) + + def test_delete_delocalize_with_pattern(self): + self._save_delocalize_config('.') + self._save_new_notebook('foo.ipynb') + + self._save_delocalize_config('.', config={ + 'destination': self.out_dir, + 'pattern': 'doesnt match' + }) + self._delete_file('foo.ipynb') + + self.assertTrue(os.path.isfile(self.out_dir + '/foo.ipynb')) + self.assertFalse(os.path.isfile(self.manager.root_dir + '/foo.ipynb')) + + def test_metadata_cache(self): + jupyter_delocalize.METADATA_TTL = timedelta(minutes=5) + # Stub out and advance time manually. + now = datetime.datetime(2018, 3, 20) + self.manager._now = lambda: now + self._save_new_notebook('foo.ipynb') + self.assertEqual(os.listdir(self.out_dir), []) + + # A "not-found" should be cached, only 1 minute passed + now += timedelta(minutes=1) + self._save_delocalize_config('.') + self._save_new_notebook('foo.ipynb') + self.assertEqual(os.listdir(self.out_dir), []) + + # Cache TTL expired, will check again for delocalization config. + now += timedelta(minutes=20) + self._save_new_notebook('foo.ipynb') + self.assertEqual(os.listdir(self.out_dir), ['foo.ipynb']) + +class TestWelderContentsManager(AsyncTestCase): + """WelderContentsManager tests""" + + def setUp(self): + super(TestWelderContentsManager, self).setUp() + self.manager = jupyter_delocalize.WelderContentsManager( + root_dir=tempfile.mkdtemp(), + delete_to_trash=False + ) + self.manager.new(model={'type': 'directory'}, path='dir') + + def tearDown(self): + shutil.rmtree(self.manager.root_dir) + super(TestWelderContentsManager, self).tearDown() + + def _save_new_notebook(self, path): + content = new_notebook() + self.manager.save({ + 'type': 'notebook', + 'content': content, + 'format': 'text' + }, path=path) + return content.dict() + + def _save_new_dir(self, path): + self.manager.save({ + 'type': 'directory', + 'content': '[]', + 'format': 'json' + }, path=path) + + @requests_mock.mock() + def test_save(self, mock_request): + mock_request.post(self.manager.welder_base_url + '/objects') + want = self._save_new_notebook('dir/foo.ipynb') + with open(self.manager.root_dir + '/dir/foo.ipynb', 'r') as got: + self.assertEqual(json.load(got), want) + + @requests_mock.mock() + def test_save_dir(self, mock_request): + self._save_new_dir('dir/foo') + self.assertTrue(os.path.isdir(self.manager.root_dir + '/dir/foo')) + + @requests_mock.mock() + def test_save_scratch_file(self, mock_request): + mock_request.post(self.manager.welder_base_url + '/objects', status_code=412, json={ + 'errorCode': 1 + }) + want = self._save_new_notebook('dir/foo.ipynb') + with open(self.manager.root_dir + '/dir/foo.ipynb', 'r') as got: + self.assertEqual(json.load(got), want) + + @requests_mock.mock() + def test_save_new_file_reverts_on_fail(self, mock_request): + mock_request.post(self.manager.welder_base_url + '/objects', status_code=412, json={ + 'errorCode': 4 + }) + try: + self._save_new_notebook('dir/foo.ipynb') + self.fail('expected error on save') + except IOError: + pass + self.assertFalse(os.path.isfile(self.manager.root_dir + '/dir/foo.ipynb')) + + @requests_mock.mock() + def test_save_reverts_on_fail(self, mock_request): + mock_request.post(self.manager.welder_base_url + '/objects') + content = new_notebook() + self.manager.save({ + 'type': 'notebook', + 'content': content, + 'format': 'text' + }, path='dir/foo.ipynb') + + mock_request.post(self.manager.welder_base_url + '/objects', status_code=412) + updated_content = copy.deepcopy(content) + updated_content['cells'] = [{ + 'cell_type': 'markdown', + 'metadata': {}, + 'source': ['XD'], + }] + try: + self.manager.save({ + 'type': 'notebook', + 'content': updated_content, + 'format': 'text' + }, path='dir/foo.ipynb') + self.fail('expected error on save') + except IOError: + pass + with open(self.manager.root_dir + '/dir/foo.ipynb', 'r') as got: + self.assertEqual(json.load(got)['cells'], content.dict()['cells']) + + def mock_edit_mode_meta(self, mock_request, edit_mode=True): + if not edit_mode: + mock_request.post(self.manager.welder_base_url + '/objects/metadata', status_code=412) + else: + mock_request.post(self.manager.welder_base_url + '/objects/metadata', json={ + 'syncMode': 'EDIT' + }) + + @requests_mock.mock() + def test_delete(self, mock_request): + self.mock_edit_mode_meta(mock_request) + mock_request.post(self.manager.welder_base_url + '/objects') + self._save_new_notebook('dir/foo.ipynb') + + self.manager.delete_file('dir/foo.ipynb') + self.assertFalse(os.path.isfile(self.manager.root_dir + '/dir/foo.ipynb')) + + @requests_mock.mock() + def test_delete_empty_dir(self, mock_request): + self.mock_edit_mode_meta(mock_request) + mock_request.post(self.manager.welder_base_url + '/objects') + self._save_new_dir('dir/foo') + + self.manager.delete_file('dir/foo') + self.assertFalse(os.path.isdir(self.manager.root_dir + '/dir/foo')) + + @requests_mock.mock() + def test_delete_dir_with_notebook(self, mock_request): + self.mock_edit_mode_meta(mock_request) + mock_request.post(self.manager.welder_base_url + '/objects') + self._save_new_dir('dir/foo') + self._save_new_notebook('dir/foo/nb.ipynb') + + try: + self.manager.delete_file('dir/foo') + self.fail('expected error on non-empty edit mode directory deletion') + except NotImplementedError: + pass + self.assertTrue(os.path.isdir(self.manager.root_dir + '/dir/foo')) + + @requests_mock.mock() + def test_delete_scratch_file(self, mock_request): + self.mock_edit_mode_meta(mock_request, edit_mode=False) + mock_request.post(self.manager.welder_base_url + '/objects') + self._save_new_notebook('dir/foo.ipynb') + + self.manager.delete_file('dir/foo.ipynb') + self.assertFalse(os.path.isfile(self.manager.root_dir + '/dir/foo.ipynb')) + + @requests_mock.mock() + def test_delete_scratch_dir(self, mock_request): + self.mock_edit_mode_meta(mock_request, edit_mode=False) + self._save_new_dir('dir/foo') + + self.manager.delete_file('dir/foo') + self.assertFalse(os.path.isdir(self.manager.root_dir + '/dir/foo')) + + @requests_mock.mock() + def test_delete_local_file_survives_welder_error(self, mock_request): + self.mock_edit_mode_meta(mock_request) + mock_request.post(self.manager.welder_base_url + '/objects') + self._save_new_notebook('dir/foo.ipynb') + + mock_request.post(self.manager.welder_base_url + '/objects', status_code=412, json={ + 'errorCode': 4 + }) + try: + self.manager.delete_file('dir/foo.ipynb') + self.fail('expected error on delete') + except IOError: + pass + self.assertTrue(os.path.isfile(self.manager.root_dir + '/dir/foo.ipynb')) + + @requests_mock.mock() + def test_rename(self, mock_request): + post_mock = mock_request.post(self.manager.welder_base_url + '/objects') + self.mock_edit_mode_meta(mock_request) + want = self._save_new_notebook('dir/foo.ipynb') + + # Creating the initial notebook above results in a Welder post. + posts_before_rename = post_mock.call_count + self.manager.rename('dir/foo.ipynb', 'dir/bar.ipynb') + self.assertFalse(os.path.isfile(self.manager.root_dir + '/dir/foo.ipynb')) + with open(self.manager.root_dir + '/dir/bar.ipynb', 'r') as got: + self.assertEqual(json.load(got), want) + + self.assertEqual(post_mock.call_count - posts_before_rename, 2) + + @requests_mock.mock() + def test_rename_empty_dir(self, mock_request): + self.mock_edit_mode_meta(mock_request) + mock_request.post(self.manager.welder_base_url + '/objects') + self._save_new_dir('dir/foo') + + self.manager.rename('dir/foo', 'dir/bar') + self.assertFalse(os.path.isdir(self.manager.root_dir + '/dir/foo')) + self.assertTrue(os.path.isdir(self.manager.root_dir + '/dir/bar')) + + @requests_mock.mock() + def test_rename_dir_with_notebook(self, mock_request): + post_mock = mock_request.post(self.manager.welder_base_url + '/objects') + self.mock_edit_mode_meta(mock_request) + self._save_new_dir('dir/foo') + self._save_new_notebook('dir/foo/nb.ipynb') + + try: + self.manager.rename('dir/foo', 'dir/bar') + self.fail('expected error on non-empty edit mode rename') + except: + pass + self.assertTrue(os.path.isdir(self.manager.root_dir + '/dir/foo')) + self.assertFalse(os.path.isdir(self.manager.root_dir + '/dir/bar')) + + @requests_mock.mock() + def test_rename_scratch_file(self, mock_request): + post_mock = mock_request.post(self.manager.welder_base_url + '/objects') + self.mock_edit_mode_meta(mock_request, edit_mode=False) + want = self._save_new_notebook('dir/foo.ipynb') + + # Creating the initial notebook above results in a Welder post. + posts_before_rename = post_mock.call_count + self.manager.rename('dir/foo.ipynb', 'dir/bar.ipynb') + self.assertFalse(os.path.isfile(self.manager.root_dir + '/dir/foo.ipynb')) + with open(self.manager.root_dir + '/dir/bar.ipynb', 'r') as got: + self.assertEqual(json.load(got), want) + + self.assertEqual(post_mock.call_count - posts_before_rename, 0) + + @requests_mock.mock() + def test_rename_scratch_dir(self, mock_request): + self.mock_edit_mode_meta(mock_request, edit_mode=False) + self._save_new_dir('dir/foo') + + self.manager.rename('dir/foo', 'dir/bar') + self.assertFalse(os.path.isdir(self.manager.root_dir + '/dir/foo')) + self.assertTrue(os.path.isdir(self.manager.root_dir + '/dir/bar')) + + def _delete_req_matcher(self, path): + def m(req): + return req.json()['action'] == 'delete' and req.json()['localPath'] == path + return m + + @requests_mock.mock() + def test_rename_cleanup_on_delete_fail(self, mock_request): + mock_request.post(self.manager.welder_base_url + '/objects') + mock_request.post(self.manager.welder_base_url + '/objects', additional_matcher=self._delete_req_matcher('dir/foo.ipynb'), status_code=500) + mock_request.post(self.manager.welder_base_url + '/objects', additional_matcher=self._delete_req_matcher('dir/bar.ipynb')) + self.mock_edit_mode_meta(mock_request) + want = self._save_new_notebook('dir/foo.ipynb') + + # Creating the initial notebook above results in a Welder post. + try: + self.manager.rename('dir/foo.ipynb', 'dir/bar.ipynb') + self.fail('expected rename exception') + except IOError: + pass + + self.assertFalse(os.path.isfile(self.manager.root_dir + '/dir/bar.ipynb')) + with open(self.manager.root_dir + '/dir/foo.ipynb', 'r') as got: + self.assertEqual(json.load(got), want) + +if __name__ == '__main__': + unittest.main() diff --git a/terra-base/custom/test/test-requirements.txt b/terra-base/custom/test/test-requirements.txt new file mode 100644 index 00000000..e7c6ae7e --- /dev/null +++ b/terra-base/custom/test/test-requirements.txt @@ -0,0 +1,3 @@ +jupyter==1.0.0 +nose==1.3.7 +requests-mock==1.6.0 diff --git a/terra-base/jupyter_notebook_config.py b/terra-base/jupyter_notebook_config.py new file mode 100755 index 00000000..8399bf54 --- /dev/null +++ b/terra-base/jupyter_notebook_config.py @@ -0,0 +1,46 @@ +# adapted from https://github.com/jupyter/docker-stacks/blob/master/base-notebook/jupyter_notebook_config.py +# Note: this file also lives in the Leonardo repo here: +# https://github.com/DataBiosphere/leonardo/blob/develop/http/src/main/resources/jupyter/jupyter_notebook_config.py +# If you change this please keep the other version consistent as well. + +import os + +c = get_config() + +if os.environ.get('JUPYTER_DEBUG_LOGGING') == 'true': + c.Application.log_level = 'DEBUG' + +c.NotebookApp.ip = '0.0.0.0' +c.NotebookApp.port = 8000 +c.NotebookApp.open_browser = False + +c.NotebookApp.token = '' +c.NotebookApp.disable_check_xsrf = True #see https://github.com/nteract/hydrogen/issues/922 +c.NotebookApp.allow_origin = '*' + +c.NotebookApp.terminado_settings={'shell_command': ['bash']} + +if 'GOOGLE_PROJECT' in os.environ and 'CLUSTER_NAME' in os.environ: + fragment = '/' + os.environ['GOOGLE_PROJECT'] + '/' + os.environ['CLUSTER_NAME'] + '/' +else: + fragment = '/' + +c.NotebookApp.base_url = '/notebooks' + fragment + +# This is also specified in run-jupyter.sh +c.NotebookApp.nbserver_extensions = { + 'jupyter_localize_extension': True +} + +c.NotebookApp.kernel_manager_class = 'notebook.services.kernels.kernelmanager.AsyncMappingKernelManager' + +c.KernelSpecManager.ensure_native_kernel = False + +mgr_class = 'DelocalizingContentsManager' +if os.environ.get('WELDER_ENABLED') == 'true': + mgr_class = 'WelderContentsManager' +c.NotebookApp.contents_manager_class = 'jupyter_delocalize.' + mgr_class + +c.NotebookApp.tornado_settings = { + 'static_url_prefix':'/notebooks' + fragment + 'static/' +} diff --git a/terra-base/pyproject.toml b/terra-base/pyproject.toml new file mode 100644 index 00000000..2d5ad515 --- /dev/null +++ b/terra-base/pyproject.toml @@ -0,0 +1,23 @@ +[project] +name = "terra-base" +version = "0.0.1" +description = "Minimal Jupyter environment for Terra notebooks" +readme = "README.md" +requires-python = ">=3.10, <3.11" +dependencies = [ + "jupyter-contrib-nbextensions==0.7.0", + "jupyter-core>=5.8.1", + "jupyter-server==1.24.0", + "jupyterlab==3.4.8", + "jupyterlab-server==2.23.0", + "nbclassic>=1.3.1", + "nbconvert>=7.16.6", + "nbstripout>=0.8.1", + "notebook==6.5.4", + "requests>=2.29.0", + "tornado>=6.5.1", +] + +[tool.uv] +no-build-isolation = true +build-constraint-dependencies = ["setuptools==59.8.0"] # required for firecloud diff --git a/terra-base/scripts/extension/install_jupyter_contrib_nbextensions.sh b/terra-base/scripts/extension/install_jupyter_contrib_nbextensions.sh new file mode 100755 index 00000000..c5c3b1e4 --- /dev/null +++ b/terra-base/scripts/extension/install_jupyter_contrib_nbextensions.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -e +# installs jupyter_contrib_nbextensions https://github.com/ipython-contrib/jupyter_contrib_nbextensions +# also installs the jupyter_nbextensions_configurator https://github.com/Jupyter-contrib/jupyter_nbextensions_configurator +sudo -E -u jupyter /usr/jupytervenv/bin/jupyter nbextensions_configurator enable --user +sudo -E -u jupyter /usr/jupytervenv/bin/jupyter contrib nbextension install --user +sudo -E -u jupyter /usr/jupytervenv/bin/jupyter nbextension enable toc2/main +sudo -E -u jupyter /usr/jupytervenv/bin/jupyter nbextension enable codefolding/main +sudo -E -u jupyter /usr/jupytervenv/bin/jupyter nbextension enable collapsible_headings/main + diff --git a/terra-base/scripts/extension/jupyter_install_combined_extension.sh b/terra-base/scripts/extension/jupyter_install_combined_extension.sh new file mode 100755 index 00000000..27139965 --- /dev/null +++ b/terra-base/scripts/extension/jupyter_install_combined_extension.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +if [ -n "$1" ]; then + JUPYTER_EXTENSION=$1 + JUPYTER_EXTENSION_NAME=`basename ${JUPYTER_EXTENSION%%.*}` + mkdir ${JUPYTER_HOME}/${JUPYTER_EXTENSION_NAME} + tar -xzf ${JUPYTER_EXTENSION} -C${JUPYTER_HOME}/${JUPYTER_EXTENSION_NAME} + pip3 install -e ${JUPYTER_HOME}/${JUPYTER_EXTENSION_NAME} + sudo -E -u jupyter /usr/jupytervenv/bin/jupyter serverextension enable --py ${JUPYTER_EXTENSION_NAME} + sudo -E -u jupyter /usr/jupytervenv/bin/jupyter nbextension install --py ${JUPYTER_EXTENSION_NAME} --user + sudo -E -u jupyter /usr/jupytervenv/bin/jupyter nbextension enable --py ${JUPYTER_EXTENSION_NAME} +fi diff --git a/terra-base/scripts/extension/jupyter_install_lab_extension.sh b/terra-base/scripts/extension/jupyter_install_lab_extension.sh new file mode 100755 index 00000000..0c3c4eaf --- /dev/null +++ b/terra-base/scripts/extension/jupyter_install_lab_extension.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -e + + +if [ -n "$1" ]; then + JUPYTER_EXTENSION=$1 + if [[ ${JUPYTER_EXTENSION} == *'.js' ]]; then + # use jupyterlab extension template to create an extension using the specified JS file + # see https://github.com/jupyterlab/extension-cookiecutter-js + JUPYTER_EXTENSION_NAME=`basename ${JUPYTER_EXTENSION%%.*}` + cookiecutter --no-input https://github.com/jupyterlab/extension-cookiecutter-js extension_name=${JUPYTER_EXTENSION_NAME} + cp -f ${JUPYTER_EXTENSION} ${JUPYTER_EXTENSION_NAME}/lib/plugin.js + cd ${JUPYTER_EXTENSION_NAME} + jlpm + jupyter labextension install . + elif [[ ${JUPYTER_EXTENSION} == *'.ts' ]]; then + # same as above but in typescript, see https://github.com/jupyterlab/extension-cookiecutter-ts + JUPYTER_EXTENSION_NAME=`basename ${JUPYTER_EXTENSION%%.*}` + cookiecutter --no-input https://github.com/jupyterlab/extension-cookiecutter-ts extension_name=${JUPYTER_EXTENSION_NAME} + cp -f ${JUPYTER_EXTENSION} ${JUPYTER_EXTENSION_NAME}/src/index.ts + cd ${JUPYTER_EXTENSION_NAME} + jlpm + jupyter labextension install . + else + jupyter labextension install $JUPYTER_EXTENSION + fi +fi + diff --git a/terra-base/scripts/extension/jupyter_install_notebook_extension.sh b/terra-base/scripts/extension/jupyter_install_notebook_extension.sh new file mode 100755 index 00000000..b3337a41 --- /dev/null +++ b/terra-base/scripts/extension/jupyter_install_notebook_extension.sh @@ -0,0 +1,16 @@ +#!/bin/bash +set -e + +if [ -n "$1" ]; then + JUPYTER_EXTENSION=$1 + JUPYTER_EXTENSION_NAME=`basename ${JUPYTER_EXTENSION%%.*}` + mkdir -p ${JUPYTER_HOME}/${JUPYTER_EXTENSION_NAME} + + if [[ ${JUPYTER_EXTENSION} == *'.tar.gz' ]]; then + tar -xzf ${JUPYTER_EXTENSION} -C${JUPYTER_HOME}/${JUPYTER_EXTENSION_NAME} + else + mv ${JUPYTER_EXTENSION} ${JUPYTER_HOME}/${JUPYTER_EXTENSION_NAME}/main.js + fi + sudo -E -u jupyter /usr/jupytervenv/bin/jupyter nbextension install ${JUPYTER_HOME}/${JUPYTER_EXTENSION_NAME}/ --user + sudo -E -u jupyter /usr/jupytervenv/bin/jupyter nbextension enable ${JUPYTER_EXTENSION_NAME}/main +fi diff --git a/terra-base/scripts/extension/jupyter_install_server_extension.sh b/terra-base/scripts/extension/jupyter_install_server_extension.sh new file mode 100755 index 00000000..88816bb9 --- /dev/null +++ b/terra-base/scripts/extension/jupyter_install_server_extension.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +set -e + +if [ -n "$1" ]; then + JUPYTER_EXTENSION=$1 + JUPYTER_EXTENSION_NAME=`basename ${JUPYTER_EXTENSION%%.*}` + mkdir ${JUPYTER_HOME}/${JUPYTER_EXTENSION_NAME} + tar -xzf ${JUPYTER_EXTENSION} -C${JUPYTER_HOME}/${JUPYTER_EXTENSION_NAME} + pip3 install -e ${JUPYTER_HOME}/${JUPYTER_EXTENSION_NAME} + sudo -E -u jupyter jupyter serverextension enable --py ${JUPYTER_EXTENSION_NAME} +fi diff --git a/terra-base/scripts/extension/jupyter_pip_install_combined_extension.sh b/terra-base/scripts/extension/jupyter_pip_install_combined_extension.sh new file mode 100755 index 00000000..1054d99f --- /dev/null +++ b/terra-base/scripts/extension/jupyter_pip_install_combined_extension.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -e + +if [ -n "$1" ]; then + JUPYTER_EXTENSION=$1 + pip3 install ${JUPYTER_EXTENSION} + sudo -E -u jupyter jupyter serverextension enable --py ${JUPYTER_EXTENSION} + sudo -E -u jupyter jupyter nbextension install --py ${JUPYTER_EXTENSION} --user + sudo -E -u jupyter jupyter nbextension enable --py ${JUPYTER_EXTENSION} +fi diff --git a/terra-base/scripts/extension/jupyter_pip_install_notebook_extension.sh b/terra-base/scripts/extension/jupyter_pip_install_notebook_extension.sh new file mode 100755 index 00000000..0beb5bff --- /dev/null +++ b/terra-base/scripts/extension/jupyter_pip_install_notebook_extension.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e + +if [ -n "$1" ]; then + JUPYTER_EXTENSION=$1 + pip3 install ${JUPYTER_EXTENSION} + sudo -E -u jupyter /usr/jupytervenv/bin/jupyter nbextension install --py ${JUPYTER_EXTENSION} --user + sudo -E -u jupyter /usr/jupytervenv/bin/jupyter nbextension enable --py ${JUPYTER_EXTENSION} +fi diff --git a/terra-base/scripts/extension/jupyter_pip_install_server_extension.sh b/terra-base/scripts/extension/jupyter_pip_install_server_extension.sh new file mode 100755 index 00000000..c643d9f2 --- /dev/null +++ b/terra-base/scripts/extension/jupyter_pip_install_server_extension.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e + +if [ -n "$1" ]; then + JUPYTER_EXTENSION=$1 + pip3 install ${JUPYTER_EXTENSION} + sudo -E -u jupyter jupyter serverextension enable --py ${JUPYTER_EXTENSION} +fi diff --git a/terra-base/scripts/kernel/kernel_bootstrap.sh b/terra-base/scripts/kernel/kernel_bootstrap.sh new file mode 100644 index 00000000..d4811a94 --- /dev/null +++ b/terra-base/scripts/kernel/kernel_bootstrap.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# This script runs at kernel startup time and sets environment variables for the +# workspace name and workspace bucket. +# +# Note: this script is highly dependent on a convention used by Terra and AllOfUs +# applications to place notebooks in the following directory structure: +# +# /home/jupyter///notebook.ipynb +# +# It exploits the fact that the CWD of a launching notebook is named after the workspace. +# If notebooks are ever launched from other directories, this script will break. + +PWD="$(pwd)" +# The workspace name is simply the CWD of the running notebook. +export WORKSPACE_NAME=`basename "$(dirname "$PWD")"` + +# Parse the .delocalize.json file (if it exists) in the workspace directory to obtain the workspace bucket. +DELOCALIZE_FILE="$PWD/.delocalize.json" +if [ -f "$DELOCALIZE_FILE" ]; then + export WORKSPACE_BUCKET="$(dirname "$(cat "$DELOCALIZE_FILE" | jq -r '.destination')")" +fi + +exec "$@" \ No newline at end of file diff --git a/terra-base/scripts/kernel/kernelspec.sh b/terra-base/scripts/kernel/kernelspec.sh new file mode 100644 index 00000000..18ed52ca --- /dev/null +++ b/terra-base/scripts/kernel/kernelspec.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +TMP_KERNELSPEC_DIR=$1 +KERNELSPEC_HOME=$2 + +# Replace the contents of the Python kernel scripts +sed -e 's/${PYTHON_VERSION}/3/g' -e 's|${JUPYTER_HOME}|'${JUPYTER_HOME}'|g' ${TMP_KERNELSPEC_DIR}/python_kernelspec.tmpl > ${KERNELSPEC_HOME}/base-python3.10/kernel.json diff --git a/terra-base/scripts/kernel/python_kernelspec.tmpl b/terra-base/scripts/kernel/python_kernelspec.tmpl new file mode 100644 index 00000000..87630bfc --- /dev/null +++ b/terra-base/scripts/kernel/python_kernelspec.tmpl @@ -0,0 +1,12 @@ +{ + "language": "python", + "display_name": "Python ${PYTHON_VERSION}", + "argv": [ + "${JUPYTER_HOME}/scripts/kernel/kernel_bootstrap.sh", + "python${PYTHON_VERSION}", + "-m", + "ipykernel_launcher", + "-f", + "{connection_file}" + ] +} diff --git a/terra-base/scripts/run-jupyter.sh b/terra-base/scripts/run-jupyter.sh new file mode 100644 index 00000000..76c3f290 --- /dev/null +++ b/terra-base/scripts/run-jupyter.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -e + +# Change the default umask to support R/W access to the shared volume with welder +umask 002 + +NOTEBOOKS_DIR=${HOME}/notebooks + +# Forces python 3 +JUPYTER_BASE="/usr/jupytervenv/bin/python /usr/jupytervenv/bin/jupyter server" +JUPYTER_CMD="$JUPYTER_BASE --NotebookApp.nbserver_extensions=\"{'jupyter_localize_extension':True}\" &> ${NOTEBOOKS_DIR}/jupyter.log" + +echo $JUPYTER_CMD + +eval $JUPYTER_CMD diff --git a/terra-base/tests/smoke_test.py b/terra-base/tests/smoke_test.py new file mode 100644 index 00000000..723b96ae --- /dev/null +++ b/terra-base/tests/smoke_test.py @@ -0,0 +1,32 @@ +"""Brief smoke tests for terra-jupyter-base. + +Minimal tests simply import packages and run 'hello world' code to determine whether any exceptions +are thrown. + +TODO(all): Add additional tests and/or tests with particular assertions, as we encounter Python package version +incompatibilities not currently detected by these tests. +""" + +import os +import pytest +import subprocess + +def test_firecloud(): + import firecloud + +def test_terra_notebook_utils(): + os.system("gcloud auth application-default login") + import terra_notebook_utils + +# Re-add this test if one day base image no longer declares `/home/jupyter` as a VOLUME +# toc2 is also not supporting our version of notebook anymore +def test_extensions(): + output = subprocess.check_output("jupyter nbextension list", shell=True) + if not "toc2/main" in str(output): + pytest.fail("toc not enabled") + + if not "codefolding/main" in str(output): + pytest.fail("codefolding not enabled") + + if not "collapsible_headings/main" in str(output): + pytest.fail("collapsible_headings not enabled") diff --git a/terra-base/uv.lock b/terra-base/uv.lock new file mode 100644 index 00000000..608f786d --- /dev/null +++ b/terra-base/uv.lock @@ -0,0 +1,1198 @@ +version = 1 +revision = 2 +requires-python = "==3.10.*" + +[manifest] +build-constraints = [{ name = "setuptools", specifier = "==59.8.0" }] + +[[package]] +name = "anyio" +version = "3.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "exceptiongroup" }, + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/99/2dfd53fd55ce9838e6ff2d4dac20ce58263798bd1a0dbe18b3a9af3fcfce/anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780", size = 142927, upload-time = "2023-07-05T16:45:02.294Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/24/44299477fe7dcc9cb58d0a57d5a7588d6af2ff403fdd2d47a246c91a3246/anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5", size = 80896, upload-time = "2023-07-05T16:44:59.805Z" }, +] + +[[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 = "23.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi-bindings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/fa/57ec2c6d16ecd2ba0cf15f3c7d1c3c2e7b5fcb83555ff56d7ab10888ec8f/argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08", size = 42798, upload-time = "2023-08-15T14:13:12.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/6a/e8a041599e78b6b3752da48000b14c8d1e8a04ded09c88c714ba047f34f5/argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea", size = 15124, upload-time = "2023-08-15T14:13:10.752Z" }, +] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/e9/184b8ccce6683b0aa2fbb7ba5683ea4b9c5763f1356347f1312c32e3c66e/argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3", size = 1779911, upload-time = "2021-12-01T08:52:55.68Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/13/838ce2620025e9666aa8f686431f67a29052241692a3dd1ae9d3692a89d3/argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367", size = 29658, upload-time = "2021-12-01T09:09:17.016Z" }, + { url = "https://files.pythonhosted.org/packages/b3/02/f7f7bb6b6af6031edb11037639c697b912e1dea2db94d436e681aea2f495/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d", size = 80583, upload-time = "2021-12-01T09:09:19.546Z" }, + { url = "https://files.pythonhosted.org/packages/ec/f7/378254e6dd7ae6f31fe40c8649eea7d4832a42243acaf0f1fff9083b2bed/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae", size = 86168, upload-time = "2021-12-01T09:09:21.445Z" }, + { url = "https://files.pythonhosted.org/packages/74/f6/4a34a37a98311ed73bb80efe422fed95f2ac25a4cacc5ae1d7ae6a144505/argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c", size = 82709, upload-time = "2021-12-01T09:09:18.182Z" }, + { url = "https://files.pythonhosted.org/packages/74/2b/73d767bfdaab25484f7e7901379d5f8793cccbb86c6e0cbc4c1b96f63896/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86", size = 83613, upload-time = "2021-12-01T09:09:22.741Z" }, + { url = "https://files.pythonhosted.org/packages/4f/fd/37f86deef67ff57c76f137a67181949c2d408077e2e3dd70c6c42912c9bf/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f", size = 84583, upload-time = "2021-12-01T09:09:24.177Z" }, + { url = "https://files.pythonhosted.org/packages/6f/52/5a60085a3dae8fded8327a4f564223029f5f54b0cb0455a31131b5363a01/argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e", size = 88475, upload-time = "2021-12-01T09:09:26.673Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/143cd64feb24a15fa4b189a3e1e7efbaeeb00f39a51e99b26fc62fbacabd/argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082", size = 27698, upload-time = "2021-12-01T09:09:27.87Z" }, + { url = "https://files.pythonhosted.org/packages/37/2c/e34e47c7dee97ba6f01a6203e0383e15b60fb85d78ac9a15cd066f6fe28b/argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f", size = 30817, upload-time = "2021-12-01T09:09:30.267Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e4/bf8034d25edaa495da3c8a3405627d2e35758e44ff6eaa7948092646fdcc/argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93", size = 53104, upload-time = "2021-12-01T09:09:31.335Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978, upload-time = "2024-11-30T04:30:14.439Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918, upload-time = "2024-11-30T04:30:10.946Z" }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, +] + +[[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 = "beautifulsoup4" +version = "4.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, +] + +[[package]] +name = "bleach" +version = "6.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "webencodings" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/9a/0e33f5054c54d349ea62c277191c020c2d6ef1d65ab2cb1993f91ec846d1/bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f", size = 203083, upload-time = "2024-10-29T18:30:40.477Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/55/96142937f66150805c25c4d0f31ee4132fd33497753400734f9dfdcbdc66/bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e", size = 163406, upload-time = "2024-10-29T18:30:38.186Z" }, +] + +[package.optional-dependencies] +css = [ + { name = "tinycss2" }, +] + +[[package]] +name = "certifi" +version = "2025.4.26" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705, upload-time = "2025-04-26T02:12:29.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618, upload-time = "2025-04-26T02:12:27.662Z" }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621, upload-time = "2024-09-04T20:45:21.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191, upload-time = "2024-09-04T20:43:30.027Z" }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592, upload-time = "2024-09-04T20:43:32.108Z" }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024, upload-time = "2024-09-04T20:43:34.186Z" }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188, upload-time = "2024-09-04T20:43:36.286Z" }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571, upload-time = "2024-09-04T20:43:38.586Z" }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687, upload-time = "2024-09-04T20:43:40.084Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211, upload-time = "2024-09-04T20:43:41.526Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325, upload-time = "2024-09-04T20:43:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784, upload-time = "2024-09-04T20:43:45.256Z" }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564, upload-time = "2024-09-04T20:43:46.779Z" }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804, upload-time = "2024-09-04T20:43:48.186Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299, upload-time = "2024-09-04T20:43:49.812Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/28/9901804da60055b406e1a1c5ba7aac1276fb77f1dde635aabfc7fd84b8ab/charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", size = 201818, upload-time = "2025-05-02T08:31:46.725Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9b/892a8c8af9110935e5adcbb06d9c6fe741b6bb02608c6513983048ba1a18/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", size = 144649, upload-time = "2025-05-02T08:31:48.889Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a5/4179abd063ff6414223575e008593861d62abfc22455b5d1a44995b7c101/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", size = 155045, upload-time = "2025-05-02T08:31:50.757Z" }, + { url = "https://files.pythonhosted.org/packages/3b/95/bc08c7dfeddd26b4be8c8287b9bb055716f31077c8b0ea1cd09553794665/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", size = 147356, upload-time = "2025-05-02T08:31:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/7a5b635aa65284bf3eab7653e8b4151ab420ecbae918d3e359d1947b4d61/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", size = 149471, upload-time = "2025-05-02T08:31:56.207Z" }, + { url = "https://files.pythonhosted.org/packages/ae/38/51fc6ac74251fd331a8cfdb7ec57beba8c23fd5493f1050f71c87ef77ed0/charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", size = 151317, upload-time = "2025-05-02T08:31:57.613Z" }, + { url = "https://files.pythonhosted.org/packages/b7/17/edee1e32215ee6e9e46c3e482645b46575a44a2d72c7dfd49e49f60ce6bf/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", size = 146368, upload-time = "2025-05-02T08:31:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/26/2c/ea3e66f2b5f21fd00b2825c94cafb8c326ea6240cd80a91eb09e4a285830/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", size = 154491, upload-time = "2025-05-02T08:32:01.219Z" }, + { url = "https://files.pythonhosted.org/packages/52/47/7be7fa972422ad062e909fd62460d45c3ef4c141805b7078dbab15904ff7/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", size = 157695, upload-time = "2025-05-02T08:32:03.045Z" }, + { url = "https://files.pythonhosted.org/packages/2f/42/9f02c194da282b2b340f28e5fb60762de1151387a36842a92b533685c61e/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", size = 154849, upload-time = "2025-05-02T08:32:04.651Z" }, + { url = "https://files.pythonhosted.org/packages/67/44/89cacd6628f31fb0b63201a618049be4be2a7435a31b55b5eb1c3674547a/charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", size = 150091, upload-time = "2025-05-02T08:32:06.719Z" }, + { url = "https://files.pythonhosted.org/packages/1f/79/4b8da9f712bc079c0f16b6d67b099b0b8d808c2292c937f267d816ec5ecc/charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", size = 98445, upload-time = "2025-05-02T08:32:08.66Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d7/96970afb4fb66497a40761cdf7bd4f6fca0fc7bafde3a84f836c1f57a926/charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", size = 105782, upload-time = "2025-05-02T08:32:10.46Z" }, + { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, +] + +[[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.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210, upload-time = "2024-03-12T16:53:41.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180, upload-time = "2024-03-12T16:53:39.226Z" }, +] + +[[package]] +name = "debugpy" +version = "1.8.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/75/087fe07d40f490a78782ff3b0a30e3968936854105487decdb33446d4b0e/debugpy-1.8.14.tar.gz", hash = "sha256:7cd287184318416850aa8b60ac90105837bb1e59531898c07569d197d2ed5322", size = 1641444, upload-time = "2025-04-10T19:46:10.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/df/156df75a41aaebd97cee9d3870fe68f8001b6c1c4ca023e221cfce69bece/debugpy-1.8.14-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:93fee753097e85623cab1c0e6a68c76308cd9f13ffdf44127e6fab4fbf024339", size = 2076510, upload-time = "2025-04-10T19:46:13.315Z" }, + { url = "https://files.pythonhosted.org/packages/69/cd/4fc391607bca0996db5f3658762106e3d2427beaef9bfd363fd370a3c054/debugpy-1.8.14-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d937d93ae4fa51cdc94d3e865f535f185d5f9748efb41d0d49e33bf3365bd79", size = 3559614, upload-time = "2025-04-10T19:46:14.647Z" }, + { url = "https://files.pythonhosted.org/packages/1a/42/4e6d2b9d63e002db79edfd0cb5656f1c403958915e0e73ab3e9220012eec/debugpy-1.8.14-cp310-cp310-win32.whl", hash = "sha256:c442f20577b38cc7a9aafecffe1094f78f07fb8423c3dddb384e6b8f49fd2987", size = 5208588, upload-time = "2025-04-10T19:46:16.233Z" }, + { url = "https://files.pythonhosted.org/packages/97/b1/cc9e4e5faadc9d00df1a64a3c2d5c5f4b9df28196c39ada06361c5141f89/debugpy-1.8.14-cp310-cp310-win_amd64.whl", hash = "sha256:f117dedda6d969c5c9483e23f573b38f4e39412845c7bc487b6f2648df30fe84", size = 5241043, upload-time = "2025-04-10T19:46:17.768Z" }, + { url = "https://files.pythonhosted.org/packages/97/1a/481f33c37ee3ac8040d3d51fc4c4e4e7e61cb08b8bc8971d6032acc2279f/debugpy-1.8.14-py2.py3-none-any.whl", hash = "sha256:5cd9a579d553b6cb9759a7908a41988ee6280b961f24f63336835d9418216a20", size = 5256230, upload-time = "2025-04-10T19:46:54.077Z" }, +] + +[[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 = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "executing" +version = "2.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/50/a9d80c47ff289c611ff12e63f7c5d13942c65d68125160cefd768c73e6e4/executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755", size = 978693, upload-time = "2025-01-22T15:41:29.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/8f/c4d9bafc34ad7ad5d8dc16dd1347ee0e507a52c3adb6bfa8887e1c6a26ba/executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa", size = 26702, upload-time = "2025-01-22T15:41:25.929Z" }, +] + +[[package]] +name = "fastjsonschema" +version = "2.21.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/50/4b769ce1ac4071a1ef6d86b1a3fb56cdc3a37615e8c5519e1af96cdac366/fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4", size = 373939, upload-time = "2024-12-02T10:55:15.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/2b/0817a2b257fe88725c25589d89aec060581aabf668707a8d03b2e9e0cb2a/fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667", size = 23924, upload-time = "2024-12-02T10:55:07.599Z" }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, +] + +[[package]] +name = "ipykernel" +version = "6.29.5" +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/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367, upload-time = "2024-07-01T14:07:22.543Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173, upload-time = "2024-07-01T14:07:19.603Z" }, +] + +[[package]] +name = "ipython" +version = "8.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "exceptiongroup" }, + { 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" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/31/10ac88f3357fc276dc8a64e8880c82e80e7459326ae1d0a211b40abf6665/ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216", size = 5606088, upload-time = "2025-05-31T16:39:09.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/d0/274fbf7b0b12643cbbc001ce13e6a5b1607ac4929d1b11c72460152c9fc3/ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2", size = 831864, upload-time = "2025-05-31T16:39:06.38Z" }, +] + +[[package]] +name = "ipython-genutils" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/69/fbeffffc05236398ebfcfb512b6d2511c622871dca1746361006da310399/ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8", size = 22208, upload-time = "2017-03-13T22:12:26.393Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/bc/9bd3b5c2b4774d5f33b2d544f1460be9df7df2fe42f352135381c347c69a/ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8", size = 26343, upload-time = "2017-03-13T22:12:25.412Z" }, +] + +[[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 = "json5" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/be/c6c745ec4c4539b25a278b70e29793f10382947df0d9efba2fa09120895d/json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a", size = 51907, upload-time = "2025-04-03T16:33:13.201Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/9f/3500910d5a98549e3098807493851eeef2b89cdd3032227558a104dfe926/json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db", size = 36079, upload-time = "2025-04-03T16:33:11.927Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.24.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/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480, upload-time = "2025-05-26T18:48:10.459Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709, upload-time = "2025-05-26T18:48:08.417Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.3" +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/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019, upload-time = "2024-09-17T10:44:17.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105, upload-time = "2024-09-17T10:44:15.218Z" }, +] + +[[package]] +name = "jupyter-contrib-core" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "notebook" }, + { name = "setuptools" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/94/0d37e5b49ea1c8bf204c46f9b0257c1f3319a4ab88acbd401da2cab25e55/jupyter_contrib_core-0.4.2.tar.gz", hash = "sha256:1887212f3ca9d4487d624c0705c20dfdf03d5a0b9ea2557d3aaeeb4c38bdcabb", size = 17490, upload-time = "2022-11-15T16:21:53.357Z" } + +[[package]] +name = "jupyter-contrib-nbextensions" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipython-genutils" }, + { name = "jupyter-contrib-core" }, + { name = "jupyter-core" }, + { name = "jupyter-highlight-selected-word" }, + { name = "jupyter-nbextensions-configurator" }, + { name = "lxml" }, + { name = "nbconvert" }, + { name = "notebook" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/91/78cc4362611dbde2b0cd068204aaf1b8899d0459c50d8ff9daca8c069791/jupyter_contrib_nbextensions-0.7.0.tar.gz", hash = "sha256:06e33f005885eb92f89cbe82711e921278201298d08ab0d886d1ba09e8c3e9ca", size = 23462252, upload-time = "2022-11-15T17:31:27.754Z" } + +[[package]] +name = "jupyter-core" +version = "5.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/1b/72906d554acfeb588332eaaa6f61577705e9ec752ddb486f302dafa292d9/jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941", size = 88923, upload-time = "2025-05-27T07:38:16.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880, upload-time = "2025-05-27T07:38:15.137Z" }, +] + +[[package]] +name = "jupyter-highlight-selected-word" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/a5/3dfeb7c8643ef502e82969fdebb201b63b33ded15a7761b27299bacebc3a/jupyter_highlight_selected_word-0.2.0.tar.gz", hash = "sha256:9fa740424859a807950ca08d2bfd28a35154cd32dd6d50ac4e0950022adc0e7b", size = 12592, upload-time = "2018-04-07T13:56:22.498Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/d7/19ab7cfd60bf268d2abbacc52d4295a40f52d74dfc0d938e4761ee5e598b/jupyter_highlight_selected_word-0.2.0-py2.py3-none-any.whl", hash = "sha256:9545dfa9cb057eebe3a5795604dcd3a5294ea18637e553f61a0b67c1b5903c58", size = 11699, upload-time = "2018-04-07T13:56:20.715Z" }, +] + +[[package]] +name = "jupyter-nbextensions-configurator" +version = "0.6.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-contrib-core" }, + { name = "jupyter-core" }, + { name = "jupyter-server" }, + { name = "notebook" }, + { name = "pyyaml" }, + { name = "tornado" }, + { name = "traitlets" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/fe/cffb14a4fbb43cf276aa3047e42c3f9ecfda851ba3c466295401f6b1e085/jupyter_nbextensions_configurator-0.6.4-py2.py3-none-any.whl", hash = "sha256:fe7a7b0805b5926449692fb077e0e659bab8b27563bc68cba26854532fdf99c7", size = 466890, upload-time = "2024-06-05T16:08:37.236Z" }, +] + +[[package]] +name = "jupyter-server" +version = "1.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "argon2-cffi" }, + { name = "jinja2" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { 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/79/0c/ce06c97c52707bc0fed9461ed5624f1a5ee76dc772d7da2c0699395653af/jupyter_server-1.24.0.tar.gz", hash = "sha256:23368e8e214baf82b313d4c5a0d828ca73015e1a192ce3829bd74e62fab8d046", size = 456590, upload-time = "2023-04-13T19:53:20.991Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/ce/142bcb35ffe215d8880e968689ab733bd7976a6c20dae24b6782cce2219a/jupyter_server-1.24.0-py3-none-any.whl", hash = "sha256:c88ddbe862966ea1aea8c3ccb89a5903abd8fbcfe5cd14090ef549d403332c37", size = 347516, upload-time = "2023-04-13T19:53:18.153Z" }, +] + +[[package]] +name = "jupyterlab" +version = "3.4.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipython" }, + { name = "jinja2" }, + { name = "jupyter-core" }, + { name = "jupyter-server" }, + { name = "jupyterlab-server" }, + { name = "nbclassic" }, + { name = "notebook" }, + { name = "packaging" }, + { name = "tomli" }, + { name = "tornado" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/13/bad942536fdec9dce4d5c32fdb6bb54633800bdf4eb43f677fe0cbe4009a/jupyterlab-3.4.8.tar.gz", hash = "sha256:1fafb8b657005d91603f3c3adfd6d9e8eaf33fdc601537fef09283332efe67cb", size = 17104727, upload-time = "2022-10-04T12:23:41.126Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/67/be3e254f846d5a143edc385bdfd61ee366be70a3223808f30f0b6b3d8f62/jupyterlab-3.4.8-py3-none-any.whl", hash = "sha256:4626a0434c76a3a22f11c4efaa1d031d2586367f72cfdbdbff6b08b6ef0060f7", size = 8782663, upload-time = "2022-10-04T12:23:35.398Z" }, +] + +[[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.23.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/02/65/f7ba44dc74f611b1d00fb37ed1ad0069edeb353a0f0cc7214e3014575b85/jupyterlab_server-2.23.0.tar.gz", hash = "sha256:83c01aa4ad9451cd61b383e634d939ff713850f4640c0056b2cdb2b6211a74c7", size = 71812, upload-time = "2023-06-13T08:46:04.901Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/14/8f1c4b9b80db855d48a762e248efd41649d102841b6bfadbd26b8c25e054/jupyterlab_server-2.23.0-py3-none-any.whl", hash = "sha256:a5ea2c839336a8ba7c38c8e7b2f24cedf919f0d439f4d2e606d9322013a95788", size = 57266, upload-time = "2023-06-13T08:46:02.298Z" }, +] + +[[package]] +name = "lxml" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/e9/9c3ca02fbbb7585116c2e274b354a2d92b5c70561687dd733ec7b2018490/lxml-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:35bc626eec405f745199200ccb5c6b36f202675d204aa29bb52e27ba2b71dea8", size = 8399057, upload-time = "2025-06-26T16:25:02.169Z" }, + { url = "https://files.pythonhosted.org/packages/86/25/10a6e9001191854bf283515020f3633b1b1f96fd1b39aa30bf8fff7aa666/lxml-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:246b40f8a4aec341cbbf52617cad8ab7c888d944bfe12a6abd2b1f6cfb6f6082", size = 4569676, upload-time = "2025-06-26T16:25:05.431Z" }, + { url = "https://files.pythonhosted.org/packages/f5/a5/378033415ff61d9175c81de23e7ad20a3ffb614df4ffc2ffc86bc6746ffd/lxml-6.0.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2793a627e95d119e9f1e19720730472f5543a6d84c50ea33313ce328d870f2dd", size = 5291361, upload-time = "2025-06-26T16:25:07.901Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a6/19c87c4f3b9362b08dc5452a3c3bce528130ac9105fc8fff97ce895ce62e/lxml-6.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:46b9ed911f36bfeb6338e0b482e7fe7c27d362c52fde29f221fddbc9ee2227e7", size = 5008290, upload-time = "2025-06-28T18:47:13.196Z" }, + { url = "https://files.pythonhosted.org/packages/09/d1/e9b7ad4b4164d359c4d87ed8c49cb69b443225cb495777e75be0478da5d5/lxml-6.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b4790b558bee331a933e08883c423f65bbcd07e278f91b2272489e31ab1e2b4", size = 5163192, upload-time = "2025-06-28T18:47:17.279Z" }, + { url = "https://files.pythonhosted.org/packages/56/d6/b3eba234dc1584744b0b374a7f6c26ceee5dc2147369a7e7526e25a72332/lxml-6.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2030956cf4886b10be9a0285c6802e078ec2391e1dd7ff3eb509c2c95a69b76", size = 5076973, upload-time = "2025-06-26T16:25:10.936Z" }, + { url = "https://files.pythonhosted.org/packages/8e/47/897142dd9385dcc1925acec0c4afe14cc16d310ce02c41fcd9010ac5d15d/lxml-6.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d23854ecf381ab1facc8f353dcd9adeddef3652268ee75297c1164c987c11dc", size = 5297795, upload-time = "2025-06-26T16:25:14.282Z" }, + { url = "https://files.pythonhosted.org/packages/fb/db/551ad84515c6f415cea70193a0ff11d70210174dc0563219f4ce711655c6/lxml-6.0.0-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:43fe5af2d590bf4691531b1d9a2495d7aab2090547eaacd224a3afec95706d76", size = 4776547, upload-time = "2025-06-26T16:25:17.123Z" }, + { url = "https://files.pythonhosted.org/packages/e0/14/c4a77ab4f89aaf35037a03c472f1ccc54147191888626079bd05babd6808/lxml-6.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74e748012f8c19b47f7d6321ac929a9a94ee92ef12bc4298c47e8b7219b26541", size = 5124904, upload-time = "2025-06-26T16:25:19.485Z" }, + { url = "https://files.pythonhosted.org/packages/70/b4/12ae6a51b8da106adec6a2e9c60f532350a24ce954622367f39269e509b1/lxml-6.0.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:43cfbb7db02b30ad3926e8fceaef260ba2fb7df787e38fa2df890c1ca7966c3b", size = 4805804, upload-time = "2025-06-26T16:25:21.949Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b6/2e82d34d49f6219cdcb6e3e03837ca5fb8b7f86c2f35106fb8610ac7f5b8/lxml-6.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34190a1ec4f1e84af256495436b2d196529c3f2094f0af80202947567fdbf2e7", size = 5323477, upload-time = "2025-06-26T16:25:24.475Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e6/b83ddc903b05cd08a5723fefd528eee84b0edd07bdf87f6c53a1fda841fd/lxml-6.0.0-cp310-cp310-win32.whl", hash = "sha256:5967fe415b1920a3877a4195e9a2b779249630ee49ece22021c690320ff07452", size = 3613840, upload-time = "2025-06-26T16:25:27.345Z" }, + { url = "https://files.pythonhosted.org/packages/40/af/874fb368dd0c663c030acb92612341005e52e281a102b72a4c96f42942e1/lxml-6.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:f3389924581d9a770c6caa4df4e74b606180869043b9073e2cec324bad6e306e", size = 3993584, upload-time = "2025-06-26T16:25:29.391Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f4/d296bc22c17d5607653008f6dd7b46afdfda12efd31021705b507df652bb/lxml-6.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:522fe7abb41309e9543b0d9b8b434f2b630c5fdaf6482bee642b34c8c70079c8", size = 3681400, upload-time = "2025-06-26T16:25:31.421Z" }, + { url = "https://files.pythonhosted.org/packages/66/e1/2c22a3cff9e16e1d717014a1e6ec2bf671bf56ea8716bb64466fcf820247/lxml-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:dbdd7679a6f4f08152818043dbb39491d1af3332128b3752c3ec5cebc0011a72", size = 3898804, upload-time = "2025-06-26T16:27:59.751Z" }, + { url = "https://files.pythonhosted.org/packages/2b/3a/d68cbcb4393a2a0a867528741fafb7ce92dac5c9f4a1680df98e5e53e8f5/lxml-6.0.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40442e2a4456e9910875ac12951476d36c0870dcb38a68719f8c4686609897c4", size = 4216406, upload-time = "2025-06-28T18:47:45.518Z" }, + { url = "https://files.pythonhosted.org/packages/15/8f/d9bfb13dff715ee3b2a1ec2f4a021347ea3caf9aba93dea0cfe54c01969b/lxml-6.0.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db0efd6bae1c4730b9c863fc4f5f3c0fa3e8f05cae2c44ae141cb9dfc7d091dc", size = 4326455, upload-time = "2025-06-28T18:47:48.411Z" }, + { url = "https://files.pythonhosted.org/packages/01/8b/fde194529ee8a27e6f5966d7eef05fa16f0567e4a8e8abc3b855ef6b3400/lxml-6.0.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ab542c91f5a47aaa58abdd8ea84b498e8e49fe4b883d67800017757a3eb78e8", size = 4268788, upload-time = "2025-06-26T16:28:02.776Z" }, + { url = "https://files.pythonhosted.org/packages/99/a8/3b8e2581b4f8370fc9e8dc343af4abdfadd9b9229970fc71e67bd31c7df1/lxml-6.0.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:013090383863b72c62a702d07678b658fa2567aa58d373d963cca245b017e065", size = 4411394, upload-time = "2025-06-26T16:28:05.179Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a5/899a4719e02ff4383f3f96e5d1878f882f734377f10dfb69e73b5f223e44/lxml-6.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c86df1c9af35d903d2b52d22ea3e66db8058d21dc0f59842ca5deb0595921141", size = 3517946, upload-time = "2025-06-26T16:28:07.665Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/90/d08277ce111dd22f77149fd1a5d4653eeb3b3eaacbdfcbae5afb2600eebd/MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", size = 14357, upload-time = "2024-10-18T15:20:51.44Z" }, + { url = "https://files.pythonhosted.org/packages/04/e1/6e2194baeae0bca1fae6629dc0cbbb968d4d941469cbab11a3872edff374/MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", size = 12393, upload-time = "2024-10-18T15:20:52.426Z" }, + { url = "https://files.pythonhosted.org/packages/1d/69/35fa85a8ece0a437493dc61ce0bb6d459dcba482c34197e3efc829aa357f/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", size = 21732, upload-time = "2024-10-18T15:20:53.578Z" }, + { url = "https://files.pythonhosted.org/packages/22/35/137da042dfb4720b638d2937c38a9c2df83fe32d20e8c8f3185dbfef05f7/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", size = 20866, upload-time = "2024-10-18T15:20:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/29/28/6d029a903727a1b62edb51863232152fd335d602def598dade38996887f0/MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", size = 20964, upload-time = "2024-10-18T15:20:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cd/07438f95f83e8bc028279909d9c9bd39e24149b0d60053a97b2bc4f8aa51/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", size = 21977, upload-time = "2024-10-18T15:20:57.189Z" }, + { url = "https://files.pythonhosted.org/packages/29/01/84b57395b4cc062f9c4c55ce0df7d3108ca32397299d9df00fedd9117d3d/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", size = 21366, upload-time = "2024-10-18T15:20:58.235Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6e/61ebf08d8940553afff20d1fb1ba7294b6f8d279df9fd0c0db911b4bbcfd/MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", size = 21091, upload-time = "2024-10-18T15:20:59.235Z" }, + { url = "https://files.pythonhosted.org/packages/11/23/ffbf53694e8c94ebd1e7e491de185124277964344733c45481f32ede2499/MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50", size = 15065, upload-time = "2024-10-18T15:21:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/44/06/e7175d06dd6e9172d4a69a72592cb3f7a996a9c396eee29082826449bbc3/MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", size = 15514, upload-time = "2024-10-18T15:21:01.122Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159, upload-time = "2024-04-15T13:44:44.803Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899, upload-time = "2024-04-15T13:44:43.265Z" }, +] + +[[package]] +name = "mistune" +version = "3.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/79/bda47f7dd7c3c55770478d6d02c9960c430b0cf1773b72366ff89126ea31/mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0", size = 94347, upload-time = "2025-03-19T14:27:24.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/4d/23c4e4f09da849e127e9f123241946c23c1e30f45a88366879e064211815/mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9", size = 53410, upload-time = "2025-03-19T14:27:23.451Z" }, +] + +[[package]] +name = "nbclassic" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ipykernel" }, + { name = "ipython-genutils" }, + { name = "nest-asyncio" }, + { name = "notebook-shim" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/ae/dea5e0f8f5c519abe52706d23f9dc1a87b4badb9f98beadda16f896f994f/nbclassic-1.3.1.tar.gz", hash = "sha256:4c52da8fc88f9f73ef512cc305091d5ce726bdca19f44ed697cb5ba12dcaad3c", size = 81488343, upload-time = "2025-05-06T16:02:05.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/70/6c5dd85072e7f82272a6dfab3698b5cb3db29949a6b16f268569d27a57a3/nbclassic-1.3.1-py3-none-any.whl", hash = "sha256:96da3b4d7f877b1285e0adc956ea2ea9ea9f70a4ba7b7c03d558f6c9799118fa", size = 26187709, upload-time = "2025-05-06T16:01:54.185Z" }, +] + +[[package]] +name = "nbclient" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/66/7ffd18d58eae90d5721f9f39212327695b749e23ad44b3881744eaf4d9e8/nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193", size = 62424, upload-time = "2024-12-19T10:32:27.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/6d/e7fa07f03a4a7b221d94b4d586edb754a9b0dc3c9e2c93353e9fa4e0d117/nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d", size = 25434, upload-time = "2024-12-19T10:32:24.139Z" }, +] + +[[package]] +name = "nbconvert" +version = "7.16.6" +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/a3/59/f28e15fc47ffb73af68a8d9b47367a8630d76e97ae85ad18271b9db96fdf/nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582", size = 857715, upload-time = "2025-01-28T09:29:14.724Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/9a/cd673b2f773a12c992f41309ef81b99da1690426bd2f96957a7ade0d3ed7/nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b", size = 258525, upload-time = "2025-01-28T09:29:12.551Z" }, +] + +[[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 = "nbstripout" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nbformat" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/92/6e/05d7e0e35598bd0d423167295f978005912a2dcd137c88ebf36e34047dc7/nbstripout-0.8.1.tar.gz", hash = "sha256:eaac8b6b4e729e8dfe1e5df2c0f8ba44abc5a17a65448f0480141f80be230bb1", size = 26399, upload-time = "2024-11-17T10:38:33.275Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/91/93b459c456b0e4389b2b3ddb3b82cd401d022691334a0f06e92c2046e780/nbstripout-0.8.1-py2.py3-none-any.whl", hash = "sha256:79a8c8da488d98c54c112fa87185045f0271a97d84f1d46918d6a3ee561b30e7", size = 16329, upload-time = "2024-11-17T10:38:31.803Z" }, +] + +[[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 = "6.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argon2-cffi" }, + { name = "ipykernel" }, + { name = "ipython-genutils" }, + { name = "jinja2" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbclassic" }, + { name = "nbconvert" }, + { name = "nbformat" }, + { name = "nest-asyncio" }, + { name = "prometheus-client" }, + { name = "pyzmq" }, + { name = "send2trash" }, + { name = "terminado" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/1e/b555b6e33c962a605e2e85b6014f609d3e1c6a5ff48f7c2480376b430d96/notebook-6.5.4.tar.gz", hash = "sha256:517209568bd47261e2def27a140e97d49070602eea0d226a696f42a7f16c9a4e", size = 5785832, upload-time = "2023-04-06T15:08:15.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/21/0e7683e7c4d51b8f6cc5df9bbd33fb2d1e114b9e5dcddeef96ebd8e86348/notebook-6.5.4-py3-none-any.whl", hash = "sha256:dd17e78aefe64c768737b32bf171c1c766666a21cc79a44d37a1700771cab56f", size = 529822, upload-time = "2023-04-06T15:08:11.457Z" }, +] + +[[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 = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[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.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609, upload-time = "2024-04-05T09:43:55.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650, upload-time = "2024-04-05T09:43:53.299Z" }, +] + +[[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.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + +[[package]] +name = "prometheus-client" +version = "0.22.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/5a/3fa1fa7e91a203759aaf316be394f70f2ef598d589b9785a8611b6094c00/prometheus_client-0.22.0.tar.gz", hash = "sha256:18da1d2241ac2d10c8d2110f13eedcd5c7c0c8af18c926e8731f04fc10cd575c", size = 74443, upload-time = "2025-05-16T20:50:18.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/c7/cee159ba3d7192e84a4c166ec1752f44a5fa859ac0eeda2d73a1da65ab47/prometheus_client-0.22.0-py3-none-any.whl", hash = "sha256:c8951bbe64e62b96cd8e8f5d917279d1b9b91ab766793f33d4dce6c228558713", size = 62658, upload-time = "2025-05-16T20:50:16.978Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/6e/9d084c929dfe9e3bfe0c6a47e31f78a25c54627d64a66e884a8bf5474f1c/prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed", size = 428940, upload-time = "2025-04-15T09:18:47.731Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810, upload-time = "2025-04-15T09:18:44.753Z" }, +] + +[[package]] +name = "psutil" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, + { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, + { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, + { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, + { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, +] + +[[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 = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736, upload-time = "2024-03-30T13:22:22.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552, upload-time = "2024-03-30T13:22:20.476Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, +] + +[[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 = "pywin32" +version = "310" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/da/a5f38fffbba2fb99aa4aa905480ac4b8e83ca486659ac8c95bce47fb5276/pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1", size = 8848240, upload-time = "2025-03-17T00:55:46.783Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fe/d873a773324fa565619ba555a82c9dabd677301720f3660a731a5d07e49a/pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d", size = 9601854, upload-time = "2025-03-17T00:55:48.783Z" }, + { url = "https://files.pythonhosted.org/packages/3c/84/1a8e3d7a15490d28a5d816efa229ecb4999cdc51a7c30dd8914f669093b8/pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213", size = 8522963, upload-time = "2025-03-17T00:55:50.969Z" }, +] + +[[package]] +name = "pywinpty" +version = "2.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/7c/917f9c4681bb8d34bfbe0b79d36bbcd902651aeab48790df3d30ba0202fb/pywinpty-2.0.15.tar.gz", hash = "sha256:312cf39153a8736c617d45ce8b6ad6cd2107de121df91c455b10ce6bba7a39b2", size = 29017, upload-time = "2025-02-03T21:53:23.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/b7/855db919ae526d2628f3f2e6c281c4cdff7a9a8af51bb84659a9f07b1861/pywinpty-2.0.15-cp310-cp310-win_amd64.whl", hash = "sha256:8e7f5de756a615a38b96cd86fa3cd65f901ce54ce147a3179c45907fa11b4c4e", size = 1405161, upload-time = "2025-02-03T21:56:25.008Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199, upload-time = "2024-08-06T20:31:40.178Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758, upload-time = "2024-08-06T20:31:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463, upload-time = "2024-08-06T20:31:44.263Z" }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280, upload-time = "2024-08-06T20:31:50.199Z" }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239, upload-time = "2024-08-06T20:31:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802, upload-time = "2024-08-06T20:31:53.836Z" }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527, upload-time = "2024-08-06T20:31:55.565Z" }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052, upload-time = "2024-08-06T20:31:56.914Z" }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774, upload-time = "2024-08-06T20:31:58.304Z" }, +] + +[[package]] +name = "pyzmq" +version = "26.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/11/b9213d25230ac18a71b39b3723494e57adebe36e066397b961657b3b41c1/pyzmq-26.4.0.tar.gz", hash = "sha256:4bd13f85f80962f91a651a7356fe0472791a5f7a92f227822b5acf44795c626d", size = 278293, upload-time = "2025-04-04T12:05:44.049Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/b8/af1d814ffc3ff9730f9a970cbf216b6f078e5d251a25ef5201d7bc32a37c/pyzmq-26.4.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:0329bdf83e170ac133f44a233fc651f6ed66ef8e66693b5af7d54f45d1ef5918", size = 1339238, upload-time = "2025-04-04T12:03:07.022Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e4/5aafed4886c264f2ea6064601ad39c5fc4e9b6539c6ebe598a859832eeee/pyzmq-26.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:398a825d2dea96227cf6460ce0a174cf7657d6f6827807d4d1ae9d0f9ae64315", size = 672848, upload-time = "2025-04-04T12:03:08.591Z" }, + { url = "https://files.pythonhosted.org/packages/79/39/026bf49c721cb42f1ef3ae0ee3d348212a7621d2adb739ba97599b6e4d50/pyzmq-26.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d52d62edc96787f5c1dfa6c6ccff9b581cfae5a70d94ec4c8da157656c73b5b", size = 911299, upload-time = "2025-04-04T12:03:10Z" }, + { url = "https://files.pythonhosted.org/packages/03/23/b41f936a9403b8f92325c823c0f264c6102a0687a99c820f1aaeb99c1def/pyzmq-26.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1410c3a3705db68d11eb2424d75894d41cff2f64d948ffe245dd97a9debfebf4", size = 867920, upload-time = "2025-04-04T12:03:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/c1/3e/2de5928cdadc2105e7c8f890cc5f404136b41ce5b6eae5902167f1d5641c/pyzmq-26.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7dacb06a9c83b007cc01e8e5277f94c95c453c5851aac5e83efe93e72226353f", size = 862514, upload-time = "2025-04-04T12:03:13.013Z" }, + { url = "https://files.pythonhosted.org/packages/ce/57/109569514dd32e05a61d4382bc88980c95bfd2f02e58fea47ec0ccd96de1/pyzmq-26.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6bab961c8c9b3a4dc94d26e9b2cdf84de9918931d01d6ff38c721a83ab3c0ef5", size = 1204494, upload-time = "2025-04-04T12:03:14.795Z" }, + { url = "https://files.pythonhosted.org/packages/aa/02/dc51068ff2ca70350d1151833643a598625feac7b632372d229ceb4de3e1/pyzmq-26.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a5c09413b924d96af2aa8b57e76b9b0058284d60e2fc3730ce0f979031d162a", size = 1514525, upload-time = "2025-04-04T12:03:16.246Z" }, + { url = "https://files.pythonhosted.org/packages/48/2a/a7d81873fff0645eb60afaec2b7c78a85a377af8f1d911aff045d8955bc7/pyzmq-26.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7d489ac234d38e57f458fdbd12a996bfe990ac028feaf6f3c1e81ff766513d3b", size = 1414659, upload-time = "2025-04-04T12:03:17.652Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ea/813af9c42ae21845c1ccfe495bd29c067622a621e85d7cda6bc437de8101/pyzmq-26.4.0-cp310-cp310-win32.whl", hash = "sha256:dea1c8db78fb1b4b7dc9f8e213d0af3fc8ecd2c51a1d5a3ca1cde1bda034a980", size = 580348, upload-time = "2025-04-04T12:03:19.384Z" }, + { url = "https://files.pythonhosted.org/packages/20/68/318666a89a565252c81d3fed7f3b4c54bd80fd55c6095988dfa2cd04a62b/pyzmq-26.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:fa59e1f5a224b5e04dc6c101d7186058efa68288c2d714aa12d27603ae93318b", size = 643838, upload-time = "2025-04-04T12:03:20.795Z" }, + { url = "https://files.pythonhosted.org/packages/91/f8/fb1a15b5f4ecd3e588bfde40c17d32ed84b735195b5c7d1d7ce88301a16f/pyzmq-26.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:a651fe2f447672f4a815e22e74630b6b1ec3a1ab670c95e5e5e28dcd4e69bbb5", size = 559565, upload-time = "2025-04-04T12:03:22.676Z" }, + { url = "https://files.pythonhosted.org/packages/47/03/96004704a84095f493be8d2b476641f5c967b269390173f85488a53c1c13/pyzmq-26.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:98d948288ce893a2edc5ec3c438fe8de2daa5bbbd6e2e865ec5f966e237084ba", size = 834408, upload-time = "2025-04-04T12:05:04.569Z" }, + { url = "https://files.pythonhosted.org/packages/e4/7f/68d8f3034a20505db7551cb2260248be28ca66d537a1ac9a257913d778e4/pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9f34f5c9e0203ece706a1003f1492a56c06c0632d86cb77bcfe77b56aacf27b", size = 569580, upload-time = "2025-04-04T12:05:06.283Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a6/2b0d6801ec33f2b2a19dd8d02e0a1e8701000fec72926e6787363567d30c/pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80c9b48aef586ff8b698359ce22f9508937c799cc1d2c9c2f7c95996f2300c94", size = 798250, upload-time = "2025-04-04T12:05:07.88Z" }, + { url = "https://files.pythonhosted.org/packages/96/2a/0322b3437de977dcac8a755d6d7ce6ec5238de78e2e2d9353730b297cf12/pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3f2a5b74009fd50b53b26f65daff23e9853e79aa86e0aa08a53a7628d92d44a", size = 756758, upload-time = "2025-04-04T12:05:09.483Z" }, + { url = "https://files.pythonhosted.org/packages/c2/33/43704f066369416d65549ccee366cc19153911bec0154da7c6b41fca7e78/pyzmq-26.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:61c5f93d7622d84cb3092d7f6398ffc77654c346545313a3737e266fc11a3beb", size = 555371, upload-time = "2025-04-04T12:05:11.062Z" }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" }, +] + +[[package]] +name = "requests" +version = "2.29.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/d2/70fc708727b62d55bc24e43cc85f073039023212d482553d853c44e57bdb/requests-2.29.0.tar.gz", hash = "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059", size = 108279, upload-time = "2023-04-26T15:24:34.435Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/e1/2aa539876d9ed0ddc95882451deb57cfd7aa8dbf0b8dbce68e045549ba56/requests-2.29.0-py3-none-any.whl", hash = "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b", size = 62499, upload-time = "2023-04-26T15:24:31.555Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/a6/60184b7fc00dd3ca80ac635dd5b8577d444c57e8e8742cecabfacb829921/rpds_py-0.25.1.tar.gz", hash = "sha256:8960b6dac09b62dac26e75d7e2c4a22efb835d827a7278c34f72b2b84fa160e3", size = 27304, upload-time = "2025-05-21T12:46:12.502Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/09/e1158988e50905b7f8306487a576b52d32aa9a87f79f7ab24ee8db8b6c05/rpds_py-0.25.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:f4ad628b5174d5315761b67f212774a32f5bad5e61396d38108bd801c0a8f5d9", size = 373140, upload-time = "2025-05-21T12:42:38.834Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4b/a284321fb3c45c02fc74187171504702b2934bfe16abab89713eedfe672e/rpds_py-0.25.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c742af695f7525e559c16f1562cf2323db0e3f0fbdcabdf6865b095256b2d40", size = 358860, upload-time = "2025-05-21T12:42:41.394Z" }, + { url = "https://files.pythonhosted.org/packages/4e/46/8ac9811150c75edeae9fc6fa0e70376c19bc80f8e1f7716981433905912b/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:605ffe7769e24b1800b4d024d24034405d9404f0bc2f55b6db3362cd34145a6f", size = 386179, upload-time = "2025-05-21T12:42:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ec/87eb42d83e859bce91dcf763eb9f2ab117142a49c9c3d17285440edb5b69/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ccc6f3ddef93243538be76f8e47045b4aad7a66a212cd3a0f23e34469473d36b", size = 400282, upload-time = "2025-05-21T12:42:44.92Z" }, + { url = "https://files.pythonhosted.org/packages/68/c8/2a38e0707d7919c8c78e1d582ab15cf1255b380bcb086ca265b73ed6db23/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f70316f760174ca04492b5ab01be631a8ae30cadab1d1081035136ba12738cfa", size = 521824, upload-time = "2025-05-21T12:42:46.856Z" }, + { url = "https://files.pythonhosted.org/packages/5e/2c/6a92790243569784dde84d144bfd12bd45102f4a1c897d76375076d730ab/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1dafef8df605fdb46edcc0bf1573dea0d6d7b01ba87f85cd04dc855b2b4479e", size = 411644, upload-time = "2025-05-21T12:42:48.838Z" }, + { url = "https://files.pythonhosted.org/packages/eb/76/66b523ffc84cf47db56efe13ae7cf368dee2bacdec9d89b9baca5e2e6301/rpds_py-0.25.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0701942049095741a8aeb298a31b203e735d1c61f4423511d2b1a41dcd8a16da", size = 386955, upload-time = "2025-05-21T12:42:50.835Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b9/a362d7522feaa24dc2b79847c6175daa1c642817f4a19dcd5c91d3e2c316/rpds_py-0.25.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e87798852ae0b37c88babb7f7bbbb3e3fecc562a1c340195b44c7e24d403e380", size = 421039, upload-time = "2025-05-21T12:42:52.348Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c4/b5b6f70b4d719b6584716889fd3413102acf9729540ee76708d56a76fa97/rpds_py-0.25.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3bcce0edc1488906c2d4c75c94c70a0417e83920dd4c88fec1078c94843a6ce9", size = 563290, upload-time = "2025-05-21T12:42:54.404Z" }, + { url = "https://files.pythonhosted.org/packages/87/a3/2e6e816615c12a8f8662c9d8583a12eb54c52557521ef218cbe3095a8afa/rpds_py-0.25.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e2f6a2347d3440ae789505693a02836383426249d5293541cd712e07e7aecf54", size = 592089, upload-time = "2025-05-21T12:42:55.976Z" }, + { url = "https://files.pythonhosted.org/packages/c0/08/9b8e1050e36ce266135994e2c7ec06e1841f1c64da739daeb8afe9cb77a4/rpds_py-0.25.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4fd52d3455a0aa997734f3835cbc4c9f32571345143960e7d7ebfe7b5fbfa3b2", size = 558400, upload-time = "2025-05-21T12:42:58.032Z" }, + { url = "https://files.pythonhosted.org/packages/f2/df/b40b8215560b8584baccd839ff5c1056f3c57120d79ac41bd26df196da7e/rpds_py-0.25.1-cp310-cp310-win32.whl", hash = "sha256:3f0b1798cae2bbbc9b9db44ee068c556d4737911ad53a4e5093d09d04b3bbc24", size = 219741, upload-time = "2025-05-21T12:42:59.479Z" }, + { url = "https://files.pythonhosted.org/packages/10/99/e4c58be18cf5d8b40b8acb4122bc895486230b08f978831b16a3916bd24d/rpds_py-0.25.1-cp310-cp310-win_amd64.whl", hash = "sha256:3ebd879ab996537fc510a2be58c59915b5dd63bccb06d1ef514fee787e05984a", size = 231553, upload-time = "2025-05-21T12:43:01.425Z" }, + { url = "https://files.pythonhosted.org/packages/78/ff/566ce53529b12b4f10c0a348d316bd766970b7060b4fd50f888be3b3b281/rpds_py-0.25.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b24bf3cd93d5b6ecfbedec73b15f143596c88ee249fa98cefa9a9dc9d92c6f28", size = 373931, upload-time = "2025-05-21T12:45:05.01Z" }, + { url = "https://files.pythonhosted.org/packages/83/5d/deba18503f7c7878e26aa696e97f051175788e19d5336b3b0e76d3ef9256/rpds_py-0.25.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:0eb90e94f43e5085623932b68840b6f379f26db7b5c2e6bcef3179bd83c9330f", size = 359074, upload-time = "2025-05-21T12:45:06.714Z" }, + { url = "https://files.pythonhosted.org/packages/0d/74/313415c5627644eb114df49c56a27edba4d40cfd7c92bd90212b3604ca84/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d50e4864498a9ab639d6d8854b25e80642bd362ff104312d9770b05d66e5fb13", size = 387255, upload-time = "2025-05-21T12:45:08.669Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c8/c723298ed6338963d94e05c0f12793acc9b91d04ed7c4ba7508e534b7385/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7c9409b47ba0650544b0bb3c188243b83654dfe55dcc173a86832314e1a6a35d", size = 400714, upload-time = "2025-05-21T12:45:10.39Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/51f1f6aa653c2e110ed482ef2ae94140d56c910378752a1b483af11019ee/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:796ad874c89127c91970652a4ee8b00d56368b7e00d3477f4415fe78164c8000", size = 523105, upload-time = "2025-05-21T12:45:12.273Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a4/7873d15c088ad3bff36910b29ceb0f178e4b3232c2adbe9198de68a41e63/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:85608eb70a659bf4c1142b2781083d4b7c0c4e2c90eff11856a9754e965b2540", size = 411499, upload-time = "2025-05-21T12:45:13.95Z" }, + { url = "https://files.pythonhosted.org/packages/90/f3/0ce1437befe1410766d11d08239333ac1b2d940f8a64234ce48a7714669c/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4feb9211d15d9160bc85fa72fed46432cdc143eb9cf6d5ca377335a921ac37b", size = 387918, upload-time = "2025-05-21T12:45:15.649Z" }, + { url = "https://files.pythonhosted.org/packages/94/d4/5551247988b2a3566afb8a9dba3f1d4a3eea47793fd83000276c1a6c726e/rpds_py-0.25.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ccfa689b9246c48947d31dd9d8b16d89a0ecc8e0e26ea5253068efb6c542b76e", size = 421705, upload-time = "2025-05-21T12:45:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/b0/25/5960f28f847bf736cc7ee3c545a7e1d2f3b5edaf82c96fb616c2f5ed52d0/rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3c5b317ecbd8226887994852e85de562f7177add602514d4ac40f87de3ae45a8", size = 564489, upload-time = "2025-05-21T12:45:19.466Z" }, + { url = "https://files.pythonhosted.org/packages/02/66/1c99884a0d44e8c2904d3c4ec302f995292d5dde892c3bf7685ac1930146/rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:454601988aab2c6e8fd49e7634c65476b2b919647626208e376afcd22019eeb8", size = 592557, upload-time = "2025-05-21T12:45:21.362Z" }, + { url = "https://files.pythonhosted.org/packages/55/ae/4aeac84ebeffeac14abb05b3bb1d2f728d00adb55d3fb7b51c9fa772e760/rpds_py-0.25.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:1c0c434a53714358532d13539272db75a5ed9df75a4a090a753ac7173ec14e11", size = 558691, upload-time = "2025-05-21T12:45:23.084Z" }, + { url = "https://files.pythonhosted.org/packages/41/b3/728a08ff6f5e06fe3bb9af2e770e9d5fd20141af45cff8dfc62da4b2d0b3/rpds_py-0.25.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f73ce1512e04fbe2bc97836e89830d6b4314c171587a99688082d090f934d20a", size = 231651, upload-time = "2025-05-21T12:45:24.72Z" }, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/3a/aec9b02217bb79b87bbc1a21bc6abc51e3d5dcf65c30487ac96c0908c722/Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf", size = 17394, upload-time = "2024-04-07T00:01:09.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/b0/4562db6223154aa4e22f939003cb92514c79f3d4dccca3444253fd17f902/Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9", size = 18072, upload-time = "2024-04-07T00:01:07.438Z" }, +] + +[[package]] +name = "setuptools" +version = "80.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, +] + +[[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.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, +] + +[[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 = "terra-base" +version = "0.0.1" +source = { virtual = "." } +dependencies = [ + { name = "jupyter-contrib-nbextensions" }, + { name = "jupyter-core" }, + { name = "jupyter-server" }, + { name = "jupyterlab" }, + { name = "jupyterlab-server" }, + { name = "nbclassic" }, + { name = "nbconvert" }, + { name = "nbstripout" }, + { name = "notebook" }, + { name = "requests" }, + { name = "tornado" }, +] + +[package.metadata] +requires-dist = [ + { name = "jupyter-contrib-nbextensions", specifier = "==0.7.0" }, + { name = "jupyter-core", specifier = ">=5.8.1" }, + { name = "jupyter-server", specifier = "==1.24.0" }, + { name = "jupyterlab", specifier = "==3.4.8" }, + { name = "jupyterlab-server", specifier = "==2.23.0" }, + { name = "nbclassic", specifier = ">=1.3.1" }, + { name = "nbconvert", specifier = ">=7.16.6" }, + { name = "nbstripout", specifier = ">=0.8.1" }, + { name = "notebook", specifier = "==6.5.4" }, + { name = "requests", specifier = ">=2.29.0" }, + { name = "tornado", specifier = ">=6.5.1" }, +] + +[[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 = "tomli" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" }, +] + +[[package]] +name = "tornado" +version = "6.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/89/c72771c81d25d53fe33e3dca61c233b665b2780f21820ba6fd2c6793c12b/tornado-6.5.1.tar.gz", hash = "sha256:84ceece391e8eb9b2b95578db65e920d2a61070260594819589609ba9bc6308c", size = 509934, upload-time = "2025-05-22T18:15:38.788Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/89/f4532dee6843c9e0ebc4e28d4be04c67f54f60813e4bf73d595fe7567452/tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7", size = 441948, upload-time = "2025-05-22T18:15:20.862Z" }, + { url = "https://files.pythonhosted.org/packages/15/9a/557406b62cffa395d18772e0cdcf03bed2fff03b374677348eef9f6a3792/tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6", size = 440112, upload-time = "2025-05-22T18:15:22.591Z" }, + { url = "https://files.pythonhosted.org/packages/55/82/7721b7319013a3cf881f4dffa4f60ceff07b31b394e459984e7a36dc99ec/tornado-6.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b77e9dfa7ed69754a54c89d82ef746398be82f749df69c4d3abe75c4d1ff4888", size = 443672, upload-time = "2025-05-22T18:15:24.027Z" }, + { url = "https://files.pythonhosted.org/packages/7d/42/d11c4376e7d101171b94e03cef0cbce43e823ed6567ceda571f54cf6e3ce/tornado-6.5.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253b76040ee3bab8bcf7ba9feb136436a3787208717a1fb9f2c16b744fba7331", size = 443019, upload-time = "2025-05-22T18:15:25.735Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f7/0c48ba992d875521ac761e6e04b0a1750f8150ae42ea26df1852d6a98942/tornado-6.5.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308473f4cc5a76227157cdf904de33ac268af770b2c5f05ca6c1161d82fdd95e", size = 443252, upload-time = "2025-05-22T18:15:27.499Z" }, + { url = "https://files.pythonhosted.org/packages/89/46/d8d7413d11987e316df4ad42e16023cd62666a3c0dfa1518ffa30b8df06c/tornado-6.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:caec6314ce8a81cf69bd89909f4b633b9f523834dc1a352021775d45e51d9401", size = 443930, upload-time = "2025-05-22T18:15:29.299Z" }, + { url = "https://files.pythonhosted.org/packages/78/b2/f8049221c96a06df89bed68260e8ca94beca5ea532ffc63b1175ad31f9cc/tornado-6.5.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:13ce6e3396c24e2808774741331638ee6c2f50b114b97a55c5b442df65fd9692", size = 443351, upload-time = "2025-05-22T18:15:31.038Z" }, + { url = "https://files.pythonhosted.org/packages/76/ff/6a0079e65b326cc222a54720a748e04a4db246870c4da54ece4577bfa702/tornado-6.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5cae6145f4cdf5ab24744526cc0f55a17d76f02c98f4cff9daa08ae9a217448a", size = 443328, upload-time = "2025-05-22T18:15:32.426Z" }, + { url = "https://files.pythonhosted.org/packages/49/18/e3f902a1d21f14035b5bc6246a8c0f51e0eef562ace3a2cea403c1fb7021/tornado-6.5.1-cp39-abi3-win32.whl", hash = "sha256:e0a36e1bc684dca10b1aa75a31df8bdfed656831489bc1e6a6ebed05dc1ec365", size = 444396, upload-time = "2025-05-22T18:15:34.205Z" }, + { url = "https://files.pythonhosted.org/packages/7b/09/6526e32bf1049ee7de3bebba81572673b19a2a8541f795d887e92af1a8bc/tornado-6.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:908e7d64567cecd4c2b458075589a775063453aeb1d2a1853eedb806922f568b", size = 444840, upload-time = "2025-05-22T18:15:36.1Z" }, + { url = "https://files.pythonhosted.org/packages/55/a7/535c44c7bea4578e48281d83c615219f3ab19e6abc67625ef637c73987be/tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7", size = 443596, upload-time = "2025-05-22T18:15:37.433Z" }, +] + +[[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 = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "urllib3" +version = "1.26.19" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/93/65e479b023bbc46dab3e092bda6b0005424ea3217d711964ccdede3f9b1b/urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429", size = 306068, upload-time = "2024-06-17T14:53:34.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/6a/99eaaeae8becaa17a29aeb334a18e5d582d873b6f084c11f02581b8d7f7f/urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3", size = 143933, upload-time = "2024-06-17T14:53:31.589Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301, upload-time = "2024-01-06T02:10:57.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, +] + +[[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.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, +] diff --git a/terra-jupyter-gatk/Dockerfile b/terra-jupyter-gatk/Dockerfile index 3e72e08b..99daf4e8 100644 --- a/terra-jupyter-gatk/Dockerfile +++ b/terra-jupyter-gatk/Dockerfile @@ -90,7 +90,7 @@ RUN pip3 install --upgrade markupsafe==2.1.2 COPY cnn-models.patch /etc/gatk-$GATK_VERSION/cnn-models.patch -RUN patch -u /opt/conda/lib/python3.10/site-packages/vqsr_cnn/vqsr_cnn/models.py -i /etc/gatk-$GATK_VERSION/cnn-models.patch +RUN patch -u /home/jupyter/.envs/base-python3.10/site-packages/vqsr_cnn/vqsr_cnn/models.py -i /etc/gatk-$GATK_VERSION/cnn-models.patch # Newer version of nbconvert fail to convert notebooks to html RUN pip3 install --ignore-installed python-dateutil==2.8.2 \ diff --git a/terra-jupyter-hail/Dockerfile b/terra-jupyter-hail/Dockerfile index c64ef516..b1e12810 100644 --- a/terra-jupyter-hail/Dockerfile +++ b/terra-jupyter-hail/Dockerfile @@ -10,7 +10,7 @@ ENV PYSPARK_PYTHON=python3 ENV HAIL_VERSION=0.2.130.post1 RUN find $JUPYTER_HOME/scripts -name '*.sh' -type f | xargs chmod +x \ - && $JUPYTER_HOME/scripts/kernel/kernelspec.sh $JUPYTER_HOME/scripts/kernel /opt/conda/share/jupyter/kernels \ + && $JUPYTER_HOME/scripts/kernel/kernelspec.sh $JUPYTER_HOME/scripts/kernel /usr/local/share/jupyter/kernels \ # Note Spark and Hadoop are mounted from the outside Dataproc VM. # Make empty conf dirs for the update-alternatives commands. && mkdir -p /etc/spark/conf.dist && mkdir -p /etc/hadoop/conf.empty && mkdir -p /etc/hive/conf.dist \ diff --git a/terra-jupyter-python/Dockerfile b/terra-jupyter-python/Dockerfile index 8648b975..1e8780a1 100644 --- a/terra-jupyter-python/Dockerfile +++ b/terra-jupyter-python/Dockerfile @@ -1,4 +1,7 @@ -FROM us.gcr.io/broad-dsp-gcr-public/terra-jupyter-base:1.1.4 +FROM us.gcr.io/broad-dsp-gcr-public/terra-base:0.0.1 + +COPY --from=gcr.io/deeplearning-platform-release/tf2-cu123.2-17.py310 +# FROM gcr.io/deeplearning-platform-release/tf2-cu123.2-17.py310 USER root # This makes it so pip runs as root, not the user. @@ -34,11 +37,11 @@ COPY requirements.txt /etc/terra-docker/ RUN pip3 -V \ && pip3 install --upgrade pip \ && pip3 install --upgrade -r /etc/terra-docker/requirements.txt \ - && sed -i 's/pandas.lib/pandas/g' /opt/conda/lib/python3.10/site-packages/ggplot/stats/smoothers.py \ + && sed -i 's/pandas.lib/pandas/g' /home/jupyter/.envs/base-python3.10/site-packages/ggplot/stats/smoothers.py \ # the next few `sed` lines are workaround for a ggplot bug. See https://github.com/yhat/ggpy/issues/662 - && sed -i 's/pandas.tslib.Timestamp/pandas.Timestamp/g' /opt/conda/lib/python3.10/site-packages/ggplot/stats/smoothers.py \ - && sed -i 's/pd.tslib.Timestamp/pd.Timestamp/g' /opt/conda/lib/python3.10/site-packages/ggplot/stats/smoothers.py \ - && sed -i 's/pd.tslib.Timestamp/pd.Timestamp/g' /opt/conda/lib/python3.10/site-packages/ggplot/utils.py + && sed -i 's/pandas.tslib.Timestamp/pandas.Timestamp/g' /home/jupyter/.envs/base-python3.10/site-packages/ggplot/stats/smoothers.py \ + && sed -i 's/pd.tslib.Timestamp/pd.Timestamp/g' /home/jupyter/.envs/base-python3.10/site-packages/ggplot/stats/smoothers.py \ + && sed -i 's/pd.tslib.Timestamp/pd.Timestamp/g'/home/jupyter/.envs/base-python3.10/site-packages/ggplot/utils.py RUN pip3 install --upgrade markupsafe==2.1.2 @@ -59,4 +62,4 @@ ENV TF_ENABLE_ONEDNN_OPTS=1 # # When Leonardo deploys this image onto a cluster, the entrypoint is overwritten to enable # # additional setup inside the container before execution. Jupyter execution occurs when the # # init-actions.sh script uses 'docker exec' to call run-jupyter.sh. -ENTRYPOINT ["/opt/conda/bin/jupyter", "notebook"] +ENTRYPOINT ["/usr/jupytervenv/bin/jupyter", "notebook"] diff --git a/terra-jupyter-r/Dockerfile b/terra-jupyter-r/Dockerfile index 264446f1..0f4f81c0 100644 --- a/terra-jupyter-r/Dockerfile +++ b/terra-jupyter-r/Dockerfile @@ -1,4 +1,6 @@ -FROM us.gcr.io/broad-dsp-gcr-public/terra-jupyter-base:1.1.4 +FROM us.gcr.io/broad-dsp-gcr-public/terra-base:0.0.1 + +# FROM gcr.io/deeplearning-platform-release/tf2-cu123.2-17.py310 USER root @@ -23,7 +25,7 @@ RUN cd /tmp \ # Add R kernel RUN find $JUPYTER_HOME/scripts -name '*.sh' -type f | xargs chmod +x \ - && $JUPYTER_HOME/scripts/kernel/kernelspec.sh $JUPYTER_HOME/scripts/kernel /opt/conda/share/jupyter/kernels + && $JUPYTER_HOME/scripts/kernel/kernelspec.sh $JUPYTER_HOME/scripts/kernel usr/local/share/jupyter/kernels # https://cran.r-project.org/bin/linux/ubuntu/README.html RUN apt-get update \ From ce16809345ce8f26356b6e0819c77331c9f69b98 Mon Sep 17 00:00:00 2001 From: lmcnatt <85642387+lucymcnatt@users.noreply.github.com> Date: Tue, 9 Sep 2025 10:48:00 -0400 Subject: [PATCH 2/2] progress --- gce-init.sh | 642 +++++ terra-base/Dockerfile | 208 +- terra-base/DockerfileAzure | 199 ++ terra-base/DockerfileGCP | 178 ++ terra-base/poetry.lock | 2225 +++++++++++++++++ .../terra-docker-versions-dev.json | 86 + .../terra-docker-versions-8-14.json | 86 + .../terra-docker-versions-new.json | 86 + .../terra-docker-versions.json | 86 + terra-docker-versions-dev.json | 86 + terra-jupyter-bioconductor/Dockerfile | 21 + terra-jupyter-hail-1.1.14-versions.json | 505 ++++ 12 files changed, 4302 insertions(+), 106 deletions(-) create mode 100644 gce-init.sh create mode 100644 terra-base/DockerfileAzure create mode 100644 terra-base/DockerfileGCP create mode 100644 terra-base/poetry.lock create mode 100644 terra-docker-image-documentation-dev/terra-docker-versions-dev.json create mode 100644 terra-docker-image-documentation/terra-docker-versions-8-14.json create mode 100644 terra-docker-image-documentation/terra-docker-versions-new.json create mode 100644 terra-docker-image-documentation/terra-docker-versions.json create mode 100644 terra-docker-versions-dev.json create mode 100644 terra-jupyter-hail-1.1.14-versions.json diff --git a/gce-init.sh b/gce-init.sh new file mode 100644 index 00000000..50196db5 --- /dev/null +++ b/gce-init.sh @@ -0,0 +1,642 @@ +#!/usr/bin/env bash + +# Borrowed from init-action.sh as our GCE offering came after the dataproc cluster one. +# This init script instantiates the tool (e.g. Jupyter) docker images on Google Compute Engine instances created by Leo. + +set -e -x + +# Set variables +# Values like $(..) are populated by Leo when a cluster is created. +# See https://github.com/DataBiosphere/leonardo/blob/e46acfcb409b11198b1f12533cefea3f6c7fdafb/http/src/main/scala/org/broadinstitute/dsde/workbench/leonardo/util/RuntimeTemplateValues.scala#L192 +# Avoid exporting variables unless they are needed by external scripts or docker-compose files. +export CLOUD_SERVICE='GCE' +export CLUSTER_NAME=$(clusterName) +export RUNTIME_NAME=$(clusterName) +export GOOGLE_PROJECT=$(googleProject) +export STAGING_BUCKET=$(stagingBucketName) +export OWNER_EMAIL=$(loginHint) +export PET_SA_EMAIL=$(petSaEmail) +export JUPYTER_SERVER_NAME=$(jupyterServerName) +export JUPYTER_DOCKER_IMAGE=$(jupyterDockerImage) +export WELDER_SERVER_NAME=$(welderServerName) +export WELDER_DOCKER_IMAGE=$(welderDockerImage) +export RSTUDIO_SERVER_NAME=$(rstudioServerName) +export RSTUDIO_DOCKER_IMAGE=$(rstudioDockerImage) +export RSTUDIO_USER_HOME=/home/rstudio +export PROXY_SERVER_NAME=$(proxyServerName) +export PROXY_DOCKER_IMAGE=$(proxyDockerImage) +export CRYPTO_DETECTOR_SERVER_NAME=$(cryptoDetectorServerName) +export CRYPTO_DETECTOR_DOCKER_IMAGE=$(cryptoDetectorDockerImage) +export MEM_LIMIT=$(memLimit) +export SHM_SIZE=$(shmSize) +export WELDER_MEM_LIMIT=$(welderMemLimit) +export PROXY_SERVER_HOST_NAME=$(proxyServerHostName) +export WELDER_ENABLED=$(welderEnabled) +export NOTEBOOKS_DIR=$(notebooksDir) + +START_USER_SCRIPT_URI=$(startUserScriptUri) +# Include a timestamp suffix to differentiate different startup logs across restarts. +START_USER_SCRIPT_OUTPUT_URI=$(startUserScriptOutputUri) +IS_GCE_FORMATTED=$(isGceFormatted) +# Needs to be in sync with terra-docker container +JUPYTER_HOME=/etc/jupyter +JUPYTER_SCRIPTS=$JUPYTER_HOME/scripts +JUPYTER_USER_HOME=$(jupyterHomeDirectory) +RSTUDIO_SCRIPTS=/etc/rstudio/scripts +SERVER_CRT=$(proxyServerCrt) +SERVER_KEY=$(proxyServerKey) +ROOT_CA=$(rootCaPem) +JUPYTER_DOCKER_COMPOSE_GCE=$(jupyterDockerCompose) +RSTUDIO_DOCKER_COMPOSE=$(rstudioDockerCompose) +PROXY_DOCKER_COMPOSE=$(proxyDockerCompose) +WELDER_DOCKER_COMPOSE=$(welderDockerCompose) +GPU_DOCKER_COMPOSE=$(gpuDockerCompose) +PROXY_SITE_CONF=$(proxySiteConf) +JUPYTER_SERVER_EXTENSIONS=$(jupyterServerExtensions) +JUPYTER_NB_EXTENSIONS=$(jupyterNbExtensions) +JUPYTER_COMBINED_EXTENSIONS=$(jupyterCombinedExtensions) +JUPYTER_LAB_EXTENSIONS=$(jupyterLabExtensions) +USER_SCRIPT_URI=$(userScriptUri) +USER_SCRIPT_OUTPUT_URI=$(userScriptOutputUri) +JUPYTER_NOTEBOOK_FRONTEND_CONFIG_URI=$(jupyterNotebookFrontendConfigUri) +CUSTOM_ENV_VARS_CONFIG_URI=$(customEnvVarsConfigUri) +GPU_ENABLED=$(gpuEnabled) +INIT_BUCKET_NAME=$(initBucketName) + +CERT_DIRECTORY='/var/certs' +DOCKER_COMPOSE_FILES_DIRECTORY='/var/docker-compose-files' +WORK_DIRECTORY='/mnt/disks/work' +# Toolbox is specific to COS images and is needed to access functionalities like gcloud +# See https://cloud.google.com/container-optimized-os/docs/how-to/toolbox +GSUTIL_CMD='docker run --rm -v /var:/var us.gcr.io/cos-cloud/toolbox:v20230714 gsutil' +GCLOUD_CMD='docker run --rm -v /var:/var us.gcr.io/cos-cloud/toolbox:v20230714 gcloud' + +# Welder configuration, Rstudio files are saved every X seconds in the background but Jupyter notebooks are not +if [ ! -z "$RSTUDIO_DOCKER_IMAGE" ] ; then + export SHOULD_BACKGROUND_SYNC="true" +else + export SHOULD_BACKGROUND_SYNC="false" +fi + +##################################################################################################### +# Functions +##################################################################################################### + +# Retry a command up to a specific number of times until it exits successfully, +# with exponential back off. For example: +# +# $ retry 5 echo "Hello" +# Hello +# +# $ retry 5 false +# Retry 1/5 exited 1, retrying in 2 seconds... +# Retry 2/5 exited 1, retrying in 4 seconds... +# Retry 3/5 exited 1, retrying in 8 seconds... +# Retry 4/5 exited 1, retrying in 16 seconds... +# Retry 5/5 exited 1, no more retries left. +function retry { + local retries=$1 + shift + + for ((i = 1; i <= $retries; i++)); do + # run with an 'or' so set -e doesn't abort the bash script on errors + exit=0 + "$@" || exit=$? + if [ $exit -eq 0 ]; then + return 0 + fi + wait=$((2 ** $i)) + if [ $i -eq $retries ]; then + log "Retry $i/$retries exited $exit, no more retries left." + break + fi + log "Retry $i/$retries exited $exit, retrying in $wait seconds..." + sleep $wait + done + return 1 +} + +function log() { + echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')]: $@" +} + +display_time() { + local T=$1 + local D=$((T/60/60/24)) + local H=$((T/60/60%24)) + local M=$((T/60%60)) + local S=$((T%60)) + (( $D > 0 )) && printf '%d days ' $D + (( $H > 0 )) && printf '%d hours ' $H + (( $M > 0 )) && printf '%d minutes ' $M + (( $D > 0 || $H > 0 || $M > 0 )) && printf 'and ' + printf '%d seconds\n' $S +} + +function apply_user_script() { + # User script to be executed once at creation time, but will not persist when the runtime is paused / resumed + local CONTAINER_NAME=$1 + local TARGET_DIR=$2 + + log "Running user script $USER_SCRIPT_URI in $CONTAINER_NAME container..." + USER_SCRIPT=`basename ${USER_SCRIPT_URI}` + if [[ "$USER_SCRIPT_URI" == 'gs://'* ]]; then + $GSUTIL_CMD cp ${USER_SCRIPT_URI} /var &> /var/user_script_copy_output.txt + else + curl "${USER_SCRIPT_URI}" -o /var/"${USER_SCRIPT}" + fi + docker cp /var/"${USER_SCRIPT}" ${CONTAINER_NAME}:${TARGET_DIR}/"${USER_SCRIPT}" + # Note that we are running as root + retry 3 docker exec -u root ${CONTAINER_NAME} chmod +x ${TARGET_DIR}/"${USER_SCRIPT}" + + # Execute the user script as privileged to allow for deeper customization of VM behavior, e.g. installing + # network egress throttling. As docker is not a security layer, it is assumed that a determined attacker + # can gain full access to the VM already, so using this flag is not a significant escalation. + EXIT_CODE=0 + docker exec --privileged -u root -e PIP_USER=false ${CONTAINER_NAME} ${TARGET_DIR}/"${USER_SCRIPT}" &> /var/us_output.txt || EXIT_CODE=$? + + # Should dump error in staging bucket so we can display that back as part of the error message + if [ $EXIT_CODE -ne 0 ]; then + log "User script failed with exit code $EXIT_CODE. Output is saved to $USER_SCRIPT_OUTPUT_URI." + retry 3 $GSUTIL_CMD -h "x-goog-meta-passed":"false" cp /var/us_output.txt ${USER_SCRIPT_OUTPUT_URI} + exit $EXIT_CODE + else + retry 3 $GSUTIL_CMD -h "x-goog-meta-passed":"true" cp /var/us_output.txt ${USER_SCRIPT_OUTPUT_URI} + fi +} + +function apply_start_user_script() { + # User script to be executed at each startup time so the changes will persist between pause/resume cycles. + # Only used by the AOU Workbench, not Terra yet. + # See https://broadworkbench.atlassian.net/browse/IA-5054 + local CONTAINER_NAME=$1 + local TARGET_DIR=$2 + + log "Running start user script $START_USER_SCRIPT_URI in $CONTAINER_NAME container..." + START_USER_SCRIPT=`basename ${START_USER_SCRIPT_URI}` + if [[ "$START_USER_SCRIPT_URI" == 'gs://'* ]]; then + $GSUTIL_CMD cp ${START_USER_SCRIPT_URI} /var + else + curl $START_USER_SCRIPT_URI -o /var/${START_USER_SCRIPT} + fi + docker cp /var/${START_USER_SCRIPT} ${CONTAINER_NAME}:${TARGET_DIR}/${START_USER_SCRIPT} + retry 3 docker exec -u root ${CONTAINER_NAME} chmod +x ${TARGET_DIR}/${START_USER_SCRIPT} + + # Keep in sync with startup.sh + EXIT_CODE=0 + docker exec --privileged -u root -e PIP_USER=false ${CONTAINER_NAME} ${TARGET_DIR}/${START_USER_SCRIPT} &> /var/start_output.txt || EXIT_CODE=$? + if [ $EXIT_CODE -ne 0 ]; then + echo "User start script failed with exit code ${EXIT_CODE}. Output is saved to ${START_USER_SCRIPT_OUTPUT_URI}" + retry 3 $GSUTIL_CMD -h "x-goog-meta-passed":"false" cp /var/start_output.txt ${START_USER_SCRIPT_OUTPUT_URI} + exit $EXIT_CODE + else + retry 3 $GSUTIL_CMD -h "x-goog-meta-passed":"true" cp /var/start_output.txt ${START_USER_SCRIPT_OUTPUT_URI} + fi +} + +##################################################################################################### +# Main starts here. +##################################################################################################### + +log "Running GCE VM init script..." + +# Array for instrumentation +# UPDATE THIS IF YOU ADD MORE STEPS: +# currently the steps are: +# START init, +# .. after persistent disk setup +# .. after copying files from the GCS init bucket +# .. after starting google-fluentd +# .. after docker compose +# .. after welder start +# .. after extension install +# .. after user script +# .. after start user script +# .. after start Jupyter +# END + +## Used for profiling +START_TIME=$(date +%s) +STEP_TIMINGS=($(date +%s)) + + +DOCKER_COMPOSE="docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /var:/var docker/compose:1.29.2" + +mkdir -p ${WORK_DIRECTORY} +mkdir -p ${CERT_DIRECTORY} +mkdir -p ${DOCKER_COMPOSE_FILES_DIRECTORY} + +log 'Formatting and mounting persistent disk...' + +# Format and mount persistent disk +## The PD should be the only `sd` disk that is not mounted yet +AllsdDisks=($(lsblk --nodeps --noheadings --output NAME --paths | grep -i "sd")) +FreesdDisks=() +for Disk in "${AllsdDisks[@]}"; do + Mounts="$(lsblk -no MOUNTPOINT "${Disk}")" + if [ -z "$Mounts" ]; then + echo "Found our unmounted persistent disk!" + FreesdDisks="${Disk}" + else + echo "Not our persistent disk!" + fi +done +DISK_DEVICE_ID=${FreesdDisks} + +## Only format disk is it hasn't already been formatted +if [ "$IS_GCE_FORMATTED" == "false" ] ; then + # It's likely that the persistent disk was previously mounted on another VM and wasn't properly unmounted + # either because the VM was terminated or there is no unmount in the shutdown sequence and occasionally + # fs is getting marked as not clean. + # Passing -F -F to mkfs.ext4 should force the tool to ignore the state of the partition. + # Note that there should be two instances command-line switch (-F -F) to override this check + + mkfs.ext4 -m 0 -E lazy_itable_init=0,lazy_journal_init=0,discard ${DISK_DEVICE_ID} -F -F +fi + +mount -t ext4 -O discard,defaults ${DISK_DEVICE_ID} ${WORK_DIRECTORY} + +# done persistent disk setup +STEP_TIMINGS+=($(date +%s)) + +# Enable GPU drivers on top of the base Google DeepLearning default image +if [ "${GPU_ENABLED}" == "true" ] ; then + log 'Installing GPU driver...' + version="535.154.05" + isAvailable=$(cos-extensions list|grep $version) + if [[ -z "$isAvailable" ]]; then + # Install default version on the COS image + cos-extensions install gpu + else + cos-extensions install gpu -- --version $version + fi + mount --bind /var/lib/nvidia /var/lib/nvidia + mount -o remount,exec /var/lib/nvidia + + $GSUTIL_CMD cp ${GPU_DOCKER_COMPOSE} ${DOCKER_COMPOSE_FILES_DIRECTORY} +fi + +log 'Copying secrets from GCS...' + +# Add the certificates from the bucket to the VM. They are used by the docker-compose file +$GSUTIL_CMD cp ${SERVER_CRT} ${CERT_DIRECTORY} +$GSUTIL_CMD cp ${SERVER_KEY} ${CERT_DIRECTORY} +$GSUTIL_CMD cp ${ROOT_CA} ${CERT_DIRECTORY} +$GSUTIL_CMD cp gs://${INIT_BUCKET_NAME}/* ${DOCKER_COMPOSE_FILES_DIRECTORY} + + +# Install env var config (e.g. AOU / Terra use it to inject workspace name) +# e.g. { + # "WORKSPACE_NAME": "CARJune24", + # "WORKSPACE_NAMESPACE": "callisto-dev", + # "WORKSPACE_BUCKET": "gs://fc-09516ff0-136e-4874-8484-1be0afa267a6", + # "GOOGLE_PROJECT": "terra-dev-e67d9572", + # "CUSTOM_IMAGE": "false", + # "DRS_RESOLVER_ENDPOINT": "api/v4/drs/resolve", + # "TERRA_DEPLOYMENT_ENV": "dev" + #} +if [ ! -z "$CUSTOM_ENV_VARS_CONFIG_URI" ] ; then + log 'Copy custom env vars config...' + $GSUTIL_CMD cp ${CUSTOM_ENV_VARS_CONFIG_URI} /var +fi + +# done GCS copy +STEP_TIMINGS+=($(date +%s)) + +log 'Starting up the Jupyter...' + +# Run docker-compose for each specified compose file. +# Note the `docker-compose pull` is retried to avoid intermittent network errors, but +# `docker-compose up` is not retried since if that fails, something is probably broken +# and wouldn't be remedied by retrying +COMPOSE_FILES=(-f ${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${PROXY_DOCKER_COMPOSE}`) +cat ${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${PROXY_DOCKER_COMPOSE}` +if [ ! -z "$WELDER_DOCKER_IMAGE" ] && [ "$WELDER_ENABLED" == "true" ] ; then + COMPOSE_FILES+=(-f ${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${WELDER_DOCKER_COMPOSE}`) + cat ${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${WELDER_DOCKER_COMPOSE}` +fi + +if [ "${GPU_ENABLED}" == "true" ] ; then + COMPOSE_FILES+=(-f ${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${GPU_DOCKER_COMPOSE}`) + if [ ! -z "$RSTUDIO_DOCKER_IMAGE" ] ; then + # Little bit of hack to switch the jupyter paths to the rstudio ones. Should have separate docker gpu compose of rJupyter and Rstudio instead + sed -i 's/jupyter/rstudio/g' ${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${GPU_DOCKER_COMPOSE}` + sed -i 's#${NOTEBOOKS_DIR}#/home/rstudio#g' ${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${GPU_DOCKER_COMPOSE}` + fi + cat ${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${GPU_DOCKER_COMPOSE}` +fi + +if [ ! -z "$JUPYTER_DOCKER_IMAGE" ] ; then + TOOL_SERVER_NAME=${JUPYTER_SERVER_NAME} + COMPOSE_FILES+=(-f ${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${JUPYTER_DOCKER_COMPOSE_GCE}`) + cat ${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${JUPYTER_DOCKER_COMPOSE_GCE}` +fi + +if [ ! -z "$RSTUDIO_DOCKER_IMAGE" ] ; then + TOOL_SERVER_NAME=${RSTUDIO_SERVER_NAME} + COMPOSE_FILES+=(-f ${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${RSTUDIO_DOCKER_COMPOSE}`) + cat ${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${RSTUDIO_DOCKER_COMPOSE}` +fi + +tee /var/variables.env << END +CERT_DIRECTORY=${CERT_DIRECTORY} +WORK_DIRECTORY=${WORK_DIRECTORY} +PROXY_SERVER_NAME=${PROXY_SERVER_NAME} +PROXY_DOCKER_IMAGE=${PROXY_DOCKER_IMAGE} +GOOGLE_PROJECT=${GOOGLE_PROJECT} +RUNTIME_NAME=${RUNTIME_NAME} +PROXY_SERVER_HOST_NAME=${PROXY_SERVER_HOST_NAME} +JUPYTER_SERVER_NAME=${JUPYTER_SERVER_NAME} +JUPYTER_DOCKER_IMAGE=${JUPYTER_DOCKER_IMAGE} +NOTEBOOKS_DIR=${NOTEBOOKS_DIR} +OWNER_EMAIL=${OWNER_EMAIL} +PET_SA_EMAIL=${PET_SA_EMAIL} +WELDER_ENABLED=${WELDER_ENABLED} +MEM_LIMIT=${MEM_LIMIT} +SHM_SIZE=${SHM_SIZE} +WELDER_SERVER_NAME=${WELDER_SERVER_NAME} +WELDER_DOCKER_IMAGE=${WELDER_DOCKER_IMAGE} +STAGING_BUCKET=${STAGING_BUCKET} +WELDER_MEM_LIMIT=${WELDER_MEM_LIMIT} +JUPYTER_SCRIPTS=${JUPYTER_SCRIPTS} +HOST_PROXY_SITE_CONF_FILE_PATH=${DOCKER_COMPOSE_FILES_DIRECTORY}/`basename ${PROXY_SITE_CONF}` +DOCKER_COMPOSE_FILES_DIRECTORY=${DOCKER_COMPOSE_FILES_DIRECTORY} +RSTUDIO_SERVER_NAME=${RSTUDIO_SERVER_NAME} +RSTUDIO_DOCKER_IMAGE=${RSTUDIO_DOCKER_IMAGE} +SHOULD_BACKGROUND_SYNC=${SHOULD_BACKGROUND_SYNC} +RSTUDIO_USER_HOME=${RSTUDIO_USER_HOME} +END + +# Create a network that allows containers to talk to each other via exposed ports +docker network create -d bridge app_network + +# Dumps the rendered yaml to the init script log. +${DOCKER_COMPOSE} --env-file=/var/variables.env "${COMPOSE_FILES[@]}" config + +# Docker Pull +log 'Pulling docker images...' +if ! retry 5 ${DOCKER_COMPOSE} --env-file=/var/variables.env "${COMPOSE_FILES[@]}" pull &> /var/docker_pull_output.txt; then + # if coming from a private repo on GCR, need to use credentials supplied in cryptopants/docker-compose-gcr + # (see https://hub.docker.com/r/cryptopants/docker-compose-gcr) + log 'Docker pull failed. Private image, trying with cryptopants/docker-compose-gcr...' + DOCKER_COMPOSE="docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v /var:/var -w=/var cryptopants/docker-compose-gcr" + retry 5 ${DOCKER_COMPOSE} --env-file=/var/variables.env "${COMPOSE_FILES[@]}" pull &> /var/docker_pull_output.txt; +fi + +# This needs to happen before we start up containers because the jupyter user needs to be the owner of the PD +chmod a+rwx ${WORK_DIRECTORY} + +# Docker compose up, starting all of the containers +${DOCKER_COMPOSE} --env-file=/var/variables.env "${COMPOSE_FILES[@]}" up -d + +# Start up crypto detector, if enabled. +# This should be started after other containers. +# Use `docker run` instead of docker-compose so we can link it to the Jupyter/RStudio container's network. +# See https://github.com/broadinstitute/terra-cryptomining-security-alerts/tree/master/v2 +if [ ! -z "$CRYPTO_DETECTOR_DOCKER_IMAGE" ] ; then + docker run --name=${CRYPTO_DETECTOR_SERVER_NAME} --rm -d \ + --net=container:${TOOL_SERVER_NAME} ${CRYPTO_DETECTOR_DOCKER_IMAGE} +fi + +# done welder start +STEP_TIMINGS+=($(date +%s)) + +# Jupyter-specific setup, only do if Jupyter is installed +if [ ! -z "$JUPYTER_DOCKER_IMAGE" ] ; then + # user package installation directory + mkdir -p ${WORK_DIRECTORY}/packages + chmod a+rwx ${WORK_DIRECTORY}/packages + + # Install everything after having mounted the empty PD + # This should not be needed anymore if the jupyter home is a directory of the PD mount point + # See: https://github.com/DataBiosphere/leonardo/pull/4465/files + if [ ! "$JUPYTER_USER_HOME" = "/home/jupyter" ] ; then + # TODO: Remove once we stop supporting non AI notebooks based images + log 'Installing Jupyter kernelspecs...(Remove once we stop supporting non AI notebooks based images)' + KERNELSPEC_HOME=/usr/local/share/jupyter/kernels + + # Install kernelspecs inside the Jupyter container + retry 3 docker exec -u root ${JUPYTER_SERVER_NAME} ${JUPYTER_SCRIPTS}/kernel/kernelspec.sh ${JUPYTER_SCRIPTS}/kernel ${KERNELSPEC_HOME} + fi + + # Install notebook.json which is used to populate Jupyter.notebook.config in JavaScript extensions. + # This is used in the edit-mode.js extension that Terra/AoU use. + if [ ! -z "$JUPYTER_NOTEBOOK_FRONTEND_CONFIG_URI" ] ; then + log 'Copy Jupyter frontend notebook config...' + $GSUTIL_CMD cp ${JUPYTER_NOTEBOOK_FRONTEND_CONFIG_URI} /var + JUPYTER_NOTEBOOK_FRONTEND_CONFIG=`basename ${JUPYTER_NOTEBOOK_FRONTEND_CONFIG_URI}` + retry 3 docker exec -u root ${JUPYTER_SERVER_NAME} /bin/bash -c "mkdir -p $JUPYTER_HOME/nbconfig" + docker cp /var/${JUPYTER_NOTEBOOK_FRONTEND_CONFIG} ${JUPYTER_SERVER_NAME}:${JUPYTER_HOME}/nbconfig/ + fi + + # Install NbExtensions. These are user-specified Jupyter extensions. + # For instance Terra UI is passing + # { + # "nbExtensions": { + # "saturn-iframe-extension": "https://bvdp-saturn-dev.appspot.com/jupyter-iframe-extension.js" + # }, + # "labExtensions": {}, + # "serverExtensions": {}, + # "combinedExtensions": {} +# } + if [ ! -z "$JUPYTER_NB_EXTENSIONS" ] ; then + for ext in ${JUPYTER_NB_EXTENSIONS} + do + log "Installing Jupyter NB extension [$ext]..." + if [[ $ext == 'gs://'* ]]; then + $GSUTIL_CMD cp $ext /var + JUPYTER_EXTENSION_ARCHIVE=`basename $ext` + docker cp /var/${JUPYTER_EXTENSION_ARCHIVE} ${JUPYTER_SERVER_NAME}:${JUPYTER_HOME}/${JUPYTER_EXTENSION_ARCHIVE} + retry 3 docker exec -u root -e PIP_USER=false ${JUPYTER_SERVER_NAME} ${JUPYTER_SCRIPTS}/extension/jupyter_install_notebook_extension.sh ${JUPYTER_HOME}/${JUPYTER_EXTENSION_ARCHIVE} + elif [[ $ext == 'http://'* || $ext == 'https://'* ]]; then + JUPYTER_EXTENSION_FILE=`basename $ext` + curl $ext -o /var/${JUPYTER_EXTENSION_FILE} + docker cp /var/${JUPYTER_EXTENSION_FILE} ${JUPYTER_SERVER_NAME}:${JUPYTER_HOME}/${JUPYTER_EXTENSION_FILE} + retry 3 docker exec -u root -e PIP_USER=false ${JUPYTER_SERVER_NAME} ${JUPYTER_SCRIPTS}/extension/jupyter_install_notebook_extension.sh ${JUPYTER_HOME}/${JUPYTER_EXTENSION_FILE} + else + retry 3 docker exec -u root -e PIP_USER=false ${JUPYTER_SERVER_NAME} ${JUPYTER_SCRIPTS}/extension/jupyter_pip_install_notebook_extension.sh $ext + fi + done + fi + + # Install serverExtensions if provided by the user + if [ ! -z "$JUPYTER_SERVER_EXTENSIONS" ] ; then + for ext in ${JUPYTER_SERVER_EXTENSIONS} + do + log "Installing Jupyter server extension [$ext]..." + if [[ $ext == 'gs://'* ]]; then + $GSUTIL_CMD cp $ext /var + JUPYTER_EXTENSION_ARCHIVE=`basename $ext` + docker cp /var/${JUPYTER_EXTENSION_ARCHIVE} ${JUPYTER_SERVER_NAME}:${JUPYTER_HOME}/${JUPYTER_EXTENSION_ARCHIVE} + retry 3 docker exec -u root -e PIP_USER=false ${JUPYTER_SERVER_NAME} ${JUPYTER_SCRIPTS}/extension/jupyter_install_server_extension.sh ${JUPYTER_HOME}/${JUPYTER_EXTENSION_ARCHIVE} + else + retry 3 docker exec -u root -e PIP_USER=false ${JUPYTER_SERVER_NAME} ${JUPYTER_SCRIPTS}/extension/jupyter_pip_install_server_extension.sh $ext + fi + done + fi + + # Install combined extensions if provided by the user + if [ ! -z "$JUPYTER_COMBINED_EXTENSIONS" ] ; then + for ext in ${JUPYTER_COMBINED_EXTENSIONS} + do + log "Installing Jupyter combined extension [$ext]..." + log $ext + if [[ $ext == 'gs://'* ]]; then + $GSUTIL_CMD cp $ext /var + JUPYTER_EXTENSION_ARCHIVE=`basename $ext` + docker cp /var/${JUPYTER_EXTENSION_ARCHIVE} ${JUPYTER_SERVER_NAME}:${JUPYTER_HOME}/${JUPYTER_EXTENSION_ARCHIVE} + retry 3 docker exec -u root -e PIP_USER=false ${JUPYTER_SERVER_NAME} ${JUPYTER_SCRIPTS}/extension/jupyter_install_combined_extension.sh ${JUPYTER_EXTENSION_ARCHIVE} + else + retry 3 docker exec -u root -e PIP_USER=false ${JUPYTER_SERVER_NAME} ${JUPYTER_SCRIPTS}/extension/jupyter_pip_install_combined_extension.sh $ext + fi + done + fi + + # Install lab extensions if provided by the user + # Note: lab extensions need to installed as jupyter user, not root + if [ ! -z "$JUPYTER_LAB_EXTENSIONS" ] ; then + for ext in ${JUPYTER_LAB_EXTENSIONS} + do + log "Installing JupyterLab extension [$ext]..." + pwd + if [[ $ext == 'gs://'* ]]; then + $GSUTIL_CMD cp -r $ext /var + JUPYTER_EXTENSION_ARCHIVE=`basename $ext` + docker cp /var/${JUPYTER_EXTENSION_ARCHIVE} ${JUPYTER_SERVER_NAME}:${JUPYTER_HOME}/${JUPYTER_EXTENSION_ARCHIVE} + retry 3 docker exec -e PIP_USER=false ${JUPYTER_SERVER_NAME} ${JUPYTER_SCRIPTS}/extension/jupyter_install_lab_extension.sh ${JUPYTER_HOME}/${JUPYTER_EXTENSION_ARCHIVE} + elif [[ $ext == 'http://'* || $ext == 'https://'* ]]; then + JUPYTER_EXTENSION_FILE=`basename $ext` + curl $ext -o /var/${JUPYTER_EXTENSION_FILE} + docker cp /var/${JUPYTER_EXTENSION_FILE} ${JUPYTER_SERVER_NAME}:${JUPYTER_HOME}/${JUPYTER_EXTENSION_FILE} + retry 3 docker exec -e PIP_USER=false ${JUPYTER_SERVER_NAME} ${JUPYTER_SCRIPTS}/extension/jupyter_install_lab_extension.sh ${JUPYTER_HOME}/${JUPYTER_EXTENSION_FILE} + else + retry 3 docker exec -e PIP_USER=false ${JUPYTER_SERVER_NAME} ${JUPYTER_SCRIPTS}/extension/jupyter_install_lab_extension.sh $ext + fi + done + fi + + # done extension setup + STEP_TIMINGS+=($(date +%s)) + + # See IA-1901: Jupyter UI stalls indefinitely on initial R kernel connection after cluster create/resume + # The intent of this is to "warm up" R at VM creation time to hopefully prevent issues when the Jupyter + # kernel tries to connect to it. + docker exec $JUPYTER_SERVER_NAME /bin/bash -c "R -e '1+1'" || true + + # For older jupyter images, jupyter_delocalize.py is using 127.0.0.1 as welder's url, which won't work now that we're no longer using `network_mode: host` for GCE VMs + docker exec $JUPYTER_SERVER_NAME /bin/bash -c "sed -i 's/127.0.0.1/welder/g' /etc/jupyter/custom/jupyter_delocalize.py" + + # Copy gitignore into jupyter container (ask AOU?) + docker exec $JUPYTER_SERVER_NAME /bin/bash -c "wget -N https://raw.githubusercontent.com/DataBiosphere/terra-docker/045a139dbac19fbf2b8c4080b8bc7fff7fc8b177/terra-jupyter-aou/gitignore_global" + + # Install nbstripout and set gitignore in Git Config (ask AOU?) + docker exec $JUPYTER_SERVER_NAME /bin/bash -c "pip install nbstripout \ + && nbstripout --install --global \ + && git config --global core.excludesfile $JUPYTER_USER_HOME/gitignore_global" + + # Starts the locking logic (used for AOU). google_sign_in.js is likely not used anymore + docker exec -u 0 $JUPYTER_SERVER_NAME /bin/bash -c "$JUPYTER_HOME/scripts/extension/install_jupyter_contrib_nbextensions.sh \ + && mkdir -p $JUPYTER_USER_HOME/.jupyter/custom/ \ + && cp $JUPYTER_HOME/custom/google_sign_in.js $JUPYTER_USER_HOME/.jupyter/custom/ \ + && ls -la $JUPYTER_HOME/custom/extension_entry_jupyter.js \ + && cp $JUPYTER_HOME/custom/extension_entry_jupyter.js $JUPYTER_USER_HOME/.jupyter/custom/custom.js \ + && cp $JUPYTER_HOME/custom/safe-mode.js $JUPYTER_USER_HOME/.jupyter/custom/ \ + && cp $JUPYTER_HOME/custom/edit-mode.js $JUPYTER_USER_HOME/.jupyter/custom/ \ + && mkdir -p $JUPYTER_HOME/nbconfig" + + # In new jupyter images, we should update jupyter_notebook_config.py in terra-docker. + # This is to make it so that older images will still work after we change notebooks location to home dir + docker exec ${JUPYTER_SERVER_NAME} sed -i '/^# to mount there as it effectively deletes existing files on the image/,+5d' ${JUPYTER_HOME}/jupyter_notebook_config.py + + # If a user script was specified, copy it into the docker container and execute it. + if [ ! -z "$USER_SCRIPT_URI" ] ; then + log 'Starting user script...' + apply_user_script $JUPYTER_SERVER_NAME $JUPYTER_HOME + fi + + # done user script + STEP_TIMINGS+=($(date +%s)) + + # If a start user script was specified, copy it into the docker container for consumption during startups. + if [ ! -z "$START_USER_SCRIPT_URI" ] ; then + apply_start_user_script $JUPYTER_SERVER_NAME $JUPYTER_HOME + fi + + # done start user script + STEP_TIMINGS+=($(date +%s)) + + log 'Starting Jupyter Notebook...' + retry 3 docker exec -d $JUPYTER_SERVER_NAME /bin/bash -c "${JUPYTER_SCRIPTS}/run-jupyter.sh ${NOTEBOOKS_DIR}" + + # done start Jupyter + STEP_TIMINGS+=($(date +%s)) +fi + +# RStudio specific setup; only do if RStudio is installed +if [ ! -z "$RSTUDIO_DOCKER_IMAGE" ] ; then + EXIT_CODE=0 + retry 3 docker exec ${RSTUDIO_SERVER_NAME} ${RSTUDIO_SCRIPTS}/set_up_package_dir.sh || EXIT_CODE=$? + if [ $EXIT_CODE -ne 0 ]; then + echo "RStudio user package installation directory creation failed, creating /packages directory" + docker exec ${RSTUDIO_SERVER_NAME} /bin/bash -c "mkdir -p ${RSTUDIO_USER_HOME}/packages && chmod a+rwx ${RSTUDIO_USER_HOME}/packages" + fi + + # Add the EVs specified in rstudio-docker-compose.yaml to Renviron.site + retry 3 docker exec ${RSTUDIO_SERVER_NAME} /bin/bash -c 'echo "GOOGLE_PROJECT=$GOOGLE_PROJECT +CLUSTER_NAME=$CLUSTER_NAME +RUNTIME_NAME=$RUNTIME_NAME +OWNER_EMAIL=$OWNER_EMAIL +SHOULD_BACKGROUND_SYNC=$SHOULD_BACKGROUND_SYNC +RSTUDIO_USER_HOME=$RSTUDIO_USER_HOME" >> /usr/local/lib/R/etc/Renviron.site' + + # Add custom_env_vars.env to Renviron.site + CUSTOM_ENV_VARS_FILE=/var/custom_env_vars.env + if [ -f "$CUSTOM_ENV_VARS_FILE" ]; then + retry 3 docker cp /var/custom_env_vars.env ${RSTUDIO_SERVER_NAME}:/usr/local/lib/R/etc/custom_env_vars.env + retry 3 docker exec ${RSTUDIO_SERVER_NAME} /bin/bash -c 'cat /usr/local/lib/R/etc/custom_env_vars.env >> /usr/local/lib/R/etc/Renviron.site' + fi + + # For older rstudio images, /etc/rstudio/rserver.conf is using 127.0.0.1 as www-address, which won't work now that we're no longer using `network_mode: host` for GCE VMs + docker exec ${RSTUDIO_SERVER_NAME} sed -i "s/127.0.0.1/0.0.0.0/g" /etc/rstudio/rserver.conf + + # If a user script was specified, copy it into the docker container and execute it. + if [ ! -z "$USER_SCRIPT_URI" ] ; then + apply_user_script $RSTUDIO_SERVER_NAME $RSTUDIO_SCRIPTS + fi + + # If a start user script was specified, copy it into the docker container for consumption during startups. + if [ ! -z "$START_USER_SCRIPT_URI" ] ; then + apply_start_user_script $RSTUDIO_SERVER_NAME $RSTUDIO_SCRIPTS + fi + + # default autosave to 10 seconds + docker exec ${RSTUDIO_SERVER_NAME} /bin/bash -c 'mkdir -p $RSTUDIO_USER_HOME/.config/rstudio \ + && echo "{ +\"initial_working_directory\": \"~\", +\"auto_save_on_blur\": true, +\"auto_save_on_idle\": \"commit\", +\"posix_terminal_shell\": \"bash\", +\"auto_save_idle_ms\": 10000 +}" > $RSTUDIO_USER_HOME/.config/rstudio/rstudio-prefs-temp.json \ + && mv $RSTUDIO_USER_HOME/.config/rstudio/rstudio-prefs-temp.json $RSTUDIO_USER_HOME/.config/rstudio/rstudio-prefs.json \ + && chown -R rstudio:users $RSTUDIO_USER_HOME/.config' + + # Start RStudio server + retry 3 docker exec -d ${RSTUDIO_SERVER_NAME} /init +fi + +# Resize persistent disk if needed. +echo "Resizing persistent disk attached to runtime $GOOGLE_PROJECT / $CLUSTER_NAME if disk size changed..." +resize2fs ${DISK_DEVICE_ID} + + +# Remove any unneeded cached images to save disk space. +# Do this asynchronously so it doesn't hold up cluster creation +log 'Pruning docker images...' +docker image prune -a -f & + +log 'All done!' + +ELAPSED_TIME=$(($END_TIME - $START_TIME)) +log "gce-init.sh took $(display_time $ELAPSED_TIME)" +log "Step timings: ${STEP_TIMINGS[@]}" diff --git a/terra-base/Dockerfile b/terra-base/Dockerfile index 940ab09d..f74b778f 100644 --- a/terra-base/Dockerfile +++ b/terra-base/Dockerfile @@ -58,16 +58,28 @@ RUN apt-get update && apt-get install -yq --no-install-recommends \ RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - -# Install Node >18 (needed for jupyterlab) +# Install Node >18 RUN apt-get update && apt-get install -yq --no-install-recommends RUN mkdir -p /etc/apt/keyrings RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list -RUN dpkg --remove --force-remove-reinstreq libnode-dev +#RUN dpkg --remove --force-remove-reinstreq libnode-dev RUN apt-get update && apt-get install -f -yq nodejs +################ +# Create Welder user +################ +# The welder uid must be consistent with the Welder docker definition here: +# https://github.com/DataBiosphere/welder/blob/master/project/Settings.scala +# Adding welder-user to the Jupyter container isn't strictly required, but it makes welder-added +# files display nicer when viewed in a terminal. +ENV WELDER_USER=welder-user +ENV WELDER_UID=1001 +RUN useradd -m -s /bin/bash -N -u $WELDER_UID $WELDER_USER + + ##################### # Create the Jupyter User (what users will see in Terra) ##################### @@ -77,23 +89,14 @@ ENV JUPYTER_UID=1002 # Create the jupyter user home and add the user to the users group ENV JUPYTER_USER_HOME=/home/$JUPYTER_USER -RUN useradd -m -s /bin/bash -d $JUPYTER_USER_HOME -N -u $JUPYTER_UID -g users $JUPYTER_USER +RUN useradd -m -s /bin/bash -d $JUPYTER_USER_HOME -N -u $JUPYTER_UID $JUPYTER_USER +RUN usermod -g users $JUPYTER_USER # We want to grant the jupyter user sudo permissions without password # so they can install the necessary packages that they want to use on the docker container RUN echo "$JUPYTER_USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$JUPYTER_USER \ && chmod 0440 /etc/sudoers.d/$JUPYTER_USER -################ -# Create Welder user -################ -# The welder uid must be consistent with the Welder docker definition here: -# https://github.com/DataBiosphere/welder/blob/master/project/Settings.scala -# Adding welder-user to the Jupyter container isn't strictly required, but it makes welder-added -# files display nicer when viewed in a terminal. -ENV WELDER_USER=welder-user -ENV WELDER_UID=1001 -RUN useradd -m -s /bin/bash -N -u $WELDER_UID $WELDER_USER ############ # Install R @@ -101,89 +104,80 @@ RUN useradd -m -s /bin/bash -N -u $WELDER_UID $WELDER_USER RUN apt-get update && apt-get install -y r-base ################ -# Install Python +## Install Miniconda ################ -# Install Python 3.10 and add to system python path -# Miniconda does come with it's own installation of python, but it -# is cleaner to do a proper system instalation here and set the path -#RUN apt-get update && apt-get install -y python$PYTHON_VERSION python3-pip -#RUN update-alternatives --install /usr/bin/python python /usr/bin/python$PYTHON_VERSION 1 \ -# && update-alternatives --set python /usr/bin/python$PYTHON_VERSION - -# Set up the path to the user python --> /home/jupyter/.envs/base-python3.10/bin/python -#ENV BASE_PYTHON_PATH=usr/bin/python3 +### Note: CONDA is not used here to manage dependencies, but as a tool to manage python environments. +### We want to store the user conda environments in a directory that will be in the persistent disk +### Attention: If you change the Conda home location, please update conda_init.txt accordingly +# +#ENV CONDA_ENV_NAME=base-python${PYTHON_VERSION} +#ENV CONDA_ENV_HOME=$JUPYTER_USER_HOME/.envs/$CONDA_ENV_NAME +#RUN curl -so $JUPYTER_USER_HOME/miniconda.sh ${CONDA_INSTALLER} \ +# && chmod +x $JUPYTER_USER_HOME/miniconda.sh \ +# && $JUPYTER_USER_HOME/miniconda.sh -b -p $CONDA_ENV_HOME \ +# && rm $JUPYTER_USER_HOME/miniconda.sh +#ENV PATH="${PATH}:${CONDA_ENV_HOME}/bin" +# +### Set up the path to the user python --> /home/jupyter/.envs/base-python3.10/bin/python +#ENV BASE_PYTHON_PATH $CONDA_ENV_HOME/bin/python +## Tell condo to NOT write byte code (aka .pyc files) +#ENV PYTHONDONTWRITEBYTECODE=tru +# +#################################################### +## Set up the user to use the conda base environment +#################################################### +### The user should have full access to the conda base environment, and can use it directly, or +### create new conda environments on top of it. The important part is that jupyter IS NOT installed +### in the base environment to provide isolation between the user environment, and the jupyter server +### to avoid cross-contamination +#COPY conda-env.yml . +#RUN conda env update --prefix $CONDA_ENV_HOME --file conda-env.yml --prune \ +# # Remove packages tarballs and python bytecode files from the image +# && conda clean -afy \ +# && rm conda-env.yml \ +# # Make sure the JUPYTER_USER is the owner of the folder where +# # the base conda is installed +# && chown -R $JUPYTER_USER:users $JUPYTER_USER_HOME \ +# # enable conda libmamba: https://www.anaconda.com/blog/a-faster-conda-for-a-growing-community \ +# && conda install -n base conda-libmamba-solver \ +# && conda config --set solver libmamba +# +## Add the user base conda environment as a jupyter kernel - this should be the default now +## This commands activates the conda environment and then calls ipykernel from within +## to install it as a kernel under the same name +#RUN conda run -p $CONDA_ENV_HOME python -m ipykernel install --name=$CONDA_ENV_NAME +# +## Prep the jupyter terminal to conda init and make sure the base conda environment is +## activated and the name is displayed in the terminal prompt +#COPY conda_init.txt . +#RUN cat conda_init.txt >> $JUPYTER_USER_HOME/.bashrc && \ +# printf "\nconda activate ${CONDA_ENV_HOME}" >> $JUPYTER_USER_HOME/.bashrc && \ +# conda config --set env_prompt '({name})' && \ +# source $JUPYTER_USER_HOME/.bashrc && \ +# rm conda_init.txt +# # -############### -# Install Miniconda -############### -## Note: CONDA is not used here to manage dependencies, but as a tool to manage python environments. -## We want to store the user conda environments in a directory that will be in the persistent disk -## Attention: If you change the Conda home location, please update conda_init.txt accordingly -ENV CONDA_ENV_NAME=base-python${PYTHON_VERSION} -ENV CONDA_ENV_HOME=$JUPYTER_USER_HOME/.envs/$CONDA_ENV_NAME -RUN curl -so $JUPYTER_USER_HOME/miniconda.sh ${CONDA_INSTALLER} \ - && chmod +x $JUPYTER_USER_HOME/miniconda.sh \ - && $JUPYTER_USER_HOME/miniconda.sh -b -p $CONDA_ENV_HOME \ - && rm $JUPYTER_USER_HOME/miniconda.sh -ENV PATH="${PATH}:${CONDA_ENV_HOME}/bin" - -## Set up the path to the user python --> /home/jupyter/.envs/base-python3.10/bin/python -ENV BASE_PYTHON_PATH=$CONDA_ENV_HOME/bin/python - -# Tell conda to NOT write bite code (aka these.pyc files) -ENV PYTHONDONTWRITEBYTECODE=true - -################################################### -# Set up the user to use the conda base environment -################################################### -## The user should have full access to the conda base environment, and can use it directly, or -## create new conda environments on top of it. The important part is that jupyter IS NOT installed -## in the base environment to provide isolation between the user environment, and the jupyter server -## to avoid cross-contamination -COPY conda-env.yml . -RUN conda env update --prefix $CONDA_ENV_HOME --file conda-env.yml --prune \ - # Remove packages tarballs and python bytecode files from the image - && conda clean -afy \ - && rm conda-env.yml \ - # Make sure the JUPYTER_USER is the owner of the folder where - # the base conda is installed - && chown -R $JUPYTER_USER:users $JUPYTER_USER_HOME \ - # enable conda libmamba: https://www.anaconda.com/blog/a-faster-conda-for-a-growing-community \ - && conda install -n base conda-libmamba-solver \ - && conda config --set solver libmamba - -# Add the user base conda environment as a jupyter kernel - this should be the default now -# This commands activates the conda environment and then calls ipykernel from within -# to install it as a kernel under the same name -RUN conda run -p $CONDA_ENV_HOME python -m ipykernel install --name=$CONDA_ENV_NAME - -# Prep the jupyter terminal to conda init and make sure the base conda environment is -# activated and the name is displayed in the terminal prompt -COPY conda_init.txt . -RUN cat conda_init.txt >> $JUPYTER_USER_HOME/.bashrc && \ - printf "\nconda activate ${CONDA_ENV_HOME}" >> $JUPYTER_USER_HOME/.bashrc && \ - conda config --set env_prompt '({name})' && \ - source $JUPYTER_USER_HOME/.bashrc && \ - rm conda_init.txt - - ########## # Setup UV ########## -# - Silence uv complaining about not being able to use hard links, +# - tells uv to copy the Python files into the container from the cache mount, # - tell uv to byte-compile packages for faster application startups, -# - prevent uv from accidentally downloading isolated Python builds, -# - specify Python version # - don't seed venv with wheel and setuptools, we need to install specific versions # - don't cache to keep the image size small -# - use system python to avoid installing a new python version +# - Set the UV_HOME to /opt/uv +# - Configure the Python directory so it is consistent +# - Only use the managed Python version ENV UV_LINK_MODE=copy \ UV_COMPILE_BYTECODE=1 \ - UV_PYTHON_DOWNLOADS=never \ - UV_PYTHON=$PYTHON_VERSION \ UV_VENV_SEED=false \ UV_NO_CACHE=true \ - UV_SYSTEM_PYTHON=1 + UV_PYTHON_INSTALL_DIR=/python \ + UV_PYTHON_PREFERENCE=only-managed + +# UV_HOME=/opt/uv \ +# UV_SYSTEM_PYTHON=1 +# UV_PYTHON_DOWNLOADS=never \ +# UV_PYTHON=$BASE_PYTHON_PATH \ ############### # Setup virtualenv @@ -191,9 +185,9 @@ ENV UV_LINK_MODE=copy \ # Using UV (Universal Virtualenv) to create a virtual environment # UV is used in place of poetry for speed and simplicity. # NOTE: this is separate from the jupyter user -ENV JUPYTER_HOME=/usr/jupytervenv +ENV JUPYTER_HOME=/etc/jupyter ENV VIRTUAL_ENV=$JUPYTER_HOME - +# COPY uv.lock . COPY pyproject.toml . @@ -201,17 +195,20 @@ COPY pyproject.toml . # but make sure to add it at the end so that the # Conda base python takes precedence # (aka the ! operator in iPython shells should NOT access the jupyter virtualenvironment) -ENV PATH "${PATH}:${JUPYTER_HOME}/bin" +ENV PATH "${PATH}:${UV_HOME}/bin" ENV PATH="/root/.local/bin/:$PATH" # Download the latest installer ADD https://astral.sh/uv/install.sh /uv-installer.sh -#--python $BASE_PYTHON_PATH -RUN sh /uv-installer.sh && rm /uv-installer.sh \ - # Create a virtual environment and activate it for UV to use - && uv venv $JUPYTER_HOME --python $BASE_PYTHON_PATH \ +RUN sh /uv-installer.sh && rm /uv-installer.sh + +# install the specific python version +#RUN uv python $PYTHON_VERSION + +# Create a virtual environment and activate it for UV to use \ +RUN uv venv $JUPYTER_HOME --python $PYTHON_VERSION \ && source $JUPYTER_HOME/bin/activate \ - && PYTHONEXECUTABLE=/usr/bin/env python3 \ +# && PYTHONEXECUTABLE=/usr/bin/python3 \ # Install the python dependencies using uv && uv pip install wheel \ && uv pip install 'setuptools==59.8.0' \ @@ -222,8 +219,7 @@ RUN sh /uv-installer.sh && rm /uv-installer.sh \ # && uv cache clean \ # && rm ~/.local/bin/uv ~/.local/bin/uvx -# Install nbstripout -RUN nbstripout --install --global +ENV PATH=$JUPYTER_HOME/bin:$PATH # ################################## # # Terra-specific Jupyter Utilities @@ -234,7 +230,7 @@ EXPOSE $JUPYTER_PORT ENV JUPYTER_KERNELSPEC_DIR=/usr/local/share/jupyter # Install nbstripout -RUN nbstripout --install --global +#RUN nbstripout --install --global # copy workspace_cromwell.py script and make it runnable by all users RUN curl -o /usr/local/bin/workspace_cromwell.py https://raw.githubusercontent.com/broadinstitute/cromwhelm/1ceedf89587cffd355f37401b179001f029f77ed/scripts/workspace_cromwell.py \ @@ -252,19 +248,19 @@ RUN chown -R $JUPYTER_USER:users $JUPYTER_HOME #RUN $JUPYTER_HOME/bin/jupyter kernelspec remove python3 -y # setup the jupyter kernel -RUN chown -R $JUPYTER_USER:users $JUPYTER_KERNELSPEC_DIR \ - && find $JUPYTER_HOME/scripts -name '*.sh' -type f | xargs chmod +x \ - # You can get kernel directory by running `jupyter kernelspec list` - && $JUPYTER_HOME/scripts/kernel/kernelspec.sh $JUPYTER_HOME/scripts/kernel $JUPYTER_KERNELSPEC_DIR/kernels - +#RUN chown -R $JUPYTER_USER:users $JUPYTER_KERNELSPEC_DIR \ +# && find $JUPYTER_HOME/scripts -name '*.sh' -type f | xargs chmod +x \ +# # You can get kernel directory by running `jupyter kernelspec list` +# && $JUPYTER_HOME/scripts/kernel/kernelspec.sh $JUPYTER_HOME/scripts/kernel $JUPYTER_KERNELSPEC_DIR/kernels +# # Set up the user and working directory, which is where the persistent disk will be mounted # this is different from where Jupyter is installed USER $JUPYTER_USER WORKDIR $JUPYTER_USER_HOME -# Note: this entrypoint is provided for running Jupyter independently of Leonardo. -# When Leonardo deploys this image onto a cluster, the entrypoint is overwritten to enable -# additional setup inside the container before execution. Jupyter execution occurs when the -# init-actions.sh script uses 'docker exec' to call run-jupyter.sh. -# .venv/bin/jupyter lab -#ENTRYPOINT ["/usr/jupytervenv/bin/jupyter", "lab"] \ No newline at end of file +## Note: this entrypoint is provided for running Jupyter independently of Leonardo. +## When Leonardo deploys this image onto a cluster, the entrypoint is overwritten to enable +## additional setup inside the container before execution. Jupyter execution occurs when the +## init-actions.sh script uses 'docker exec' to call run-jupyter.sh. +## .venv/bin/jupyter lab +##ENTRYPOINT ["/usr/jupytervenv/bin/jupyter", "lab"] \ No newline at end of file diff --git a/terra-base/DockerfileAzure b/terra-base/DockerfileAzure new file mode 100644 index 00000000..88986a6a --- /dev/null +++ b/terra-base/DockerfileAzure @@ -0,0 +1,199 @@ +# Smallest image with ubuntu jammy, CUDA and NVDIA drivers installed - 80 mb +FROM --platform=linux/amd64 nvidia/cuda:12.2.0-base-ubuntu22.04 + +# Use bash as the shell, like the jupyter terminal (just nicer to work with than sh) +ENV SHELL /usr/bin/bash +SHELL ["/usr/bin/bash", "-c"] + +####################### +# Environment Variables +####################### +ENV DEBIAN_FRONTEND noninteractive +ENV LC_ALL en_US.UTF-8 + +# We need node >18 for jupyter to work +ENV NODE_MAJOR 20 + +# Set the python version and corresponding conda installer +ENV PYTHON_VERSION 3.10 +ENV CONDA_INSTALLER https://repo.anaconda.com/miniconda/Miniconda3-py310_23.5.1-0-Linux-x86_64.sh + +############### +# Prerequisites +############### +RUN apt-get update && apt-get install -yq --no-install-recommends \ + # basic necessities + sudo \ + ca-certificates \ + curl \ + jq \ + tree \ + # gnupg requirement + gnupg \ + dirmngr \ + # useful utilities for debugging within docker itself + nano \ + less \ + procps \ + lsb-release \ + # gcc compiler + build-essential \ + locales \ + # for ssh-agent and ssh-add + keychain \ + # extras \ + wget \ + aria2 \ + bzip2 \ + # git + git \ + # Uncomment en_US.UTF-8 for inclusion in generation + && sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen \ + # Generate locale + && locale-gen \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install Node >18 (needed for jupyterlab) +RUN apt-get update && apt-get install -yq --no-install-recommends +RUN mkdir -p /etc/apt/keyrings +RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg + +RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list +RUN apt-get update && apt-get install -f -yq nodejs + +############# +# Users Setup +############# +# Create the welder user +# The welder uid is consistent with the Welder docker definition here: +# https://github.com/DataBiosphere/welder/blob/master/project/Settings.scala +# Adding welder-user to the Jupyter container isn't strictly required, but it makes welder-added +# files display nicer when viewed in a terminal. +ENV WELDER_USER welder-user +ENV WELDER_UID 1001 +RUN useradd -m -N -u $WELDER_UID $WELDER_USER + +# Create the jupyter user +ENV JUPYTER_USER jupyter-user +ENV JUPYTER_UID 1002 +# Create the jupyter user home +ENV JUPYTER_USER_HOME /home/$JUPYTER_USER +RUN useradd -m -d $JUPYTER_USER_HOME -N -u $JUPYTER_UID -g users $JUPYTER_USER +# We want to grant the jupyter user sudo permissions +# without password so they can install the necessary packages that they +# want to use on the docker container +RUN echo "$JUPYTER_USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$JUPYTER_USER \ + && chmod 0440 /etc/sudoers.d/$JUPYTER_USER + +##################################### +# Install Python via Miniconda +##################################### +## Note: CONDA should NOT be used by terra devs to manage dependencies (see the use of poetry below instead), +## but is a widely used tool to manage python environments in a runtime and we should provide it to users +## We want to store the user conda environments in a directory +## that will be in the persistent disk +## Attention: If you change the Conda home location, please update conda_init.txt accordingly +ENV CONDA_ENV_NAME base-python${PYTHON_VERSION} +ENV CONDA_ENV_HOME $JUPYTER_USER_HOME/.envs/$CONDA_ENV_NAME +RUN curl -so $JUPYTER_USER_HOME/miniconda.sh ${CONDA_INSTALLER} \ + && chmod +x $JUPYTER_USER_HOME/miniconda.sh \ + && $JUPYTER_USER_HOME/miniconda.sh -b -p $CONDA_ENV_HOME \ + && rm $JUPYTER_USER_HOME/miniconda.sh +ENV PATH "${PATH}:${CONDA_ENV_HOME}/bin" + +# Set up the path to the user python +ENV BASE_PYTHON_PATH $CONDA_ENV_HOME/bin/python +# Tell condo to NOT write bite code (aka these.pyc files) +ENV PYTHONDONTWRITEBYTECODE=true + +################################################### +# Set up the user to use the conda base environment +################################################### +## The user should have full access to the conda base environment, and can use it directly, or +## create new conda environments on top of it. The important part is that jupyter IS NOT installed +## in the base environment to provide isolation between the user environment, and the jupyter server +## to avoid cross-contamination +COPY conda-env.yml . +RUN conda env update --prefix $CONDA_ENV_HOME --file conda-env.yml --prune \ + # Remove packages tarballs and python bytecode files from the image + && conda clean -afy \ + && rm conda-env.yml \ + # Make sure the JUPYTER_USER is the owner of the folder where + # the base conda is installed + && chown -R $JUPYTER_USER:users $JUPYTER_USER_HOME + +# Add the user base conda environment as a jupyter kernel - this should be the default now +# This commands activates the conda environment and then calls ipykernel from within +# to install it as a kernel under the same name +RUN conda run -p ${CONDA_ENV_HOME} python -m ipykernel install --name=$CONDA_ENV_NAME + +# Prep the jupyter terminal to conda init and make sure the base conda environment is +# activated and the name is displayed in the terminal prompt +COPY conda_init.txt . +RUN cat conda_init.txt >> $JUPYTER_USER_HOME/.bashrc && \ + printf "\nconda activate ${CONDA_ENV_HOME}" >> $JUPYTER_USER_HOME/.bashrc && \ + conda config --set env_prompt '({name})' && \ + source $JUPYTER_USER_HOME/.bashrc && \ + rm conda_init.txt + +#################################################### +# Install Jupyter in an isolated virtual environment +#################################################### +## Virtualenv and POETRY are the prefered tool to create virtual environments and +## manage dependencies for Terra Devs - poetry docs: https://python-poetry.org/docs/ +ENV POETRY_HOME /opt/poetry +# Append POETRY_HOME to PATH +ENV PATH "${PATH}:${POETRY_HOME}/bin" +COPY poetry.lock . +COPY pyproject.toml . + +ENV JUPYTER_HOME /usr/jupytervenv +# Add jupyter virtual environmemt to PATH, +# but make sure to add it at the end so that the +# Conda base python takes precedence +# (aka the ! operator in iPython shells should NOT access the jupyter virtualenvironment) +ENV PATH "${PATH}:${JUPYTER_HOME}/bin" + +# Install Poetry, set up the virtual environment for jupyter to run and then cleanup / uninstall poetry +RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=$POETRY_HOME $BASE_PYTHON_PATH \ + # Create a virtual environment and activate it for poetry to use + && $BASE_PYTHON_PATH -m venv $JUPYTER_HOME \ + && source $JUPYTER_HOME/bin/activate \ + # Install python dependencies with poetry + && poetry install --no-interaction --no-ansi --no-cache \ + # Cleanup + && rm poetry.lock && rm pyproject.toml \ + && curl -sSL https://install.python-poetry.org | POETRY_HOME=$POETRY_HOME $BASE_PYTHON_PATH - --uninstall + +# ################################## +# # Terra-specific Jupyter Utilities +# ################################## +# Ensure this matches c.ServerApp.port in 'jupyter_server_config.py' +ENV JUPYTER_PORT 8888 +EXPOSE $JUPYTER_PORT + +# Install the custom extensions to enable welder for file syncing +COPY custom $JUPYTER_HOME/etc/jupyter/custom +COPY custom/jupyter_delocalize.py $JUPYTER_HOME/lib/python${PYTHON_VERSION}/site-packages +COPY jupyter_notebook_config.py $JUPYTER_HOME/etc/jupyter + +# Remove the jupyter environment from the list of available kernels so it is hidden from the user +# Note that this needs to be done with setting the c.KernelSpecManager.ensure_native_kernel flag +# to False in 'jupyter_server_config.py' +RUN $JUPYTER_HOME/bin/jupyter kernelspec remove python3 -y + +# Copy the script that the service deploying to Terra (e.g. leonardo) will use for docker exec +COPY scripts $JUPYTER_HOME/scripts +COPY custom $JUPYTER_HOME/custom +RUN chmod +x $JUPYTER_HOME/scripts/run-jupyter.sh + +# Set up the user and working directory, which is where the persistent disk will be mounted +USER $JUPYTER_USER +WORKDIR $JUPYTER_USER_HOME/persistent_disk + +# Note: this entrypoint is provided for running Jupyter independently of Leonardo. +# When Leonardo deploys this image onto a cluster, the entrypoint is overwritten to enable +# additional setup inside the container before execution. Jupyter execution occurs when the +# init-actions.sh script uses 'docker exec' to call run-jupyter.sh. +# ENTRYPOINT ["/usr/jupytervenv/bin/jupyter", "lab"] \ No newline at end of file diff --git a/terra-base/DockerfileGCP b/terra-base/DockerfileGCP new file mode 100644 index 00000000..9af6eef6 --- /dev/null +++ b/terra-base/DockerfileGCP @@ -0,0 +1,178 @@ +# Smallest image with ubuntu jammy, CUDA and NVDIA drivers installed - 80 mb +FROM --platform=linux/amd64 nvidia/cuda:12.2.0-base-ubuntu22.04 + +####################### +# Environment Variables +####################### +ENV DEBIAN_FRONTEND noninteractive +ENV LC_ALL en_US.UTF-8 +# Ensure this matches c.NotebookApp.port in jupyter_notebook_config.py +ENV JUPYTER_PORT 8000 +ENV JUPYTER_HOME /etc/jupyter +ENV JUPYTER_KERNELSPEC_DIR /usr/local/share/jupyter +# We need node >18 for jupyter to work +ENV NODE_MAJOR 20 + +####################### +# Users Setup +####################### +# Create the jupyter user, add it to the `users` group and specify the home directory path +ENV USER jupyter +ENV USER_HOME /home/$USER +RUN useradd -m -s /bin/bash -d $USER_HOME -N -g users $USER +# Create the welder user +# The welder uid is consistent with the Welder docker definition here: +# https://github.com/DataBiosphere/welder/blob/master/project/Settings.scala +# Adding welder-user to the Jupyter container isn't strictly required, but it makes welder-added +# files display nicer when viewed in a terminal. +ENV WELDER_USER welder-user +ENV WELDER_UID 1001 +RUN useradd -m -s /bin/bash -N -u $WELDER_UID $WELDER_USER + +# We want to grant the jupyter user limited sudo permissions +# without password so they can install the necessary packages that they +# want to use on the docker container +RUN mkdir -p /etc/sudoers.d \ + && echo "$USER ALL=(ALL) NOPASSWD: /usr/bin/apt-get install *, /opt/conda/bin/conda install *, /opt/poetry/bin/poetry install" > /etc/sudoers.d/$USER \ + && chmod 0440 /etc/sudoers.d/$USER + +####################### +# Prerequisites +####################### +RUN apt-get update && apt-get install -yq --no-install-recommends \ + sudo \ + ca-certificates \ + curl \ + jq \ + # gnupg requirement + gnupg \ + dirmngr \ + # useful utilities for debugging within the docker + nano \ + procps \ + lsb-release \ + # python requirements + checkinstall \ + build-essential \ + zlib1g-dev \ + # pip requirements + libssl-dev \ + libbz2-dev \ + libreadline-dev \ + libsqlite3-dev \ + libexempi8 \ + libnode-dev \ + llvm \ + libncurses5-dev \ + libncursesw5-dev \ + tk-dev \ + libffi-dev \ + liblzma-dev \ + python3-openssl \ + # install script requirements + locales \ + # for ssh-agent and ssh-add + keychain \ + # extras \ + wget \ + bzip2 \ + # git + git \ + # Uncomment en_US.UTF-8 for inclusion in generation + && sed -i 's/^# *\(en_US.UTF-8\)/\1/' /etc/locale.gen \ + # Generate locale + && locale-gen \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - +RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - + +# Install Node >18 (needed for jupyterlab) +RUN apt-get update && apt-get install -yq --no-install-recommends +RUN mkdir -p /etc/apt/keyrings +RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg + +RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list +RUN dpkg --remove --force-remove-reinstreq libnode-dev +RUN apt-get update && apt-get install -f -yq nodejs + +############ +# Install R +############ +RUN apt-get update && apt-get install -y r-base + +################ +# Install Python +################ +# Install Python 3.10 and add to system python path +# Note that miniconda does come with it's own installation of python, +# but it is cleaner to do a proper system instalation here and set the +# path properly +RUN apt-get update && apt-get install -y python3.10 python3-pip +RUN update-alternatives --install /usr/bin/python python /usr/bin/python3.10 1 \ + && update-alternatives --set python /usr/bin/python3.10 + +############################ +# Manage python dependencies +############################ +## POETRY is the prefered tool to create virtual environments and +## manage dependencies - Should be used by Terra devs +# Install Poetry +ENV POETRY_HOME /opt/poetry +RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=$POETRY_HOME python3 +# Append '/home/jupyter/.local/bin' to PATH +# poetry docs: https://python-poetry.org/docs/ +ENV PATH "${PATH}:${POETRY_HOME}/bin" + +# Prevent poetry from creating a virtual environment (we want to install on the docker system python) +RUN poetry config virtualenvs.create false + +# Install python dependencies with poetry +COPY poetry.lock . +COPY pyproject.toml . +RUN poetry install --no-cache --no-interaction + +################### +# Install Miniconda +################### +## CONDA should not be used by terra devs, but is a widely used tools +## to manage python environments in a runtime and we should provide it to users +ENV CONDA_HOME /opt/conda +RUN curl -so $HOME/miniconda.sh https://repo.anaconda.com/miniconda/Miniconda3-py310_23.5.1-0-Linux-x86_64.sh \ + && chmod +x $HOME/miniconda.sh \ + && $HOME/miniconda.sh -b -p $CONDA_HOME \ + && rm $HOME/miniconda.sh +ENV PATH "${PATH}:${CONDA_HOME}/bin" + +# ####################### +# # Utilities +# ####################### +COPY scripts $JUPYTER_HOME/scripts +COPY custom $JUPYTER_HOME/custom +COPY jupyter_notebook_config.py $JUPYTER_HOME +RUN chown -R $USER:users $JUPYTER_HOME + +# copy workspace_cromwell.py script and make it runnable by all users +RUN curl -o /usr/local/bin/workspace_cromwell.py https://raw.githubusercontent.com/broadinstitute/cromwhelm/1ceedf89587cffd355f37401b179001f029f77ed/scripts/workspace_cromwell.py \ + && chmod +x /usr/local/bin/workspace_cromwell.py + +RUN chown -R $USER:users $JUPYTER_KERNELSPEC_DIR \ + && find $JUPYTER_HOME/scripts -name '*.sh' -type f | xargs chmod +x \ + # You can get kernel directory by running `jupyter kernelspec list` + && $JUPYTER_HOME/scripts/kernel/kernelspec.sh $JUPYTER_HOME/scripts/kernel $JUPYTER_KERNELSPEC_DIR/kernels + +# Make sure that the jupyter user will have access to the jupyter path in the working directory +EXPOSE $JUPYTER_PORT +WORKDIR $USER_HOME + +# make pip install to a user directory, instead of a system directory which requires root. +# this is useful so `pip install` commands can be run in the context of a notebook. +ENV PIP_USER true +USER $USER + +# Note: this entrypoint is provided for running Jupyter independently of Leonardo. +# When Leonardo deploys this image onto a cluster, the entrypoint is overwritten to enable +# additional setup inside the container before execution. Jupyter execution occurs when the +# init-actions.sh script uses 'docker exec' to call run-jupyter.sh. +ENTRYPOINT ["/usr/local/bin/jupyter-nbclassic"] \ No newline at end of file diff --git a/terra-base/poetry.lock b/terra-base/poetry.lock new file mode 100644 index 00000000..17e092c3 --- /dev/null +++ b/terra-base/poetry.lock @@ -0,0 +1,2225 @@ +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. + +[[package]] +name = "anyio" +version = "3.7.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, + {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, +] + +[package.dependencies] +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] +test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4) ; python_version < \"3.8\"", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17) ; python_version < \"3.12\" and platform_python_implementation == \"CPython\" and platform_system != \"Windows\""] +trio = ["trio (<0.22)"] + +[[package]] +name = "appnope" +version = "0.1.4" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = ">=3.6" +groups = ["main"] +markers = "platform_system == \"Darwin\"" +files = [ + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, +] + +[[package]] +name = "argon2-cffi" +version = "25.1.0" +description = "Argon2 for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741"}, + {file = "argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1"}, +] + +[package.dependencies] +argon2-cffi-bindings = "*" + +[[package]] +name = "argon2-cffi-bindings" +version = "25.1.0" +description = "Low-level CFFI bindings for Argon2" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94"}, + {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6dca33a9859abf613e22733131fc9194091c1fa7cb3e131c143056b4856aa47e"}, + {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:21378b40e1b8d1655dd5310c84a40fc19a9aa5e6366e835ceb8576bf0fea716d"}, + {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d588dec224e2a83edbdc785a5e6f3c6cd736f46bfd4b441bbb5aa1f5085e584"}, + {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5acb4e41090d53f17ca1110c3427f0a130f944b896fc8c83973219c97f57b690"}, + {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:da0c79c23a63723aa5d782250fbf51b768abca630285262fb5144ba5ae01e520"}, + {file = "argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d"}, +] + +[package.dependencies] +cffi = {version = ">=1.0.1", markers = "python_version < \"3.14\""} + +[[package]] +name = "asttokens" +version = "3.0.0" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, + {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, +] + +[package.extras] +astroid = ["astroid (>=2,<4)"] +test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] + +[[package]] +name = "attrs" +version = "25.3.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, +] + +[package.extras] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] + +[[package]] +name = "babel" +version = "2.17.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, +] + +[package.extras] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] + +[[package]] +name = "beautifulsoup4" +version = "4.13.4" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.7.0" +groups = ["main"] +files = [ + {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, + {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, +] + +[package.dependencies] +soupsieve = ">1.2" +typing-extensions = ">=4.0.0" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "bleach" +version = "6.2.0" +description = "An easy safelist-based HTML-sanitizing tool." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e"}, + {file = "bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f"}, +] + +[package.dependencies] +tinycss2 = {version = ">=1.1.0,<1.5", optional = true, markers = "extra == \"css\""} +webencodings = "*" + +[package.extras] +css = ["tinycss2 (>=1.1.0,<1.5)"] + +[[package]] +name = "certifi" +version = "2025.8.3" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "implementation_name == \"pypy\"" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.4.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, + {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, + {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "comm" +version = "0.2.3" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417"}, + {file = "comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971"}, +] + +[package.extras] +test = ["pytest"] + +[[package]] +name = "debugpy" +version = "1.8.15" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "debugpy-1.8.15-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:e9a8125c85172e3ec30985012e7a81ea5e70bbb836637f8a4104f454f9b06c97"}, + {file = "debugpy-1.8.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd0b6b5eccaa745c214fd240ea82f46049d99ef74b185a3517dad3ea1ec55d9"}, + {file = "debugpy-1.8.15-cp310-cp310-win32.whl", hash = "sha256:8181cce4d344010f6bfe94a531c351a46a96b0f7987750932b2908e7a1e14a55"}, + {file = "debugpy-1.8.15-cp310-cp310-win_amd64.whl", hash = "sha256:af2dcae4e4cd6e8b35f982ccab29fe65f7e8766e10720a717bc80c464584ee21"}, + {file = "debugpy-1.8.15-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:babc4fb1962dd6a37e94d611280e3d0d11a1f5e6c72ac9b3d87a08212c4b6dd3"}, + {file = "debugpy-1.8.15-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f778e68f2986a58479d0ac4f643e0b8c82fdd97c2e200d4d61e7c2d13838eb53"}, + {file = "debugpy-1.8.15-cp311-cp311-win32.whl", hash = "sha256:f9d1b5abd75cd965e2deabb1a06b0e93a1546f31f9f621d2705e78104377c702"}, + {file = "debugpy-1.8.15-cp311-cp311-win_amd64.whl", hash = "sha256:62954fb904bec463e2b5a415777f6d1926c97febb08ef1694da0e5d1463c5c3b"}, + {file = "debugpy-1.8.15-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:3dcc7225cb317469721ab5136cda9ff9c8b6e6fb43e87c9e15d5b108b99d01ba"}, + {file = "debugpy-1.8.15-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:047a493ca93c85ccede1dbbaf4e66816794bdc214213dde41a9a61e42d27f8fc"}, + {file = "debugpy-1.8.15-cp312-cp312-win32.whl", hash = "sha256:b08e9b0bc260cf324c890626961dad4ffd973f7568fbf57feb3c3a65ab6b6327"}, + {file = "debugpy-1.8.15-cp312-cp312-win_amd64.whl", hash = "sha256:e2a4fe357c92334272eb2845fcfcdbec3ef9f22c16cf613c388ac0887aed15fa"}, + {file = "debugpy-1.8.15-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:f5e01291ad7d6649aed5773256c5bba7a1a556196300232de1474c3c372592bf"}, + {file = "debugpy-1.8.15-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94dc0f0d00e528d915e0ce1c78e771475b2335b376c49afcc7382ee0b146bab6"}, + {file = "debugpy-1.8.15-cp313-cp313-win32.whl", hash = "sha256:fcf0748d4f6e25f89dc5e013d1129ca6f26ad4da405e0723a4f704583896a709"}, + {file = "debugpy-1.8.15-cp313-cp313-win_amd64.whl", hash = "sha256:73c943776cb83e36baf95e8f7f8da765896fd94b05991e7bc162456d25500683"}, + {file = "debugpy-1.8.15-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:054cd4935bd2e4964dfe1aeee4d6bca89d0c833366776fc35387f8a2f517dd00"}, + {file = "debugpy-1.8.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21c4288e662997df3176c4b9d93ee1393913fbaf320732be332d538000c53208"}, + {file = "debugpy-1.8.15-cp38-cp38-win32.whl", hash = "sha256:aaa8ce6a37d764f93fe583d7c6ca58eb7550b36941387483db113125f122bb0d"}, + {file = "debugpy-1.8.15-cp38-cp38-win_amd64.whl", hash = "sha256:71cdf7f676af78e70f005c7fad2ef9da0edc2a24befbf3ab146a51f0d58048c2"}, + {file = "debugpy-1.8.15-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:085b6d0adb3eb457c2823ac497a0690b10a99eff8b01c01a041e84579f114b56"}, + {file = "debugpy-1.8.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd546a405381d17527814852642df0a74b7da8acc20ae5f3cfad0b7c86419511"}, + {file = "debugpy-1.8.15-cp39-cp39-win32.whl", hash = "sha256:ae0d445fe11ff4351428e6c2389e904e1cdcb4a47785da5a5ec4af6c5b95fce5"}, + {file = "debugpy-1.8.15-cp39-cp39-win_amd64.whl", hash = "sha256:de7db80189ca97ab4b10a87e4039cfe4dd7ddfccc8f33b5ae40fcd33792fc67a"}, + {file = "debugpy-1.8.15-py2.py3-none-any.whl", hash = "sha256:bce2e6c5ff4f2e00b98d45e7e01a49c7b489ff6df5f12d881c67d2f1ac635f3d"}, + {file = "debugpy-1.8.15.tar.gz", hash = "sha256:58d7a20b7773ab5ee6bdfb2e6cf622fdf1e40c9d5aef2857d85391526719ac00"}, +] + +[[package]] +name = "decorator" +version = "5.2.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, + {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] +files = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, + {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "executing" +version = "2.2.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, + {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] + +[[package]] +name = "fastjsonschema" +version = "2.21.1" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667"}, + {file = "fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "ipykernel" +version = "6.30.0" +description = "IPython Kernel for Jupyter" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "ipykernel-6.30.0-py3-none-any.whl", hash = "sha256:fd2936e55c4a1c2ee8b1e5fa6a372b8eecc0ab1338750dee76f48fa5cca1301e"}, + {file = "ipykernel-6.30.0.tar.gz", hash = "sha256:b7b808ddb2d261aae2df3a26ff3ff810046e6de3dfbc6f7de8c98ea0a6cb632c"}, +] + +[package.dependencies] +appnope = {version = ">=0.1.2", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=8.0.0" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = ">=1.4" +packaging = ">=22" +psutil = ">=5.7" +pyzmq = ">=25" +tornado = ">=6.2" +traitlets = ">=5.4.0" + +[package.extras] +cov = ["coverage[toml]", "matplotlib", "pytest-cov", "trio"] +docs = ["intersphinx-registry", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0,<9)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "8.37.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.10" +groups = ["main"] +files = [ + {file = "ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2"}, + {file = "ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt_toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack_data = "*" +traitlets = ">=5.13.0" +typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} + +[package.extras] +all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli ; python_version < \"3.11\"", "typing_extensions"] +kernel = ["ipykernel"] +matplotlib = ["matplotlib"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "jupyter_ai", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] + +[[package]] +name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] + +[[package]] +name = "jedi" +version = "0.19.2" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, +] + +[package.dependencies] +parso = ">=0.8.4,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "json5" +version = "0.12.0" +description = "A Python implementation of the JSON5 data format." +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +files = [ + {file = "json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db"}, + {file = "json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a"}, +] + +[package.extras] +dev = ["build (==1.2.2.post1)", "coverage (==7.5.4) ; python_version < \"3.9\"", "coverage (==7.8.0) ; python_version >= \"3.9\"", "mypy (==1.14.1) ; python_version < \"3.9\"", "mypy (==1.15.0) ; python_version >= \"3.9\"", "pip (==25.0.1)", "pylint (==3.2.7) ; python_version < \"3.9\"", "pylint (==3.3.6) ; python_version >= \"3.9\"", "ruff (==0.11.2)", "twine (==6.1.0)", "uv (==0.6.11)"] + +[[package]] +name = "jsonschema" +version = "4.25.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716"}, + {file = "jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"}, + {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + +[[package]] +name = "jupyter-client" +version = "8.6.3" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, + {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, +] + +[package.dependencies] +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko ; sys_platform == \"win32\"", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-contrib-core" +version = "0.4.2" +description = "Common utilities for jupyter-contrib projects." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "jupyter_contrib_core-0.4.2.tar.gz", hash = "sha256:1887212f3ca9d4487d624c0705c20dfdf03d5a0b9ea2557d3aaeeb4c38bdcabb"}, +] + +[package.dependencies] +jupyter_core = "*" +notebook = ">=4.0" +setuptools = "*" +tornado = "*" +traitlets = "*" + +[package.extras] +testing-utils = ["mock ; python_version == \"2.7\"", "nose"] + +[[package]] +name = "jupyter-contrib-nbextensions" +version = "0.7.0" +description = "A collection of Jupyter nbextensions." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "jupyter_contrib_nbextensions-0.7.0.tar.gz", hash = "sha256:06e33f005885eb92f89cbe82711e921278201298d08ab0d886d1ba09e8c3e9ca"}, +] + +[package.dependencies] +ipython_genutils = "*" +jupyter_contrib_core = ">=0.3.3" +jupyter_core = "*" +jupyter_highlight_selected_word = ">=0.1.1" +jupyter_nbextensions_configurator = ">=0.4.0" +lxml = "*" +nbconvert = ">=6.0" +notebook = ">=6.0" +tornado = "*" +traitlets = ">=4.1" + +[package.extras] +test = ["mock ; python_version == \"3.8\"", "nbformat", "nose", "pip", "requests"] + +[[package]] +name = "jupyter-core" +version = "5.8.1" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0"}, + {file = "jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["intersphinx-registry", "myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<9)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "jupyter-highlight-selected-word" +version = "0.2.0" +description = "Jupyter notebook extension that enables highlighting every instance of the current word in the notebook." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "jupyter_highlight_selected_word-0.2.0-py2.py3-none-any.whl", hash = "sha256:9545dfa9cb057eebe3a5795604dcd3a5294ea18637e553f61a0b67c1b5903c58"}, + {file = "jupyter_highlight_selected_word-0.2.0.tar.gz", hash = "sha256:9fa740424859a807950ca08d2bfd28a35154cd32dd6d50ac4e0950022adc0e7b"}, +] + +[[package]] +name = "jupyter-nbextensions-configurator" +version = "0.6.4" +description = "jupyter serverextension providing configuration interfaces for nbextensions." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "jupyter_nbextensions_configurator-0.6.4-py2.py3-none-any.whl", hash = "sha256:fe7a7b0805b5926449692fb077e0e659bab8b27563bc68cba26854532fdf99c7"}, +] + +[package.dependencies] +jupyter-contrib-core = ">=0.3.3" +jupyter-core = "*" +jupyter-server = "*" +notebook = ">=6.0" +pyyaml = "*" +tornado = "*" +traitlets = "*" + +[package.extras] +test = ["jupyter-contrib-core[testing-utils]", "mock ; python_version == \"3.9\"", "nose", "requests", "selenium"] + +[[package]] +name = "jupyter-server" +version = "1.24.0" +description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jupyter_server-1.24.0-py3-none-any.whl", hash = "sha256:c88ddbe862966ea1aea8c3ccb89a5903abd8fbcfe5cd14090ef549d403332c37"}, + {file = "jupyter_server-1.24.0.tar.gz", hash = "sha256:23368e8e214baf82b313d4c5a0d828ca73015e1a192ce3829bd74e62fab8d046"}, +] + +[package.dependencies] +anyio = ">=3.1.0,<4" +argon2-cffi = "*" +jinja2 = "*" +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +nbconvert = ">=6.4.4" +nbformat = ">=5.2.0" +packaging = "*" +prometheus-client = "*" +pywinpty = {version = "*", markers = "os_name == \"nt\""} +pyzmq = ">=17" +Send2Trash = "*" +terminado = ">=0.8.3" +tornado = ">=6.1.0" +traitlets = ">=5.1" +websocket-client = "*" + +[package.extras] +test = ["coverage", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-mock", "pytest-timeout", "pytest-tornasync", "requests"] + +[[package]] +name = "jupyterlab" +version = "3.4.8" +description = "JupyterLab computational environment" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jupyterlab-3.4.8-py3-none-any.whl", hash = "sha256:4626a0434c76a3a22f11c4efaa1d031d2586367f72cfdbdbff6b08b6ef0060f7"}, + {file = "jupyterlab-3.4.8.tar.gz", hash = "sha256:1fafb8b657005d91603f3c3adfd6d9e8eaf33fdc601537fef09283332efe67cb"}, +] + +[package.dependencies] +ipython = "*" +jinja2 = ">=2.1" +jupyter-core = "*" +jupyter-server = ">=1.16,<2.0" +jupyterlab-server = ">=2.10,<3.0" +nbclassic = "*" +notebook = "<7" +packaging = "*" +tomli = "*" +tornado = ">=6.1.0" + +[package.extras] +test = ["check-manifest", "coverage", "jupyterlab-server[test]", "pre-commit", "pytest (>=6.0)", "pytest-check-links (>=0.5)", "pytest-console-scripts", "pytest-cov", "requests", "requests-cache", "virtualenv"] +ui-tests = ["build"] + +[[package]] +name = "jupyterlab-pygments" +version = "0.3.0" +description = "Pygments theme using JupyterLab CSS variables" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"}, + {file = "jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d"}, +] + +[[package]] +name = "jupyterlab-server" +version = "2.23.0" +description = "A set of server components for JupyterLab and JupyterLab like applications." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "jupyterlab_server-2.23.0-py3-none-any.whl", hash = "sha256:a5ea2c839336a8ba7c38c8e7b2f24cedf919f0d439f4d2e606d9322013a95788"}, + {file = "jupyterlab_server-2.23.0.tar.gz", hash = "sha256:83c01aa4ad9451cd61b383e634d939ff713850f4640c0056b2cdb2b6211a74c7"}, +] + +[package.dependencies] +babel = ">=2.10" +jinja2 = ">=3.0.3" +json5 = ">=0.9.0" +jsonschema = ">=4.17.3" +jupyter-server = ">=1.21,<3" +packaging = ">=21.3" +requests = ">=2.28" + +[package.extras] +docs = ["autodoc-traits", "jinja2 (<3.2.0)", "mistune (<4)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi (>0.8)"] +openapi = ["openapi-core (>=0.16.1,<0.17.0)", "ruamel-yaml"] +test = ["hatch", "ipykernel", "jupyterlab-server[openapi]", "openapi-spec-validator (>=0.5.1,<0.6.0)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter[server] (>=0.6.2)", "pytest-timeout", "requests-mock", "sphinxcontrib-spelling", "strict-rfc3339", "werkzeug"] + +[[package]] +name = "lxml" +version = "6.0.0" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "lxml-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:35bc626eec405f745199200ccb5c6b36f202675d204aa29bb52e27ba2b71dea8"}, + {file = "lxml-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:246b40f8a4aec341cbbf52617cad8ab7c888d944bfe12a6abd2b1f6cfb6f6082"}, + {file = "lxml-6.0.0-cp310-cp310-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2793a627e95d119e9f1e19720730472f5543a6d84c50ea33313ce328d870f2dd"}, + {file = "lxml-6.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:46b9ed911f36bfeb6338e0b482e7fe7c27d362c52fde29f221fddbc9ee2227e7"}, + {file = "lxml-6.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b4790b558bee331a933e08883c423f65bbcd07e278f91b2272489e31ab1e2b4"}, + {file = "lxml-6.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2030956cf4886b10be9a0285c6802e078ec2391e1dd7ff3eb509c2c95a69b76"}, + {file = "lxml-6.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d23854ecf381ab1facc8f353dcd9adeddef3652268ee75297c1164c987c11dc"}, + {file = "lxml-6.0.0-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:43fe5af2d590bf4691531b1d9a2495d7aab2090547eaacd224a3afec95706d76"}, + {file = "lxml-6.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74e748012f8c19b47f7d6321ac929a9a94ee92ef12bc4298c47e8b7219b26541"}, + {file = "lxml-6.0.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:43cfbb7db02b30ad3926e8fceaef260ba2fb7df787e38fa2df890c1ca7966c3b"}, + {file = "lxml-6.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:34190a1ec4f1e84af256495436b2d196529c3f2094f0af80202947567fdbf2e7"}, + {file = "lxml-6.0.0-cp310-cp310-win32.whl", hash = "sha256:5967fe415b1920a3877a4195e9a2b779249630ee49ece22021c690320ff07452"}, + {file = "lxml-6.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:f3389924581d9a770c6caa4df4e74b606180869043b9073e2cec324bad6e306e"}, + {file = "lxml-6.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:522fe7abb41309e9543b0d9b8b434f2b630c5fdaf6482bee642b34c8c70079c8"}, + {file = "lxml-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ee56288d0df919e4aac43b539dd0e34bb55d6a12a6562038e8d6f3ed07f9e36"}, + {file = "lxml-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8dd6dd0e9c1992613ccda2bcb74fc9d49159dbe0f0ca4753f37527749885c25"}, + {file = "lxml-6.0.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:d7ae472f74afcc47320238b5dbfd363aba111a525943c8a34a1b657c6be934c3"}, + {file = "lxml-6.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5592401cdf3dc682194727c1ddaa8aa0f3ddc57ca64fd03226a430b955eab6f6"}, + {file = "lxml-6.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58ffd35bd5425c3c3b9692d078bf7ab851441434531a7e517c4984d5634cd65b"}, + {file = "lxml-6.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f720a14aa102a38907c6d5030e3d66b3b680c3e6f6bc95473931ea3c00c59967"}, + {file = "lxml-6.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2a5e8d207311a0170aca0eb6b160af91adc29ec121832e4ac151a57743a1e1e"}, + {file = "lxml-6.0.0-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:2dd1cc3ea7e60bfb31ff32cafe07e24839df573a5e7c2d33304082a5019bcd58"}, + {file = "lxml-6.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2cfcf84f1defed7e5798ef4f88aa25fcc52d279be731ce904789aa7ccfb7e8d2"}, + {file = "lxml-6.0.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a52a4704811e2623b0324a18d41ad4b9fabf43ce5ff99b14e40a520e2190c851"}, + {file = "lxml-6.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c16304bba98f48a28ae10e32a8e75c349dd742c45156f297e16eeb1ba9287a1f"}, + {file = "lxml-6.0.0-cp311-cp311-win32.whl", hash = "sha256:f8d19565ae3eb956d84da3ef367aa7def14a2735d05bd275cd54c0301f0d0d6c"}, + {file = "lxml-6.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b2d71cdefda9424adff9a3607ba5bbfc60ee972d73c21c7e3c19e71037574816"}, + {file = "lxml-6.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:8a2e76efbf8772add72d002d67a4c3d0958638696f541734304c7f28217a9cab"}, + {file = "lxml-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78718d8454a6e928470d511bf8ac93f469283a45c354995f7d19e77292f26108"}, + {file = "lxml-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:84ef591495ffd3f9dcabffd6391db7bb70d7230b5c35ef5148354a134f56f2be"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2930aa001a3776c3e2601cb8e0a15d21b8270528d89cc308be4843ade546b9ab"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:219e0431ea8006e15005767f0351e3f7f9143e793e58519dc97fe9e07fae5563"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bd5913b4972681ffc9718bc2d4c53cde39ef81415e1671ff93e9aa30b46595e7"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:390240baeb9f415a82eefc2e13285016f9c8b5ad71ec80574ae8fa9605093cd7"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d6e200909a119626744dd81bae409fc44134389e03fbf1d68ed2a55a2fb10991"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca50bd612438258a91b5b3788c6621c1f05c8c478e7951899f492be42defc0da"}, + {file = "lxml-6.0.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:c24b8efd9c0f62bad0439283c2c795ef916c5a6b75f03c17799775c7ae3c0c9e"}, + {file = "lxml-6.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afd27d8629ae94c5d863e32ab0e1d5590371d296b87dae0a751fb22bf3685741"}, + {file = "lxml-6.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:54c4855eabd9fc29707d30141be99e5cd1102e7d2258d2892314cf4c110726c3"}, + {file = "lxml-6.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c907516d49f77f6cd8ead1322198bdfd902003c3c330c77a1c5f3cc32a0e4d16"}, + {file = "lxml-6.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36531f81c8214e293097cd2b7873f178997dae33d3667caaae8bdfb9666b76c0"}, + {file = "lxml-6.0.0-cp312-cp312-win32.whl", hash = "sha256:690b20e3388a7ec98e899fd54c924e50ba6693874aa65ef9cb53de7f7de9d64a"}, + {file = "lxml-6.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:310b719b695b3dd442cdfbbe64936b2f2e231bb91d998e99e6f0daf991a3eba3"}, + {file = "lxml-6.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:8cb26f51c82d77483cdcd2b4a53cda55bbee29b3c2f3ddeb47182a2a9064e4eb"}, + {file = "lxml-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da"}, + {file = "lxml-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d18a25b19ca7307045581b18b3ec9ead2b1db5ccd8719c291f0cd0a5cec6cb81"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4f0c66df4386b75d2ab1e20a489f30dc7fd9a06a896d64980541506086be1f1"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a78d6c9168f5bcb20971bf3329c2b83078611fbe1f807baadc64afc70523b3a"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29"}, + {file = "lxml-6.0.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4"}, + {file = "lxml-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca"}, + {file = "lxml-6.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf"}, + {file = "lxml-6.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4d6036c3a296707357efb375cfc24bb64cd955b9ec731abf11ebb1e40063949f"}, + {file = "lxml-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef"}, + {file = "lxml-6.0.0-cp313-cp313-win32.whl", hash = "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181"}, + {file = "lxml-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e"}, + {file = "lxml-6.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03"}, + {file = "lxml-6.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4eb114a0754fd00075c12648d991ec7a4357f9cb873042cc9a77bf3a7e30c9db"}, + {file = "lxml-6.0.0-cp38-cp38-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:7da298e1659e45d151b4028ad5c7974917e108afb48731f4ed785d02b6818994"}, + {file = "lxml-6.0.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7bf61bc4345c1895221357af8f3e89f8c103d93156ef326532d35c707e2fb19d"}, + {file = "lxml-6.0.0-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63b634facdfbad421d4b61c90735688465d4ab3a8853ac22c76ccac2baf98d97"}, + {file = "lxml-6.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:e380e85b93f148ad28ac15f8117e2fd8e5437aa7732d65e260134f83ce67911b"}, + {file = "lxml-6.0.0-cp38-cp38-win32.whl", hash = "sha256:185efc2fed89cdd97552585c624d3c908f0464090f4b91f7d92f8ed2f3b18f54"}, + {file = "lxml-6.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:f97487996a39cb18278ca33f7be98198f278d0bc3c5d0fd4d7b3d63646ca3c8a"}, + {file = "lxml-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85b14a4689d5cff426c12eefe750738648706ea2753b20c2f973b2a000d3d261"}, + {file = "lxml-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f64ccf593916e93b8d36ed55401bb7fe9c7d5de3180ce2e10b08f82a8f397316"}, + {file = "lxml-6.0.0-cp39-cp39-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:b372d10d17a701b0945f67be58fae4664fd056b85e0ff0fbc1e6c951cdbc0512"}, + {file = "lxml-6.0.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a674c0948789e9136d69065cc28009c1b1874c6ea340253db58be7622ce6398f"}, + {file = "lxml-6.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:edf6e4c8fe14dfe316939711e3ece3f9a20760aabf686051b537a7562f4da91a"}, + {file = "lxml-6.0.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:048a930eb4572829604982e39a0c7289ab5dc8abc7fc9f5aabd6fbc08c154e93"}, + {file = "lxml-6.0.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0b5fa5eda84057a4f1bbb4bb77a8c28ff20ae7ce211588d698ae453e13c6281"}, + {file = "lxml-6.0.0-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:c352fc8f36f7e9727db17adbf93f82499457b3d7e5511368569b4c5bd155a922"}, + {file = "lxml-6.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8db5dc617cb937ae17ff3403c3a70a7de9df4852a046f93e71edaec678f721d0"}, + {file = "lxml-6.0.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:2181e4b1d07dde53986023482673c0f1fba5178ef800f9ab95ad791e8bdded6a"}, + {file = "lxml-6.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b3c98d5b24c6095e89e03d65d5c574705be3d49c0d8ca10c17a8a4b5201b72f5"}, + {file = "lxml-6.0.0-cp39-cp39-win32.whl", hash = "sha256:04d67ceee6db4bcb92987ccb16e53bef6b42ced872509f333c04fb58a3315256"}, + {file = "lxml-6.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:e0b1520ef900e9ef62e392dd3d7ae4f5fa224d1dd62897a792cf353eb20b6cae"}, + {file = "lxml-6.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:e35e8aaaf3981489f42884b59726693de32dabfc438ac10ef4eb3409961fd402"}, + {file = "lxml-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:dbdd7679a6f4f08152818043dbb39491d1af3332128b3752c3ec5cebc0011a72"}, + {file = "lxml-6.0.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40442e2a4456e9910875ac12951476d36c0870dcb38a68719f8c4686609897c4"}, + {file = "lxml-6.0.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:db0efd6bae1c4730b9c863fc4f5f3c0fa3e8f05cae2c44ae141cb9dfc7d091dc"}, + {file = "lxml-6.0.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ab542c91f5a47aaa58abdd8ea84b498e8e49fe4b883d67800017757a3eb78e8"}, + {file = "lxml-6.0.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:013090383863b72c62a702d07678b658fa2567aa58d373d963cca245b017e065"}, + {file = "lxml-6.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c86df1c9af35d903d2b52d22ea3e66db8058d21dc0f59842ca5deb0595921141"}, + {file = "lxml-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4337e4aec93b7c011f7ee2e357b0d30562edd1955620fdd4aeab6aacd90d43c5"}, + {file = "lxml-6.0.0-pp39-pypy39_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ae74f7c762270196d2dda56f8dd7309411f08a4084ff2dfcc0b095a218df2e06"}, + {file = "lxml-6.0.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:059c4cbf3973a621b62ea3132934ae737da2c132a788e6cfb9b08d63a0ef73f9"}, + {file = "lxml-6.0.0-pp39-pypy39_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17f090a9bc0ce8da51a5632092f98a7e7f84bca26f33d161a98b57f7fb0004ca"}, + {file = "lxml-6.0.0-pp39-pypy39_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9da022c14baeec36edfcc8daf0e281e2f55b950249a455776f0d1adeeada4734"}, + {file = "lxml-6.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a55da151d0b0c6ab176b4e761670ac0e2667817a1e0dadd04a01d0561a219349"}, + {file = "lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml_html_clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] + +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mistune" +version = "3.1.3" +description = "A sane and fast Markdown parser with useful plugins and renderers" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9"}, + {file = "mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0"}, +] + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.11\""} + +[[package]] +name = "nbclassic" +version = "1.3.1" +description = "Jupyter Notebook as a Jupyter Server extension." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "nbclassic-1.3.1-py3-none-any.whl", hash = "sha256:96da3b4d7f877b1285e0adc956ea2ea9ea9f70a4ba7b7c03d558f6c9799118fa"}, + {file = "nbclassic-1.3.1.tar.gz", hash = "sha256:4c52da8fc88f9f73ef512cc305091d5ce726bdca19f44ed697cb5ba12dcaad3c"}, +] + +[package.dependencies] +ipykernel = "*" +ipython-genutils = "*" +nest-asyncio = ">=1.5" +notebook-shim = ">=0.2.3" + +[package.extras] +dev = ["hatch", "pre-commit"] +docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] +json-logging = ["json-logging"] +test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-jupyter", "pytest-playwright", "pytest-tornasync", "requests", "requests-unixsocket ; sys_platform != \"win32\"", "testpath"] + +[[package]] +name = "nbclient" +version = "0.10.2" +description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +optional = false +python-versions = ">=3.9.0" +groups = ["main"] +files = [ + {file = "nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d"}, + {file = "nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193"}, +] + +[package.dependencies] +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +nbformat = ">=5.1" +traitlets = ">=5.4" + +[package.extras] +dev = ["pre-commit"] +docs = ["autodoc-traits", "flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "mock", "moto", "myst-parser", "nbconvert (>=7.1.0)", "pytest (>=7.0,<8)", "pytest-asyncio", "pytest-cov (>=4.0)", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling", "testpath", "xmltodict"] +test = ["flaky", "ipykernel (>=6.19.3)", "ipython", "ipywidgets", "nbconvert (>=7.1.0)", "pytest (>=7.0,<8)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] + +[[package]] +name = "nbconvert" +version = "7.16.6" +description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b"}, + {file = "nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +bleach = {version = "!=5.0.0", extras = ["css"]} +defusedxml = "*" +jinja2 = ">=3.0" +jupyter-core = ">=4.7" +jupyterlab-pygments = "*" +markupsafe = ">=2.0" +mistune = ">=2.0.3,<4" +nbclient = ">=0.5.0" +nbformat = ">=5.7" +packaging = "*" +pandocfilters = ">=1.4.1" +pygments = ">=2.4.1" +traitlets = ">=5.1" + +[package.extras] +all = ["flaky", "ipykernel", "ipython", "ipywidgets (>=7.5)", "myst-parser", "nbsphinx (>=0.2.12)", "playwright", "pydata-sphinx-theme", "pyqtwebengine (>=5.15)", "pytest (>=7)", "sphinx (==5.0.2)", "sphinxcontrib-spelling", "tornado (>=6.1)"] +docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sphinx-theme", "sphinx (==5.0.2)", "sphinxcontrib-spelling"] +qtpdf = ["pyqtwebengine (>=5.15)"] +qtpng = ["pyqtwebengine (>=5.15)"] +serve = ["tornado (>=6.1)"] +test = ["flaky", "ipykernel", "ipywidgets (>=7.5)", "pytest (>=7)"] +webpdf = ["playwright"] + +[[package]] +name = "nbformat" +version = "5.10.4" +description = "The Jupyter Notebook format" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b"}, + {file = "nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a"}, +] + +[package.dependencies] +fastjsonschema = ">=2.15" +jsonschema = ">=2.6" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +traitlets = ">=5.1" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["pep440", "pre-commit", "pytest", "testpath"] + +[[package]] +name = "nbstripout" +version = "0.8.1" +description = "Strips outputs from Jupyter and IPython notebooks" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "nbstripout-0.8.1-py2.py3-none-any.whl", hash = "sha256:79a8c8da488d98c54c112fa87185045f0271a97d84f1d46918d6a3ee561b30e7"}, + {file = "nbstripout-0.8.1.tar.gz", hash = "sha256:eaac8b6b4e729e8dfe1e5df2c0f8ba44abc5a17a65448f0480141f80be230bb1"}, +] + +[package.dependencies] +nbformat = "*" + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +groups = ["main"] +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + +[[package]] +name = "notebook" +version = "6.5.4" +description = "A web-based notebook environment for interactive computing" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "notebook-6.5.4-py3-none-any.whl", hash = "sha256:dd17e78aefe64c768737b32bf171c1c766666a21cc79a44d37a1700771cab56f"}, + {file = "notebook-6.5.4.tar.gz", hash = "sha256:517209568bd47261e2def27a140e97d49070602eea0d226a696f42a7f16c9a4e"}, +] + +[package.dependencies] +argon2-cffi = "*" +ipykernel = "*" +ipython-genutils = "*" +jinja2 = "*" +jupyter-client = ">=5.3.4" +jupyter-core = ">=4.6.1" +nbclassic = ">=0.4.7" +nbconvert = ">=5" +nbformat = "*" +nest-asyncio = ">=1.5" +prometheus-client = "*" +pyzmq = ">=17" +Send2Trash = ">=1.8.0" +terminado = ">=0.8.3" +tornado = ">=6.1" +traitlets = ">=4.2.1" + +[package.extras] +docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] +json-logging = ["json-logging"] +test = ["coverage", "nbval", "pytest", "pytest-cov", "requests", "requests-unixsocket ; sys_platform != \"win32\"", "selenium (==4.1.5)", "testpath"] + +[[package]] +name = "notebook-shim" +version = "0.2.4" +description = "A shim layer for notebook traits and config" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef"}, + {file = "notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb"}, +] + +[package.dependencies] +jupyter-server = ">=1.8,<3" + +[package.extras] +test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync"] + +[[package]] +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "pandocfilters" +version = "1.5.1" +description = "Utilities for writing pandoc filters in python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] +files = [ + {file = "pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"}, + {file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e"}, +] + +[[package]] +name = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +groups = ["main"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "platformdirs" +version = "4.3.8" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] + +[[package]] +name = "prometheus-client" +version = "0.22.1" +description = "Python client for the Prometheus monitoring system." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "prometheus_client-0.22.1-py3-none-any.whl", hash = "sha256:cca895342e308174341b2cbf99a56bef291fbc0ef7b9e5412a0f26d653ba7094"}, + {file = "prometheus_client-0.22.1.tar.gz", hash = "sha256:190f1331e783cf21eb60bca559354e0a4d4378facecf78f5428c39b675d20d28"}, +] + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, + {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "7.0.0" +description = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"}, + {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"}, + {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"}, + {file = "psutil-7.0.0-cp36-cp36m-win32.whl", hash = "sha256:84df4eb63e16849689f76b1ffcb36db7b8de703d1bc1fe41773db487621b6c17"}, + {file = "psutil-7.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1e744154a6580bc968a0195fd25e80432d3afec619daf145b9e5ba16cc1d688e"}, + {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"}, + {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"}, + {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"}, +] + +[package.extras] +dev = ["abi3audit", "black (==24.10.0)", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest", "pytest-cov", "pytest-xdist", "requests", "rstcheck", "ruff", "setuptools", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +groups = ["main"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\" or os_name != \"nt\"" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "implementation_name == \"pypy\"" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pywin32" +version = "311" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +groups = ["main"] +markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\"" +files = [ + {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, + {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, + {file = "pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b"}, + {file = "pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151"}, + {file = "pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503"}, + {file = "pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2"}, + {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"}, + {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"}, + {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"}, + {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"}, + {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"}, + {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"}, + {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"}, + {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"}, + {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"}, + {file = "pywin32-311-cp38-cp38-win32.whl", hash = "sha256:6c6f2969607b5023b0d9ce2541f8d2cbb01c4f46bc87456017cf63b73f1e2d8c"}, + {file = "pywin32-311-cp38-cp38-win_amd64.whl", hash = "sha256:c8015b09fb9a5e188f83b7b04de91ddca4658cee2ae6f3bc483f0b21a77ef6cd"}, + {file = "pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b"}, + {file = "pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91"}, + {file = "pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d"}, +] + +[[package]] +name = "pywinpty" +version = "2.0.15" +description = "Pseudo terminal support for Windows from Python." +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "os_name == \"nt\"" +files = [ + {file = "pywinpty-2.0.15-cp310-cp310-win_amd64.whl", hash = "sha256:8e7f5de756a615a38b96cd86fa3cd65f901ce54ce147a3179c45907fa11b4c4e"}, + {file = "pywinpty-2.0.15-cp311-cp311-win_amd64.whl", hash = "sha256:9a6bcec2df2707aaa9d08b86071970ee32c5026e10bcc3cc5f6f391d85baf7ca"}, + {file = "pywinpty-2.0.15-cp312-cp312-win_amd64.whl", hash = "sha256:83a8f20b430bbc5d8957249f875341a60219a4e971580f2ba694fbfb54a45ebc"}, + {file = "pywinpty-2.0.15-cp313-cp313-win_amd64.whl", hash = "sha256:ab5920877dd632c124b4ed17bc6dd6ef3b9f86cd492b963ffdb1a67b85b0f408"}, + {file = "pywinpty-2.0.15-cp313-cp313t-win_amd64.whl", hash = "sha256:a4560ad8c01e537708d2790dbe7da7d986791de805d89dd0d3697ca59e9e4901"}, + {file = "pywinpty-2.0.15-cp39-cp39-win_amd64.whl", hash = "sha256:d261cd88fcd358cfb48a7ca0700db3e1c088c9c10403c9ebc0d8a8b57aa6a117"}, + {file = "pywinpty-2.0.15.tar.gz", hash = "sha256:312cf39153a8736c617d45ce8b6ad6cd2107de121df91c455b10ce6bba7a39b2"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "pyzmq" +version = "27.0.1" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pyzmq-27.0.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:90a4da42aa322de8a3522461e3b5fe999935763b27f69a02fced40f4e3cf9682"}, + {file = "pyzmq-27.0.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e648dca28178fc879c814cf285048dd22fd1f03e1104101106505ec0eea50a4d"}, + {file = "pyzmq-27.0.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bca8abc31799a6f3652d13f47e0b0e1cab76f9125f2283d085a3754f669b607"}, + {file = "pyzmq-27.0.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:092f4011b26d6b0201002f439bd74b38f23f3aefcb358621bdc3b230afc9b2d5"}, + {file = "pyzmq-27.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f02f30a4a6b3efe665ab13a3dd47109d80326c8fd286311d1ba9f397dc5f247"}, + {file = "pyzmq-27.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f293a1419266e3bf3557d1f8778f9e1ffe7e6b2c8df5c9dca191caf60831eb74"}, + {file = "pyzmq-27.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ce181dd1a7c6c012d0efa8ab603c34b5ee9d86e570c03415bbb1b8772eeb381c"}, + {file = "pyzmq-27.0.1-cp310-cp310-win32.whl", hash = "sha256:f65741cc06630652e82aa68ddef4986a3ab9073dd46d59f94ce5f005fa72037c"}, + {file = "pyzmq-27.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:44909aa3ed2234d69fe81e1dade7be336bcfeab106e16bdaa3318dcde4262b93"}, + {file = "pyzmq-27.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:4401649bfa0a38f0f8777f8faba7cd7eb7b5b8ae2abc7542b830dd09ad4aed0d"}, + {file = "pyzmq-27.0.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:9729190bd770314f5fbba42476abf6abe79a746eeda11d1d68fd56dd70e5c296"}, + {file = "pyzmq-27.0.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:696900ef6bc20bef6a242973943574f96c3f97d2183c1bd3da5eea4f559631b1"}, + {file = "pyzmq-27.0.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96a63aecec22d3f7fdea3c6c98df9e42973f5856bb6812c3d8d78c262fee808"}, + {file = "pyzmq-27.0.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c512824360ea7490390566ce00bee880e19b526b312b25cc0bc30a0fe95cb67f"}, + {file = "pyzmq-27.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dfb2bb5e0f7198eaacfb6796fb0330afd28f36d985a770745fba554a5903595a"}, + {file = "pyzmq-27.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4f6886c59ba93ffde09b957d3e857e7950c8fe818bd5494d9b4287bc6d5bc7f1"}, + {file = "pyzmq-27.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b99ea9d330e86ce1ff7f2456b33f1bf81c43862a5590faf4ef4ed3a63504bdab"}, + {file = "pyzmq-27.0.1-cp311-cp311-win32.whl", hash = "sha256:571f762aed89025ba8cdcbe355fea56889715ec06d0264fd8b6a3f3fa38154ed"}, + {file = "pyzmq-27.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee16906c8025fa464bea1e48128c048d02359fb40bebe5333103228528506530"}, + {file = "pyzmq-27.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:ba068f28028849da725ff9185c24f832ccf9207a40f9b28ac46ab7c04994bd41"}, + {file = "pyzmq-27.0.1-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:af7ebce2a1e7caf30c0bb64a845f63a69e76a2fadbc1cac47178f7bb6e657bdd"}, + {file = "pyzmq-27.0.1-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8f617f60a8b609a13099b313e7e525e67f84ef4524b6acad396d9ff153f6e4cd"}, + {file = "pyzmq-27.0.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d59dad4173dc2a111f03e59315c7bd6e73da1a9d20a84a25cf08325b0582b1a"}, + {file = "pyzmq-27.0.1-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5b6133c8d313bde8bd0d123c169d22525300ff164c2189f849de495e1344577"}, + {file = "pyzmq-27.0.1-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:58cca552567423f04d06a075f4b473e78ab5bdb906febe56bf4797633f54aa4e"}, + {file = "pyzmq-27.0.1-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:4b9d8e26fb600d0d69cc9933e20af08552e97cc868a183d38a5c0d661e40dfbb"}, + {file = "pyzmq-27.0.1-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2329f0c87f0466dce45bba32b63f47018dda5ca40a0085cc5c8558fea7d9fc55"}, + {file = "pyzmq-27.0.1-cp312-abi3-win32.whl", hash = "sha256:57bb92abdb48467b89c2d21da1ab01a07d0745e536d62afd2e30d5acbd0092eb"}, + {file = "pyzmq-27.0.1-cp312-abi3-win_amd64.whl", hash = "sha256:ff3f8757570e45da7a5bedaa140489846510014f7a9d5ee9301c61f3f1b8a686"}, + {file = "pyzmq-27.0.1-cp312-abi3-win_arm64.whl", hash = "sha256:df2c55c958d3766bdb3e9d858b911288acec09a9aab15883f384fc7180df5bed"}, + {file = "pyzmq-27.0.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:497bd8af534ae55dc4ef67eebd1c149ff2a0b0f1e146db73c8b5a53d83c1a5f5"}, + {file = "pyzmq-27.0.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:a066ea6ad6218b4c233906adf0ae67830f451ed238419c0db609310dd781fbe7"}, + {file = "pyzmq-27.0.1-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:72d235d6365ca73d8ce92f7425065d70f5c1e19baa458eb3f0d570e425b73a96"}, + {file = "pyzmq-27.0.1-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:313a7b374e3dc64848644ca348a51004b41726f768b02e17e689f1322366a4d9"}, + {file = "pyzmq-27.0.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:119ce8590409702394f959c159d048002cbed2f3c0645ec9d6a88087fc70f0f1"}, + {file = "pyzmq-27.0.1-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45c3e00ce16896ace2cd770ab9057a7cf97d4613ea5f2a13f815141d8b6894b9"}, + {file = "pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:678e50ec112bdc6df5a83ac259a55a4ba97a8b314c325ab26b3b5b071151bc61"}, + {file = "pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d0b96c30be9f9387b18b18b6133c75a7b1b0065da64e150fe1feb5ebf31ece1c"}, + {file = "pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88dc92d9eb5ea4968123e74db146d770b0c8d48f0e2bfb1dbc6c50a8edb12d64"}, + {file = "pyzmq-27.0.1-cp313-cp313t-win32.whl", hash = "sha256:6dcbcb34f5c9b0cefdfc71ff745459241b7d3cda5b27c7ad69d45afc0821d1e1"}, + {file = "pyzmq-27.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9fd0fda730461f510cfd9a40fafa5355d65f5e3dbdd8d6dfa342b5b3f5d1949"}, + {file = "pyzmq-27.0.1-cp313-cp313t-win_arm64.whl", hash = "sha256:56a3b1853f3954ec1f0e91085f1350cc57d18f11205e4ab6e83e4b7c414120e0"}, + {file = "pyzmq-27.0.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f98f6b7787bd2beb1f0dde03f23a0621a0c978edf673b7d8f5e7bc039cbe1b60"}, + {file = "pyzmq-27.0.1-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:351bf5d8ca0788ca85327fda45843b6927593ff4c807faee368cc5aaf9f809c2"}, + {file = "pyzmq-27.0.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5268a5a9177afff53dc6d70dffe63114ba2a6e7b20d9411cc3adeba09eeda403"}, + {file = "pyzmq-27.0.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4aca06ba295aa78bec9b33ec028d1ca08744c36294338c41432b7171060c808"}, + {file = "pyzmq-27.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1c363c6dc66352331d5ad64bb838765c6692766334a6a02fdb05e76bd408ae18"}, + {file = "pyzmq-27.0.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:87aebf4acd7249bdff8d3df03aed4f09e67078e6762cfe0aecf8d0748ff94cde"}, + {file = "pyzmq-27.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e4f22d67756518d71901edf73b38dc0eb4765cce22c8fe122cc81748d425262b"}, + {file = "pyzmq-27.0.1-cp314-cp314t-win32.whl", hash = "sha256:8c62297bc7aea2147b472ca5ca2b4389377ad82898c87cabab2a94aedd75e337"}, + {file = "pyzmq-27.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bee5248d5ec9223545f8cc4f368c2d571477ae828c99409125c3911511d98245"}, + {file = "pyzmq-27.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0fc24bf45e4a454e55ef99d7f5c8b8712539200ce98533af25a5bfa954b6b390"}, + {file = "pyzmq-27.0.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:9d16fdfd7d70a6b0ca45d36eb19f7702fa77ef6256652f17594fc9ce534c9da6"}, + {file = "pyzmq-27.0.1-cp38-cp38-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d0356a21e58c3e99248930ff73cc05b1d302ff50f41a8a47371aefb04327378a"}, + {file = "pyzmq-27.0.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a27fa11ebaccc099cac4309c799aa33919671a7660e29b3e465b7893bc64ec81"}, + {file = "pyzmq-27.0.1-cp38-cp38-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b25e72e115399a4441aad322258fa8267b873850dc7c276e3f874042728c2b45"}, + {file = "pyzmq-27.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f8c3b74f1cd577a5a9253eae7ed363f88cbb345a990ca3027e9038301d47c7f4"}, + {file = "pyzmq-27.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:19dce6c93656f9c469540350d29b128cd8ba55b80b332b431b9a1e9ff74cfd01"}, + {file = "pyzmq-27.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:da81512b83032ed6cdf85ca62e020b4c23dda87f1b6c26b932131222ccfdbd27"}, + {file = "pyzmq-27.0.1-cp38-cp38-win32.whl", hash = "sha256:7418fb5736d0d39b3ecc6bec4ff549777988feb260f5381636d8bd321b653038"}, + {file = "pyzmq-27.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:af2ee67b3688b067e20fea3fe36b823a362609a1966e7e7a21883ae6da248804"}, + {file = "pyzmq-27.0.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:05a94233fdde585eb70924a6e4929202a747eea6ed308a6171c4f1c715bbe39e"}, + {file = "pyzmq-27.0.1-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:c96702e1082eab62ae583d64c4e19c9b848359196697e536a0c57ae9bd165bd5"}, + {file = "pyzmq-27.0.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c9180d1f5b4b73e28b64e63cc6c4c097690f102aa14935a62d5dd7426a4e5b5a"}, + {file = "pyzmq-27.0.1-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e971d8680003d0af6020713e52f92109b46fedb463916e988814e04c8133578a"}, + {file = "pyzmq-27.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe632fa4501154d58dfbe1764a0495734d55f84eaf1feda4549a1f1ca76659e9"}, + {file = "pyzmq-27.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4c3874344fd5fa6d58bb51919708048ac4cab21099f40a227173cddb76b4c20b"}, + {file = "pyzmq-27.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ec09073ed67ae236785d543df3b322282acc0bdf6d1b748c3e81f3043b21cb5"}, + {file = "pyzmq-27.0.1-cp39-cp39-win32.whl", hash = "sha256:f44e7ea288d022d4bf93b9e79dafcb4a7aea45a3cbeae2116792904931cefccf"}, + {file = "pyzmq-27.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ffe6b809a97ac6dea524b3b837d5b28743d8c2f121141056d168ff0ba8f614ef"}, + {file = "pyzmq-27.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:fde26267416c8478c95432c81489b53f57b0b5d24cd5c8bfaebf5bbaac4dc90c"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:544b995a6a1976fad5d7ff01409b4588f7608ccc41be72147700af91fd44875d"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0f772eea55cccce7f45d6ecdd1d5049c12a77ec22404f6b892fae687faa87bee"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9d63d66059114a6756d09169c9209ffceabacb65b9cb0f66e6fc344b20b73e6"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1da8e645c655d86f0305fb4c65a0d848f461cd90ee07d21f254667287b5dbe50"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1843fd0daebcf843fe6d4da53b8bdd3fc906ad3e97d25f51c3fed44436d82a49"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7fb0ee35845bef1e8c4a152d766242164e138c239e3182f558ae15cb4a891f94"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f379f11e138dfd56c3f24a04164f871a08281194dd9ddf656a278d7d080c8ad0"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b978c0678cffbe8860ec9edc91200e895c29ae1ac8a7085f947f8e8864c489fb"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ebccf0d760bc92a4a7c751aeb2fef6626144aace76ee8f5a63abeb100cae87f"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:77fed80e30fa65708546c4119840a46691290efc231f6bfb2ac2a39b52e15811"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9d7b6b90da7285642f480b48c9efd1d25302fd628237d8f6f6ee39ba6b2d2d34"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d2976b7079f09f48d59dc123293ed6282fca6ef96a270f4ea0364e4e54c8e855"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2852f67371918705cc18b321695f75c5d653d5d8c4a9b946c1eec4dab2bd6fdf"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be45a895f98877271e8a0b6cf40925e0369121ce423421c20fa6d7958dc753c2"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64ca3c7c614aefcdd5e358ecdd41d1237c35fe1417d01ec0160e7cdb0a380edc"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d97b59cbd8a6c8b23524a8ce237ff9504d987dc07156258aa68ae06d2dd5f34d"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:27a78bdd384dbbe7b357af95f72efe8c494306b5ec0a03c31e2d53d6763e5307"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b007e5dcba684e888fbc90554cb12a2f4e492927c8c2761a80b7590209821743"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:95594b2ceeaa94934e3e94dd7bf5f3c3659cf1a26b1fb3edcf6e42dad7e0eaf2"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:70b719a130b81dd130a57ac0ff636dc2c0127c5b35ca5467d1b67057e3c7a4d2"}, + {file = "pyzmq-27.0.1.tar.gz", hash = "sha256:45c549204bc20e7484ffd2555f6cf02e572440ecf2f3bdd60d4404b20fddf64b"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "referencing" +version = "0.36.2" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, + {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" +typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} + +[[package]] +name = "requests" +version = "2.32.4" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rpds-py" +version = "0.26.0" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "rpds_py-0.26.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4c70c70f9169692b36307a95f3d8c0a9fcd79f7b4a383aad5eaa0e9718b79b37"}, + {file = "rpds_py-0.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:777c62479d12395bfb932944e61e915741e364c843afc3196b694db3d669fcd0"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec671691e72dff75817386aa02d81e708b5a7ec0dec6669ec05213ff6b77e1bd"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a1cb5d6ce81379401bbb7f6dbe3d56de537fb8235979843f0d53bc2e9815a79"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f789e32fa1fb6a7bf890e0124e7b42d1e60d28ebff57fe806719abb75f0e9a3"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c55b0a669976cf258afd718de3d9ad1b7d1fe0a91cd1ab36f38b03d4d4aeaaf"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70d9ec912802ecfd6cd390dadb34a9578b04f9bcb8e863d0a7598ba5e9e7ccc"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3021933c2cb7def39d927b9862292e0f4c75a13d7de70eb0ab06efed4c508c19"}, + {file = "rpds_py-0.26.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a7898b6ca3b7d6659e55cdac825a2e58c638cbf335cde41f4619e290dd0ad11"}, + {file = "rpds_py-0.26.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:12bff2ad9447188377f1b2794772f91fe68bb4bbfa5a39d7941fbebdbf8c500f"}, + {file = "rpds_py-0.26.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:191aa858f7d4902e975d4cf2f2d9243816c91e9605070aeb09c0a800d187e323"}, + {file = "rpds_py-0.26.0-cp310-cp310-win32.whl", hash = "sha256:b37a04d9f52cb76b6b78f35109b513f6519efb481d8ca4c321f6a3b9580b3f45"}, + {file = "rpds_py-0.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:38721d4c9edd3eb6670437d8d5e2070063f305bfa2d5aa4278c51cedcd508a84"}, + {file = "rpds_py-0.26.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed"}, + {file = "rpds_py-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3"}, + {file = "rpds_py-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107"}, + {file = "rpds_py-0.26.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a"}, + {file = "rpds_py-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318"}, + {file = "rpds_py-0.26.0-cp311-cp311-win32.whl", hash = "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a"}, + {file = "rpds_py-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03"}, + {file = "rpds_py-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41"}, + {file = "rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d"}, + {file = "rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323"}, + {file = "rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158"}, + {file = "rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3"}, + {file = "rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2"}, + {file = "rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44"}, + {file = "rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c"}, + {file = "rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8"}, + {file = "rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d"}, + {file = "rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1"}, + {file = "rpds_py-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9"}, + {file = "rpds_py-0.26.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9"}, + {file = "rpds_py-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba"}, + {file = "rpds_py-0.26.0-cp313-cp313-win32.whl", hash = "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b"}, + {file = "rpds_py-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5"}, + {file = "rpds_py-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256"}, + {file = "rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618"}, + {file = "rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed"}, + {file = "rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632"}, + {file = "rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c"}, + {file = "rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0"}, + {file = "rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9"}, + {file = "rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9"}, + {file = "rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a"}, + {file = "rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387"}, + {file = "rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af"}, + {file = "rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33"}, + {file = "rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953"}, + {file = "rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9"}, + {file = "rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37"}, + {file = "rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867"}, + {file = "rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da"}, + {file = "rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b"}, + {file = "rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a"}, + {file = "rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170"}, + {file = "rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e"}, + {file = "rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f"}, + {file = "rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7"}, + {file = "rpds_py-0.26.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:7a48af25d9b3c15684059d0d1fc0bc30e8eee5ca521030e2bffddcab5be40226"}, + {file = "rpds_py-0.26.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0c71c2f6bf36e61ee5c47b2b9b5d47e4d1baad6426bfed9eea3e858fc6ee8806"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d815d48b1804ed7867b539236b6dd62997850ca1c91cad187f2ddb1b7bbef19"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84cfbd4d4d2cdeb2be61a057a258d26b22877266dd905809e94172dff01a42ae"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbaa70553ca116c77717f513e08815aec458e6b69a028d4028d403b3bc84ff37"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39bfea47c375f379d8e87ab4bb9eb2c836e4f2069f0f65731d85e55d74666387"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1533b7eb683fb5f38c1d68a3c78f5fdd8f1412fa6b9bf03b40f450785a0ab915"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c5ab0ee51f560d179b057555b4f601b7df909ed31312d301b99f8b9fc6028284"}, + {file = "rpds_py-0.26.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e5162afc9e0d1f9cae3b577d9c29ddbab3505ab39012cb794d94a005825bde21"}, + {file = "rpds_py-0.26.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:43f10b007033f359bc3fa9cd5e6c1e76723f056ffa9a6b5c117cc35720a80292"}, + {file = "rpds_py-0.26.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e3730a48e5622e598293eee0762b09cff34dd3f271530f47b0894891281f051d"}, + {file = "rpds_py-0.26.0-cp39-cp39-win32.whl", hash = "sha256:4b1f66eb81eab2e0ff5775a3a312e5e2e16bf758f7b06be82fb0d04078c7ac51"}, + {file = "rpds_py-0.26.0-cp39-cp39-win_amd64.whl", hash = "sha256:519067e29f67b5c90e64fb1a6b6e9d2ec0ba28705c51956637bac23a2f4ddae1"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3c0909c5234543ada2515c05dc08595b08d621ba919629e94427e8e03539c958"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c1fb0cda2abcc0ac62f64e2ea4b4e64c57dfd6b885e693095460c61bde7bb18e"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d142d2d6cf9b31c12aa4878d82ed3b2324226270b89b676ac62ccd7df52d08"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a547e21c5610b7e9093d870be50682a6a6cf180d6da0f42c47c306073bfdbbf6"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35e9a70a0f335371275cdcd08bc5b8051ac494dd58bff3bbfb421038220dc871"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dfa6115c6def37905344d56fb54c03afc49104e2ca473d5dedec0f6606913b4"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:313cfcd6af1a55a286a3c9a25f64af6d0e46cf60bc5798f1db152d97a216ff6f"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7bf2496fa563c046d05e4d232d7b7fd61346e2402052064b773e5c378bf6f73"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:aa81873e2c8c5aa616ab8e017a481a96742fdf9313c40f14338ca7dbf50cb55f"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:68ffcf982715f5b5b7686bdd349ff75d422e8f22551000c24b30eaa1b7f7ae84"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6188de70e190847bb6db3dc3981cbadff87d27d6fe9b4f0e18726d55795cee9b"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1c962145c7473723df9722ba4c058de12eb5ebedcb4e27e7d902920aa3831ee8"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a90a13408a7a856b87be8a9f008fff53c5080eea4e4180f6c2e546e4a972fb5d"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ac51b65e8dc76cf4949419c54c5528adb24fc721df722fd452e5fbc236f5c40"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b2093224a18c6508d95cfdeba8db9cbfd6f3494e94793b58972933fcee4c6d"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f01a5d6444a3258b00dc07b6ea4733e26f8072b788bef750baa37b370266137"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6e2c12160c72aeda9d1283e612f68804621f448145a210f1bf1d79151c47090"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb28c1f569f8d33b2b5dcd05d0e6ef7005d8639c54c2f0be824f05aedf715255"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1766b5724c3f779317d5321664a343c07773c8c5fd1532e4039e6cc7d1a815be"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b6d9e5a2ed9c4988c8f9b28b3bc0e3e5b1aaa10c28d210a594ff3a8c02742daf"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:b5f7a446ddaf6ca0fad9a5535b56fbfc29998bf0e0b450d174bbec0d600e1d72"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:eed5ac260dd545fbc20da5f4f15e7efe36a55e0e7cf706e4ec005b491a9546a0"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:582462833ba7cee52e968b0341b85e392ae53d44c0f9af6a5927c80e539a8b67"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69a607203441e07e9a8a529cff1d5b73f6a160f22db1097211e6212a68567d11"}, + {file = "rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0"}, +] + +[[package]] +name = "send2trash" +version = "1.8.3" +description = "Send file to trash natively under Mac OS X, Windows and Linux" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main"] +files = [ + {file = "Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9"}, + {file = "Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf"}, +] + +[package.extras] +nativelib = ["pyobjc-framework-Cocoa ; sys_platform == \"darwin\"", "pywin32 ; sys_platform == \"win32\""] +objc = ["pyobjc-framework-Cocoa ; sys_platform == \"darwin\""] +win32 = ["pywin32 ; sys_platform == \"win32\""] + +[[package]] +name = "setuptools" +version = "80.9.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "soupsieve" +version = "2.7" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, + {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "terminado" +version = "0.18.1" +description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0"}, + {file = "terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e"}, +] + +[package.dependencies] +ptyprocess = {version = "*", markers = "os_name != \"nt\""} +pywinpty = {version = ">=1.1.0", markers = "os_name == \"nt\""} +tornado = ">=6.1.0" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] +typing = ["mypy (>=1.6,<2.0)", "traitlets (>=5.11.1)"] + +[[package]] +name = "tinycss2" +version = "1.4.0" +description = "A tiny CSS parser" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289"}, + {file = "tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7"}, +] + +[package.dependencies] +webencodings = ">=0.4" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["pytest", "ruff"] + +[[package]] +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[[package]] +name = "tornado" +version = "6.5.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7"}, + {file = "tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6"}, + {file = "tornado-6.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b77e9dfa7ed69754a54c89d82ef746398be82f749df69c4d3abe75c4d1ff4888"}, + {file = "tornado-6.5.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253b76040ee3bab8bcf7ba9feb136436a3787208717a1fb9f2c16b744fba7331"}, + {file = "tornado-6.5.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308473f4cc5a76227157cdf904de33ac268af770b2c5f05ca6c1161d82fdd95e"}, + {file = "tornado-6.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:caec6314ce8a81cf69bd89909f4b633b9f523834dc1a352021775d45e51d9401"}, + {file = "tornado-6.5.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:13ce6e3396c24e2808774741331638ee6c2f50b114b97a55c5b442df65fd9692"}, + {file = "tornado-6.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5cae6145f4cdf5ab24744526cc0f55a17d76f02c98f4cff9daa08ae9a217448a"}, + {file = "tornado-6.5.1-cp39-abi3-win32.whl", hash = "sha256:e0a36e1bc684dca10b1aa75a31df8bdfed656831489bc1e6a6ebed05dc1ec365"}, + {file = "tornado-6.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:908e7d64567cecd4c2b458075589a775063453aeb1d2a1853eedb806922f568b"}, + {file = "tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7"}, + {file = "tornado-6.5.1.tar.gz", hash = "sha256:84ceece391e8eb9b2b95578db65e920d2a61070260594819589609ba9bc6308c"}, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typing-extensions" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "websocket-client" +version = "1.8.0" +description = "WebSocket client for Python with low level API options" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, + {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, +] + +[package.extras] +docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[metadata] +lock-version = "2.1" +python-versions = ">=3.10, <3.11" +content-hash = "91c5226f4b17adb11b3b6ea05e8411afc1d35578101a3b183c2449c8b6c1720c" diff --git a/terra-docker-image-documentation-dev/terra-docker-versions-dev.json b/terra-docker-image-documentation-dev/terra-docker-versions-dev.json new file mode 100644 index 00000000..6987afc6 --- /dev/null +++ b/terra-docker-image-documentation-dev/terra-docker-versions-dev.json @@ -0,0 +1,86 @@ +[ + { + "id": "terra-jupyter-bioconductor", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-bioconductor:2.2.7", + "label": "R / Bioconductor: (Python 3.10.16, R 4.5.0, Bioconductor 3.21, tidyverse 2.0.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-bioconductor-2.2.7-versions.json", + "requiresSpark": false, + "updated": "2025-04-18", + "version": "2.2.7" + }, + { + "id": "terra-jupyter-hail", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-hail:1.1.14", + "label": "Hail: (Python 3.10.16, Spark 3.5.0, hail 0.2.134)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-hail-1.1.14-versions.json", + "requiresSpark": true, + "updated": "2025-05-01", + "version": "1.1.14" + }, + { + "id": "terra-jupyter-python", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-python:1.1.6", + "label": "Python: (Python 3.10.16, pandas 2.0.3, scikit-learn 1.6.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-python-1.1.6-versions.json", + "requiresSpark": false, + "updated": "2025-04-17", + "version": "1.1.6" + }, + { + "id": "terra-jupyter-gatk", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-gatk:2.3.9", + "label": "Default: (GATK 4.3.0.0, Python 3.10.16, R 4.5.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-gatk-2.3.9-versions.json", + "requiresSpark": false, + "updated": "2025-04-20", + "version": "2.3.9" + }, + { + "id": "Pegasus", + "image": "cumulusprod/pegasus-terra:1.6.0", + "isCommunity": true, + "label": "Pegasus (Pegasuspy 1.6.0, Python 3.7.12, harmony-pytorch 0.1.7, nmf-torch 0.1.1, scVI-tools 0.16.0)", + "packages": "https://raw.githubusercontent.com/lilab-bcb/cumulus/master/docker/pegasus-terra/1.6.0/pegasus-terra-1_6_0-versions.json", + "requiresSpark": false, + "updated": "2022-04-16", + "version": "1.6.0" + }, + { + "id": "RStudio", + "image": "us.gcr.io/broad-dsp-gcr-public/anvil-rstudio-bioconductor:3.21.0", + "isRStudio": true, + "label": "RStudio (R 4.5.1, Bioconductor 3.21, Python 3.12.3)", + "packages": "https://raw.githubusercontent.com/anvilproject/anvil-docker/main/anvil-rstudio-bioconductor/info/anvil-rstudio-bioconductor-3.21.0-versions.json", + "requiresSpark": false, + "updated": "2025-08-04", + "version": "3.21.0" + }, + { + "id": "LegacyRStudio", + "image": "us.gcr.io/broad-dsp-gcr-public/anvil-rstudio-bioconductor:3.20.1", + "isRStudio": true, + "label": "Legacy RStudio (R 4.4.2, Bioconductor 3.20, Python 3.12.3)", + "packages": "https://raw.githubusercontent.com/anvilproject/anvil-docker/main/anvil-rstudio-bioconductor/info/anvil-rstudio-bioconductor-3.20.1-versions.json", + "requiresSpark": false, + "updated": "2024-12-12", + "version": "3.20.1" + }, + { + "id": "terra-jupyter-gatk_legacy", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-gatk:2.2.14", + "label": "Legacy GATK: (GATK 4.2.4.0, Python 3.7.12, R 4.3.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-gatk-2.2.14-versions.json", + "requiresSpark": false, + "updated": "2023-06-09", + "version": "2.2.14" + }, + { + "id": "terra-jupyter-bioconductor_legacy", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-bioconductor:2.2.6", + "label": "Legacy R / Bioconductor (R 4.4.0, Bioconductor 3.19, Python 3.10.12)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-bioconductor-2.2.6-versions.json", + "requiresSpark": false, + "updated": "2025-04-24", + "version": "2.2.6" + } +] \ No newline at end of file diff --git a/terra-docker-image-documentation/terra-docker-versions-8-14.json b/terra-docker-image-documentation/terra-docker-versions-8-14.json new file mode 100644 index 00000000..e9d2af2b --- /dev/null +++ b/terra-docker-image-documentation/terra-docker-versions-8-14.json @@ -0,0 +1,86 @@ +[ + { + "id": "terra-jupyter-bioconductor", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-bioconductor:2.2.7", + "label": "R / Bioconductor: (Python 3.10.16, R 4.5.0, Bioconductor 3.21, tidyverse 2.0.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-bioconductor-2.2.7-versions.json", + "requiresSpark": false, + "updated": "2025-04-18", + "version": "2.2.7" + }, + { + "id": "terra-jupyter-hail", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-hail:1.1.14", + "label": "Hail: (Python 3.10.16, Spark 3.5.0, hail 0.2.134)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-hail-1.1.14-versions.json", + "requiresSpark": true, + "updated": "2025-05-01", + "version": "1.1.14" + }, + { + "id": "terra-jupyter-python", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-python:1.1.6", + "label": "Python: (Python 3.10.16, pandas 2.0.3, scikit-learn 1.6.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-python-1.1.6-versions.json", + "requiresSpark": false, + "updated": "2025-04-17", + "version": "1.1.6" + }, + { + "id": "terra-jupyter-gatk", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-gatk:2.3.9", + "label": "Default: (GATK 4.3.0.0, Python 3.10.16, R 4.5.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-gatk-2.3.9-versions.json", + "requiresSpark": false, + "updated": "2025-04-20", + "version": "2.3.9" + }, + { + "id": "Pegasus", + "image": "cumulusprod/pegasus-terra:1.6.0", + "isCommunity": true, + "label": "Pegasus (Pegasuspy 1.6.0, Python 3.7.12, harmony-pytorch 0.1.7, nmf-torch 0.1.1, scVI-tools 0.16.0)", + "packages": "https://raw.githubusercontent.com/lilab-bcb/cumulus/master/docker/pegasus-terra/1.6.0/pegasus-terra-1_6_0-versions.json", + "requiresSpark": false, + "updated": "2022-04-16", + "version": "1.6.0" + }, + { + "id": "RStudio", + "image": "us.gcr.io/broad-dsp-gcr-public/anvil-rstudio-bioconductor:3.21.0", + "isRStudio": true, + "label": "RStudio (R 4.5.1, Bioconductor 3.21, Python 3.12.3)", + "packages": "https://raw.githubusercontent.com/anvilproject/anvil-docker/main/anvil-rstudio-bioconductor/info/anvil-rstudio-bioconductor-3.21.0-versions.json", + "requiresSpark": false, + "updated": "2025-08-04", + "version": "3.21.0" + }, + { + "id": "LegacyRStudio", + "image": "us.gcr.io/broad-dsp-gcr-public/anvil-rstudio-bioconductor:3.20.1", + "isRStudio": true, + "label": "Legacy RStudio (R 4.4.2, Bioconductor 3.20, Python 3.12.3)", + "packages": "https://raw.githubusercontent.com/anvilproject/anvil-docker/main/anvil-rstudio-bioconductor/info/anvil-rstudio-bioconductor-3.20.1-versions.json", + "requiresSpark": false, + "updated": "2024-12-12", + "version": "3.20.1" + }, + { + "id": "terra-jupyter-gatk_legacy", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-gatk:2.0.9", + "label": "Legacy GATK: (GATK 4.2.4.0, Python 3.7.12, R 4.1.3)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-gatk-2.0.9-versions.json", + "requiresSpark": false, + "updated": "2022-04-25", + "version": "2.0.9" + }, + { + "id": "terra-jupyter-bioconductor_legacy", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-bioconductor:2.2.6", + "label": "Legacy R / Bioconductor (R 4.4.0, Bioconductor 3.19, Python 3.10.12)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-bioconductor-2.2.6-versions.json", + "requiresSpark": false, + "updated": "2025-04-24", + "version": "2.2.6" + } +] \ No newline at end of file diff --git a/terra-docker-image-documentation/terra-docker-versions-new.json b/terra-docker-image-documentation/terra-docker-versions-new.json new file mode 100644 index 00000000..e9d2af2b --- /dev/null +++ b/terra-docker-image-documentation/terra-docker-versions-new.json @@ -0,0 +1,86 @@ +[ + { + "id": "terra-jupyter-bioconductor", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-bioconductor:2.2.7", + "label": "R / Bioconductor: (Python 3.10.16, R 4.5.0, Bioconductor 3.21, tidyverse 2.0.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-bioconductor-2.2.7-versions.json", + "requiresSpark": false, + "updated": "2025-04-18", + "version": "2.2.7" + }, + { + "id": "terra-jupyter-hail", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-hail:1.1.14", + "label": "Hail: (Python 3.10.16, Spark 3.5.0, hail 0.2.134)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-hail-1.1.14-versions.json", + "requiresSpark": true, + "updated": "2025-05-01", + "version": "1.1.14" + }, + { + "id": "terra-jupyter-python", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-python:1.1.6", + "label": "Python: (Python 3.10.16, pandas 2.0.3, scikit-learn 1.6.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-python-1.1.6-versions.json", + "requiresSpark": false, + "updated": "2025-04-17", + "version": "1.1.6" + }, + { + "id": "terra-jupyter-gatk", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-gatk:2.3.9", + "label": "Default: (GATK 4.3.0.0, Python 3.10.16, R 4.5.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-gatk-2.3.9-versions.json", + "requiresSpark": false, + "updated": "2025-04-20", + "version": "2.3.9" + }, + { + "id": "Pegasus", + "image": "cumulusprod/pegasus-terra:1.6.0", + "isCommunity": true, + "label": "Pegasus (Pegasuspy 1.6.0, Python 3.7.12, harmony-pytorch 0.1.7, nmf-torch 0.1.1, scVI-tools 0.16.0)", + "packages": "https://raw.githubusercontent.com/lilab-bcb/cumulus/master/docker/pegasus-terra/1.6.0/pegasus-terra-1_6_0-versions.json", + "requiresSpark": false, + "updated": "2022-04-16", + "version": "1.6.0" + }, + { + "id": "RStudio", + "image": "us.gcr.io/broad-dsp-gcr-public/anvil-rstudio-bioconductor:3.21.0", + "isRStudio": true, + "label": "RStudio (R 4.5.1, Bioconductor 3.21, Python 3.12.3)", + "packages": "https://raw.githubusercontent.com/anvilproject/anvil-docker/main/anvil-rstudio-bioconductor/info/anvil-rstudio-bioconductor-3.21.0-versions.json", + "requiresSpark": false, + "updated": "2025-08-04", + "version": "3.21.0" + }, + { + "id": "LegacyRStudio", + "image": "us.gcr.io/broad-dsp-gcr-public/anvil-rstudio-bioconductor:3.20.1", + "isRStudio": true, + "label": "Legacy RStudio (R 4.4.2, Bioconductor 3.20, Python 3.12.3)", + "packages": "https://raw.githubusercontent.com/anvilproject/anvil-docker/main/anvil-rstudio-bioconductor/info/anvil-rstudio-bioconductor-3.20.1-versions.json", + "requiresSpark": false, + "updated": "2024-12-12", + "version": "3.20.1" + }, + { + "id": "terra-jupyter-gatk_legacy", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-gatk:2.0.9", + "label": "Legacy GATK: (GATK 4.2.4.0, Python 3.7.12, R 4.1.3)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-gatk-2.0.9-versions.json", + "requiresSpark": false, + "updated": "2022-04-25", + "version": "2.0.9" + }, + { + "id": "terra-jupyter-bioconductor_legacy", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-bioconductor:2.2.6", + "label": "Legacy R / Bioconductor (R 4.4.0, Bioconductor 3.19, Python 3.10.12)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-bioconductor-2.2.6-versions.json", + "requiresSpark": false, + "updated": "2025-04-24", + "version": "2.2.6" + } +] \ No newline at end of file diff --git a/terra-docker-image-documentation/terra-docker-versions.json b/terra-docker-image-documentation/terra-docker-versions.json new file mode 100644 index 00000000..e9d2af2b --- /dev/null +++ b/terra-docker-image-documentation/terra-docker-versions.json @@ -0,0 +1,86 @@ +[ + { + "id": "terra-jupyter-bioconductor", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-bioconductor:2.2.7", + "label": "R / Bioconductor: (Python 3.10.16, R 4.5.0, Bioconductor 3.21, tidyverse 2.0.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-bioconductor-2.2.7-versions.json", + "requiresSpark": false, + "updated": "2025-04-18", + "version": "2.2.7" + }, + { + "id": "terra-jupyter-hail", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-hail:1.1.14", + "label": "Hail: (Python 3.10.16, Spark 3.5.0, hail 0.2.134)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-hail-1.1.14-versions.json", + "requiresSpark": true, + "updated": "2025-05-01", + "version": "1.1.14" + }, + { + "id": "terra-jupyter-python", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-python:1.1.6", + "label": "Python: (Python 3.10.16, pandas 2.0.3, scikit-learn 1.6.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-python-1.1.6-versions.json", + "requiresSpark": false, + "updated": "2025-04-17", + "version": "1.1.6" + }, + { + "id": "terra-jupyter-gatk", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-gatk:2.3.9", + "label": "Default: (GATK 4.3.0.0, Python 3.10.16, R 4.5.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-gatk-2.3.9-versions.json", + "requiresSpark": false, + "updated": "2025-04-20", + "version": "2.3.9" + }, + { + "id": "Pegasus", + "image": "cumulusprod/pegasus-terra:1.6.0", + "isCommunity": true, + "label": "Pegasus (Pegasuspy 1.6.0, Python 3.7.12, harmony-pytorch 0.1.7, nmf-torch 0.1.1, scVI-tools 0.16.0)", + "packages": "https://raw.githubusercontent.com/lilab-bcb/cumulus/master/docker/pegasus-terra/1.6.0/pegasus-terra-1_6_0-versions.json", + "requiresSpark": false, + "updated": "2022-04-16", + "version": "1.6.0" + }, + { + "id": "RStudio", + "image": "us.gcr.io/broad-dsp-gcr-public/anvil-rstudio-bioconductor:3.21.0", + "isRStudio": true, + "label": "RStudio (R 4.5.1, Bioconductor 3.21, Python 3.12.3)", + "packages": "https://raw.githubusercontent.com/anvilproject/anvil-docker/main/anvil-rstudio-bioconductor/info/anvil-rstudio-bioconductor-3.21.0-versions.json", + "requiresSpark": false, + "updated": "2025-08-04", + "version": "3.21.0" + }, + { + "id": "LegacyRStudio", + "image": "us.gcr.io/broad-dsp-gcr-public/anvil-rstudio-bioconductor:3.20.1", + "isRStudio": true, + "label": "Legacy RStudio (R 4.4.2, Bioconductor 3.20, Python 3.12.3)", + "packages": "https://raw.githubusercontent.com/anvilproject/anvil-docker/main/anvil-rstudio-bioconductor/info/anvil-rstudio-bioconductor-3.20.1-versions.json", + "requiresSpark": false, + "updated": "2024-12-12", + "version": "3.20.1" + }, + { + "id": "terra-jupyter-gatk_legacy", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-gatk:2.0.9", + "label": "Legacy GATK: (GATK 4.2.4.0, Python 3.7.12, R 4.1.3)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-gatk-2.0.9-versions.json", + "requiresSpark": false, + "updated": "2022-04-25", + "version": "2.0.9" + }, + { + "id": "terra-jupyter-bioconductor_legacy", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-bioconductor:2.2.6", + "label": "Legacy R / Bioconductor (R 4.4.0, Bioconductor 3.19, Python 3.10.12)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-bioconductor-2.2.6-versions.json", + "requiresSpark": false, + "updated": "2025-04-24", + "version": "2.2.6" + } +] \ No newline at end of file diff --git a/terra-docker-versions-dev.json b/terra-docker-versions-dev.json new file mode 100644 index 00000000..6987afc6 --- /dev/null +++ b/terra-docker-versions-dev.json @@ -0,0 +1,86 @@ +[ + { + "id": "terra-jupyter-bioconductor", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-bioconductor:2.2.7", + "label": "R / Bioconductor: (Python 3.10.16, R 4.5.0, Bioconductor 3.21, tidyverse 2.0.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-bioconductor-2.2.7-versions.json", + "requiresSpark": false, + "updated": "2025-04-18", + "version": "2.2.7" + }, + { + "id": "terra-jupyter-hail", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-hail:1.1.14", + "label": "Hail: (Python 3.10.16, Spark 3.5.0, hail 0.2.134)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-hail-1.1.14-versions.json", + "requiresSpark": true, + "updated": "2025-05-01", + "version": "1.1.14" + }, + { + "id": "terra-jupyter-python", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-python:1.1.6", + "label": "Python: (Python 3.10.16, pandas 2.0.3, scikit-learn 1.6.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-python-1.1.6-versions.json", + "requiresSpark": false, + "updated": "2025-04-17", + "version": "1.1.6" + }, + { + "id": "terra-jupyter-gatk", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-gatk:2.3.9", + "label": "Default: (GATK 4.3.0.0, Python 3.10.16, R 4.5.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-gatk-2.3.9-versions.json", + "requiresSpark": false, + "updated": "2025-04-20", + "version": "2.3.9" + }, + { + "id": "Pegasus", + "image": "cumulusprod/pegasus-terra:1.6.0", + "isCommunity": true, + "label": "Pegasus (Pegasuspy 1.6.0, Python 3.7.12, harmony-pytorch 0.1.7, nmf-torch 0.1.1, scVI-tools 0.16.0)", + "packages": "https://raw.githubusercontent.com/lilab-bcb/cumulus/master/docker/pegasus-terra/1.6.0/pegasus-terra-1_6_0-versions.json", + "requiresSpark": false, + "updated": "2022-04-16", + "version": "1.6.0" + }, + { + "id": "RStudio", + "image": "us.gcr.io/broad-dsp-gcr-public/anvil-rstudio-bioconductor:3.21.0", + "isRStudio": true, + "label": "RStudio (R 4.5.1, Bioconductor 3.21, Python 3.12.3)", + "packages": "https://raw.githubusercontent.com/anvilproject/anvil-docker/main/anvil-rstudio-bioconductor/info/anvil-rstudio-bioconductor-3.21.0-versions.json", + "requiresSpark": false, + "updated": "2025-08-04", + "version": "3.21.0" + }, + { + "id": "LegacyRStudio", + "image": "us.gcr.io/broad-dsp-gcr-public/anvil-rstudio-bioconductor:3.20.1", + "isRStudio": true, + "label": "Legacy RStudio (R 4.4.2, Bioconductor 3.20, Python 3.12.3)", + "packages": "https://raw.githubusercontent.com/anvilproject/anvil-docker/main/anvil-rstudio-bioconductor/info/anvil-rstudio-bioconductor-3.20.1-versions.json", + "requiresSpark": false, + "updated": "2024-12-12", + "version": "3.20.1" + }, + { + "id": "terra-jupyter-gatk_legacy", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-gatk:2.2.14", + "label": "Legacy GATK: (GATK 4.2.4.0, Python 3.7.12, R 4.3.0)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-gatk-2.2.14-versions.json", + "requiresSpark": false, + "updated": "2023-06-09", + "version": "2.2.14" + }, + { + "id": "terra-jupyter-bioconductor_legacy", + "image": "us.gcr.io/broad-dsp-gcr-public/terra-jupyter-bioconductor:2.2.6", + "label": "Legacy R / Bioconductor (R 4.4.0, Bioconductor 3.19, Python 3.10.12)", + "packages": "https://storage.googleapis.com/terra-docker-image-documentation/terra-jupyter-bioconductor-2.2.6-versions.json", + "requiresSpark": false, + "updated": "2025-04-24", + "version": "2.2.6" + } +] \ No newline at end of file diff --git a/terra-jupyter-bioconductor/Dockerfile b/terra-jupyter-bioconductor/Dockerfile index d7b5efb2..0c856c35 100644 --- a/terra-jupyter-bioconductor/Dockerfile +++ b/terra-jupyter-bioconductor/Dockerfile @@ -1 +1,22 @@ FROM us.gcr.io/broad-dsp-gcr-public/terra-jupyter-r:2.2.7 + + { + "id": "RStudio", + "image": "us.gcr.io/broad-dsp-gcr-public/anvil-rstudio-bioconductor:3.21.0", + "isRStudio": true, + "label": "RStudio (R 4.5.1, Bioconductor 3.21, Python 3.12.3)", + "packages": "https://raw.githubusercontent.com/anvilproject/anvil-docker/main/anvil-rstudio-bioconductor/info/anvil-rstudio-bioconductor-3.21.0-versions.json", + "requiresSpark": false, + "updated": "2025-08-04", + "version": "3.21.0" + }, + { + "id": "LegacyRStudio", + "image": "us.gcr.io/broad-dsp-gcr-public/anvil-rstudio-bioconductor:3.20.1", + "isRStudio": true, + "label": "Legacy RStudio (R 4.4.2, Bioconductor 3.20, Python 3.12.3)", + "packages": "https://raw.githubusercontent.com/anvilproject/anvil-docker/main/anvil-rstudio-bioconductor/info/anvil-rstudio-bioconductor-3.20.1-versions.json", + "requiresSpark": false, + "updated": "2024-12-12", + "version": "3.20.1" + }, \ No newline at end of file diff --git a/terra-jupyter-hail-1.1.14-versions.json b/terra-jupyter-hail-1.1.14-versions.json new file mode 100644 index 00000000..57e947fd --- /dev/null +++ b/terra-jupyter-hail-1.1.14-versions.json @@ -0,0 +1,505 @@ +{ + "python": { + "Brotli": "1.1.0", + "Cython": "3.0.12", + "Deprecated": "1.2.15", + "Farama-Notifications": "0.0.4", + "GitPython": "3.1.43", + "ImageHash": "4.3.1", + "Jinja2": "3.1.5", + "Mako": "1.3.10", + "Markdown": "3.7", + "MarkupSafe": "2.1.2", + "PyJWT": "2.10.1", + "PySocks": "1.7.1", + "PyVCF3": "1.0.3", + "PyWavelets": "1.8.0", + "PyYAML": "6.0.2", + "Pygments": "2.18.0", + "SQLAlchemy": "2.0.36", + "SecretStorage": "3.3.3", + "Send2Trash": "1.8.3", + "Shapely": "1.8.5.post1", + "Theano": "1.0.5", + "Theano-PyMC": "1.1.2", + "Werkzeug": "3.1.3", + "absl-py": "1.4.0", + "aiodns": "2.0.0", + "aiofiles": "22.1.0", + "aiohttp": "3.9.5", + "aiohttp-cors": "0.7.0", + "aiosignal": "1.3.2", + "aiosqlite": "0.20.0", + "ansicolors": "1.1.8", + "anyio": "3.7.1", + "apache-beam": "2.46.0", + "appdirs": "1.4.4", + "archspec": "0.2.3", + "argon2-cffi": "23.1.0", + "argon2-cffi-bindings": "21.2.0", + "array_record": "0.6.0", + "arrow": "1.3.0", + "arviz": "0.21.0", + "astroid": "3.3.9", + "asttokens": "3.0.0", + "astunparse": "1.6.3", + "async-timeout": "4.0.3", + "atpublic": "4.1.0", + "attrs": "24.3.0", + "avro": "1.11.3", + "azure-common": "1.1.28", + "azure-core": "1.33.0", + "azure-identity": "1.21.0", + "azure-mgmt-core": "1.5.0", + "azure-mgmt-storage": "20.1.0", + "azure-storage-blob": "12.25.1", + "babel": "2.16.0", + "backports.tarfile": "1.2.0", + "beatrix_jupyterlab": "2024.920.84202", + "beautifulsoup4": "4.12.3", + "bgzip": "0.3.5", + "bidict": "0.23.1", + "bigframes": "0.22.0", + "biopython": "1.85", + "bleach": "6.0.0", + "blessed": "1.20.0", + "bokeh": "3.4.3", + "boltons": "24.0.0", + "boto3": "1.38.6", + "botocore": "1.38.6", + "brewer2mpl": "1.4.1", + "bx-python": "0.13.0", + "cached-property": "2.0.1", + "cachetools": "4.2.4", + "certifi": "2025.1.31", + "cffi": "1.17.1", + "charset-normalizer": "3.4.0", + "cli-builder": "0.1.5", + "click": "8.1.8", + "click-plugins": "1.1.1", + "cligj": "0.7.2", + "cloud-tpu-client": "0.10", + "cloud-tpu-profiler": "2.4.0", + "cloudpickle": "2.2.1", + "colorama": "0.4.6", + "colorful": "0.5.6", + "comm": "0.2.2", + "commonmark": "0.9.1", + "conda": "24.11.2", + "conda-libmamba-solver": "24.11.1", + "conda-package-handling": "2.4.0", + "conda_package_streaming": "0.11.0", + "contourpy": "1.3.1", + "crcmod": "1.7", + "cromshell": "2.0.0", + "cryptography": "42.0.8", + "cupy-cuda12x": "13.3.0", + "cycler": "0.12.1", + "daal": "2025.4.0", + "dacite": "1.8.1", + "db-dtypes": "1.3.1", + "debugpy": "1.8.11", + "decorator": "4.4.2", + "defusedxml": "0.7.1", + "dill": "0.3.9", + "distlib": "0.3.9", + "distro": "1.9.0", + "dm-tree": "0.1.8", + "docker": "7.1.0", + "docopt": "0.6.2", + "docstring_parser": "0.16", + "entrypoints": "0.4", + "etils": "1.11.0", + "exceptiongroup": "1.2.2", + "executing": "2.1.0", + "explainable-ai-sdk": "1.3.3", + "fastapi": "0.115.6", + "fastavro": "1.10.0", + "fasteners": "0.19", + "fastinterval": "0.1.1", + "fastjsonschema": "2.21.1", + "fastprogress": "1.0.3", + "fastrlock": "0.8.3", + "filelock": "3.16.1", + "fiona": "1.10.1", + "firecloud": "0.16.38", + "flatbuffers": "24.12.23", + "fonttools": "4.55.3", + "fqdn": "1.5.1", + "frozendict": "2.4.6", + "frozenlist": "1.5.0", + "fsspec": "2024.12.0", + "gast": "0.6.0", + "gcsfs": "2024.12.0", + "geopandas": "0.14.4", + "getm": "0.0.5", + "ggplot": "0.11.5", + "gitdb": "4.0.11", + "gnomad": "0.6.4", + "google-api-core": "1.34.1", + "google-api-python-client": "1.8.0", + "google-apitools": "0.5.31", + "google-auth": "2.37.0", + "google-auth-httplib2": "0.1.1", + "google-auth-oauthlib": "0.8.0", + "google-cloud-aiplatform": "1.75.0", + "google-cloud-artifact-registry": "1.14.0", + "google-cloud-bigquery": "2.34.4", + "google-cloud-bigquery-connection": "1.17.0", + "google-cloud-bigquery-datatransfer": "3.17.1", + "google-cloud-bigquery-storage": "2.16.2", + "google-cloud-bigtable": "1.7.3", + "google-cloud-core": "2.4.1", + "google-cloud-datastore": "1.15.5", + "google-cloud-dlp": "3.26.0", + "google-cloud-functions": "1.19.0", + "google-cloud-iam": "2.17.0", + "google-cloud-jupyter-config": "0.0.10", + "google-cloud-language": "1.3.2", + "google-cloud-monitoring": "2.24.0", + "google-cloud-pubsub": "2.27.1", + "google-cloud-pubsublite": "1.11.1", + "google-cloud-recommendations-ai": "0.7.1", + "google-cloud-resource-manager": "1.10.3", + "google-cloud-spanner": "3.51.0", + "google-cloud-storage": "1.44.0", + "google-cloud-videointelligence": "1.16.3", + "google-cloud-vision": "3.9.0", + "google-crc32c": "1.6.0", + "google-pasta": "0.2.0", + "google-resumable-media": "2.7.2", + "googleapis-common-protos": "1.66.0", + "gpustat": "1.0.0", + "greenlet": "3.1.1", + "grpc-google-iam-v1": "0.12.7", + "grpc-interceptor": "0.15.4", + "grpcio": "1.68.1", + "grpcio-status": "1.48.2", + "gs-chunked-io": "0.5.2", + "gviz-api": "1.10.0", + "gymnasium": "1.0.0", + "h11": "0.14.0", + "h2": "4.1.0", + "h5netcdf": "1.6.1", + "h5py": "3.13.0", + "hail": "0.2.134", + "hdbscan": "0.8.40", + "hdfs": "2.7.3", + "hpack": "4.0.0", + "html5lib": "1.1", + "htmlmin": "0.1.12", + "httplib2": "0.21.0", + "httptools": "0.6.4", + "humanize": "4.11.0", + "hyperframe": "6.0.1", + "ibis-framework": "7.1.0", + "idna": "3.10", + "imageio": "2.36.1", + "immutabledict": "4.2.1", + "importlib_metadata": "8.4.0", + "importlib_resources": "6.4.5", + "intel-cmplr-lib-ur": "2025.1.0", + "intel-openmp": "2025.1.0", + "ipykernel": "6.29.5", + "ipython": "8.21.0", + "ipython-genutils": "0.2.0", + "ipython-sql": "0.5.0", + "ipywidgets": "8.1.5", + "isodate": "0.7.2", + "isoduration": "20.11.0", + "isort": "6.0.1", + "janus": "1.0.0", + "jaraco.classes": "3.4.0", + "jaraco.context": "6.0.1", + "jaraco.functools": "4.1.0", + "jedi": "0.19.2", + "jeepney": "0.8.0", + "jmespath": "0.10.0", + "joblib": "1.4.2", + "jproperties": "2.1.2", + "json5": "0.10.0", + "jsonpatch": "1.33", + "jsonpointer": "3.0.0", + "jsonschema": "4.23.0", + "jsonschema-specifications": "2024.10.1", + "jupyter-events": "0.7.0", + "jupyter-highlight-selected-word": "0.2.0", + "jupyter-http-over-ws": "0.0.8", + "jupyter-nbextensions-configurator": "0.6.3", + "jupyter-server": "1.24.0", + "jupyter-server-mathjax": "0.2.6", + "jupyter-ydoc": "0.2.5", + "jupyter_client": "7.4.9", + "jupyter_contrib_core": "0.4.2", + "jupyter_contrib_nbextensions": "0.7.0", + "jupyter_core": "5.3.1", + "jupyter_server_fileid": "0.9.3", + "jupyter_server_proxy": "4.0.0", + "jupyter_server_terminals": "0.4.4", + "jupyter_server_ydoc": "0.8.0", + "jupyterlab": "3.4.8", + "jupyterlab-pygments": "0.2.2", + "jupyterlab_git": "0.42.0", + "jupyterlab_server": "2.23.0", + "jupyterlab_widgets": "3.0.14", + "jupytext": "1.15.0", + "keras": "3.5.0", + "keras-tuner": "1.4.7", + "kernels-mixer": "0.0.15", + "keyring": "25.5.0", + "keyrings.google-artifactregistry-auth": "1.1.2", + "kfp": "2.5.0", + "kfp-pipeline-spec": "0.2.2", + "kfp-server-api": "2.0.5", + "kiwisolver": "1.4.8", + "kt-legacy": "1.0.5", + "kubernetes": "26.1.0", + "lazy_loader": "0.4", + "libclang": "18.1.1", + "libmambapy": "2.0.5", + "linkify-it-py": "2.0.3", + "llvmlite": "0.41.1", + "lxml": "5.3.2", + "lz4": "4.3.3", + "markdown-it-py": "3.0.0", + "matplotlib": "3.7.3", + "matplotlib-inline": "0.1.7", + "matplotlib-venn": "1.1.2", + "mccabe": "0.7.0", + "mdit-py-plugins": "0.4.2", + "mdurl": "0.1.2", + "memray": "1.15.0", + "menuinst": "2.2.0", + "mistune": "3.0.2", + "mizani": "0.9.2", + "mkl": "2025.1.0", + "ml-dtypes": "0.4.1", + "more-itertools": "10.5.0", + "msal": "1.32.0", + "msal-extensions": "1.3.1", + "msgpack": "1.1.0", + "msrest": "0.7.1", + "multidict": "6.1.0", + "multimethod": "1.12", + "multipledispatch": "1.0.0", + "namex": "0.0.8", + "narwhals": "1.35.0", + "nbclassic": "0.4.8", + "nbclient": "0.10.2", + "nbconvert": "7.16.6", + "nbdime": "3.2.0", + "nbformat": "5.10.4", + "nest-asyncio": "1.6.0", + "networkx": "3.4.2", + "nose": "1.3.7", + "notebook": "6.5.4", + "notebook_executor": "0.2", + "notebook_shim": "0.2.3", + "numba": "0.58.1", + "numpy": "1.24.4", + "nvidia-cublas-cu12": "12.3.4.1", + "nvidia-cuda-cupti-cu12": "12.3.101", + "nvidia-cuda-nvcc-cu12": "12.3.107", + "nvidia-cuda-nvrtc-cu12": "12.3.107", + "nvidia-cuda-runtime-cu12": "12.3.101", + "nvidia-cudnn-cu12": "8.9.7.29", + "nvidia-cufft-cu12": "11.0.12.1", + "nvidia-curand-cu12": "10.3.4.107", + "nvidia-cusolver-cu12": "11.5.4.101", + "nvidia-cusparse-cu12": "12.2.0.103", + "nvidia-ml-py": "11.495.46", + "nvidia-nccl-cu12": "2.19.3", + "nvidia-nvjitlink-cu12": "12.3.101", + "oauth2client": "4.1.3", + "oauthlib": "3.2.2", + "objsize": "0.6.1", + "opencensus": "0.11.4", + "opencensus-context": "0.1.3", + "opentelemetry-api": "1.27.0", + "opentelemetry-exporter-otlp": "1.27.0", + "opentelemetry-exporter-otlp-proto-common": "1.27.0", + "opentelemetry-exporter-otlp-proto-grpc": "1.27.0", + "opentelemetry-exporter-otlp-proto-http": "1.27.0", + "opentelemetry-proto": "1.27.0", + "opentelemetry-sdk": "1.27.0", + "opentelemetry-semantic-conventions": "0.48b0", + "opt_einsum": "3.4.0", + "optree": "0.13.1", + "orjson": "3.10.12", + "overrides": "7.7.0", + "packaging": "21.3", + "pandas": "2.0.3", + "pandas-gbq": "0.17.9", + "pandas-profiling": "3.6.6", + "pandocfilters": "1.5.1", + "papermill": "2.6.0", + "parsimonious": "0.10.0", + "parso": "0.8.4", + "parsy": "2.1", + "patsy": "1.0.1", + "pdoc3": "0.11.6", + "pendulum": "3.0.0", + "pexpect": "4.9.0", + "phik": "0.12.4", + "pillow": "11.0.0", + "pins": "0.8.7", + "pip": "25.0.1", + "platformdirs": "4.3.6", + "plotly": "5.24.1", + "plotnine": "0.10.1", + "pluggy": "1.5.0", + "prettytable": "3.12.0", + "prometheus_client": "0.21.1", + "promise": "2.3", + "prompt_toolkit": "3.0.48", + "propcache": "0.2.1", + "proto-plus": "1.25.0", + "protobuf": "3.20.2", + "psutil": "5.9.3", + "ptyprocess": "0.7.0", + "pure_eval": "0.2.3", + "py-spy": "0.4.0", + "py4j": "0.10.9.9", + "pyOpenSSL": "24.0.0", + "pyarrow": "9.0.0", + "pyarrow-hotfix": "0.6", + "pyasn1": "0.6.1", + "pyasn1_modules": "0.4.1", + "pycares": "4.6.1", + "pycosat": "0.6.6", + "pycparser": "2.22", + "pydantic": "1.10.19", + "pydata-google-auth": "1.9.0", + "pydot": "1.4.2", + "pyfasta": "0.5.2", + "pylint": "3.3.6", + "pymc3": "3.11.4", + "pymongo": "3.13.0", + "pyogrio": "0.10.0", + "pypandoc": "1.15", + "pyparsing": "3.2.0", + "pyproj": "3.7.0", + "pysam": "0.23.0", + "python": "3.10.16", + "python-datauri": "3.0.2", + "python-dateutil": "2.9.0.post0", + "python-dotenv": "1.0.1", + "python-json-logger": "2.0.7", + "python-lzo": "1.15", + "pytz": "2023.3", + "pyzmq": "26.2.0", + "ray": "2.40.0", + "referencing": "0.35.1", + "regex": "2024.11.6", + "requests": "2.32.3", + "requests-oauthlib": "2.0.0", + "requests-toolbelt": "0.10.1", + "retrying": "1.3.4", + "rfc3339-validator": "0.1.4", + "rfc3986-validator": "0.1.1", + "rich": "12.6.0", + "rpds-py": "0.22.3", + "rsa": "4.9", + "ruamel.yaml": "0.18.6", + "ruamel.yaml.clib": "0.2.8", + "s3transfer": "0.12.0", + "scikit-image": "0.25.0", + "scikit-learn": "1.6.0", + "scikit-learn-intelex": "2025.4.0", + "scipy": "1.11.4", + "seaborn": "0.13.2", + "semver": "3.0.4", + "setuptools": "75.6.0", + "shellingham": "1.5.4", + "simpervisor": "1.0.0", + "simple-parsing": "0.1.6", + "six": "1.17.0", + "slackclient": "2.5.0", + "smart-open": "7.1.0", + "smmap": "5.0.1", + "sniffio": "1.3.1", + "sortedcontainers": "2.4.0", + "soupsieve": "2.6", + "sqlglot": "19.9.0", + "sqlparse": "0.5.3", + "stack-data": "0.6.3", + "starlette": "0.41.3", + "statsmodels": "0.14.4", + "tabulate": "0.9.0", + "tangled-up-in-unicode": "0.2.0", + "tbb": "2022.1.0", + "tcmlib": "1.3.0", + "tenacity": "9.0.0", + "tensorboard": "2.17.1", + "tensorboard-data-server": "0.7.2", + "tensorboard-plugin-profile": "2.18.0", + "tensorboardX": "2.6.2.2", + "tensorflow": "2.17.0", + "tensorflow-cloud": "0.1.16", + "tensorflow-datasets": "4.9.7", + "tensorflow-estimator": "2.15.0", + "tensorflow-hub": "0.16.1", + "tensorflow-io": "0.37.1", + "tensorflow-io-gcs-filesystem": "0.37.1", + "tensorflow-metadata": "0.14.0", + "tensorflow-probability": "0.25.0", + "tensorflow-serving-api": "2.17.0", + "tensorflow-transform": "0.14.0", + "termcolor": "2.5.0", + "terminado": "0.18.1", + "terra-notebook-utils": "0.13.0", + "textual": "1.0.0", + "tf_keras": "2.17.0", + "threadpoolctl": "3.5.0", + "tifffile": "2024.12.12", + "time-machine": "2.16.0", + "tinycss2": "1.1.1", + "toml": "0.10.2", + "tomli": "2.2.1", + "tomlkit": "0.13.2", + "toolz": "0.12.1", + "tornado": "6.4.2", + "tqdm": "4.67.1", + "traitlets": "5.9.0", + "truststore": "0.10.0", + "typeguard": "4.4.1", + "typer": "0.15.1", + "types-python-dateutil": "2.9.0.20241206", + "typing_extensions": "4.12.2", + "tzdata": "2024.2", + "uc-micro-py": "1.0.3", + "umf": "0.10.0", + "uri-template": "1.3.0", + "uritemplate": "3.0.1", + "urllib3": "1.26.20", + "uvicorn": "0.34.0", + "uvloop": "0.21.0", + "virtualenv": "20.28.0", + "visions": "0.7.5", + "watchfiles": "1.0.3", + "wcwidth": "0.2.13", + "webcolors": "24.11.1", + "webencodings": "0.5.1", + "websocket-client": "1.8.0", + "websockets": "14.1", + "wheel": "0.45.1", + "widgetsnbextension": "4.0.13", + "witwidget": "1.8.1", + "wordcloud": "1.9.4", + "wrapt": "1.17.0", + "xarray": "2023.12.0", + "xarray-einstats": "0.8.0", + "xgboost": "3.0.0", + "xxhash": "3.5.0", + "xyzservices": "2025.1.0", + "y-py": "0.6.2", + "yarl": "1.18.3", + "ydata-profiling": "4.6.0", + "ypy-websocket": "0.8.4", + "zipp": "3.21.0", + "zstandard": "0.23.0" + }, + "spark": { + "spark": "3.5.0" + } +} \ No newline at end of file