rowan-python 3.2.0__py3-none-any.whl → 3.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rowan/folder.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from datetime import datetime
3
4
  from typing import TYPE_CHECKING, Any, Self
4
5
 
5
6
  from pydantic import BaseModel
@@ -8,8 +9,6 @@ from .project import default_project, retrieve_project
8
9
  from .utils import api_client, get_project_uuid
9
10
 
10
11
  if TYPE_CHECKING:
11
- from datetime import datetime
12
-
13
12
  from .workflows.base import Workflow
14
13
 
15
14
 
@@ -65,8 +64,6 @@ class Folder(BaseModel):
65
64
  for field_name in self.__class__.model_fields:
66
65
  setattr(self, field_name, getattr(updated_folder, field_name))
67
66
 
68
- self.model_rebuild()
69
-
70
67
  return self
71
68
 
72
69
  def update(
rowan/protein.py CHANGED
@@ -1,4 +1,3 @@
1
- import time
2
1
  import warnings
3
2
  from datetime import datetime
4
3
  from pathlib import Path
@@ -22,7 +21,6 @@ class Protein(BaseModel):
22
21
  created_at: creation date of the protein
23
22
  used_in_workflow: whether the protein is used in a workflow
24
23
  ancestor_uuid: UUID of the ancestor protein
25
- sanitized: whether the protein is sanitized
26
24
  name: name of the protein
27
25
  data: data of the protein
28
26
  public: whether the protein is public
@@ -32,7 +30,6 @@ class Protein(BaseModel):
32
30
  created_at: datetime | None = None
33
31
  used_in_workflow: bool | None = None
34
32
  ancestor_uuid: str | None = None
35
- sanitized: int | None = None
36
33
  name: str | None = None
37
34
  data: dict | None = None
38
35
  public: bool | None = None
@@ -188,7 +185,6 @@ class Protein(BaseModel):
188
185
  self.data = protein_data.get("data")
189
186
  self.public = protein_data.get("public")
190
187
  self.pocket = protein_data.get("pocket")
191
- self.sanitized = protein_data.get("sanitized")
192
188
  self.used_in_workflow = protein_data.get("used_in_workflow")
193
189
  self._workflow_uuid = workflow_uuid
194
190
  return self
@@ -241,118 +237,6 @@ class Protein(BaseModel):
241
237
  response = client.delete(f"/protein/{self.uuid}")
242
238
  response.raise_for_status()
243
239
 
244
- def sanitize(self, poll_interval: float = 10.0, timeout: float = 300.0) -> None:
245
- """Sanitizes a protein and waits for the process to complete.
246
-
247
- Protein sanitization runs asynchronously on the server. This method
248
- submits the request then polls until sanitization succeeds, fails, or
249
- times out.
250
-
251
- Args:
252
- poll_interval: seconds between status checks (default 10)
253
- timeout: maximum seconds to wait before raising (default 300)
254
-
255
- Raises:
256
- RuntimeError: sanitization fails, is stopped, or times out
257
- httpx.HTTPStatusError: any API request fails
258
- """
259
- with api_client() as client:
260
- response = client.post(f"/protein/sanitize/{self.uuid}")
261
- response.raise_for_status()
262
-
263
- deadline = time.monotonic() + timeout
264
- while time.monotonic() < deadline:
265
- time.sleep(poll_interval)
266
- self.refresh()
267
- match self.sanitized:
268
- case 2: # success
269
- return
270
- case 3: # failed
271
- raise RuntimeError(
272
- f"Protein sanitization failed for {self.uuid}. "
273
- "Check the protein in the Rowan UI for details."
274
- )
275
- case 4: # stopped
276
- raise RuntimeError(f"Protein sanitization was stopped for {self.uuid}.")
277
- case _: # 1 (in progress) or None: keep polling
278
- pass
279
-
280
- raise RuntimeError(f"Protein sanitization timed out after {timeout:.0f}s for {self.uuid}.")
281
-
282
- def prepare(
283
- self,
284
- find_missing_residues: bool = True,
285
- add_missing_atoms: bool = True,
286
- remove_heterogens: bool = True,
287
- keep_waters: bool = False,
288
- remove_hydrogens: bool = False,
289
- remove_invalid_hydrogens: bool = False,
290
- add_hydrogens: bool = True,
291
- add_hydrogen_ph: float = 7.0,
292
- optimize_hydrogens: bool = True,
293
- poll_interval: float = 10.0,
294
- timeout: float = 300.0,
295
- ) -> None:
296
- """Quickly prepare a protein in place using PDBFixer and OpenMM.
297
-
298
- Runs PDBFixer to fix nonstandard residues, add missing atoms/hydrogens,
299
- and optionally optimizes hydrogen positions with OpenMM. This is the fast
300
- preparation option and typically finishes in about a minute or less. Use
301
- `submit_protein_preparation_workflow` for the full protein preparation workflow,
302
- which can take around ten minutes but includes Boltz-2 missing-structure modeling,
303
- terminal capping, selectable protonation methods, and retained non-polymers.
304
-
305
- Args:
306
- find_missing_residues: identify and model missing residues
307
- add_missing_atoms: add missing heavy atoms to residues
308
- remove_heterogens: remove ligands, salts, and other heterogens
309
- keep_waters: preserve water molecules when removing heterogens
310
- remove_hydrogens: remove all existing hydrogens before adding new ones
311
- remove_invalid_hydrogens: remove hydrogens not matching the forcefield template
312
- add_hydrogens: add missing hydrogen atoms
313
- add_hydrogen_ph: pH used to determine protonation states when adding hydrogens
314
- optimize_hydrogens: optimize hydrogen positions with OpenMM energy minimization
315
- poll_interval: seconds between status checks (default 10)
316
- timeout: maximum seconds to wait before raising (default 300)
317
-
318
- Raises:
319
- RuntimeError: preparation fails, is stopped, or times out
320
- httpx.HTTPStatusError: any API request fails
321
- """
322
- params = {
323
- "find_missing_residues": find_missing_residues,
324
- "add_missing_atoms": add_missing_atoms,
325
- "remove_heterogens": remove_heterogens,
326
- "keep_waters": keep_waters,
327
- "remove_hydrogens": remove_hydrogens,
328
- "remove_invalid_hydrogens": remove_invalid_hydrogens,
329
- "add_hydrogens": add_hydrogens,
330
- "add_hydrogen_ph": add_hydrogen_ph,
331
- "optimize_hydrogens": optimize_hydrogens,
332
- }
333
- with api_client() as client:
334
- response = client.post(f"/protein/prepare/{self.uuid}", params=params)
335
- response.raise_for_status()
336
-
337
- deadline = time.monotonic() + timeout
338
- while time.monotonic() < deadline:
339
- time.sleep(poll_interval)
340
- self.refresh()
341
- match self.sanitized:
342
- case 2:
343
- return
344
- case 3:
345
- raise RuntimeError(
346
- f"Protein preparation failed for {self.uuid}. "
347
- "Check the protein in the Rowan UI for details."
348
- )
349
- case 4:
350
- raise RuntimeError(f"Protein preparation was stopped for {self.uuid}.")
351
- case _:
352
- pass
353
-
354
- raise RuntimeError(f"Protein preparation timed out after {timeout:.0f}s for {self.uuid}.")
355
-
356
240
  def validate_protein_forcefield(self, exclude_residues: list[str | int] | None = None) -> None:
357
241
  """Validate that this protein can be parameterized with the MD forcefield.
358
242
 
@@ -82,7 +82,8 @@ def submit_binding_affinity_workflow(
82
82
  input modes are supported:
83
83
 
84
84
  **Mode 1 – holo protein:** protein already contains the bound ligand. Pass
85
- `ligand_residue_name` to identify which residue is the ligand vs. the receptor.
85
+ `ligand_residue_name` to identify which residue is the ligand vs. the receptor,
86
+ and exactly one entry in `ligand_smiles` describing that bound ligand.
86
87
  Do not pass `ligand_structures`.
87
88
 
88
89
  **Mode 2 – apo protein + external poses:** protein has no bound ligand. Pass
@@ -101,13 +102,15 @@ def submit_binding_affinity_workflow(
101
102
  protein: protein structure. Can be input as a UUID or a Protein object. Required
102
103
  unless `protein_sequences` is set (mode 3)
103
104
  ligand_residue_name: residue name identifying the ligand in a holo protein PDB
104
- (mode 1 only)
105
+ (mode 1 only); requires exactly one matching `ligand_smiles` entry
105
106
  ligand_structures: external ligand poses to score, already in the protein's
106
107
  coordinate frame. Must have 3D coordinates (mode 2 only)
107
108
  protein_sequences: protein sequences to score against, in place of `protein`
108
109
  (mode 3, NESSO only)
109
- ligand_smiles: ligand SMILES to score, in place of `ligand_residue_name`/
110
- `ligand_structures` (mode 3, NESSO only)
110
+ ligand_smiles: exactly one SMILES describing the bound ligand with
111
+ `ligand_residue_name` (mode 1, all methods), or ligand SMILES to score without
112
+ structural ligand inputs (mode 3, NESSO only). Not accepted with
113
+ `ligand_structures`
111
114
  binding_affinity_settings: settings controlling how binding affinity is
112
115
  computed: `SinglePointEnergySettings` (SQM), `GninaAffinitySettings`,
113
116
  `AEVPLIGAffinitySettings`, or `NessoAffinitySettings`. Defaults to
@@ -142,6 +142,7 @@ def submit_protein_md_workflow(
142
142
  nonbonded_cutoff: float = 8.0,
143
143
  ionic_strength_M: float = 0.0,
144
144
  water_buffer: float = 8.0,
145
+ frame_save_interval_ps: float = 10.0,
145
146
  save_solvent: bool = False,
146
147
  num_solvent_to_save: int | None = None,
147
148
  small_molecules: dict[str | int, str | None] | None = None,
@@ -178,6 +179,8 @@ def submit_protein_md_workflow(
178
179
  nonbonded_cutoff: nonbonded cutoff for particle-mesh Ewald, in A
179
180
  ionic_strength_M: ionic strength of the solution, in M (molar)
180
181
  water_buffer: amount of water to add around the protein, in A
182
+ frame_save_interval_ps: how often to save a frame to the trajectory, in ps; must be
183
+ an integer multiple of timestep_fs
181
184
  save_solvent: whether solvent should be saved
182
185
  num_solvent_to_save: keep this many solvent molecules nearest the binder, or all if None;
183
186
  only meaningful when save_solvent is True and a binder is present
@@ -246,6 +249,7 @@ def submit_protein_md_workflow(
246
249
  nonbonded_cutoff=nonbonded_cutoff,
247
250
  ionic_strength_M=ionic_strength_M,
248
251
  water_buffer=water_buffer,
252
+ frame_save_interval_ps=frame_save_interval_ps,
249
253
  save_solvent=save_solvent,
250
254
  num_solvent_to_save=num_solvent_to_save,
251
255
  small_molecules=small_molecules,
@@ -75,7 +75,7 @@ def submit_protein_preparation_workflow(
75
75
  """Submit a protein-preparation workflow to the API.
76
76
 
77
77
  Full protein preparation can take around ten minutes, depending on the structure and
78
- settings. For a faster PDBFixer/OpenMM-only path, use `Protein.prepare()`.
78
+ settings.
79
79
 
80
80
  Args:
81
81
  protein: protein to prepare, as a UUID or Protein object
@@ -140,6 +140,8 @@ class RelativeBindingFreeEnergyPerturbationResult(WorkflowResult):
140
140
  lambda_vals: list[float] | None = None,
141
141
  path: Path | str | None = None,
142
142
  name: str | None = None,
143
+ *,
144
+ leg: Literal["complex", "solvent"] = "complex",
143
145
  ) -> Path:
