jfaustin commited on
Commit
a4dc94e
·
2 Parent(s): 371f76e b8a625b

Merge remote-tracking branch 'origin/main' into tab_2_correlation

Browse files
.gitignore CHANGED
@@ -1,5 +1,5 @@
1
  .envrc
2
-
3
- boltz_results/
4
-
5
  **/__pycache__/
 
 
 
 
1
  .envrc
 
 
 
2
  **/__pycache__/
3
+ output/
4
+ sequences/
5
+ boltz_results/
Dockerfile CHANGED
@@ -8,7 +8,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
8
  wget build-essential git && \
9
  rm -rf /var/lib/apt/lists/*
10
 
11
- COPY --from=uv-fetcher /uv /uvx /usr/local/bin/
12
 
13
  ENV VIRTUAL_ENV=/modules/.venv
14
  RUN uv venv "$VIRTUAL_ENV" && . "$VIRTUAL_ENV/bin/activate"
@@ -17,35 +17,28 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH"
17
  # Set the working directory to the user's home directory
18
  WORKDIR /app
19
 
20
- COPY pyproject.toml uv.lock /app/
 
 
 
 
 
21
 
22
- RUN uv sync --frozen --active --directory /app --inexact
 
23
 
24
- COPY folding-studio /app/folding-studio
25
- RUN cd /app/folding-studio && uv pip install -e .
 
 
26
 
27
- COPY app.py /app/app.py
28
- COPY molecule.py /app/molecule.py
29
  # COPY pred.cif /app/boltz_results/pred_model_0.cif
30
  # COPY plddt_0.npz /app/boltz_results/plddt_0.npz
31
  COPY spr_af_scores_mapped.csv /app/spr_af_scores_mapped.csv
32
 
33
  EXPOSE 7860
34
  ENV GRADIO_SERVER_NAME="0.0.0.0"
35
- ARG FOLDING_PROJECT_CODE
36
  ENV FOLDING_PROJECT_CODE=$FOLDING_PROJECT_CODE
37
 
38
- # Create directory for HTML output
39
- RUN mkdir -p /app/output/html
40
-
41
- # Create volume for HTML output
42
- VOLUME ["/app/output/html"]
43
-
44
- # Create a non-root user and set permissions
45
- RUN useradd -m -u 1000 appuser && \
46
- chown -R appuser:appuser /app
47
-
48
- # Switch to non-root user
49
- USER appuser
50
-
51
- CMD ["python3", "app.py"]
 
8
  wget build-essential git && \
9
  rm -rf /var/lib/apt/lists/*
10
 
11
+ COPY --from=uv-fetcher /uv /uvx /bin/
12
 
13
  ENV VIRTUAL_ENV=/modules/.venv
14
  RUN uv venv "$VIRTUAL_ENV" && . "$VIRTUAL_ENV/bin/activate"
 
17
  # Set the working directory to the user's home directory
18
  WORKDIR /app
19
 
20
+ # Create a non-root user and set permissions
21
+ RUN useradd -m -u 1000 appuser && \
22
+ chown -R appuser:appuser /app
23
+
24
+ # Switch to non-root user
25
+ USER appuser
26
 
27
+ COPY pyproject.toml uv.lock README.md ./
28
+ COPY folding-studio ./folding-studio
29
 
30
+ RUN uv sync --locked --no-install-project --no-dev
31
+
32
+ COPY folding_studio_demo ./folding_studio_demo
33
+ RUN uv sync --locked --no-dev
34
 
 
 
35
  # COPY pred.cif /app/boltz_results/pred_model_0.cif
36
  # COPY plddt_0.npz /app/boltz_results/plddt_0.npz
37
  COPY spr_af_scores_mapped.csv /app/spr_af_scores_mapped.csv
38
 
39
  EXPOSE 7860
40
  ENV GRADIO_SERVER_NAME="0.0.0.0"
41
+ ARG FOLDING_PROJECT_CODE="folding_dev"
42
  ENV FOLDING_PROJECT_CODE=$FOLDING_PROJECT_CODE
43
 
44
+ CMD ["uv", "run", "folding-studio-demo"]
 
 
 
 
 
 
 
 
 
 
 
 
 
Makefile CHANGED
@@ -1,5 +1,5 @@
1
  # Variables
2
- DOCKER_IMAGE_NAME = folding-studio
3
  DOCKER_IMAGE_TAG = latest
4
  PORT = 7860
5
  OUTPUT_DIR = ./output
 
1
  # Variables
2
+ DOCKER_IMAGE_NAME = folding-studio-demo
3
  DOCKER_IMAGE_TAG = latest
4
  PORT = 7860
5
  OUTPUT_DIR = ./output
folding_studio_demo/__init__.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Folding Studio Demo."""
2
+
3
+ import logging
4
+
5
+ # Configure logging
6
+ logging.basicConfig(
7
+ level=logging.INFO,
8
+ format="%(asctime)s - %(levelname)s - %(message)s",
9
+ handlers=[
10
+ logging.StreamHandler(),
11
+ ],
12
+ )
app.py → folding_studio_demo/app.py RENAMED
@@ -1,192 +1,148 @@
1
- from Bio.PDB import MMCIFParser, PDBIO
2
- from folding_studio.client import Client
3
- from folding_studio.query.boltz import BoltzQuery, BoltzParameters
4
- from pathlib import Path
5
- import gradio as gr
6
- import hashlib
7
  import logging
8
- import numpy as np
9
- import os
10
- import plotly.graph_objects as go
 
11
  import pandas as pd
 
12
  from scipy.stats import spearmanr
 
13
 
14
- from molecule import molecule
15
 
16
- # Configure logging
17
- logging.basicConfig(
18
- level=logging.INFO,
19
- format='%(asctime)s - %(levelname)s - %(message)s',
20
- handlers=[
21
- logging.StreamHandler(),
22
- ]
23
- )
24
  logger = logging.getLogger(__name__)
25
 
26
 
27
- def convert_cif_to_pdb(cif_path: str, pdb_path: str) -> None:
28
- """Convert a .cif file to .pdb format using Biopython.
29
-
30
- Args:
31
- cif_path (str): Path to input .cif file
32
- pdb_path (str): Path to output .pdb file
33
- """
34
- # Parse the CIF file
35
- parser = MMCIFParser()
36
- structure = parser.get_structure("structure", cif_path)
37
-
38
- # Save as PDB
39
- io = PDBIO()
40
- io.set_structure(structure)
41
- io.save(pdb_path)
42
-
43
- def call_boltz(seq_file: Path | str, api_key: str, output_dir: Path) -> None:
44
- """Call Boltz prediction."""
45
- # Initialize parameters with CLI-provided values
46
- parameters = {
47
- "recycling_steps": 3,
48
- "sampling_steps": 200,
49
- "diffusion_samples": 1,
50
- "step_scale": 1.638,
51
- "msa_pairing_strategy": "greedy",
52
- "write_full_pae": False,
53
- "write_full_pde": False,
54
- "use_msa_server": True,
55
- "seed": 0,
56
- "custom_msa_paths": None,
57
  }
58
-
59
- # Create a client using API key
60
- logger.info("Authenticating client with API key")
61
- client = Client.from_api_key(api_key=api_key)
62
 
63
- # Define query
64
- seq_file = Path(seq_file)
65
- query = BoltzQuery.from_file(seq_file, query_name="gradio", parameters=BoltzParameters(**parameters))
66
- query.save_parameters(output_dir)
67
 
68
- logger.info("Payload: %s", query.payload)
 
 
 
 
 
 
 
69
 
70
- # Send a request
71
- logger.info("Sending request to Folding Studio API")
72
- response = client.send_request(query, project_code=os.environ["FOLDING_PROJECT_CODE"])
73
 
74
- # Access confidence data
75
- logger.info("Confidence data: %s", response.confidence_data)
76
 
77
- response.download_results(output_dir=output_dir, force=True, unzip=True)
78
- logger.info("Results downloaded to %s", output_dir)
 
 
 
 
 
 
 
 
 
 
 
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
- def predict(sequence: str, api_key: str) -> str:
82
- """Predict protein structure from amino acid sequence using Boltz model.
83
-
84
  Args:
85
- sequence (str): Amino acid sequence to predict structure for
86
- api_key (str): Folding API key
87
-
88
- Returns:
89
- str: HTML iframe containing 3D molecular visualization
90
  """
91
-
92
- # Set up unique output directory based on sequence hash
93
- seq_id = hashlib.sha1(sequence.encode()).hexdigest()
94
- seq_file = Path(f"sequence_{seq_id}.fasta")
95
- _write_fasta_file(seq_file, sequence)
96
- output_dir = Path(f"sequence_{seq_id}")
97
- output_dir.mkdir(parents=True, exist_ok=True)
98
-
99
- # Check if prediction already exists
100
- pred_cif = list(output_dir.rglob("*_model_0.cif"))
101
- if not pred_cif:
102
- # Run Boltz prediction
103
- logger.info(f"Predicting {seq_file.stem}")
104
- call_boltz(seq_file=seq_file, api_key=api_key, output_dir=output_dir)
105
- logger.info("Prediction done. Output directory: %s", output_dir)
106
- else:
107
- logger.info("Prediction already exists. Output directory: %s", output_dir)
108
-
109
- # output_dir = Path("boltz_results") # debug
110
- # Convert output CIF to PDB
111
- pred_cif = list(output_dir.rglob("*_model_0.cif"))[0]
112
- logger.info("Output file: %s", pred_cif)
113
-
114
- converted_pdb_path = str(output_dir / "pred.pdb")
115
- convert_cif_to_pdb(str(pred_cif), str(converted_pdb_path))
116
- logger.info("Converted PDB file: %s", converted_pdb_path)
117
-
118
-
119
- # Generate molecular visualization
120
- mol = _create_molecule_visualization(
121
- converted_pdb_path,
122
- sequence,
123
- )
124
-
125
- plddt_file = list(pred_cif.parent.glob("plddt_*.npz"))[0]
126
- logger.info("plddt file: %s", plddt_file)
127
- plddt_vals = np.load(plddt_file)["plddt"]
128
-
129
- return _wrap_in_iframe(mol), add_plddt_plot(plddt_vals=plddt_vals)
130
-
131
-
132
- def _write_fasta_file(filepath: Path, sequence: str) -> None:
133
- """Write sequence to FASTA file."""
134
- with open(filepath, "w") as f:
135
- f.write(f">A|protein\n{sequence}")
136
-
137
-
138
- def _create_molecule_visualization(pdb_path: Path, sequence: str) -> str:
139
- """Create molecular visualization using molecule module."""
140
- return molecule(
141
- str(pdb_path),
142
- lenSeqs=1,
143
- num_res=len(sequence),
144
- selectedResidues=list(range(1, len(sequence) + 1)),
145
- allSeqs=[sequence],
146
- sequences=[{
147
- "Score": 0,
148
- "RMSD": 0,
149
- "Recovery": 0,
150
- "Mean pLDDT": 0,
151
- "seq": sequence
152
- }],
153
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
 
 
 
155
 
156
- def _wrap_in_iframe(content: str) -> str:
157
- """Wrap content in an HTML iframe with appropriate styling and permissions."""
158
- return f"""<iframe
159
- name="result"
160
- style="width: 100%; height: 100vh;"
161
- allow="midi; geolocation; microphone; camera; display-capture; encrypted-media;"
162
- sandbox="allow-modals allow-forms allow-scripts allow-same-origin allow-popups allow-top-navigation-by-user-activation allow-downloads"
163
- allowfullscreen=""
164
- allowpaymentrequest=""
165
- frameborder="0"
166
- srcdoc='{content}'
167
- ></iframe>"""
168
-
169
- def add_plddt_plot(plddt_vals: list[float]) -> str:
170
- """Create a plot of metrics."""
171
- visible = True
172
- plddt_trace = go.Scatter(
173
- x=np.arange(len(plddt_vals)),
174
- y=plddt_vals,
175
- hovertemplate="<i>pLDDT</i>: %{y:.2f} <br><i>Residue index:</i> %{x}<br>",
176
- name="seq",
177
- visible=visible,
178
  )
179
-
180
- plddt_fig = go.Figure(data=[plddt_trace])
181
- plddt_fig.update_layout(
182
- title="pLDDT",
183
- xaxis_title="Residue index",
184
- yaxis_title="pLDDT",
185
- height=500,
186
- template="simple_white",
187
- legend=dict(yanchor="bottom", y=0.01, xanchor="left", x=0.99),
 
 
 
 
188
  )
189
- return plddt_fig
190
 
191
  def fake_predict_and_correlate(spr_data_with_scores: pd.DataFrame, score_cols: list[str]) -> tuple[pd.DataFrame, go.Figure]:
192
  """Fake predict structures of all complexes and correlate the results."""
@@ -243,31 +199,29 @@ def fake_predict_and_correlate(spr_data_with_scores: pd.DataFrame, score_cols: l
243
 
244
  return spr_data_with_scores[cols_to_show], corr_ranking_plot, corr_plot
245
 
246
- demo = gr.Blocks(title="Folding Studio: structure prediction with Boltz-1")
 
 
 
 
247
 
248
- with demo:
249
- with gr.Tabs() as tabs:
250
- with gr.TabItem("Inference"):
251
- gr.Markdown("# Input")
252
- with gr.Row():
253
- with gr.Column():
254
- sequence = gr.Textbox(label="Sequence", value="")
255
- api_key = gr.Textbox(label="Folding API Key", type="password")
256
- gr.Markdown("# Output")
257
- with gr.Row():
258
- predict_btn = gr.Button("Predict")
259
- with gr.Row():
260
- with gr.Column():
261
- mol_output = gr.HTML()
262
- with gr.Column():
263
- metrics_plot = gr.Plot(label="pLDDT")
264
-
265
- predict_btn.click(
266
- fn=predict,
267
- inputs=[sequence, api_key],
268
- outputs=[mol_output, metrics_plot]
269
- )
270
- with gr.TabItem("Correlations"):
271
  gr.Markdown("# Upload binding affinity data")
272
  spr_data_with_scores = pd.read_csv("spr_af_scores_mapped.csv")
273
  with gr.Row():
@@ -292,8 +246,6 @@ with demo:
292
  "confidence_score_boltz",
293
  "ptm_boltz",
294
  "iptm_boltz",
295
- # "ligand_iptm_boltz",
296
- # "protein_iptm_boltz",
297
  "complex_plddt_boltz",
298
  "complex_iplddt_boltz",
299
  "complex_pde_boltz",
@@ -324,7 +276,4 @@ with demo:
324
  outputs=[prediction_dataframe, correlation_ranking_plot, correlation_plot]
325
  )
326
 
327
-
328
-
329
- demo.launch()
330
-
 
1
+ """Folding Studio Demo App."""
2
+
 
 
 
 
3
  import logging
4
+
5
+ import gradio as gr
6
+ from folding_studio_data_models import FoldingModel
7
+ from gradio_molecule3d import Molecule3D
8
  import pandas as pd
9
+ import numpy as np
10
  from scipy.stats import spearmanr
11
+ import plotly.graph_objects as go
12
 
13
+ from folding_studio_demo.predict import predict
14
 
 
 
 
 
 
 
 
 
15
  logger = logging.getLogger(__name__)
16
 
17
 
18
+ MOLECULE_REPS = [
19
+ {
20
+ "model": 0,
21
+ "chain": "",
22
+ "resname": "",
23
+ "style": "cartoon",
24
+ "color": "alphafold",
25
+ # "residue_range": "",
26
+ "around": 0,
27
+ "byres": False,
28
+ # "visible": False,
29
+ # "opacity": 0.5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
31
+ ]
 
 
 
32
 
33
+ DEFAULT_PROTEIN_SEQ = ">protein description\nMALWMRLLPLLALLALWGPDPAAA"
 
 
 
34
 
35
+ MODEL_CHOICES = [
36
+ # ("AlphaFold2", FoldingModel.AF2),
37
+ # ("OpenFold", FoldingModel.OPENFOLD),
38
+ # ("SoloSeq", FoldingModel.SOLOSEQ),
39
+ ("Boltz-1", FoldingModel.BOLTZ),
40
+ ("Chai-1", FoldingModel.CHAI),
41
+ ("Protenix", FoldingModel.PROTENIX),
42
+ ]
43
 
 
 
 
44
 
45
+ def sequence_input() -> gr.Textbox:
46
+ """Sequence input component.
47
 
48
+ Returns:
49
+ gr.Textbox: Sequence input component
50
+ """
51
+ sequence = gr.Textbox(
52
+ label="Protein Sequence",
53
+ value=DEFAULT_PROTEIN_SEQ,
54
+ lines=2,
55
+ placeholder="Enter a protein sequence or upload a FASTA file",
56
+ )
57
+ file_input = gr.File(
58
+ label="Upload a FASTA file",
59
+ file_types=[".fasta", ".fa"],
60
+ )
61
 
62
+ def _process_file(file: gr.File | None) -> gr.Textbox:
63
+ if file is None:
64
+ return gr.Textbox()
65
+ try:
66
+ with open(file.name, "r") as f:
67
+ content = f.read().strip()
68
+ return gr.Textbox(value=content)
69
+ except Exception as e:
70
+ logger.error(f"Error reading file: {e}")
71
+ return gr.Textbox()
72
+
73
+ file_input.change(fn=_process_file, inputs=[file_input], outputs=[sequence])
74
+ return sequence
75
+
76
+
77
+ def simple_prediction(api_key: str) -> None:
78
+ """Simple prediction tab.
79
 
 
 
 
80
  Args:
81
+ api_key (str): Folding Studio API key
 
 
 
 
82
  """
83
+ gr.Markdown(
84
+ """
85
+ ### Predict a Protein Structure
86
+
87
+ It will be run in the background and the results will be displayed in the output section.
88
+ The output will contain the protein structure and the pLDDT plot.
89
+
90
+ Select a model to run the inference with and enter a protein sequence or upload a FASTA file.
91
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  )
93
+ with gr.Row():
94
+ dropdown = gr.Dropdown(
95
+ label="Model",
96
+ choices=MODEL_CHOICES,
97
+ scale=0,
98
+ value=FoldingModel.BOLTZ,
99
+ )
100
+ with gr.Column():
101
+ sequence = sequence_input()
102
+
103
+ predict_btn = gr.Button("Predict")
104
+
105
+ with gr.Row():
106
+ mol_output = Molecule3D(label="Protein Structure", reps=MOLECULE_REPS)
107
+ metrics_plot = gr.Plot(label="pLDDT")
108
+
109
+ predict_btn.click(
110
+ fn=predict,
111
+ inputs=[sequence, api_key, dropdown],
112
+ outputs=[mol_output, metrics_plot],
113
+ )
114
+
115
+
116
+ def model_comparison(api_key: str) -> None:
117
+ """Model comparison tab.
118
 
119
+ Args:
120
+ api_key (str): Folding Studio API key
121
+ """
122
 
123
+ with gr.Row():
124
+ model = gr.Dropdown(
125
+ label="Model",
126
+ choices=MODEL_CHOICES,
127
+ multiselect=True,
128
+ scale=0,
129
+ min_width=300,
130
+ value=[FoldingModel.BOLTZ, FoldingModel.CHAI, FoldingModel.PROTENIX],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  )
132
+ with gr.Column():
133
+ sequence = sequence_input()
134
+
135
+ predict_btn = gr.Button("Compare Models")
136
+
137
+ with gr.Row():
138
+ mol_output = Molecule3D(label="Protein Structure", reps=MOLECULE_REPS)
139
+ metrics_plot = gr.Plot(label="pLDDT")
140
+
141
+ predict_btn.click(
142
+ fn=predict,
143
+ inputs=[sequence, api_key, model],
144
+ outputs=[mol_output, metrics_plot],
145
  )
 
146
 
147
  def fake_predict_and_correlate(spr_data_with_scores: pd.DataFrame, score_cols: list[str]) -> tuple[pd.DataFrame, go.Figure]:
148
  """Fake predict structures of all complexes and correlate the results."""
 
199
 
200
  return spr_data_with_scores[cols_to_show], corr_ranking_plot, corr_plot
201
 
202
+ def __main__():
203
+ with gr.Blocks(title="Folding Studio Demo") as demo:
204
+ gr.Markdown(
205
+ """
206
+ # Folding Studio: Harness the Power of Protein Folding 🧬
207
 
208
+ Folding Studio is a platform for protein structure prediction.
209
+ It uses the latest AI-powered folding models to predict the structure of a protein.
210
+
211
+ Available models are : AlphaFold2, OpenFold, SoloSeq, Boltz-1, Chai and Protenix.
212
+
213
+ ## API Key
214
+ To use the Folding Studio API, you need to provide an API key.
215
+ You can get your API key by asking to the Folding Studio team.
216
+ """
217
+ )
218
+ api_key = gr.Textbox(label="Folding Studio API Key", type="password")
219
+ gr.Markdown("## Demo Usage")
220
+ with gr.Tab("🚀 Simple Prediction"):
221
+ simple_prediction(api_key)
222
+ with gr.Tab("📊 Model Comparison"):
223
+ model_comparison(api_key)
224
+ with gr.Tab("🔍 Correlations"):
 
 
 
 
 
 
225
  gr.Markdown("# Upload binding affinity data")
226
  spr_data_with_scores = pd.read_csv("spr_af_scores_mapped.csv")
227
  with gr.Row():
 
246
  "confidence_score_boltz",
247
  "ptm_boltz",
248
  "iptm_boltz",
 
 
249
  "complex_plddt_boltz",
250
  "complex_iplddt_boltz",
251
  "complex_pde_boltz",
 
276
  outputs=[prediction_dataframe, correlation_ranking_plot, correlation_plot]
277
  )
278
 
279
+ demo.launch()
 
 
 
folding_studio_demo/model_fasta_validators.py ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Utils for validating the FASTA files for the AF3 like models."""
2
+
3
+ import logging
4
+ import re
5
+ import shutil
6
+ from abc import abstractmethod
7
+ from collections import defaultdict
8
+ from enum import Enum
9
+ from pathlib import Path
10
+
11
+ from Bio import SeqIO
12
+
13
+
14
+ class EntityType(str, Enum):
15
+ """Enum for the entity type of a given sequence."""
16
+
17
+ DNA = "dna"
18
+ RNA = "rna"
19
+ PROTEIN = "protein"
20
+ PEPTIDE = "peptide"
21
+ ION = "ion"
22
+ LIGAND = "ligand"
23
+ SMILES = "smiles"
24
+ CCD = "ccd"
25
+
26
+
27
+ def get_entity_type(sequence: str) -> EntityType:
28
+ """Get the entity type of a given sequence.
29
+
30
+ The entity type is determined based on the sequence composition.
31
+
32
+ Args:
33
+ sequence (str): The input sequence.
34
+
35
+ Returns:
36
+ EntityType: The entity type of the input sequence.
37
+ """
38
+ DNA_SEQUENCE_SET = set("ACGT")
39
+ RNA_SEQUENCE_SET = set("ACGU")
40
+ PROTEIN_SEQUENCE_SET = set("ACDEFGHIKLMNPQRSTVWY")
41
+
42
+ # Detect IONS (e.g., Mg2+, Na+, Cl-)
43
+ if re.fullmatch(r"[A-Za-z]{1,2}[\d\+\-]*", sequence):
44
+ return EntityType.ION
45
+
46
+ # Detect DNA
47
+ if set(sequence.upper()).issubset(DNA_SEQUENCE_SET):
48
+ return EntityType.DNA
49
+
50
+ # Detect RNA
51
+ elif set(sequence.upper()).issubset(RNA_SEQUENCE_SET):
52
+ return EntityType.RNA
53
+
54
+ # Detect PROTEIN
55
+ elif set(sequence.upper()).issubset(PROTEIN_SEQUENCE_SET):
56
+ return EntityType.PROTEIN
57
+
58
+ # Default to LIGAND
59
+ return EntityType.LIGAND
60
+
61
+
62
+ def has_multiple_chains(header: str) -> bool:
63
+ """Check if a given header contains multiple chains in RCSB format.
64
+
65
+ A header with multiple chains will have the following in the header:
66
+ ```
67
+ Chains A, B, C, ...
68
+ ```
69
+ where `A`, `B`, `C`, ... are the chain identifiers.
70
+
71
+
72
+ Args:
73
+ header (str): The input header string containing chain information.
74
+
75
+ Returns:
76
+ bool: True if the header contains multiple chains, False otherwise.
77
+ """
78
+ match = re.search(r"chains?\s+([A-Za-z, ]+)", header, re.I)
79
+ return len(match.group(1).replace(" ", "").split(",")) > 1 if match else False
80
+
81
+
82
+ class BaseFastaValidator:
83
+ """Base class for validating FASTA files."""
84
+
85
+ @abstractmethod
86
+ def is_valid_fasta(self, fasta_path: Path) -> tuple[bool, str | None]:
87
+ """Validate whether a given FASTA file follows the required format.
88
+
89
+ Args:
90
+ fasta_path (Path): Path to the FASTA file.
91
+
92
+ Returns:
93
+ tuple[bool, str | None]: Tuple containing a boolean indicating if the format is correct and an error message if not
94
+ """
95
+ raise NotImplementedError("Subclasses must implement this method")
96
+
97
+ @abstractmethod
98
+ def transform_fasta(self, fasta_path: Path) -> str:
99
+ """Transform a FASTA file into the required format.
100
+
101
+ Args:
102
+ fasta_path (Path): Path to the FASTA file.
103
+
104
+ Returns:
105
+ Transformed FASTA content in the required format.
106
+ """
107
+ raise NotImplementedError("Subclasses must implement this method")
108
+
109
+ def process_directory(self, input_dir: str, output_dir: str) -> None:
110
+ """Process all FASTA files in the input directory, validate or transform them, and save them to the output directory.
111
+
112
+ Args:
113
+ input_dir (str): Path to the directory containing FASTA files.
114
+ output_dir (str): Path to the output directory where processed files will be saved.
115
+ """
116
+
117
+ output_path = Path(output_dir)
118
+ output_path.mkdir(parents=True, exist_ok=True)
119
+
120
+ for fasta_file in Path(input_dir).glob("*.fasta"):
121
+ output_file = output_path / fasta_file.name
122
+ if has_multiple_chains(fasta_file.read_text()):
123
+ logging.warning(
124
+ f"Skipping {fasta_file} because it contains multiple chains in a single sequence.\n"
125
+ "Please split multiple chains into separate sequences using the following format:\n"
126
+ ">Chain A\n"
127
+ "MTEIVLKFL...\n"
128
+ ">Chain B\n"
129
+ "MTEIVLKFL...\n\n"
130
+ "Instead of:\n"
131
+ ">Chains A, B\n"
132
+ "MTEIVLKFL..."
133
+ )
134
+ continue
135
+ if self.is_valid_fasta(fasta_file):
136
+ shutil.copy(fasta_file, output_file)
137
+ else:
138
+ transformed_content = self.transform_fasta(fasta_file)
139
+ output_file.write_text(transformed_content)
140
+
141
+
142
+ class BoltzFastaValidator(BaseFastaValidator):
143
+ """Validate whether a given FASTA file follows the required format for Boltz."""
144
+
145
+ SUPPORTED_ENTITY_TYPES = {
146
+ EntityType.PROTEIN,
147
+ EntityType.RNA,
148
+ EntityType.DNA,
149
+ EntityType.SMILES,
150
+ EntityType.CCD,
151
+ }
152
+
153
+ def is_valid_fasta(self, fasta_path: Path) -> tuple[bool, str | None]:
154
+ """Validate whether a given FASTA file follows the required format.
155
+
156
+ The expected FASTA header format is:
157
+ ```
158
+ >CHAIN_ID|ENTITY_TYPE
159
+ ```
160
+ where `ENTITY_TYPE` must be one of: "protein", "rna", "dna", "smiles" or "ccd".
161
+
162
+ Args:
163
+ fasta_path (Path): Path to the FASTA file.
164
+
165
+ Returns:
166
+ tuple[bool, str | None]: Tuple containing a boolean indicating if the format is correct and an error message if not
167
+ """
168
+ with fasta_path.open("r") as f:
169
+ for record in SeqIO.parse(f, "fasta"):
170
+ header_parts = record.id.split("|")
171
+ if not (1 < len(header_parts) <= 3):
172
+ msg = "BOLTZ Validation Error: Invalid header format. Expected '>CHAIN_ID|ENTITY_TYPE'"
173
+ return False, msg
174
+ if header_parts[1].lower() not in self.SUPPORTED_ENTITY_TYPES:
175
+ return (
176
+ False,
177
+ f"BOLTZ Validation Error: Invalid entity type '{header_parts[1]}'. Supported types: {', '.join(self.SUPPORTED_ENTITY_TYPES)}",
178
+ )
179
+ return True, None
180
+
181
+ def transform_fasta(self, fasta_path: Path) -> str:
182
+ """Transform a FASTA file into the '>CHAIN_ID|ENTITY_TYPE|MSA_ID' format.
183
+
184
+ This function extracts chain identifiers from the FASTA header and determines
185
+ the entity type (DNA, RNA, or PROTEIN) based on the sequence composition.
186
+
187
+ Args:
188
+ fasta_path (Path): Path to the FASTA file.
189
+
190
+ Returns:
191
+ Transformed FASTA content in the required format.
192
+ """
193
+ transformed_lines = []
194
+
195
+ with fasta_path.open("r") as f:
196
+ for record_index, record in enumerate(SeqIO.parse(f, "fasta")):
197
+ chain = chr(ord("A") + record_index)
198
+ # extract entity type
199
+ entity_type = get_entity_type(str(record.seq))
200
+ transformed_lines.append(f">{chain.upper()}|{entity_type.value}")
201
+ # append sequence
202
+ transformed_lines.append(str(record.seq))
203
+
204
+ return "\n".join(transformed_lines)
205
+
206
+
207
+ class ChaiFastaValidator(BaseFastaValidator):
208
+ """Validate whether a given FASTA file follows the required format for Chai."""
209
+
210
+ SUPPORTED_ENTITY_TYPES = EntityType.__members__.values()
211
+
212
+ def is_valid_fasta(self, fasta_path: Path) -> tuple[bool, str | None]:
213
+ """Validate whether a given FASTA file follows the required format.
214
+
215
+ The expected FASTA header format is:
216
+ ```
217
+ >ENTITY_TYPE|name=NAME
218
+ ```
219
+ Args:
220
+ fasta_path (Path): Path to the FASTA file.
221
+
222
+ Returns:
223
+ tuple[bool, str | None]: Tuple containing a boolean indicating if the format is correct and an error message if not
224
+ """
225
+
226
+ seen_names = set()
227
+ with fasta_path.open("r") as f:
228
+ for record in SeqIO.parse(f, "fasta"):
229
+ # validate header format
230
+ match = re.match(r"^([A-Za-z]+)\|name=([\w\-]+)$", record.description)
231
+ if not match:
232
+ return (
233
+ False,
234
+ "CHAI Validation Error: Invalid header format. Expected '>ENTITY_TYPE|name=NAME'",
235
+ )
236
+ # validate entity type
237
+ entity_type, name = match.groups()
238
+ if entity_type not in self.SUPPORTED_ENTITY_TYPES or not name:
239
+ return (
240
+ False,
241
+ f"CHAI Validation Error: Invalid entity type '{entity_type}'. Supported types: {', '.join(self.SUPPORTED_ENTITY_TYPES)}",
242
+ )
243
+ # check uniqueness of name
244
+ if name in seen_names:
245
+ return (
246
+ False,
247
+ f"CHAI Validation Error: Duplicate name '{name}'. Each sequence must have a unique name",
248
+ )
249
+ seen_names.add(name)
250
+ # validate sequence format
251
+ sequence = str(record.seq).strip()
252
+ if (
253
+ entity_type in {EntityType.PEPTIDE, EntityType.PROTEIN}
254
+ and not get_entity_type(sequence) == entity_type
255
+ ):
256
+ return (
257
+ False,
258
+ f"CHAI Validation Error: Sequence type mismatch. Expected '{entity_type}' but found '{get_entity_type(sequence)}'",
259
+ )
260
+
261
+ return True, None
262
+
263
+ def transform_fasta(self, fasta_path: Path) -> str:
264
+ """Transform a FASTA file into the '>TYPE|name=NAME' format by ensuring each main header
265
+ is unique (adding a number if necessary).
266
+
267
+ The expected output format is:
268
+ '>protein|name=NAME'
269
+ 'SEQUENCE'
270
+
271
+ Args:
272
+ fasta_path (Path): Path to the FASTA file.
273
+
274
+ Returns:
275
+ Transformed FASTA content in the required Chai format.
276
+ """
277
+ transformed_lines = []
278
+ header_map = {}
279
+
280
+ with fasta_path.open("r") as f:
281
+ for record in SeqIO.parse(f, "fasta"):
282
+ main_header = record.description.split("|")[0].strip()
283
+
284
+ if main_header not in header_map:
285
+ header_map[main_header] = 1
286
+ updated_header = main_header
287
+
288
+ else:
289
+ header_map[main_header] += 1
290
+ updated_header = main_header + "_" + str(header_map[main_header])
291
+
292
+ entity_type = get_entity_type(str(record.seq))
293
+ header = f">{entity_type.value}|name={updated_header}"
294
+
295
+ transformed_lines.append(header)
296
+ transformed_lines.append(str(record.seq))
297
+
298
+ return "\n".join(transformed_lines)
299
+
300
+
301
+ class ProtenixFastaValidator(BaseFastaValidator):
302
+ """Validate whether a given FASTA file follows the required format for Protenix."""
303
+
304
+ def is_valid_fasta(self, fasta_path: Path) -> tuple[bool, str | None]:
305
+ """Validate whether a given FASTA file follows the required format.
306
+
307
+ The expected FASTA header format is:
308
+ ```
309
+ > UNIQUE ID[|...]
310
+ ```
311
+
312
+ Args:
313
+ fasta_path (Path): Path to the FASTA file.
314
+
315
+ Returns:
316
+ tuple[bool, str | None]: Tuple containing a boolean indicating if the format is correct and an error message if not
317
+ """
318
+ seen_headers = set()
319
+
320
+ with fasta_path.open("r") as f:
321
+ for record in SeqIO.parse(f, "fasta"):
322
+ main_header = record.description.split("|")[0].strip()
323
+ if main_header in seen_headers:
324
+ return (
325
+ False,
326
+ f"PROTENIX Validation Error: Duplicate header '{main_header}'. Each sequence must have a unique header",
327
+ )
328
+ seen_headers.add(main_header)
329
+
330
+ return True, None
331
+
332
+ def transform_fasta(self, fasta_path: Path) -> str:
333
+ """Transform a FASTA file into the '>NAME|Chain X' format by ensuring each main header
334
+ is unique (adding a number if necessary).
335
+
336
+ The expected output format is:
337
+ '>protein_1 | Chain A'
338
+ 'SEQUENCE'
339
+ '>protein_2 | Chain B'
340
+ 'SEQUENCE'
341
+
342
+ Args:
343
+ fasta_path (Path): Path to the FASTA file.
344
+
345
+ Returns:
346
+ Transformed FASTA content in the required Protenix format.
347
+ """
348
+ transformed_lines = []
349
+ header_count = defaultdict(int)
350
+
351
+ with fasta_path.open("r") as f:
352
+ for record in SeqIO.parse(f, "fasta"):
353
+ header_parts = [part.strip() for part in record.description.split("|")]
354
+ main_header = header_parts[0]
355
+
356
+ # Ensure unique headers
357
+ header_count[main_header] += 1
358
+ updated_main_header = (
359
+ f"{main_header}_{header_count[main_header]}"
360
+ if header_count[main_header] > 1
361
+ else main_header
362
+ )
363
+
364
+ transformed_lines.append(f">{updated_main_header}")
365
+ transformed_lines.append(str(record.seq))
366
+
367
+ return "\n".join(transformed_lines)
molecule.py → folding_studio_demo/molecule.py RENAMED
@@ -1,15 +1,16 @@
1
- import logging
2
 
3
  # Configure logging
4
  logging.basicConfig(
5
  level=logging.INFO,
6
- format='%(asctime)s - %(levelname)s - %(message)s',
7
  handlers=[
8
  logging.StreamHandler(),
9
- ]
10
  )
11
  logger = logging.getLogger(__name__)
12
 
 
13
  def read_mol(molpath):
14
  with open(molpath, "r") as fp:
15
  lines = fp.readlines()
@@ -19,10 +20,7 @@ def read_mol(molpath):
19
  return mol
20
 
21
 
22
- def molecule(
23
- input_pdb, lenSeqs, num_res, selectedResidues, allSeqs, sequences
24
- ):
25
-
26
  options = ""
27
  pred_mol = "["
28
  seqdata = "{"
@@ -265,5 +263,5 @@ def molecule(
265
  </script>
266
  </body></html>"""
267
  )
268
-
269
- return x
 
1
+ import logging
2
 
3
  # Configure logging
4
  logging.basicConfig(
5
  level=logging.INFO,
6
+ format="%(asctime)s - %(levelname)s - %(message)s",
7
  handlers=[
8
  logging.StreamHandler(),
9
+ ],
10
  )
11
  logger = logging.getLogger(__name__)
12
 
13
+
14
  def read_mol(molpath):
15
  with open(molpath, "r") as fp:
16
  lines = fp.readlines()
 
20
  return mol
21
 
22
 
23
+ def molecule(input_pdb, lenSeqs, num_res, selectedResidues, allSeqs, sequences):
 
 
 
24
  options = ""
25
  pred_mol = "["
26
  seqdata = "{"
 
263
  </script>
264
  </body></html>"""
265
  )
266
+
267
+ return x
folding_studio_demo/predict.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Predict protein structure using Folding Studio."""
2
+
3
+ import hashlib
4
+ import logging
5
+ import os
6
+ from pathlib import Path
7
+
8
+ import gradio as gr
9
+ import numpy as np
10
+ import plotly.graph_objects as go
11
+ from Bio import SeqIO
12
+ from Bio.PDB import PDBIO, MMCIFParser
13
+ from folding_studio.client import Client
14
+ from folding_studio.query import Query
15
+ from folding_studio.query.boltz import BoltzQuery
16
+ from folding_studio.query.chai import ChaiQuery
17
+ from folding_studio.query.protenix import ProtenixQuery
18
+ from folding_studio_data_models import FoldingModel
19
+
20
+ from folding_studio_demo.model_fasta_validators import (
21
+ BaseFastaValidator,
22
+ BoltzFastaValidator,
23
+ ChaiFastaValidator,
24
+ ProtenixFastaValidator,
25
+ )
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ SEQUENCE_DIR = Path("sequences")
30
+ SEQUENCE_DIR.mkdir(parents=True, exist_ok=True)
31
+
32
+ OUTPUT_DIR = Path("output")
33
+ OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
34
+
35
+
36
+ def convert_cif_to_pdb(cif_path: str, pdb_path: str) -> None:
37
+ """Convert a .cif file to .pdb format using Biopython.
38
+
39
+ Args:
40
+ cif_path (str): Path to input .cif file
41
+ pdb_path (str): Path to output .pdb file
42
+ """
43
+ # Parse the CIF file
44
+ parser = MMCIFParser()
45
+ structure = parser.get_structure("structure", cif_path)
46
+
47
+ # Save as PDB
48
+ io = PDBIO()
49
+ io.set_structure(structure)
50
+ io.save(pdb_path)
51
+
52
+
53
+ def add_plddt_plot(plddt_vals: list[float]) -> str:
54
+ """Create a plot of metrics."""
55
+ visible = True
56
+ plddt_trace = go.Scatter(
57
+ x=np.arange(len(plddt_vals)),
58
+ y=plddt_vals,
59
+ hovertemplate="<i>pLDDT</i>: %{y:.2f} <br><i>Residue index:</i> %{x}<br>",
60
+ name="seq",
61
+ visible=visible,
62
+ )
63
+
64
+ plddt_fig = go.Figure(data=[plddt_trace])
65
+ plddt_fig.update_layout(
66
+ title="pLDDT",
67
+ xaxis_title="Residue index",
68
+ yaxis_title="pLDDT",
69
+ height=500,
70
+ template="simple_white",
71
+ legend=dict(yanchor="bottom", y=0.01, xanchor="left", x=0.99),
72
+ )
73
+ return plddt_fig
74
+
75
+
76
+ def _write_fasta_file(
77
+ sequence: str, directory: Path = SEQUENCE_DIR
78
+ ) -> tuple[str, Path]:
79
+ """Write sequence to FASTA file.
80
+
81
+ Args:
82
+ sequence (str): Sequence to write to FASTA file
83
+ directory (Path): Directory to write FASTA file to (default: SEQUENCE_DIR)
84
+
85
+ Returns:
86
+ tuple[str, Path]: Tuple containing the sequence ID and the path to the FASTA file
87
+ """
88
+ seq_id = hashlib.sha1(sequence.encode()).hexdigest()
89
+ seq_file = directory / f"sequence_{seq_id}.fasta"
90
+ with open(seq_file, "w") as f:
91
+ f.write(sequence)
92
+ return seq_id, seq_file
93
+
94
+
95
+ class AF3Model:
96
+ def __init__(
97
+ self, api_key: str, model_name: str, query: Query, validator: BaseFastaValidator
98
+ ):
99
+ self.api_key = api_key
100
+ self.model_name = model_name
101
+ self.query = query
102
+ self.validator = validator
103
+
104
+ def call(self, seq_file: Path | str, output_dir: Path) -> None:
105
+ """Predict protein structure from amino acid sequence using AF3 model.
106
+
107
+ Args:
108
+ seq_file (Path | str): Path to FASTA file containing amino acid sequence
109
+ output_dir (Path): Path to output directory
110
+ """
111
+ # Validate FASTA format before calling
112
+ is_valid, error_msg = self.check_file_description(seq_file)
113
+ if not is_valid:
114
+ logger.error(error_msg)
115
+ raise gr.Error(error_msg)
116
+
117
+ # Create a client using API key
118
+ logger.info("Authenticating client with API key")
119
+ client = Client.from_api_key(api_key=self.api_key)
120
+
121
+ # Define query
122
+ query: Query = self.query.from_file(path=seq_file, query_name="gradio")
123
+ query.save_parameters(output_dir)
124
+
125
+ logger.info("Payload: %s", query.payload)
126
+
127
+ # Send a request
128
+ logger.info(f"Sending {self.model_name} request to Folding Studio API")
129
+ response = client.send_request(
130
+ query, project_code=os.environ["FOLDING_PROJECT_CODE"]
131
+ )
132
+
133
+ # Access confidence data
134
+ logger.info("Confidence data: %s", response.confidence_data)
135
+
136
+ response.download_results(output_dir=output_dir, force=True, unzip=True)
137
+ logger.info("Results downloaded to %s", output_dir)
138
+
139
+ def format_fasta(self, sequence: str) -> str:
140
+ """Format sequence to FASTA format."""
141
+ return f">{self.model_name}\n{sequence}"
142
+
143
+ def predictions(self, output_dir: Path) -> list[Path]:
144
+ """Get the path to the prediction."""
145
+ raise NotImplementedError("Not implemented")
146
+
147
+ def has_prediction(self, output_dir: Path) -> bool:
148
+ """Check if prediction exists in output directory."""
149
+ return any(self.predictions(output_dir))
150
+
151
+ def check_file_description(self, seq_file: Path | str) -> tuple[bool, str | None]:
152
+ """Check if the file description is correct.
153
+
154
+ Args:
155
+ seq_file (Path | str): Path to FASTA file
156
+
157
+ Returns:
158
+ tuple[bool, str | None]: Tuple containing a boolean indicating if the format is correct and an error message if not
159
+ """
160
+ input_rep = list(SeqIO.parse(seq_file, "fasta"))
161
+ if not input_rep:
162
+ error_msg = f"{self.model_name.upper()} Validation Error: No sequence found"
163
+ return False, error_msg
164
+
165
+ is_valid, error_msg = self.validator.is_valid_fasta(seq_file)
166
+ if not is_valid:
167
+ return False, error_msg
168
+
169
+ return True, None
170
+
171
+
172
+ class ChaiModel(AF3Model):
173
+ def __init__(self, api_key: str):
174
+ super().__init__(api_key, "Chai", ChaiQuery, ChaiFastaValidator())
175
+
176
+ def call(self, seq_file: Path | str, output_dir: Path) -> None:
177
+ """Predict protein structure from amino acid sequence using Chai model.
178
+
179
+ Args:
180
+ seq_file (Path | str): Path to FASTA file containing amino acid sequence
181
+ output_dir (Path): Path to output directory
182
+ """
183
+ super().call(seq_file, output_dir)
184
+
185
+ def predictions(self, output_dir: Path) -> list[Path]:
186
+ """Get the path to the prediction."""
187
+ return list(output_dir.rglob("*_model_[0-9].cif"))
188
+
189
+
190
+ class ProtenixModel(AF3Model):
191
+ def __init__(self, api_key: str):
192
+ super().__init__(api_key, "Protenix", ProtenixQuery, ProtenixFastaValidator())
193
+
194
+ def call(self, seq_file: Path | str, output_dir: Path) -> None:
195
+ """Predict protein structure from amino acid sequence using Protenix model.
196
+
197
+ Args:
198
+ seq_file (Path | str): Path to FASTA file containing amino acid sequence
199
+ output_dir (Path): Path to output directory
200
+ """
201
+ super().call(seq_file, output_dir)
202
+
203
+ def predictions(self, output_dir: Path) -> list[Path]:
204
+ """Get the path to the prediction."""
205
+ return list(output_dir.rglob("*_model_[0-9].cif"))
206
+
207
+
208
+ class BoltzModel(AF3Model):
209
+ def __init__(self, api_key: str):
210
+ super().__init__(api_key, "Boltz", BoltzQuery, BoltzFastaValidator())
211
+
212
+ def call(self, seq_file: Path | str, output_dir: Path) -> None:
213
+ """Predict protein structure from amino acid sequence using Boltz model.
214
+
215
+ Args:
216
+ seq_file (Path | str): Path to FASTA file containing amino acid sequence
217
+ output_dir (Path): Path to output directory
218
+ """
219
+
220
+ super().call(seq_file, output_dir)
221
+
222
+ def predictions(self, output_dir: Path) -> list[Path]:
223
+ """Get the path to the prediction."""
224
+ return list(output_dir.rglob("*_model_[0-9].cif"))
225
+
226
+
227
+ def predict(sequence: str, api_key: str, model_type: FoldingModel) -> tuple[str, str]:
228
+ """Predict protein structure from amino acid sequence using Boltz model.
229
+
230
+ Args:
231
+ sequence (str): Amino acid sequence to predict structure for
232
+ api_key (str): Folding API key
233
+ model (FoldingModel): Folding model to use
234
+
235
+ Returns:
236
+ tuple[str, str]: Tuple containing the path to the PDB file and the pLDDT plot
237
+ """
238
+
239
+ # Set up unique output directory based on sequence hash
240
+ seq_id, seq_file = _write_fasta_file(sequence)
241
+ output_dir = OUTPUT_DIR / seq_id / model_type
242
+ output_dir.mkdir(parents=True, exist_ok=True)
243
+
244
+ if model_type == FoldingModel.BOLTZ:
245
+ model = BoltzModel(api_key)
246
+ elif model_type == FoldingModel.CHAI:
247
+ model = ChaiModel(api_key)
248
+ elif model_type == FoldingModel.PROTENIX:
249
+ model = ProtenixModel(api_key)
250
+ else:
251
+ raise ValueError(f"Model {model_type} not supported")
252
+
253
+ # Check if prediction already exists
254
+ if not model.has_prediction(output_dir):
255
+ # Run Boltz prediction
256
+ logger.info(f"Predicting {seq_id}")
257
+ model.call(seq_file=seq_file, output_dir=output_dir)
258
+ logger.info("Prediction done. Output directory: %s", output_dir)
259
+ else:
260
+ logger.info("Prediction already exists. Output directory: %s", output_dir)
261
+
262
+ # output_dir = Path("boltz_results") # debug
263
+
264
+ # Convert output CIF to PDB
265
+ if not model.has_prediction(output_dir):
266
+ raise gr.Error("No prediction found")
267
+
268
+ pred_cif = model.predictions(output_dir)[0]
269
+ logger.info("Output file: %s", pred_cif)
270
+
271
+ converted_pdb_path = str(output_dir / f"pred_{seq_id}.pdb")
272
+ convert_cif_to_pdb(str(pred_cif), str(converted_pdb_path))
273
+ logger.info("Converted PDB file: %s", converted_pdb_path)
274
+
275
+ plddt_file = list(pred_cif.parent.glob("plddt_*.npz"))[0]
276
+ logger.info("plddt file: %s", plddt_file)
277
+ plddt_vals = np.load(plddt_file)["plddt"]
278
+
279
+ return converted_pdb_path, add_plddt_plot(plddt_vals=plddt_vals)
folding_studio_demo/protein.py ADDED
File without changes
pyproject.toml CHANGED
@@ -1,14 +1,27 @@
1
  [project]
2
- name = "fs"
3
  version = "0.1.0"
4
  description = "Add your description here"
5
  readme = "README.md"
6
  requires-python = ">=3.11"
7
  dependencies = [
8
  "gradio==5.30.0",
 
9
  "ipython>=9.2.0",
10
  "jupyter>=1.1.1",
11
  "numpy>=2.2.6",
12
  "plotly>=6.1.1",
13
  "scipy>=1.15.3",
 
 
14
  ]
 
 
 
 
 
 
 
 
 
 
 
1
  [project]
2
+ name = "folding-studio-demo"
3
  version = "0.1.0"
4
  description = "Add your description here"
5
  readme = "README.md"
6
  requires-python = ">=3.11"
7
  dependencies = [
8
  "gradio==5.30.0",
9
+ "gradio-molecule3d>=0.0.7",
10
  "ipython>=9.2.0",
11
  "jupyter>=1.1.1",
12
  "numpy>=2.2.6",
13
  "plotly>=6.1.1",
14
  "scipy>=1.15.3",
15
+ "folding-studio",
16
+ "biopython>=1.85",
17
  ]
18
+
19
+ [build-system]
20
+ requires = ["hatchling"]
21
+ build-backend = "hatchling.build"
22
+
23
+ [tool.uv.sources]
24
+ folding-studio = { path = "./folding-studio" }
25
+
26
+ [project.scripts]
27
+ folding-studio-demo = "folding_studio_demo.app:__main__"
uv.lock CHANGED
@@ -183,6 +183,36 @@ wheels = [
183
  { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285 },
184
  ]
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  [[package]]
187
  name = "bleach"
188
  version = "6.2.0"
@@ -200,6 +230,15 @@ css = [
200
  { name = "tinycss2" },
201
  ]
202
 
 
 
 
 
 
 
 
 
 
203
  [[package]]
204
  name = "certifi"
205
  version = "2025.4.26"
@@ -314,6 +353,20 @@ wheels = [
314
  { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 },
315
  ]
316
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  [[package]]
318
  name = "colorama"
319
  version = "0.4.6"
@@ -425,20 +478,61 @@ wheels = [
425
  ]
426
 
427
  [[package]]
428
- name = "fqdn"
429
- version = "1.5.1"
430
- source = { registry = "https://pypi.org/simple" }
431
- sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
432
  wheels = [
433
- { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 },
 
 
 
 
 
 
 
 
434
  ]
435
 
436
  [[package]]
437
- name = "fs"
438
  version = "0.1.0"
439
- source = { virtual = "." }
440
  dependencies = [
 
 
441
  { name = "gradio" },
 
442
  { name = "ipython" },
443
  { name = "jupyter" },
444
  { name = "numpy" },
@@ -448,7 +542,10 @@ dependencies = [
448
 
449
  [package.metadata]
450
  requires-dist = [
 
 
451
  { name = "gradio", specifier = "==5.30.0" },
 
452
  { name = "ipython", specifier = ">=9.2.0" },
453
  { name = "jupyter", specifier = ">=1.1.1" },
454
  { name = "numpy", specifier = ">=2.2.6" },
@@ -456,13 +553,133 @@ requires-dist = [
456
  { name = "scipy", specifier = ">=1.15.3" },
457
  ]
458
 
 
 
 
 
 
 
 
 
 
459
  [[package]]
460
  name = "fsspec"
461
- version = "2025.5.0"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  source = { registry = "https://pypi.org/simple" }
463
- sdist = { url = "https://files.pythonhosted.org/packages/f2/77/deb99b97981e2e191913454da82d406702405178631c31cd623caebaf1b1/fsspec-2025.5.0.tar.gz", hash = "sha256:e4f4623bb6221f7407fd695cc535d1f857a077eb247580f4ada34f5dc25fd5c8", size = 300989 }
 
 
 
464
  wheels = [
465
- { url = "https://files.pythonhosted.org/packages/2c/a9/a7022f58e081149ec0184c31ea81dcee605e1d46380b48122e1ef94ac24e/fsspec-2025.5.0-py3-none-any.whl", hash = "sha256:0ca253eca6b5333d8a2b8bd98c7326fe821f1f0fdbd34e1b445bddde8e804c95", size = 196164 },
466
  ]
467
 
468
  [[package]]
@@ -522,6 +739,18 @@ wheels = [
522
  { url = "https://files.pythonhosted.org/packages/55/6f/03eb8e0e0ec80eced5ed35a63376dabfc7391b1538502f8e85e9dc5bab02/gradio_client-1.10.1-py3-none-any.whl", hash = "sha256:fcff53f6aad3dfa9dd082adedb94256172d6b20666b1ef66480d82023e1907db", size = 323141 },
523
  ]
524
 
 
 
 
 
 
 
 
 
 
 
 
 
525
  [[package]]
526
  name = "groovy"
527
  version = "0.1.2"
@@ -540,6 +769,21 @@ wheels = [
540
  { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 },
541
  ]
542
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
543
  [[package]]
544
  name = "httpcore"
545
  version = "1.0.9"
@@ -570,20 +814,21 @@ wheels = [
570
 
571
  [[package]]
572
  name = "huggingface-hub"
573
- version = "0.31.4"
574
  source = { registry = "https://pypi.org/simple" }
575
  dependencies = [
576
  { name = "filelock" },
577
  { name = "fsspec" },
 
578
  { name = "packaging" },
579
  { name = "pyyaml" },
580
  { name = "requests" },
581
  { name = "tqdm" },
582
  { name = "typing-extensions" },
583
  ]
584
- sdist = { url = "https://files.pythonhosted.org/packages/05/a0/7445e07427a917399db619e3c7383de3cd723c20d3b3a8a527a096c49a44/huggingface_hub-0.31.4.tar.gz", hash = "sha256:5a7bc710b9f9c028aee5b1476867b4ec5c1b92f043cb364d5fdc54354757e4ce", size = 407736 }
585
  wheels = [
586
- { url = "https://files.pythonhosted.org/packages/33/c7/852d4473788cfd7d79b73951244b87a6d75fdac296c90aeb5e85dbb2fb5e/huggingface_hub-0.31.4-py3-none-any.whl", hash = "sha256:4f70704760296cc69b612916056e9845f5490a33782b924fc531767967acc15d", size = 489319 },
587
  ]
588
 
589
  [[package]]
@@ -1054,11 +1299,11 @@ wheels = [
1054
 
1055
  [[package]]
1056
  name = "narwhals"
1057
- version = "1.40.0"
1058
  source = { registry = "https://pypi.org/simple" }
1059
- sdist = { url = "https://files.pythonhosted.org/packages/f0/57/283881d06788c2fddd05eb7f0d6c82c5116d2827e83b845c796c74417c56/narwhals-1.40.0.tar.gz", hash = "sha256:17064abffd264ea1cfe6aefc8a0080f3a4ffb3659a98bcad5456ca80b88f2a0a", size = 487625 }
1060
  wheels = [
1061
- { url = "https://files.pythonhosted.org/packages/2c/e6/4d16dfa26f40230593c216bf695da01682fdbdf6af4e79abef572ab26bce/narwhals-1.40.0-py3-none-any.whl", hash = "sha256:1e6c731811d01c61147c52433b4d4edfb6511aaf2c859aa01c2e8ca6ff4d27e5", size = 357340 },
1062
  ]
1063
 
1064
  [[package]]
@@ -1413,15 +1658,15 @@ wheels = [
1413
 
1414
  [[package]]
1415
  name = "plotly"
1416
- version = "6.1.1"
1417
  source = { registry = "https://pypi.org/simple" }
1418
  dependencies = [
1419
  { name = "narwhals" },
1420
  { name = "packaging" },
1421
  ]
1422
- sdist = { url = "https://files.pythonhosted.org/packages/8a/7c/f396bc817975252afbe7af102ce09cd12ac40a8e90b8699a857d1b15c8a3/plotly-6.1.1.tar.gz", hash = "sha256:84a4f3d36655f1328fa3155377c7e8a9533196697d5b79a4bc5e905bdd09a433", size = 7543694 }
1423
  wheels = [
1424
- { url = "https://files.pythonhosted.org/packages/75/f3/f8cb7066f761e2530e1280889e3413769891e349fca35ee7290e4ace35f5/plotly-6.1.1-py3-none-any.whl", hash = "sha256:9cca7167406ebf7ff541422738402159ec3621a608ff7b3e2f025573a1c76225", size = 16118469 },
1425
  ]
1426
 
1427
  [[package]]
@@ -1445,6 +1690,32 @@ wheels = [
1445
  { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 },
1446
  ]
1447
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1448
  [[package]]
1449
  name = "psutil"
1450
  version = "7.0.0"
@@ -1478,6 +1749,27 @@ wheels = [
1478
  { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 },
1479
  ]
1480
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1481
  [[package]]
1482
  name = "pycparser"
1483
  version = "2.22"
@@ -1489,63 +1781,82 @@ wheels = [
1489
 
1490
  [[package]]
1491
  name = "pydantic"
1492
- version = "2.9.2"
1493
  source = { registry = "https://pypi.org/simple" }
1494
  dependencies = [
1495
  { name = "annotated-types" },
1496
  { name = "pydantic-core" },
1497
  { name = "typing-extensions" },
 
1498
  ]
1499
- sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 }
1500
  wheels = [
1501
- { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 },
1502
  ]
1503
 
1504
  [[package]]
1505
  name = "pydantic-core"
1506
- version = "2.23.4"
1507
  source = { registry = "https://pypi.org/simple" }
1508
  dependencies = [
1509
  { name = "typing-extensions" },
1510
  ]
1511
- sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 }
1512
- wheels = [
1513
- { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 },
1514
- { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 },
1515
- { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 },
1516
- { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 },
1517
- { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 },
1518
- { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 },
1519
- { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 },
1520
- { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 },
1521
- { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 },
1522
- { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 },
1523
- { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 },
1524
- { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 },
1525
- { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 },
1526
- { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 },
1527
- { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 },
1528
- { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 },
1529
- { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 },
1530
- { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 },
1531
- { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 },
1532
- { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 },
1533
- { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 },
1534
- { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 },
1535
- { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 },
1536
- { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 },
1537
- { url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 },
1538
- { url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 },
1539
- { url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 },
1540
- { url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 },
1541
- { url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 },
1542
- { url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 },
1543
- { url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 },
1544
- { url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 },
1545
- { url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 },
1546
- { url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 },
1547
- { url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 },
1548
- { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1549
  ]
1550
 
1551
  [[package]]
@@ -1578,6 +1889,15 @@ wheels = [
1578
  { 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 },
1579
  ]
1580
 
 
 
 
 
 
 
 
 
 
1581
  [[package]]
1582
  name = "python-json-logger"
1583
  version = "3.3.0"
@@ -1862,29 +2182,41 @@ wheels = [
1862
  { url = "https://files.pythonhosted.org/packages/2e/ba/31239736f29e4dfc7a58a45955c5db852864c306131fd6320aea214d5437/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a46c2fb2545e21181445515960006e85d22025bd2fe6db23e76daec6eb689fe", size = 558781 },
1863
  ]
1864
 
 
 
 
 
 
 
 
 
 
 
 
 
1865
  [[package]]
1866
  name = "ruff"
1867
- version = "0.11.10"
1868
- source = { registry = "https://pypi.org/simple" }
1869
- sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632 }
1870
- wheels = [
1871
- { url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243 },
1872
- { url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636 },
1873
- { url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624 },
1874
- { url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358 },
1875
- { url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850 },
1876
- { url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787 },
1877
- { url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479 },
1878
- { url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760 },
1879
- { url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747 },
1880
- { url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657 },
1881
- { url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671 },
1882
- { url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135 },
1883
- { url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179 },
1884
- { url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021 },
1885
- { url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958 },
1886
- { url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285 },
1887
- { url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278 },
1888
  ]
1889
 
1890
  [[package]]
@@ -2112,7 +2444,7 @@ wheels = [
2112
 
2113
  [[package]]
2114
  name = "typer"
2115
- version = "0.12.5"
2116
  source = { registry = "https://pypi.org/simple" }
2117
  dependencies = [
2118
  { name = "click" },
@@ -2120,9 +2452,9 @@ dependencies = [
2120
  { name = "shellingham" },
2121
  { name = "typing-extensions" },
2122
  ]
2123
- sdist = { url = "https://files.pythonhosted.org/packages/c5/58/a79003b91ac2c6890fc5d90145c662fd5771c6f11447f116b63300436bc9/typer-0.12.5.tar.gz", hash = "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722", size = 98953 }
2124
  wheels = [
2125
- { url = "https://files.pythonhosted.org/packages/a8/2b/886d13e742e514f704c33c4caa7df0f3b89e5a25ef8db02aa9ca3d9535d5/typer-0.12.5-py3-none-any.whl", hash = "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b", size = 47288 },
2126
  ]
2127
 
2128
  [[package]]
@@ -2143,6 +2475,18 @@ wheels = [
2143
  { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
2144
  ]
2145
 
 
 
 
 
 
 
 
 
 
 
 
 
2146
  [[package]]
2147
  name = "tzdata"
2148
  version = "2025.2"
 
183
  { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285 },
184
  ]
185
 
186
+ [[package]]
187
+ name = "biopython"
188
+ version = "1.85"
189
+ source = { registry = "https://pypi.org/simple" }
190
+ dependencies = [
191
+ { name = "numpy" },
192
+ ]
193
+ sdist = { url = "https://files.pythonhosted.org/packages/db/ca/1d5fab0fedaf5c2f376d9746d447cdce04241c433602c3861693361ce54c/biopython-1.85.tar.gz", hash = "sha256:5dafab74059de4e78f49f6b5684eddae6e7ce46f09cfa059c1d1339e8b1ea0a6", size = 19909902 }
194
+ wheels = [
195
+ { url = "https://files.pythonhosted.org/packages/c3/73/c3a1323a3fe0d07212b09c04fb903e2cbb98aebfbb58e55e8717473e1bc0/biopython-1.85-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:db8822adab0cd75a6e6ae845acf312addd8eab5f9b731c191454b961fc2c2cdc", size = 2787585 },
196
+ { url = "https://files.pythonhosted.org/packages/ff/cf/299524e896fa49beb7588143e1509cce4848572215ebafb8eea83a861820/biopython-1.85-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e2bbe58cc1a592b239ef6d68396745d3fbfaafc668ce38283871d8ff070dbab", size = 2765608 },
197
+ { url = "https://files.pythonhosted.org/packages/b1/dd/be3e95b72a35ee6d52c84412e15af828951e5c69175080d4619985fd54ce/biopython-1.85-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5916eb56df7ecd4a3babc07a48d4894c40cfb45dc18ccda1c148d0131017ce04", size = 3237552 },
198
+ { url = "https://files.pythonhosted.org/packages/25/9c/612821b946930b6caa5d795cfe4169ed6a522562eced9776914be7efaf21/biopython-1.85-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0cec8833bf3036049129944ee5382dd576dac9670c3814ff2314b52aa94f199", size = 3257287 },
199
+ { url = "https://files.pythonhosted.org/packages/a1/77/316e51dd42fd8225429574a268bdc627ce4f42067a3976c4c8c457a42023/biopython-1.85-cp311-cp311-win32.whl", hash = "sha256:cf88a4c8d8af13138be115949639a5e4a201618185a72ff09adbe175b7946b28", size = 2786411 },
200
+ { url = "https://files.pythonhosted.org/packages/f2/11/3c4e8c049b91998bbbd51ddebc6f790b1aa66211babfbf5ff008a72fb1f9/biopython-1.85-cp311-cp311-win_amd64.whl", hash = "sha256:d3c99db65d57ae4fc5034e42ac6cd8ddce069e664903f04c8a4f684d7609d6fa", size = 2820912 },
201
+ { url = "https://files.pythonhosted.org/packages/a3/25/e46f05359df7f0049c3adc5eaeb9aee0f5fbde1d959d05c78eb1de8f4d12/biopython-1.85-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc5b981b9e3060db7c355b6145dfe3ce0b6572e1601b31211f6d742b10543874", size = 2789327 },
202
+ { url = "https://files.pythonhosted.org/packages/54/5b/8b3b029c94c63ab4c1781d141615b4a837e658422381d460c5573d5d8262/biopython-1.85-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6fe47d704c2d3afac99aeb461219ec5f00273120d2d99835dc0a9a617f520141", size = 2765805 },
203
+ { url = "https://files.pythonhosted.org/packages/69/0a/9a8a38eff03c4607b9cec8d0e08c76b346b1cee1f77bc6d00efebfc7ec83/biopython-1.85-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54e495239e623660ad367498c2f7a1a294b1997ba603f2ceafb36fd18f0eba6", size = 3249276 },
204
+ { url = "https://files.pythonhosted.org/packages/b4/0d/b7a0f10f5100dcf51ae36ba31490169bfa45617323bd82af43e1fb0098fb/biopython-1.85-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d024ad48997ad53d53a77da24b072aaba8a550bd816af8f2e7e606a9918a3b43", size = 3268869 },
205
+ { url = "https://files.pythonhosted.org/packages/07/51/646a4b7bdb4c1153786a70d33588ed09178bfcdda0542dfdc976294f4312/biopython-1.85-cp312-cp312-win32.whl", hash = "sha256:6985e17a2911defcbd30275a12f5ed5de2765e4bc91a60439740d572fdbfdf43", size = 2787011 },
206
+ { url = "https://files.pythonhosted.org/packages/c1/84/c583fa2ac6e7d392d24ebdc5c99e95e517507de22cf143efb6cf1fc93ff5/biopython-1.85-cp312-cp312-win_amd64.whl", hash = "sha256:d6f8efb2db03f2ec115c5e8c764dbadf635e0c9ecd4c0e91fc8216c1b62f85f5", size = 2820972 },
207
+ { url = "https://files.pythonhosted.org/packages/c3/3f/65814bf221f0bfdd2633830e573ac8594794686f54110571dce98cc32fd3/biopython-1.85-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bae6f17262f20e9587d419ba5683e9dc93a31ee1858b98fa0cff203694d5b786", size = 2789844 },
208
+ { url = "https://files.pythonhosted.org/packages/be/61/1443ce34226e261c20ae4a154b2bab72c109cf31415c92c54c1aada36b00/biopython-1.85-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b1e4918e6399ab0183dd863527fec18b53b7c61b6f0ef95db84b4adfa430ce75", size = 2765767 },
209
+ { url = "https://files.pythonhosted.org/packages/53/51/bec4c763c704e2715691bb087cfab5907804a1bbef5873588698cece1a30/biopython-1.85-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86e487b6fe0f20a2b0138cb53f3d4dc26a7e4060ac4cb6fb1d19e194d445ef46", size = 3248867 },
210
+ { url = "https://files.pythonhosted.org/packages/9e/a4/552f20253a7c95988067c4955831bd17dd9b864fd5c215d15c2f63f2d415/biopython-1.85-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:327184048b5a50634ae0970119bcb8a35b76d7cefb2658a01f772915f2fb7686", size = 3268479 },
211
+ { url = "https://files.pythonhosted.org/packages/97/f4/6dfc6ef3e0997f792f93893551d4a9bf7f6052a70d34f4e1b1e5e4a359f6/biopython-1.85-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66b08905fb1b3f5194f908aaf04bbad474c4e3eaebad6d9f889a04e64dd1faf4", size = 3192338 },
212
+ { url = "https://files.pythonhosted.org/packages/9d/8c/73da6588fab3a7d734118f21ac619c0a949f51d3cf7b1b8343d175c33460/biopython-1.85-cp313-cp313-win32.whl", hash = "sha256:5a236ab1e2797c7dcf1577d80fdaafabead2908bc338eaed0aa1509dab769fef", size = 2787017 },
213
+ { url = "https://files.pythonhosted.org/packages/60/ff/fe8f03ac0ccc7219e0959010750c6ac1a5b1411c91c7f4ec3a37d8313673/biopython-1.85-cp313-cp313-win_amd64.whl", hash = "sha256:1b61593765e9ebdb71d73307d55fd4b46eb976608d329ae6803c084d90ed34c7", size = 2821012 },
214
+ ]
215
+
216
  [[package]]
217
  name = "bleach"
218
  version = "6.2.0"
 
230
  { name = "tinycss2" },
231
  ]
232
 
233
+ [[package]]
234
+ name = "cachetools"
235
+ version = "5.5.2"
236
+ source = { registry = "https://pypi.org/simple" }
237
+ sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380 }
238
+ wheels = [
239
+ { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080 },
240
+ ]
241
+
242
  [[package]]
243
  name = "certifi"
244
  version = "2025.4.26"
 
353
  { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 },
354
  ]
355
 
356
+ [[package]]
357
+ name = "cloudpathlib"
358
+ version = "0.19.0"
359
+ source = { registry = "https://pypi.org/simple" }
360
+ sdist = { url = "https://files.pythonhosted.org/packages/10/c9/90cb80cf70669ed9a978ef46d8a8b39bad8ca768c4ce4ae086f0d05aeeb7/cloudpathlib-0.19.0.tar.gz", hash = "sha256:919edbfd9a4e935d2423da210b143df89cb0ec6d378366a0dffa2e9fd0664fe8", size = 41875 }
361
+ wheels = [
362
+ { url = "https://files.pythonhosted.org/packages/4d/4e/f83794cb311019c385d061d9b7a9dc444c7023c5523c3f4161191221429c/cloudpathlib-0.19.0-py3-none-any.whl", hash = "sha256:eb7758648812d5906af44f14cf9a6a64f687342a6f547a1c20deb7241d769dcb", size = 49433 },
363
+ ]
364
+
365
+ [package.optional-dependencies]
366
+ gs = [
367
+ { name = "google-cloud-storage" },
368
+ ]
369
+
370
  [[package]]
371
  name = "colorama"
372
  version = "0.4.6"
 
478
  ]
479
 
480
  [[package]]
481
+ name = "folding-studio"
482
+ version = "3.3.1"
483
+ source = { directory = "folding-studio" }
484
+ dependencies = [
485
+ { name = "folding-studio-data-models" },
486
+ { name = "pydantic" },
487
+ { name = "python-dotenv" },
488
+ { name = "pyyaml" },
489
+ { name = "requests" },
490
+ { name = "tqdm" },
491
+ { name = "typer" },
492
+ ]
493
+
494
+ [package.metadata]
495
+ requires-dist = [
496
+ { name = "folding-studio-data-models", path = "folding-studio/offline-packages/folding_studio_data_models-0.13.3-py3-none-any.whl" },
497
+ { name = "pydantic", specifier = ">=2.11.4,<3.0.0" },
498
+ { name = "python-dotenv", specifier = ">=1.0.1,<2.0.0" },
499
+ { name = "pyyaml", specifier = ">=6.0.2,<7.0.0" },
500
+ { name = "requests", specifier = ">=2.32.3,<3.0.0" },
501
+ { name = "tqdm", specifier = ">=4.67.0,<5.0.0" },
502
+ { name = "typer", specifier = ">=0.15.4,<0.16.0" },
503
+ ]
504
+
505
+ [[package]]
506
+ name = "folding-studio-data-models"
507
+ version = "0.13.3"
508
+ source = { path = "folding-studio/offline-packages/folding_studio_data_models-0.13.3-py3-none-any.whl" }
509
+ dependencies = [
510
+ { name = "biopython" },
511
+ { name = "cloudpathlib", extra = ["gs"] },
512
+ { name = "pydantic" },
513
+ { name = "typing-extensions" },
514
+ ]
515
  wheels = [
516
+ { filename = "folding_studio_data_models-0.13.3-py3-none-any.whl", hash = "sha256:2dcf684bd47c255566f90631499bd1f5755077e74dbe18c98ecc32c77e9b1390" },
517
+ ]
518
+
519
+ [package.metadata]
520
+ requires-dist = [
521
+ { name = "biopython", specifier = ">=1.81,<2.0" },
522
+ { name = "cloudpathlib", extras = ["gs"], specifier = ">=0.19.0,<0.20.0" },
523
+ { name = "pydantic", specifier = ">=2.9.2,<3.0.0" },
524
+ { name = "typing-extensions", specifier = ">=4.12.2,<5.0.0" },
525
  ]
526
 
527
  [[package]]
528
+ name = "folding-studio-demo"
529
  version = "0.1.0"
530
+ source = { editable = "." }
531
  dependencies = [
532
+ { name = "biopython" },
533
+ { name = "folding-studio" },
534
  { name = "gradio" },
535
+ { name = "gradio-molecule3d" },
536
  { name = "ipython" },
537
  { name = "jupyter" },
538
  { name = "numpy" },
 
542
 
543
  [package.metadata]
544
  requires-dist = [
545
+ { name = "biopython", specifier = ">=1.85" },
546
+ { name = "folding-studio", directory = "folding-studio" },
547
  { name = "gradio", specifier = "==5.30.0" },
548
+ { name = "gradio-molecule3d", specifier = ">=0.0.7" },
549
  { name = "ipython", specifier = ">=9.2.0" },
550
  { name = "jupyter", specifier = ">=1.1.1" },
551
  { name = "numpy", specifier = ">=2.2.6" },
 
553
  { name = "scipy", specifier = ">=1.15.3" },
554
  ]
555
 
556
+ [[package]]
557
+ name = "fqdn"
558
+ version = "1.5.1"
559
+ source = { registry = "https://pypi.org/simple" }
560
+ sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015 }
561
+ wheels = [
562
+ { url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121 },
563
+ ]
564
+
565
  [[package]]
566
  name = "fsspec"
567
+ version = "2025.5.1"
568
+ source = { registry = "https://pypi.org/simple" }
569
+ sdist = { url = "https://files.pythonhosted.org/packages/00/f7/27f15d41f0ed38e8fcc488584b57e902b331da7f7c6dcda53721b15838fc/fsspec-2025.5.1.tar.gz", hash = "sha256:2e55e47a540b91843b755e83ded97c6e897fa0942b11490113f09e9c443c2475", size = 303033 }
570
+ wheels = [
571
+ { url = "https://files.pythonhosted.org/packages/bb/61/78c7b3851add1481b048b5fdc29067397a1784e2910592bc81bb3f608635/fsspec-2025.5.1-py3-none-any.whl", hash = "sha256:24d3a2e663d5fc735ab256263c4075f374a174c3410c0b25e5bd1970bceaa462", size = 199052 },
572
+ ]
573
+
574
+ [[package]]
575
+ name = "google-api-core"
576
+ version = "2.24.2"
577
+ source = { registry = "https://pypi.org/simple" }
578
+ dependencies = [
579
+ { name = "google-auth" },
580
+ { name = "googleapis-common-protos" },
581
+ { name = "proto-plus" },
582
+ { name = "protobuf" },
583
+ { name = "requests" },
584
+ ]
585
+ sdist = { url = "https://files.pythonhosted.org/packages/09/5c/085bcb872556934bb119e5e09de54daa07873f6866b8f0303c49e72287f7/google_api_core-2.24.2.tar.gz", hash = "sha256:81718493daf06d96d6bc76a91c23874dbf2fac0adbbf542831b805ee6e974696", size = 163516 }
586
+ wheels = [
587
+ { url = "https://files.pythonhosted.org/packages/46/95/f472d85adab6e538da2025dfca9e976a0d125cc0af2301f190e77b76e51c/google_api_core-2.24.2-py3-none-any.whl", hash = "sha256:810a63ac95f3c441b7c0e43d344e372887f62ce9071ba972eacf32672e072de9", size = 160061 },
588
+ ]
589
+
590
+ [[package]]
591
+ name = "google-auth"
592
+ version = "2.40.2"
593
+ source = { registry = "https://pypi.org/simple" }
594
+ dependencies = [
595
+ { name = "cachetools" },
596
+ { name = "pyasn1-modules" },
597
+ { name = "rsa" },
598
+ ]
599
+ sdist = { url = "https://files.pythonhosted.org/packages/66/84/f67f53c505a6b2c5da05c988e2a5483f5ba9eee4b1841d2e3ff22f547cd5/google_auth-2.40.2.tar.gz", hash = "sha256:a33cde547a2134273226fa4b853883559947ebe9207521f7afc707efbf690f58", size = 280990 }
600
+ wheels = [
601
+ { url = "https://files.pythonhosted.org/packages/6a/c7/e2d82e6702e2a9e2311c138f8e1100f21d08aed0231290872b229ae57a86/google_auth-2.40.2-py2.py3-none-any.whl", hash = "sha256:f7e568d42eedfded58734f6a60c58321896a621f7c116c411550a4b4a13da90b", size = 216102 },
602
+ ]
603
+
604
+ [[package]]
605
+ name = "google-cloud-core"
606
+ version = "2.4.3"
607
+ source = { registry = "https://pypi.org/simple" }
608
+ dependencies = [
609
+ { name = "google-api-core" },
610
+ { name = "google-auth" },
611
+ ]
612
+ sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861 }
613
+ wheels = [
614
+ { url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348 },
615
+ ]
616
+
617
+ [[package]]
618
+ name = "google-cloud-storage"
619
+ version = "3.1.0"
620
+ source = { registry = "https://pypi.org/simple" }
621
+ dependencies = [
622
+ { name = "google-api-core" },
623
+ { name = "google-auth" },
624
+ { name = "google-cloud-core" },
625
+ { name = "google-crc32c" },
626
+ { name = "google-resumable-media" },
627
+ { name = "requests" },
628
+ ]
629
+ sdist = { url = "https://files.pythonhosted.org/packages/f3/08/52143124415a889bbab60a8ecede1e31ea0e8d992ca078317886f26dc3be/google_cloud_storage-3.1.0.tar.gz", hash = "sha256:944273179897c7c8a07ee15f2e6466a02da0c7c4b9ecceac2a26017cb2972049", size = 7666527 }
630
+ wheels = [
631
+ { url = "https://files.pythonhosted.org/packages/13/b8/c99c965659f45efa73080477c49ffddf7b9aecb00806be8422560bb5b824/google_cloud_storage-3.1.0-py2.py3-none-any.whl", hash = "sha256:eaf36966b68660a9633f03b067e4a10ce09f1377cae3ff9f2c699f69a81c66c6", size = 174861 },
632
+ ]
633
+
634
+ [[package]]
635
+ name = "google-crc32c"
636
+ version = "1.7.1"
637
+ source = { registry = "https://pypi.org/simple" }
638
+ sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495 }
639
+ wheels = [
640
+ { url = "https://files.pythonhosted.org/packages/f7/94/220139ea87822b6fdfdab4fb9ba81b3fff7ea2c82e2af34adc726085bffc/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6fbab4b935989e2c3610371963ba1b86afb09537fd0c633049be82afe153ac06", size = 30468 },
641
+ { url = "https://files.pythonhosted.org/packages/94/97/789b23bdeeb9d15dc2904660463ad539d0318286d7633fe2760c10ed0c1c/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ed66cbe1ed9cbaaad9392b5259b3eba4a9e565420d734e6238813c428c3336c9", size = 30313 },
642
+ { url = "https://files.pythonhosted.org/packages/81/b8/976a2b843610c211e7ccb3e248996a61e87dbb2c09b1499847e295080aec/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6547b657621b6cbed3562ea7826c3e11cab01cd33b74e1f677690652883e77", size = 33048 },
643
+ { url = "https://files.pythonhosted.org/packages/c9/16/a3842c2cf591093b111d4a5e2bfb478ac6692d02f1b386d2a33283a19dc9/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d68e17bad8f7dd9a49181a1f5a8f4b251c6dbc8cc96fb79f1d321dfd57d66f53", size = 32669 },
644
+ { url = "https://files.pythonhosted.org/packages/04/17/ed9aba495916fcf5fe4ecb2267ceb851fc5f273c4e4625ae453350cfd564/google_crc32c-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:6335de12921f06e1f774d0dd1fbea6bf610abe0887a1638f64d694013138be5d", size = 33476 },
645
+ { url = "https://files.pythonhosted.org/packages/dd/b7/787e2453cf8639c94b3d06c9d61f512234a82e1d12d13d18584bd3049904/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194", size = 30470 },
646
+ { url = "https://files.pythonhosted.org/packages/ed/b4/6042c2b0cbac3ec3a69bb4c49b28d2f517b7a0f4a0232603c42c58e22b44/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e", size = 30315 },
647
+ { url = "https://files.pythonhosted.org/packages/29/ad/01e7a61a5d059bc57b702d9ff6a18b2585ad97f720bd0a0dbe215df1ab0e/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337", size = 33180 },
648
+ { url = "https://files.pythonhosted.org/packages/3b/a5/7279055cf004561894ed3a7bfdf5bf90a53f28fadd01af7cd166e88ddf16/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65", size = 32794 },
649
+ { url = "https://files.pythonhosted.org/packages/0f/d6/77060dbd140c624e42ae3ece3df53b9d811000729a5c821b9fd671ceaac6/google_crc32c-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6", size = 33477 },
650
+ { url = "https://files.pythonhosted.org/packages/8b/72/b8d785e9184ba6297a8620c8a37cf6e39b81a8ca01bb0796d7cbb28b3386/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:df8b38bdaf1629d62d51be8bdd04888f37c451564c2042d36e5812da9eff3c35", size = 30467 },
651
+ { url = "https://files.pythonhosted.org/packages/34/25/5f18076968212067c4e8ea95bf3b69669f9fc698476e5f5eb97d5b37999f/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:e42e20a83a29aa2709a0cf271c7f8aefaa23b7ab52e53b322585297bb94d4638", size = 30309 },
652
+ { url = "https://files.pythonhosted.org/packages/92/83/9228fe65bf70e93e419f38bdf6c5ca5083fc6d32886ee79b450ceefd1dbd/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:905a385140bf492ac300026717af339790921f411c0dfd9aa5a9e69a08ed32eb", size = 33133 },
653
+ { url = "https://files.pythonhosted.org/packages/c3/ca/1ea2fd13ff9f8955b85e7956872fdb7050c4ace8a2306a6d177edb9cf7fe/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b211ddaf20f7ebeec5c333448582c224a7c90a9d98826fbab82c0ddc11348e6", size = 32773 },
654
+ { url = "https://files.pythonhosted.org/packages/89/32/a22a281806e3ef21b72db16f948cad22ec68e4bdd384139291e00ff82fe2/google_crc32c-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:0f99eaa09a9a7e642a61e06742856eec8b19fc0037832e03f941fe7cf0c8e4db", size = 33475 },
655
+ { url = "https://files.pythonhosted.org/packages/b8/c5/002975aff514e57fc084ba155697a049b3f9b52225ec3bc0f542871dd524/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d1da0d74ec5634a05f53ef7df18fc646666a25efaaca9fc7dcfd4caf1d98c3", size = 33243 },
656
+ { url = "https://files.pythonhosted.org/packages/61/cb/c585282a03a0cea70fcaa1bf55d5d702d0f2351094d663ec3be1c6c67c52/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e10554d4abc5238823112c2ad7e4560f96c7bf3820b202660373d769d9e6e4c9", size = 32870 },
657
+ { url = "https://files.pythonhosted.org/packages/16/1b/1693372bf423ada422f80fd88260dbfd140754adb15cbc4d7e9a68b1cb8e/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85fef7fae11494e747c9fd1359a527e5970fc9603c90764843caabd3a16a0a48", size = 28241 },
658
+ { url = "https://files.pythonhosted.org/packages/fd/3c/2a19a60a473de48717b4efb19398c3f914795b64a96cf3fbe82588044f78/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efb97eb4369d52593ad6f75e7e10d053cf00c48983f7a973105bc70b0ac4d82", size = 28048 },
659
+ ]
660
+
661
+ [[package]]
662
+ name = "google-resumable-media"
663
+ version = "2.7.2"
664
+ source = { registry = "https://pypi.org/simple" }
665
+ dependencies = [
666
+ { name = "google-crc32c" },
667
+ ]
668
+ sdist = { url = "https://files.pythonhosted.org/packages/58/5a/0efdc02665dca14e0837b62c8a1a93132c264bd02054a15abb2218afe0ae/google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0", size = 2163099 }
669
+ wheels = [
670
+ { url = "https://files.pythonhosted.org/packages/82/35/b8d3baf8c46695858cb9d8835a53baa1eeb9906ddaf2f728a5f5b640fd1e/google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", size = 81251 },
671
+ ]
672
+
673
+ [[package]]
674
+ name = "googleapis-common-protos"
675
+ version = "1.70.0"
676
  source = { registry = "https://pypi.org/simple" }
677
+ dependencies = [
678
+ { name = "protobuf" },
679
+ ]
680
+ sdist = { url = "https://files.pythonhosted.org/packages/39/24/33db22342cf4a2ea27c9955e6713140fedd51e8b141b5ce5260897020f1a/googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257", size = 145903 }
681
  wheels = [
682
+ { url = "https://files.pythonhosted.org/packages/86/f1/62a193f0227cf15a920390abe675f386dec35f7ae3ffe6da582d3ade42c7/googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8", size = 294530 },
683
  ]
684
 
685
  [[package]]
 
739
  { url = "https://files.pythonhosted.org/packages/55/6f/03eb8e0e0ec80eced5ed35a63376dabfc7391b1538502f8e85e9dc5bab02/gradio_client-1.10.1-py3-none-any.whl", hash = "sha256:fcff53f6aad3dfa9dd082adedb94256172d6b20666b1ef66480d82023e1907db", size = 323141 },
740
  ]
741
 
742
+ [[package]]
743
+ name = "gradio-molecule3d"
744
+ version = "0.0.7"
745
+ source = { registry = "https://pypi.org/simple" }
746
+ dependencies = [
747
+ { name = "gradio" },
748
+ ]
749
+ sdist = { url = "https://files.pythonhosted.org/packages/4a/01/7ee3f6909385210fda7129bc3330545537ec814cc3b6e101bdb8704bf5ab/gradio_molecule3d-0.0.7.tar.gz", hash = "sha256:ab3a99e806c012c02c5a081085d442944b61fefc1ad952a0ff3844dcffc7dc42", size = 1468341 }
750
+ wheels = [
751
+ { url = "https://files.pythonhosted.org/packages/ad/04/5883e077a8ff08b353de4be28bd8517aeaba14ad80ccb4d8d9ab7fe4291d/gradio_molecule3d-0.0.7-py3-none-any.whl", hash = "sha256:e40721d68c085f66c5594bf4b758ed31a32b0745f0f3d21d0c4adc248d1a7ed0", size = 1397339 },
752
+ ]
753
+
754
  [[package]]
755
  name = "groovy"
756
  version = "0.1.2"
 
769
  { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 },
770
  ]
771
 
772
+ [[package]]
773
+ name = "hf-xet"
774
+ version = "1.1.2"
775
+ source = { registry = "https://pypi.org/simple" }
776
+ sdist = { url = "https://files.pythonhosted.org/packages/95/be/58f20728a5b445f8b064e74f0618897b3439f5ef90934da1916b9dfac76f/hf_xet-1.1.2.tar.gz", hash = "sha256:3712d6d4819d3976a1c18e36db9f503e296283f9363af818f50703506ed63da3", size = 467009 }
777
+ wheels = [
778
+ { url = "https://files.pythonhosted.org/packages/45/ae/f1a63f75d9886f18a80220ba31a1c7b9c4752f03aae452f358f538c6a991/hf_xet-1.1.2-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dfd1873fd648488c70735cb60f7728512bca0e459e61fcd107069143cd798469", size = 2642559 },
779
+ { url = "https://files.pythonhosted.org/packages/50/ab/d2c83ae18f1015d926defd5bfbe94c62d15e93f900e6a192e318ee947105/hf_xet-1.1.2-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:29b584983b2d977c44157d9241dcf0fd50acde0b7bff8897fe4386912330090d", size = 2541360 },
780
+ { url = "https://files.pythonhosted.org/packages/9f/a7/693dc9f34f979e30a378125e2150a0b2d8d166e6d83ce3950eeb81e560aa/hf_xet-1.1.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b29ac84298147fe9164cc55ad994ba47399f90b5d045b0b803b99cf5f06d8ec", size = 5183081 },
781
+ { url = "https://files.pythonhosted.org/packages/3d/23/c48607883f692a36c0a7735f47f98bad32dbe459a32d1568c0f21576985d/hf_xet-1.1.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d921ba32615676e436a0d15e162331abc9ed43d440916b1d836dc27ce1546173", size = 5356100 },
782
+ { url = "https://files.pythonhosted.org/packages/eb/5b/b2316c7f1076da0582b52ea228f68bea95e243c388440d1dc80297c9d813/hf_xet-1.1.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:d9b03c34e13c44893ab6e8fea18ee8d2a6878c15328dd3aabedbdd83ee9f2ed3", size = 5647688 },
783
+ { url = "https://files.pythonhosted.org/packages/2c/98/e6995f0fa579929da7795c961f403f4ee84af36c625963f52741d56f242c/hf_xet-1.1.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01b18608955b3d826307d37da8bd38b28a46cd2d9908b3a3655d1363274f941a", size = 5322627 },
784
+ { url = "https://files.pythonhosted.org/packages/59/40/8f1d5a44a64d8bf9e3c19576e789f716af54875b46daae65426714e75db1/hf_xet-1.1.2-cp37-abi3-win_amd64.whl", hash = "sha256:3562902c81299b09f3582ddfb324400c6a901a2f3bc854f83556495755f4954c", size = 2739542 },
785
+ ]
786
+
787
  [[package]]
788
  name = "httpcore"
789
  version = "1.0.9"
 
814
 
815
  [[package]]
816
  name = "huggingface-hub"
817
+ version = "0.32.2"
818
  source = { registry = "https://pypi.org/simple" }
819
  dependencies = [
820
  { name = "filelock" },
821
  { name = "fsspec" },
822
+ { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
823
  { name = "packaging" },
824
  { name = "pyyaml" },
825
  { name = "requests" },
826
  { name = "tqdm" },
827
  { name = "typing-extensions" },
828
  ]
829
+ sdist = { url = "https://files.pythonhosted.org/packages/d0/76/44f7025d1b3f29336aeb7324a57dd7c19f7c69f6612b7637b39ac7c17302/huggingface_hub-0.32.2.tar.gz", hash = "sha256:64a288b1eadad6b60bbfd50f0e52fd6cfa2ef77ab13c3e8a834a038ae929de54", size = 422847 }
830
  wheels = [
831
+ { url = "https://files.pythonhosted.org/packages/32/30/532fe57467a6cc7ff2e39f088db1cb6d6bf522f724a4a5c7beda1282d5a6/huggingface_hub-0.32.2-py3-none-any.whl", hash = "sha256:f8fcf14603237eadf96dbe577d30b330f8c27b4a0a31e8f6c94fdc25e021fdb8", size = 509968 },
832
  ]
833
 
834
  [[package]]
 
1299
 
1300
  [[package]]
1301
  name = "narwhals"
1302
+ version = "1.41.0"
1303
  source = { registry = "https://pypi.org/simple" }
1304
+ sdist = { url = "https://files.pythonhosted.org/packages/32/fc/7b9a3689911662be59889b1b0b40e17d5dba6f98080994d86ca1f3154d41/narwhals-1.41.0.tar.gz", hash = "sha256:0ab2e5a1757a19b071e37ca74b53b0b5426789321d68939738337dfddea629b5", size = 488446 }
1305
  wheels = [
1306
+ { url = "https://files.pythonhosted.org/packages/c9/e0/ade8619846645461c012498f02b93a659e50f07d9d9a6ffefdf5ea2c02a0/narwhals-1.41.0-py3-none-any.whl", hash = "sha256:d958336b40952e4c4b7aeef259a7074851da0800cf902186a58f2faeff97be02", size = 357968 },
1307
  ]
1308
 
1309
  [[package]]
 
1658
 
1659
  [[package]]
1660
  name = "plotly"
1661
+ version = "6.1.2"
1662
  source = { registry = "https://pypi.org/simple" }
1663
  dependencies = [
1664
  { name = "narwhals" },
1665
  { name = "packaging" },
1666
  ]
1667
+ sdist = { url = "https://files.pythonhosted.org/packages/ae/77/431447616eda6a432dc3ce541b3f808ecb8803ea3d4ab2573b67f8eb4208/plotly-6.1.2.tar.gz", hash = "sha256:4fdaa228926ba3e3a213f4d1713287e69dcad1a7e66cf2025bd7d7026d5014b4", size = 7662971 }
1668
  wheels = [
1669
+ { url = "https://files.pythonhosted.org/packages/bf/6f/759d5da0517547a5d38aabf05d04d9f8adf83391d2c7fc33f904417d3ba2/plotly-6.1.2-py3-none-any.whl", hash = "sha256:f1548a8ed9158d59e03d7fed548c7db5549f3130d9ae19293c8638c202648f6d", size = 16265530 },
1670
  ]
1671
 
1672
  [[package]]
 
1690
  { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 },
1691
  ]
1692
 
1693
+ [[package]]
1694
+ name = "proto-plus"
1695
+ version = "1.26.1"
1696
+ source = { registry = "https://pypi.org/simple" }
1697
+ dependencies = [
1698
+ { name = "protobuf" },
1699
+ ]
1700
+ sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142 }
1701
+ wheels = [
1702
+ { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163 },
1703
+ ]
1704
+
1705
+ [[package]]
1706
+ name = "protobuf"
1707
+ version = "6.31.0"
1708
+ source = { registry = "https://pypi.org/simple" }
1709
+ sdist = { url = "https://files.pythonhosted.org/packages/13/48/718c1e104a2e89970a8ff3b06d87e152834b576c570a6908f8c17ba88d65/protobuf-6.31.0.tar.gz", hash = "sha256:314fab1a6a316469dc2dd46f993cbbe95c861ea6807da910becfe7475bc26ffe", size = 441644 }
1710
+ wheels = [
1711
+ { url = "https://files.pythonhosted.org/packages/b6/77/8671682038b08237c927215fa3296bc1c54e4086fe542c87017c1b626663/protobuf-6.31.0-cp310-abi3-win32.whl", hash = "sha256:10bd62802dfa0588649740a59354090eaf54b8322f772fbdcca19bc78d27f0d6", size = 423437 },
1712
+ { url = "https://files.pythonhosted.org/packages/e4/07/cc9b0cbf7593f6ef8cf87fa9b0e55cd74c5cb526dd89ad84aa7d6547ef8d/protobuf-6.31.0-cp310-abi3-win_amd64.whl", hash = "sha256:3e987c99fd634be8347246a02123250f394ba20573c953de133dc8b2c107dd71", size = 435118 },
1713
+ { url = "https://files.pythonhosted.org/packages/21/46/33f884aa8bc59114dc97e0d954ca4618c556483670236008c88fbb7e834f/protobuf-6.31.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2c812f0f96ceb6b514448cefeb1df54ec06dde456783f5099c0e2f8a0f2caa89", size = 425439 },
1714
+ { url = "https://files.pythonhosted.org/packages/9b/f2/9a676b50229ce37b12777d7b21de90ae7bc0f9505d07e72e2e8d47b8d165/protobuf-6.31.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:67ce50195e4e584275623b8e6bc6d3d3dfd93924bf6116b86b3b8975ab9e4571", size = 321950 },
1715
+ { url = "https://files.pythonhosted.org/packages/a1/a7/243fa2d3c1b7675d54744b32dacf30356f4c27c0d3ad940ca8745a1c6b2c/protobuf-6.31.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:5353e38844168a327acd2b2aa440044411cd8d1b6774d5701008bd1dba067c79", size = 320904 },
1716
+ { url = "https://files.pythonhosted.org/packages/ee/01/1ed1d482960a5718fd99c82f6d79120181947cfd4667ec3944d448ed44a3/protobuf-6.31.0-py3-none-any.whl", hash = "sha256:6ac2e82556e822c17a8d23aa1190bbc1d06efb9c261981da95c71c9da09e9e23", size = 168558 },
1717
+ ]
1718
+
1719
  [[package]]
1720
  name = "psutil"
1721
  version = "7.0.0"
 
1749
  { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 },
1750
  ]
1751
 
1752
+ [[package]]
1753
+ name = "pyasn1"
1754
+ version = "0.6.1"
1755
+ source = { registry = "https://pypi.org/simple" }
1756
+ sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322 }
1757
+ wheels = [
1758
+ { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135 },
1759
+ ]
1760
+
1761
+ [[package]]
1762
+ name = "pyasn1-modules"
1763
+ version = "0.4.2"
1764
+ source = { registry = "https://pypi.org/simple" }
1765
+ dependencies = [
1766
+ { name = "pyasn1" },
1767
+ ]
1768
+ sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892 }
1769
+ wheels = [
1770
+ { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259 },
1771
+ ]
1772
+
1773
  [[package]]
1774
  name = "pycparser"
1775
  version = "2.22"
 
1781
 
1782
  [[package]]
1783
  name = "pydantic"
1784
+ version = "2.11.5"
1785
  source = { registry = "https://pypi.org/simple" }
1786
  dependencies = [
1787
  { name = "annotated-types" },
1788
  { name = "pydantic-core" },
1789
  { name = "typing-extensions" },
1790
+ { name = "typing-inspection" },
1791
  ]
1792
+ sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102 }
1793
  wheels = [
1794
+ { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229 },
1795
  ]
1796
 
1797
  [[package]]
1798
  name = "pydantic-core"
1799
+ version = "2.33.2"
1800
  source = { registry = "https://pypi.org/simple" }
1801
  dependencies = [
1802
  { name = "typing-extensions" },
1803
  ]
1804
+ sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 }
1805
+ wheels = [
1806
+ { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584 },
1807
+ { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071 },
1808
+ { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823 },
1809
+ { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792 },
1810
+ { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338 },
1811
+ { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998 },
1812
+ { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200 },
1813
+ { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890 },
1814
+ { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359 },
1815
+ { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883 },
1816
+ { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074 },
1817
+ { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538 },
1818
+ { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909 },
1819
+ { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786 },
1820
+ { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 },
1821
+ { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 },
1822
+ { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 },
1823
+ { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 },
1824
+ { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 },
1825
+ { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 },
1826
+ { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 },
1827
+ { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 },
1828
+ { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 },
1829
+ { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 },
1830
+ { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 },
1831
+ { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 },
1832
+ { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 },
1833
+ { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 },
1834
+ { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 },
1835
+ { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 },
1836
+ { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 },
1837
+ { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 },
1838
+ { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 },
1839
+ { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 },
1840
+ { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 },
1841
+ { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 },
1842
+ { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 },
1843
+ { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 },
1844
+ { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 },
1845
+ { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 },
1846
+ { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 },
1847
+ { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 },
1848
+ { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 },
1849
+ { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 },
1850
+ { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 },
1851
+ { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200 },
1852
+ { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123 },
1853
+ { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852 },
1854
+ { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484 },
1855
+ { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896 },
1856
+ { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475 },
1857
+ { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013 },
1858
+ { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715 },
1859
+ { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757 },
1860
  ]
1861
 
1862
  [[package]]
 
1889
  { 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 },
1890
  ]
1891
 
1892
+ [[package]]
1893
+ name = "python-dotenv"
1894
+ version = "1.1.0"
1895
+ source = { registry = "https://pypi.org/simple" }
1896
+ sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 }
1897
+ wheels = [
1898
+ { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 },
1899
+ ]
1900
+
1901
  [[package]]
1902
  name = "python-json-logger"
1903
  version = "3.3.0"
 
2182
  { url = "https://files.pythonhosted.org/packages/2e/ba/31239736f29e4dfc7a58a45955c5db852864c306131fd6320aea214d5437/rpds_py-0.25.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:9a46c2fb2545e21181445515960006e85d22025bd2fe6db23e76daec6eb689fe", size = 558781 },
2183
  ]
2184
 
2185
+ [[package]]
2186
+ name = "rsa"
2187
+ version = "4.9.1"
2188
+ source = { registry = "https://pypi.org/simple" }
2189
+ dependencies = [
2190
+ { name = "pyasn1" },
2191
+ ]
2192
+ sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034 }
2193
+ wheels = [
2194
+ { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696 },
2195
+ ]
2196
+
2197
  [[package]]
2198
  name = "ruff"
2199
+ version = "0.11.11"
2200
+ source = { registry = "https://pypi.org/simple" }
2201
+ sdist = { url = "https://files.pythonhosted.org/packages/b2/53/ae4857030d59286924a8bdb30d213d6ff22d8f0957e738d0289990091dd8/ruff-0.11.11.tar.gz", hash = "sha256:7774173cc7c1980e6bf67569ebb7085989a78a103922fb83ef3dfe230cd0687d", size = 4186707 }
2202
+ wheels = [
2203
+ { url = "https://files.pythonhosted.org/packages/b1/14/f2326676197bab099e2a24473158c21656fbf6a207c65f596ae15acb32b9/ruff-0.11.11-py3-none-linux_armv6l.whl", hash = "sha256:9924e5ae54125ed8958a4f7de320dab7380f6e9fa3195e3dc3b137c6842a0092", size = 10229049 },
2204
+ { url = "https://files.pythonhosted.org/packages/9a/f3/bff7c92dd66c959e711688b2e0768e486bbca46b2f35ac319bb6cce04447/ruff-0.11.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8a93276393d91e952f790148eb226658dd275cddfde96c6ca304873f11d2ae4", size = 11053601 },
2205
+ { url = "https://files.pythonhosted.org/packages/e2/38/8e1a3efd0ef9d8259346f986b77de0f62c7a5ff4a76563b6b39b68f793b9/ruff-0.11.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6e333dbe2e6ae84cdedefa943dfd6434753ad321764fd937eef9d6b62022bcd", size = 10367421 },
2206
+ { url = "https://files.pythonhosted.org/packages/b4/50/557ad9dd4fb9d0bf524ec83a090a3932d284d1a8b48b5906b13b72800e5f/ruff-0.11.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7885d9a5e4c77b24e8c88aba8c80be9255fa22ab326019dac2356cff42089fc6", size = 10581980 },
2207
+ { url = "https://files.pythonhosted.org/packages/c4/b2/e2ed82d6e2739ece94f1bdbbd1d81b712d3cdaf69f0a1d1f1a116b33f9ad/ruff-0.11.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b5ab797fcc09121ed82e9b12b6f27e34859e4227080a42d090881be888755d4", size = 10089241 },
2208
+ { url = "https://files.pythonhosted.org/packages/3d/9f/b4539f037a5302c450d7c695c82f80e98e48d0d667ecc250e6bdeb49b5c3/ruff-0.11.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e231ff3132c1119ece836487a02785f099a43992b95c2f62847d29bace3c75ac", size = 11699398 },
2209
+ { url = "https://files.pythonhosted.org/packages/61/fb/32e029d2c0b17df65e6eaa5ce7aea5fbeaed22dddd9fcfbbf5fe37c6e44e/ruff-0.11.11-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:a97c9babe1d4081037a90289986925726b802d180cca784ac8da2bbbc335f709", size = 12427955 },
2210
+ { url = "https://files.pythonhosted.org/packages/6e/e3/160488dbb11f18c8121cfd588e38095ba779ae208292765972f7732bfd95/ruff-0.11.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d8c4ddcbe8a19f59f57fd814b8b117d4fcea9bee7c0492e6cf5fdc22cfa563c8", size = 12069803 },
2211
+ { url = "https://files.pythonhosted.org/packages/ff/16/3b006a875f84b3d0bff24bef26b8b3591454903f6f754b3f0a318589dcc3/ruff-0.11.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6224076c344a7694c6fbbb70d4f2a7b730f6d47d2a9dc1e7f9d9bb583faf390b", size = 11242630 },
2212
+ { url = "https://files.pythonhosted.org/packages/65/0d/0338bb8ac0b97175c2d533e9c8cdc127166de7eb16d028a43c5ab9e75abd/ruff-0.11.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:882821fcdf7ae8db7a951df1903d9cb032bbe838852e5fc3c2b6c3ab54e39875", size = 11507310 },
2213
+ { url = "https://files.pythonhosted.org/packages/6f/bf/d7130eb26174ce9b02348b9f86d5874eafbf9f68e5152e15e8e0a392e4a3/ruff-0.11.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:dcec2d50756463d9df075a26a85a6affbc1b0148873da3997286caf1ce03cae1", size = 10441144 },
2214
+ { url = "https://files.pythonhosted.org/packages/b3/f3/4be2453b258c092ff7b1761987cf0749e70ca1340cd1bfb4def08a70e8d8/ruff-0.11.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:99c28505ecbaeb6594701a74e395b187ee083ee26478c1a795d35084d53ebd81", size = 10081987 },
2215
+ { url = "https://files.pythonhosted.org/packages/6c/6e/dfa4d2030c5b5c13db158219f2ec67bf333e8a7748dccf34cfa2a6ab9ebc/ruff-0.11.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9263f9e5aa4ff1dec765e99810f1cc53f0c868c5329b69f13845f699fe74f639", size = 11073922 },
2216
+ { url = "https://files.pythonhosted.org/packages/ff/f4/f7b0b0c3d32b593a20ed8010fa2c1a01f2ce91e79dda6119fcc51d26c67b/ruff-0.11.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:64ac6f885e3ecb2fdbb71de2701d4e34526651f1e8503af8fb30d4915a3fe345", size = 11568537 },
2217
+ { url = "https://files.pythonhosted.org/packages/d2/46/0e892064d0adc18bcc81deed9aaa9942a27fd2cd9b1b7791111ce468c25f/ruff-0.11.11-py3-none-win32.whl", hash = "sha256:1adcb9a18802268aaa891ffb67b1c94cd70578f126637118e8099b8e4adcf112", size = 10536492 },
2218
+ { url = "https://files.pythonhosted.org/packages/1b/d9/232e79459850b9f327e9f1dc9c047a2a38a6f9689e1ec30024841fc4416c/ruff-0.11.11-py3-none-win_amd64.whl", hash = "sha256:748b4bb245f11e91a04a4ff0f96e386711df0a30412b9fe0c74d5bdc0e4a531f", size = 11612562 },
2219
+ { url = "https://files.pythonhosted.org/packages/ce/eb/09c132cff3cc30b2e7244191dcce69437352d6d6709c0adf374f3e6f476e/ruff-0.11.11-py3-none-win_arm64.whl", hash = "sha256:6c51f136c0364ab1b774767aa8b86331bd8e9d414e2d107db7a2189f35ea1f7b", size = 10735951 },
2220
  ]
2221
 
2222
  [[package]]
 
2444
 
2445
  [[package]]
2446
  name = "typer"
2447
+ version = "0.15.4"
2448
  source = { registry = "https://pypi.org/simple" }
2449
  dependencies = [
2450
  { name = "click" },
 
2452
  { name = "shellingham" },
2453
  { name = "typing-extensions" },
2454
  ]
2455
+ sdist = { url = "https://files.pythonhosted.org/packages/6c/89/c527e6c848739be8ceb5c44eb8208c52ea3515c6cf6406aa61932887bf58/typer-0.15.4.tar.gz", hash = "sha256:89507b104f9b6a0730354f27c39fae5b63ccd0c95b1ce1f1a6ba0cfd329997c3", size = 101559 }
2456
  wheels = [
2457
+ { url = "https://files.pythonhosted.org/packages/c9/62/d4ba7afe2096d5659ec3db8b15d8665bdcb92a3c6ff0b95e99895b335a9c/typer-0.15.4-py3-none-any.whl", hash = "sha256:eb0651654dcdea706780c466cf06d8f174405a659ffff8f163cfbfee98c0e173", size = 45258 },
2458
  ]
2459
 
2460
  [[package]]
 
2475
  { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
2476
  ]
2477
 
2478
+ [[package]]
2479
+ name = "typing-inspection"
2480
+ version = "0.4.1"
2481
+ source = { registry = "https://pypi.org/simple" }
2482
+ dependencies = [
2483
+ { name = "typing-extensions" },
2484
+ ]
2485
+ sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 }
2486
+ wheels = [
2487
+ { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 },
2488
+ ]
2489
+
2490
  [[package]]
2491
  name = "tzdata"
2492
  version = "2025.2"