144
146
  """Download DCD trajectory files for a specific perturbation edge.
145
147
 
@@ -148,6 +150,7 @@ class RelativeBindingFreeEnergyPerturbationResult(WorkflowResult):
148
150
  lambda_vals: lambda values to download. Defaults to all windows
149
151
  path: directory to save the file to. Defaults to current directory
150
152
  name: custom name for the tar.gz file (without extension)
153
+ leg: thermodynamic leg whose trajectories should be downloaded
151
154
 
152
155
  Returns:
153
156
  path to the downloaded tar.gz file
@@ -163,27 +166,34 @@ class RelativeBindingFreeEnergyPerturbationResult(WorkflowResult):
163
166
  path = Path(path) if path is not None else Path.cwd()
164
167
  path.mkdir(parents=True, exist_ok=True)
165
168
 
166
- params: dict = {"edge_index": edge_index}
167
- if lambda_vals is not None:
168
- params["lambda_vals"] = lambda_vals
169
+ params: dict = {"edge_index": edge_index, "leg": leg}
169
170
 
170
- file_name = f"{name or f'edge_{edge_index}_trajectories'}.tar.gz"
171
+ default_name = (
172
+ f"edge_{edge_index}_trajectories"
173
+ if leg == "complex"
174
+ else f"edge_{edge_index}_{leg}_trajectories"
175
+ )
176
+ file_name = f"{name or default_name}.tar.gz"
171
177
  file_path = path / file_name
172
178
  return download_file(
173
179
  file_path,
174
180
  "POST",
175
181
  f"/trajectory/{self.workflow_uuid}/rbfe_trajectory_dcds",
176
182
  params=params,
183
+ json=lambda_vals,
177
184
  )
178
185
 
179
186
  def download_all_trajectories(
180
187
  self,
181
188
  path: Path | str | None = None,
189
+ *,
190
+ leg: Literal["complex", "solvent"] = "complex",
182
191
  ) -> list[Path]:
183
192
  """Download DCD trajectory files for all perturbation edges.
184
193
 
185
194
  Args:
186
195
  path: directory to save the files to. Defaults to current directory
196
+ leg: thermodynamic leg whose trajectories should be downloaded
187
197
 
188
198
  Returns:
189
199
  list of paths to the downloaded tar.gz files, one per edge
@@ -191,7 +201,9 @@ class RelativeBindingFreeEnergyPerturbationResult(WorkflowResult):
191
201
  Raises:
192
202
  httpx.HTTPStatusError: any API request fails
193
203
  """
194
- return [self.download_edge_trajectories(i, path=path) for i in range(len(self.edges))]
204
+ return [
205
+ self.download_edge_trajectories(i, path=path, leg=leg) for i in range(len(self.edges))
206
+ ]
195
207
 
196
208
  @property
197
209
  def ligand_dg_results(self) -> dict[str, RelativeBindingFreeEnergyResult] | None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: rowan-python
3
- Version: 3.2.0
3
+ Version: 3.2.1
4
4
  Summary: Rowan Python Library
5
5
  Project-URL: Homepage, https://github.com/rowansci/rowan-client
6
6
  Project-URL: Bug Tracker, https://github.com/rowansci/rowan-client/issues
@@ -12,22 +12,19 @@ Requires-Dist: httpx
12
12
  Requires-Dist: nest-asyncio
13
13
  Requires-Dist: rdkit
14
14
  Requires-Dist: setuptools
15
- Requires-Dist: stjames>=0.0.261
15
+ Requires-Dist: stjames>=0.0.275
16
16
  Description-Content-Type: text/markdown
17
17
 
18
18
  # Rowan Python Library
19
19
 
20
20
  [![pypi](https://img.shields.io/pypi/v/rowan-python.svg)](https://pypi.python.org/pypi/rowan-python)
21
21
  [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://docs.astral.sh/uv/)
22
- [![ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json)](https://github.com/charliermarsh/ruff)
22
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
23
23
  [![Downloads](https://img.shields.io/pypi/dm/rowan-python.svg)](https://pypi.python.org/pypi/rowan-python/)
24
24
  [![License](https://img.shields.io/github/license/rowansci/rowan-python)](LICENSE)
25
25
  [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/rowansci/rowan-python/test.yml?branch=master&logo=github-actions)](https://github.com/rowansci/rowan-python/actions)
26
- [![Typing: ty](https://img.shields.io/badge/typing-ty-EFC621.svg)](https://github.com/astral-sh/ty)
27
- <!-- Enable these badges with the corresponding tooling/services.
26
+ [![ty](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ty/main/assets/badge/v0.json)](https://github.com/astral-sh/ty)
28
27
  [![Markdown style: rumdl](https://img.shields.io/badge/md%20style-rumdl-000000.svg)](https://rumdl.dev)
29
- [![Codecov](https://img.shields.io/codecov/c/github/rowansci/rowan-python)](https://codecov.io/gh/rowansci/rowan-python)
30
- -->
31
28
 
32
29
  The Rowan Python library is the Python SDK for running Rowan computational chemistry workflows
33
30
  programmatically. Use it from scripts and applications to submit calculations, monitor workflow
@@ -3,10 +3,10 @@ rowan/api_keys.py,sha256=ms8NAkLreFQcaEUPCf8lSf7T0x2OmrWCAT44N1cgocE,6955
3
3
  rowan/calculation.py,sha256=E1dAox4ScE3NHJXiKFZ3Lpmw2h7DKCVzcwRvGZ2H2cQ,3802
4
4
  rowan/config.py,sha256=ay6UxKLE9bbTxHFXxDfUXGRVJWJ1T-eS2_L4HF8mb_E,20927
5
5
  rowan/constants.py,sha256=emCH4m9OL2Hm5E-6mJGM_FgzrK_JrZT-FiKJ6pMNQ4Y,84
6
- rowan/folder.py,sha256=IhTPR2Wm4_roJJJdprRPd5C9GQQh6JkPDN8G_r6XSxU,13918
6
+ rowan/folder.py,sha256=8Gb60oes78G7QTGm0EX-ofkmQEggv5cwseA7dRBIhok,13883
7
7
  rowan/molecule.py,sha256=1ueC_M2Yaubkt5eQlONCagShWPnoJfNNXgd0s7sslPM,15826
8
8
  rowan/project.py,sha256=N8DdNt7xCYHunIAqTvPmBGkH8thzwmguATo4Thfa13o,5479
9
- rowan/protein.py,sha256=6e_lvP-QEdEQ-WnAkPMsn3XdqDgItRG6fbepK6Xw-Kw,24544
9
+ rowan/protein.py,sha256=AGHarvWfciSwWXfHnVjdUW47FML7vI4Kk4dYTeFfKAQ,19322
10
10
  rowan/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  rowan/types.py,sha256=D0d6MW6MvLMs6UtNgGKlapFGOnPlzqFTk9EAgB9dwks,636
12
12
  rowan/user.py,sha256=wGwyp0uzifzJeC88yCC7pgxMhxuZ-LLWHfCcuPXcx44,6199
@@ -19,7 +19,7 @@ rowan/workflows/base.py,sha256=DR2k5VFMmv2sa9v4hYU_Fvzf8k2unp5nhPqvOd7NX64,39520
19
19
  rowan/workflows/basic_calculation.py,sha256=7o9jxEpDMiong2k3Ke1WCLwaSWZvKY5tqhbixnbQWbo,12945
20
20
  rowan/workflows/batch_docking.py,sha256=AtCp2-D_LOVxzSPmQ144zYIz5kft9NeRljELdRDU6vY,4941
21
21
  rowan/workflows/bde.py,sha256=4Yw27U2RPsuUDf9tw-7wPbw_4gaUcvDlQP8je7VJktU,7099
22
- rowan/workflows/binding_affinity.py,sha256=UbsaSLJwyaPxFgEAmDxqIYSxFm4ioKoXIJJPBeLDzJg,6285
22
+ rowan/workflows/binding_affinity.py,sha256=O0fL_oQtMpYlmdaCvHTR7WSNfOENMPAN0OUBxO4640k,6553
23
23
  rowan/workflows/conformer_search.py,sha256=enx9HbFZVVYgHJP-HUFam63E0qCDq6jjGeXNuBXXlys,16311
24
24
  rowan/workflows/constants.py,sha256=iM9-6DyIe0N2ydyq-lRYP7VDufFmMrN5tDjnpUSlxF8,588
25
25
  rowan/workflows/covalent_inhibitor_scan.py,sha256=VcZhwW3HRuFuGZNedMPZBWbzuhGvz_B5pNJSIPyi9Kw,6721
@@ -43,18 +43,18 @@ rowan/workflows/pocket_detection.py,sha256=dFeCLB59v5-Izf54blrRzJhwUtBQX2SJyVKT_
43
43
  rowan/workflows/pose_analysis_md.py,sha256=ndfGS8oP8oQ3EXe83Wgbx1H8ZFURlm8IJZN34aDpqeE,10599
44
44
  rowan/workflows/protein_binder_design.py,sha256=ee3qYGzTzQsca9otnRz9Qjwj1X66LTCKqtTQ5EOzBHk,9181
45
45
  rowan/workflows/protein_cofolding.py,sha256=s4bLxlix4kAO_NzS75tc2wiEYqUxpFofxziUnxI2iUY,17422
46
- rowan/workflows/protein_md.py,sha256=4atIPGKxA1bISw6MlwrrRCwIj2Qhy7evgt5v6UPRBZc,12028
47
- rowan/workflows/protein_preparation.py,sha256=cpRkWHX5hOkR5LMGKJ-iS3ZJy2vMtuPGLNpqWN4q02g,5390
46
+ rowan/workflows/protein_md.py,sha256=M4lbfEEvyxmhBWfz_Hf3whmNIYuHe3Q3HZzDcq-jYII,12264
47
+ rowan/workflows/protein_preparation.py,sha256=8C04rhR4AiGiYJWZ8V06O4kpV443tGihcTk8rnNgscE,5325
48
48
  rowan/workflows/rbfe_graph.py,sha256=DBNxtmgw7BNSwOXFLpHDxJtfpheTpDPs8U3SYB2MXdE,8070
49
49
  rowan/workflows/redox_potential.py,sha256=0AV2nKTngEFw7VDDpDQvdeBo5u03Aklc2ak4bDWxUz0,5366
50
- rowan/workflows/relative_binding_free_energy_perturbation.py,sha256=52oUh8pZQGyFrpE5SRsRhYQU5MavnE92QwV5bR0N_Wg,15450
50
+ rowan/workflows/relative_binding_free_energy_perturbation.py,sha256=dBQkM3wtbNVVkiXbB3RBBDUcme2OnWZkWFZdTUR8vlM,15872
51
51
  rowan/workflows/scan.py,sha256=MqV0NcjfInwxGlegOyWvAt8pwN0BdpsKvgCgTRkzdzU,7440
52
52
  rowan/workflows/solubility.py,sha256=Lws0lk4ifmtj84W_jyeOuaTpt8KM8-fceOggHrEYnmc,11211
53
53
  rowan/workflows/solvent_dependent_conformers.py,sha256=1dYf8UBVMRFELdwHzv3MI0pe12qoCAfohkaMiW3ak50,7460
54
54
  rowan/workflows/spin_states.py,sha256=C3hJEY4hccE1fCW4UjI4nIqGZLw9l2C2VKWCLiopSlY,9044
55
55
  rowan/workflows/strain.py,sha256=hq0dOlxnN72V4Sohr3qNFbOrjGmV01tc5zcF0HrL2-Y,6372
56
56
  rowan/workflows/tautomer_search.py,sha256=o5iFXHfcb6DvvWH7itA05hvWIrzxrc3a_9IDikYftiM,6351
57
- rowan_python-3.2.0.dist-info/METADATA,sha256=MbZFH7zmFqIBO5tVSvQCQwJ2JIdk3ZjXefIES14TvB0,4082
58
- rowan_python-3.2.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
59
- rowan_python-3.2.0.dist-info/licenses/LICENSE,sha256=i05z7xEhyrg6f8j0lR3XYjShnF-MJGFQ-DnpsZ8yiVI,1084
60
- rowan_python-3.2.0.dist-info/RECORD,,
57
+ rowan_python-3.2.1.dist-info/METADATA,sha256=g0STX2NMLAvsvtvLRpJ8gaR6qBV-9SckC_SQzSaJzE0,3934
58
+ rowan_python-3.2.1.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
59
+ rowan_python-3.2.1.dist-info/licenses/LICENSE,sha256=i05z7xEhyrg6f8j0lR3XYjShnF-MJGFQ-DnpsZ8yiVI,1084
60
+ rowan_python-3.2.1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.32.0
2
+ Generator: hatchling 1.32.4
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any